├── .commitlintrc.json ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc.json ├── .prettierrc.json ├── LICENSE ├── README.md ├── jest-setup.ts ├── jest.config.ts ├── package.json ├── src ├── index.test.tsx └── index.tsx ├── tsconfig.json ├── tsup.config.ts └── yarn.lock /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es2021": true 6 | }, 7 | "extends": [ 8 | "plugin:react/recommended", 9 | "airbnb", 10 | "airbnb/hooks", 11 | "airbnb-typescript", 12 | "prettier" 13 | ], 14 | "parser": "@typescript-eslint/parser", 15 | "parserOptions": { 16 | "project": "./tsconfig.json", 17 | "ecmaFeatures": { 18 | "jsx": true 19 | }, 20 | "ecmaVersion": "latest", 21 | "sourceType": "module" 22 | }, 23 | "plugins": ["react", "@typescript-eslint", "prettier"], 24 | "rules": { 25 | "prettier/prettier": "error", 26 | "no-unused-vars": "off", 27 | "@typescript-eslint/no-unused-vars": "error", 28 | "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], 29 | "react/require-default-props": "off", 30 | "react/no-unstable-nested-components": ["error", { "allowAsProps": true }], 31 | "react/jsx-no-useless-fragment": "off", 32 | "react/jsx-props-no-spreading": "off" 33 | }, 34 | "overrides": [ 35 | { 36 | "files": ["**/__tests__/**/*", "**/*.{spec,test}.*"], 37 | "env": { 38 | "jest/globals": true 39 | }, 40 | "plugins": ["jest", "jest-dom", "testing-library"], 41 | "extends": [ 42 | "plugin:jest/recommended", 43 | "plugin:jest-dom/recommended", 44 | "plugin:testing-library/react" 45 | ], 46 | "rules": {} 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,js,ts,tsx}": [ 3 | "eslint --fix --max-warnings 0", 4 | "prettier --write", 5 | "cross-env CI=true npm run test --if-present -- --findRelatedTests --bail" 6 | ], 7 | "*.{html,json,md,mdx,yml,yaml}": ["prettier --write"] 8 | } 9 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "jsxSingleQuote": false, 8 | "quoteProps": "as-needed", 9 | "trailingComma": "all", 10 | "bracketSpacing": true, 11 | "bracketSameLine": false, 12 | "arrowParens": "avoid", 13 | "proseWrap": "preserve", 14 | "endOfLine": "lf" 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mo'men Sherif 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React HTML String 🚀 2 | 3 | A React declarative component for converting HTML strings into React components. Avoids the use of dangerouslySetInnerHTML and converts standard HTML elements, attributes and inline styles into their React equivalents or `Custom Components`. 4 | 5 | ## Install 6 | 7 | ```sh 8 | $ npm install react-html-string 9 | 10 | or 11 | 12 | $ yarn add react-html-string 13 | ``` 14 | 15 | ## How to use 16 | 17 | ```jsx 18 | import HTMLString from 'react-html-string'; 19 | 20 | const html = ` 21 |
22 |

23 | Hello from 24 | React HTML String 29 |

30 |
31 | 35 |

Don't forget to ⭐️ the project

36 |
37 | `; 38 | 39 | export default function App() { 40 | return ( 41 |
42 |

Hello, React!

43 | 44 |
45 | ); 46 | } 47 | ``` 48 | 49 | ### With Custom Components 50 | 51 | ```jsx 52 | import HTMLString from 'react-html-string'; 53 | 54 | import Heading from './components/Heading'; 55 | 56 | const components = { 57 | a: props => , 58 | h1: Heading, 59 | }; 60 | 61 | const html = ` 62 |
63 |

64 | Hello from 65 | React HTML String 70 |

71 |
72 | 76 |

Don't forget to ⭐️ the project

77 |
78 | `; 79 | 80 | export default function App() { 81 | return ( 82 |
83 |

Hello, React!

84 | 85 |
86 | ); 87 | } 88 | ``` 89 | 90 | ### TypeScript example 91 | 92 | `Each component is strongly typed with the equivalent dom node type.` 93 | 94 | ```tsx 95 | import HTMLString, { Components } from 'react-html-string'; 96 | 97 | import Heading from './components/Heading'; 98 | 99 | const components: Components = { 100 | a: props => , 101 | h1: Heading, 102 | }; 103 | 104 | const html = ` 105 |
106 |

107 | Hello from 108 | React HTML String 113 |

114 |
115 | 119 |

Don't forget to ⭐️ the project

120 |
121 | `; 122 | 123 | export default function App() { 124 | return ( 125 |
126 |

Hello, React!

127 | 128 |
129 | ); 130 | } 131 | ``` 132 | -------------------------------------------------------------------------------- /jest-setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | // All imported modules in your tests should be mocked automatically 8 | // automock: false, 9 | 10 | // Stop running tests after `n` failures 11 | // bail: 0, 12 | 13 | // The directory where Jest should store its cached dependency information 14 | // cacheDirectory: "/private/var/folders/h7/wgk7k_b1755f4sby25gy4k680000gn/T/jest_dx", 15 | 16 | // Automatically clear mock calls, instances, contexts and results before every test 17 | // clearMocks: false, 18 | 19 | // Indicates whether the coverage information should be collected while executing the test 20 | // collectCoverage: false, 21 | 22 | // An array of glob patterns indicating a set of files for which coverage information should be collected 23 | // collectCoverageFrom: undefined, 24 | 25 | // The directory where Jest should output its coverage files 26 | // coverageDirectory: undefined, 27 | 28 | // An array of regexp pattern strings used to skip coverage collection 29 | // coveragePathIgnorePatterns: [ 30 | // "/node_modules/" 31 | // ], 32 | 33 | // Indicates which provider should be used to instrument code for coverage 34 | coverageProvider: 'v8', 35 | 36 | // A list of reporter names that Jest uses when writing coverage reports 37 | // coverageReporters: [ 38 | // "json", 39 | // "text", 40 | // "lcov", 41 | // "clover" 42 | // ], 43 | 44 | // An object that configures minimum threshold enforcement for coverage results 45 | // coverageThreshold: undefined, 46 | 47 | // A path to a custom dependency extractor 48 | // dependencyExtractor: undefined, 49 | 50 | // Make calling deprecated APIs throw helpful error messages 51 | // errorOnDeprecated: false, 52 | 53 | // The default configuration for fake timers 54 | // fakeTimers: { 55 | // "enableGlobally": false 56 | // }, 57 | 58 | // Force coverage collection from ignored files using an array of glob patterns 59 | // forceCoverageMatch: [], 60 | 61 | // A path to a module which exports an async function that is triggered once before all test suites 62 | // globalSetup: undefined, 63 | 64 | // A path to a module which exports an async function that is triggered once after all test suites 65 | // globalTeardown: undefined, 66 | 67 | // A set of global variables that need to be available in all test environments 68 | // globals: {}, 69 | 70 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 71 | // maxWorkers: "50%", 72 | 73 | // An array of directory names to be searched recursively up from the requiring module's location 74 | // moduleDirectories: [ 75 | // "node_modules" 76 | // ], 77 | 78 | // An array of file extensions your modules use 79 | // moduleFileExtensions: [ 80 | // "js", 81 | // "mjs", 82 | // "cjs", 83 | // "jsx", 84 | // "ts", 85 | // "tsx", 86 | // "json", 87 | // "node" 88 | // ], 89 | 90 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 91 | // moduleNameMapper: {}, 92 | 93 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 94 | // modulePathIgnorePatterns: [], 95 | 96 | // Activates notifications for test results 97 | // notify: false, 98 | 99 | // An enum that specifies notification mode. Requires { notify: true } 100 | // notifyMode: "failure-change", 101 | 102 | // A preset that is used as a base for Jest's configuration 103 | // preset: undefined, 104 | 105 | // Run tests from one or more projects 106 | // projects: undefined, 107 | 108 | // Use this configuration option to add custom reporters to Jest 109 | // reporters: undefined, 110 | 111 | // Automatically reset mock state before every test 112 | // resetMocks: false, 113 | 114 | // Reset the module registry before running each individual test 115 | // resetModules: false, 116 | 117 | // A path to a custom resolver 118 | // resolver: undefined, 119 | 120 | // Automatically restore mock state and implementation before every test 121 | // restoreMocks: false, 122 | 123 | // The root directory that Jest should scan for tests and modules within 124 | // rootDir: undefined, 125 | 126 | // A list of paths to directories that Jest should use to search for files in 127 | // roots: [ 128 | // "" 129 | // ], 130 | 131 | // Allows you to use a custom runner instead of Jest's default test runner 132 | // runner: "jest-runner", 133 | 134 | // The paths to modules that run some code to configure or set up the testing environment before each test 135 | // setupFiles: [], 136 | 137 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 138 | setupFilesAfterEnv: ['/jest-setup.ts'], 139 | 140 | // The number of seconds after which a test is considered as slow and reported as such in the results. 141 | // slowTestThreshold: 5, 142 | 143 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 144 | // snapshotSerializers: [], 145 | 146 | // The test environment that will be used for testing 147 | testEnvironment: 'jsdom', 148 | 149 | // Options that will be passed to the testEnvironment 150 | // testEnvironmentOptions: {}, 151 | 152 | // Adds a location field to test results 153 | // testLocationInResults: false, 154 | 155 | // The glob patterns Jest uses to detect test files 156 | // testMatch: [ 157 | // "**/__tests__/**/*.[jt]s?(x)", 158 | // "**/?(*.)+(spec|test).[tj]s?(x)" 159 | // ], 160 | 161 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 162 | // testPathIgnorePatterns: [ 163 | // "/node_modules/" 164 | // ], 165 | 166 | // The regexp pattern or array of patterns that Jest uses to detect test files 167 | // testRegex: [], 168 | 169 | // This option allows the use of a custom results processor 170 | // testResultsProcessor: undefined, 171 | 172 | // This option allows use of a custom test runner 173 | // testRunner: "jest-circus/runner", 174 | 175 | // A map from regular expressions to paths to transformers 176 | transform: { 177 | '^.+\\.tsx?$': 'esbuild-jest', 178 | }, 179 | 180 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 181 | // transformIgnorePatterns: [ 182 | // "/node_modules/", 183 | // "\\.pnp\\.[^\\/]+$" 184 | // ], 185 | 186 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 187 | // unmockedModulePathPatterns: undefined, 188 | 189 | // Indicates whether each individual test should be reported during the run 190 | // verbose: undefined, 191 | 192 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 193 | // watchPathIgnorePatterns: [], 194 | 195 | // Whether to use watchman for file crawling 196 | // watchman: true, 197 | }; 198 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-html-string", 3 | "version": "0.1.1", 4 | "module": "./dist/index.esm.js", 5 | "main": "./dist/index.cjs.js", 6 | "types": "./dist/index.d.ts", 7 | "files": [ 8 | "dist" 9 | ], 10 | "license": "MIT", 11 | "scripts": { 12 | "dev": "tsup --watch", 13 | "test": "jest", 14 | "test:watch": "jest --watch", 15 | "build": "tsup", 16 | "prerelease": "npm run build", 17 | "release": "np", 18 | "eslint:check": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-path .gitignore --report-unused-disable-directives --max-warnings 0", 19 | "eslint:fix": "eslint . --fix --ext .js,.jsx,.ts,.tsx --ignore-path .gitignore --report-unused-disable-directives --max-warnings 0", 20 | "prettier:check": "prettier . --check --ignore-unknown --ignore-path .gitignore", 21 | "prettier:fix": "prettier . --write --ignore-unknown --ignore-path .gitignore", 22 | "commit": "cz", 23 | "prepare": "is-ci || husky install" 24 | }, 25 | "publishConfig": { 26 | "access": "public" 27 | }, 28 | "author": { 29 | "name": "Mo'men Sherif", 30 | "email": "momensherif.2019@gmail.com", 31 | "url": "https://github.com/MomenSherif" 32 | }, 33 | "homepage": "https://github.com/MomenSherif/react-html-string", 34 | "repository": { 35 | "type": "git", 36 | "url": "https://github.com/MomenSherif/react-html-string" 37 | }, 38 | "bugs": { 39 | "url": "https://github.com/MomenSherif/react-html-string/issues", 40 | "email": "momensherif.2019@gmail.com" 41 | }, 42 | "keywords": [ 43 | "react", 44 | "html", 45 | "htmlparser", 46 | "react html string", 47 | "inner html", 48 | "dangerouslySetInnerHTML" 49 | ], 50 | "devDependencies": { 51 | "@commitlint/cli": "^16.1.0", 52 | "@commitlint/config-conventional": "^16.0.0", 53 | "@testing-library/jest-dom": "^5.16.5", 54 | "@testing-library/react": "^13.4.0", 55 | "@types/jest": "^29.0.0", 56 | "@types/react": "^18.0.18", 57 | "@typescript-eslint/eslint-plugin": "^5.10.0", 58 | "@typescript-eslint/parser": "^5.10.0", 59 | "commitizen": "^4.2.4", 60 | "cross-env": "^7.0.3", 61 | "cz-conventional-changelog": "^3.3.0", 62 | "esbuild": "^0.15.7", 63 | "esbuild-jest": "^0.5.0", 64 | "eslint": "^8.7.0", 65 | "eslint-config-airbnb": "^19.0.4", 66 | "eslint-config-airbnb-typescript": "^16.1.0", 67 | "eslint-config-prettier": "^8.3.0", 68 | "eslint-plugin-import": "^2.25.4", 69 | "eslint-plugin-jest": "^26.0.0", 70 | "eslint-plugin-jest-dom": "^4.0.1", 71 | "eslint-plugin-jsx-a11y": "^6.5.1", 72 | "eslint-plugin-prettier": "^4.0.0", 73 | "eslint-plugin-react": "^7.28.0", 74 | "eslint-plugin-react-hooks": "^4.3.0", 75 | "eslint-plugin-testing-library": "^5.0.4", 76 | "husky": "^7.0.4", 77 | "is-ci": "^3.0.1", 78 | "jest": "^29.0.2", 79 | "jest-environment-jsdom": "^29.0.2", 80 | "lint-staged": "^12.3.1", 81 | "np": "^7.6.2", 82 | "prettier": "2.5.1", 83 | "react": "^18.2.0", 84 | "react-dom": "^18.2.0", 85 | "tsup": "^6.2.3", 86 | "typescript": "^4.8.2" 87 | }, 88 | "dependencies": { 89 | "html-react-parser": "^3.0.4" 90 | }, 91 | "peerDependencies": { 92 | "react": ">=16.8.0", 93 | "react-dom": ">=16.8.0" 94 | }, 95 | "config": { 96 | "commitizen": { 97 | "path": "cz-conventional-changelog" 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/index.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import HTMLString from '.'; 4 | 5 | describe('', () => { 6 | const html = ` 7 |
8 |

9 | Hello from 10 | React HTML String 15 |

16 |
17 |
    18 |
  • Render HTML string safely
  • 19 |
  • Provide Custom Components if needed
  • 20 |
21 |

Don't forget to ⭐️ the project

22 |
23 | `; 24 | 25 | it('renders react tree successfully from html string', () => { 26 | render(); 27 | 28 | expect( 29 | screen.getByRole('heading', { 30 | level: 1, 31 | name: /hello from react html string/i, 32 | }), 33 | ).toBeInTheDocument(); 34 | expect( 35 | screen.getByRole('link', { name: /react html string/i }), 36 | ).toBeInTheDocument(); 37 | 38 | expect(screen.getByRole('separator')).toBeInTheDocument(); 39 | 40 | expect(screen.getByRole('list')).toBeInTheDocument(); 41 | expect(screen.getAllByRole('listitem')).toHaveLength(2); 42 | }); 43 | 44 | it('renders custom react components', () => { 45 | render( 46 |

Custom {children}

, 50 | a: () => HAHA this is fake link, 51 | }} 52 | />, 53 | ); 54 | 55 | expect( 56 | screen.getByRole('heading', { 57 | level: 1, 58 | name: /Custom Hello from HAHA this is fake link/i, 59 | }), 60 | ).toBeInTheDocument(); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import parse, { HTMLReactParserOptions, domToReact } from 'html-react-parser'; 3 | 4 | export type Components = { 5 | [Property in keyof React.ReactHTML]?: React.ComponentType< 6 | React.ComponentProps 7 | >; 8 | }; 9 | 10 | export type HTMLStringProps = { 11 | html: string; 12 | components?: Components; 13 | }; 14 | 15 | export default function HTMLString({ 16 | html = '', 17 | components = {}, 18 | }: HTMLStringProps) { 19 | const parserOptions: HTMLReactParserOptions = useMemo( 20 | () => ({ 21 | // @ts-ignore 22 | replace: ({ name, attribs, children: childNodes }) => { 23 | // @ts-ignore 24 | const Component = components[name]; 25 | 26 | if (!Component) return null; 27 | 28 | const children = childNodes?.length 29 | ? domToReact(childNodes, parserOptions) 30 | : null; 31 | 32 | return React.createElement(Component, { name, ...attribs }, children); 33 | }, 34 | }), 35 | [components], 36 | ); 37 | 38 | const content = useMemo( 39 | () => parse(html, parserOptions), 40 | [html, parserOptions], 41 | ); 42 | 43 | return <>{content}; 44 | } 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React HTML String", 4 | "compilerOptions": { 5 | "target": "ES2019", 6 | "module": "ESNext", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "declaration": true, 9 | "emitDeclarationOnly": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "moduleResolution": "node", 16 | "isolatedModules": true, 17 | "resolveJsonModule": true, 18 | "jsx": "react" 19 | }, 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | import { dependencies, peerDependencies } from './package.json'; 3 | 4 | export default defineConfig(options => ({ 5 | entry: ['src/index.tsx'], 6 | outDir: 'dist', 7 | sourcemap: false, 8 | clean: true, 9 | dts: true, 10 | format: options.watch ? 'esm' : ['esm', 'cjs'], 11 | external: Object.keys(dependencies).concat(Object.keys(peerDependencies)), 12 | outExtension({ format }) { 13 | return { 14 | js: `.${format}.js`, 15 | }; 16 | }, 17 | minify: !options.watch, 18 | })); 19 | --------------------------------------------------------------------------------