react-from-dom
Advanced tools
Comparing version 0.6.2 to 0.7.0
106
package.json
{ | ||
"name": "react-from-dom", | ||
"version": "0.6.2", | ||
"version": "0.7.0", | ||
"description": "Convert HTML/XML source code or DOM nodes to React elements", | ||
@@ -23,10 +23,13 @@ "author": "Gil Barbara <gilbarbara@gmail.com>", | ||
"homepage": "https://github.com/gilbarbara/react-from-dom#readme", | ||
"main": "lib/index.js", | ||
"module": "esm/index.js", | ||
"main": "./dist/index.js", | ||
"module": "./dist/index.mjs", | ||
"exports": { | ||
"import": "./dist/index.mjs", | ||
"require": "./dist/index.js" | ||
}, | ||
"files": [ | ||
"esm", | ||
"lib", | ||
"dist", | ||
"src" | ||
], | ||
"types": "./lib", | ||
"types": "dist/index.d.ts", | ||
"sideEffects": false, | ||
@@ -37,44 +40,57 @@ "peerDependencies": { | ||
"devDependencies": { | ||
"@gilbarbara/eslint-config": "^0.2.1", | ||
"@gilbarbara/prettier-config": "^0.1.0", | ||
"@gilbarbara/tsconfig": "^0.1.0", | ||
"@size-limit/preset-small-lib": "^7.0.8", | ||
"@testing-library/jest-dom": "^5.16.4", | ||
"@testing-library/react": "^13.0.1", | ||
"@types/jest": "^27.4.1", | ||
"@types/node": "^17.0.23", | ||
"@types/react": "^18.0.3", | ||
"@types/react-dom": "^18.0.0", | ||
"del-cli": "^4.0.1", | ||
"husky": "^7.0.4", | ||
"@arethetypeswrong/cli": "^0.15.0", | ||
"@gilbarbara/eslint-config": "^0.7.4", | ||
"@gilbarbara/prettier-config": "^1.0.0", | ||
"@gilbarbara/tsconfig": "^0.2.3", | ||
"@size-limit/preset-small-lib": "^11.0.2", | ||
"@swc/core": "^1.4.2", | ||
"@testing-library/jest-dom": "^6.4.2", | ||
"@testing-library/react": "^14.2.1", | ||
"@types/node": "^20.11.21", | ||
"@types/react": "^18.2.60", | ||
"@types/react-dom": "^18.2.19", | ||
"@vitejs/plugin-react-swc": "^3.6.0", | ||
"@vitest/coverage-v8": "^1.3.1", | ||
"del-cli": "^5.1.0", | ||
"husky": "^9.0.11", | ||
"is-ci-cli": "^2.2.0", | ||
"jest": "^27.5.1", | ||
"jest-extended": "^2.0.0", | ||
"jest-watch-typeahead": "^1.0.0", | ||
"react": "^18.0.0", | ||
"react-dom": "^18.0.0", | ||
"repo-tools": "^0.2.2", | ||
"size-limit": "^7.0.8", | ||
"ts-jest": "^27.1.4", | ||
"ts-node": "^10.7.0", | ||
"typescript": "^4.6.3" | ||
"jest-extended": "^4.0.2", | ||
"jsdom": "^24.0.0", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"repo-tools": "^0.3.1", | ||
"size-limit": "^11.0.2", | ||
"ts-node": "^10.9.2", | ||
"tsup": "^8.0.2", | ||
"typescript": "^5.3.3", | ||
"vitest": "^1.3.1" | ||
}, | ||
"scripts": { | ||
"build": "npm run clean && npm run build:cjs && npm run build:esm", | ||
"build:cjs": "tsc", | ||
"build:esm": "tsc -m es6 --outDir esm", | ||
"watch:cjs": "npm run build:cjs -- -w", | ||
"watch:esm": "npm run build:esm -- -w", | ||
"clean": "del lib/* && del esm/*", | ||
"lint": "eslint --ext .ts,.tsx src test", | ||
"build": "npm run clean && tsup", | ||
"watch": "tsup --watch", | ||
"clean": "del dist/*", | ||
"lint": "eslint --fix src test", | ||
"test": "is-ci \"test:coverage\" \"test:watch\"", | ||
"test:coverage": "jest --coverage --bail", | ||
"test:watch": "jest --watchAll --verbose", | ||
"typecheck": "tsc --noEmit", | ||
"test:coverage": "vitest run --coverage", | ||
"test:watch": "vitest watch", | ||
"typecheck": "tsc", | ||
"typevalidation": "attw -P", | ||
"format": "prettier \"**/*.{js,jsx,json,yml,yaml,css,less,scss,ts,tsx,md,graphql,mdx}\" --write", | ||
"validate": "npm run lint && npm run typecheck && npm run test:coverage && npm run build && npm run size", | ||
"validate": "npm run lint && npm run typecheck && npm run test:coverage && npm run build && npm run size && npm run typevalidation", | ||
"size": "size-limit", | ||
"prepublishOnly": "npm run validate", | ||
"prepare": "husky install" | ||
"prepare": "husky" | ||
}, | ||
"tsup": { | ||
"dts": true, | ||
"entry": [ | ||
"src/index.ts" | ||
], | ||
"format": [ | ||
"cjs", | ||
"esm" | ||
], | ||
"sourcemap": true, | ||
"splitting": false | ||
}, | ||
"eslintConfig": { | ||
@@ -88,12 +104,12 @@ "extends": [ | ||
{ | ||
"name": "lib", | ||
"path": "./lib/index.js", | ||
"limit": "6 kB" | ||
"name": "commonjs", | ||
"path": "./dist/index.js", | ||
"limit": "5 kB" | ||
}, | ||
{ | ||
"name": "esm", | ||
"path": "./esm/index.js", | ||
"limit": "6 kB" | ||
"path": "./dist/index.mjs", | ||
"limit": "5 kB" | ||
} | ||
] | ||
} |
export const styleToObject = (input: string): Record<string, any> => { | ||
const attributes = input.split(/ ?; ?/); | ||
/* c8 ignore next 3 */ | ||
if (typeof input !== 'string') { | ||
return {}; | ||
} | ||
return attributes.reduce((acc: Record<string, any>, d: string) => { | ||
const attributes = input.replace(/\n/, '').split(/ ?; ?/); | ||
return attributes.reduce<Record<string, string | number>>((acc, d: string) => { | ||
const [key, value] = d.split(/ ?: ?/); | ||
if (key && value) { | ||
acc[key.replace(/-(\w)/g, (_$0, $1) => $1.toUpperCase())] = Number.isNaN(Number(value)) | ||
const nextKey = key.replace(/-(\w)/g, (_$0, $1) => $1.toUpperCase()); | ||
acc[key.startsWith('-') ? key : nextKey] = Number.isNaN(Number(value)) | ||
? value | ||
@@ -17,3 +24,2 @@ : Number(value); | ||
/* istanbul ignore next */ | ||
export function randomString(length = 6): string { | ||
@@ -20,0 +26,0 @@ const characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; |
186
src/index.ts
@@ -1,2 +0,1 @@ | ||
/* eslint-disable @typescript-eslint/no-use-before-define */ | ||
import * as React from 'react'; | ||
@@ -7,11 +6,48 @@ | ||
export interface Options { | ||
/** | ||
* An array of actions to modify the nodes before they are converted to ReactNodes. | ||
*/ | ||
actions?: Action[]; | ||
/** | ||
* Skip removing white spaces in the output. | ||
*/ | ||
allowWhiteSpaces?: boolean; | ||
/** | ||
* Parse all nodes instead of just a single parent node. | ||
* This will return a ReactNode array (or a NodeList if `nodeOnly` is true). | ||
*/ | ||
includeAllNodes?: boolean; | ||
/** | ||
* The index to start with. | ||
* @default 0 | ||
*/ | ||
index?: number; | ||
/** | ||
* The level to start with. | ||
* @default 0 | ||
*/ | ||
level?: number; | ||
/** | ||
* Only return the node (or NodeList) without converting it to a ReactNode. | ||
*/ | ||
nodeOnly?: boolean; | ||
/** | ||
* Add a random key to the root element. | ||
* @default false | ||
*/ | ||
randomKey?: boolean; | ||
/** | ||
* The selector to use for in the `document.querySelector` method. | ||
* @default 'body > *' | ||
*/ | ||
selector?: string; | ||
type?: string; | ||
/** | ||
* The type of the input string. | ||
* @default 'text/html' | ||
*/ | ||
type?: DOMParserSupportedType; | ||
} | ||
export type Output = React.ReactNode | Node | NodeList; | ||
interface Attributes { | ||
@@ -23,2 +59,7 @@ [index: string]: any; | ||
interface GetReactNodeOptions extends Options { | ||
key: string; | ||
level: number; | ||
} | ||
export interface Action { | ||
@@ -37,2 +78,58 @@ // If this returns true, the two following functions are called if they are defined | ||
function getReactNode(node: Node, options: GetReactNodeOptions): React.ReactNode { | ||
const { key, level, ...rest } = options; | ||
switch (node.nodeType) { | ||
case 1: { | ||
// regular dom-node | ||
return React.createElement( | ||
parseName(node.nodeName), | ||
parseAttributes(node, key), | ||
parseChildren(node.childNodes, level, rest), | ||
); | ||
} | ||
case 3: { | ||
// textnode | ||
const nodeText = node.nodeValue?.toString() ?? ''; | ||
if (!rest.allowWhiteSpaces && /^\s+$/.test(nodeText) && !/[\u00A0\u202F]/.test(nodeText)) { | ||
return null; | ||
} | ||
/* c8 ignore next 3 */ | ||
if (!node.parentNode) { | ||
return nodeText; | ||
} | ||
const parentNodeName = node.parentNode.nodeName.toLowerCase(); | ||
if (noTextChildNodes.includes(parentNodeName)) { | ||
if (/\S/.test(nodeText)) { | ||
// eslint-disable-next-line no-console | ||
console.warn( | ||
`A textNode is not allowed inside '${parentNodeName}'. Your text "${nodeText}" will be ignored`, | ||
); | ||
} | ||
return null; | ||
} | ||
return nodeText; | ||
} | ||
case 8: { | ||
// html-comment | ||
return null; | ||
} | ||
case 11: { | ||
// fragment | ||
return parseChildren(node.childNodes, level, options); | ||
} | ||
/* c8 ignore next 3 */ | ||
default: { | ||
return null; | ||
} | ||
} | ||
} | ||
function parseAttributes(node: Node, reactKey: string): Attributes { | ||
@@ -43,3 +140,2 @@ const attributes: Attributes = { | ||
/* istanbul ignore else */ | ||
if (node instanceof Element) { | ||
@@ -137,3 +233,2 @@ const nodeClassNames = node.getAttribute('class'); | ||
/* istanbul ignore else */ | ||
if (Array.isArray(actions)) { | ||
@@ -148,3 +243,2 @@ actions.forEach((action: Action) => { | ||
/* istanbul ignore else */ | ||
if (process.env.NODE_ENV !== 'production') { | ||
@@ -170,64 +264,34 @@ // eslint-disable-next-line no-console | ||
switch (node.nodeType) { | ||
case 1: { | ||
// regular dom-node | ||
return React.createElement( | ||
parseName(node.nodeName), | ||
parseAttributes(node, key), | ||
parseChildren(node.childNodes, level, options), | ||
); | ||
} | ||
case 3: { | ||
// textnode | ||
const nodeText = node.nodeValue?.toString() || ''; | ||
return getReactNode(node, { key, level, ...options }); | ||
} | ||
/* istanbul ignore else */ | ||
if (/^\s+$/.test(nodeText) && !/[\u00A0\u202F]/.test(nodeText)) { | ||
return null; | ||
} | ||
export function convertFromString(input: string, options: Options = {}): Output { | ||
if (!input || typeof input !== 'string') { | ||
return null; | ||
} | ||
/* istanbul ignore next */ | ||
if (!node.parentNode) { | ||
return nodeText; | ||
} | ||
const { | ||
includeAllNodes = false, | ||
nodeOnly = false, | ||
selector = 'body > *', | ||
type = 'text/html', | ||
} = options; | ||
const parentNodeName = node.parentNode.nodeName.toLowerCase(); | ||
try { | ||
const parser = new DOMParser(); | ||
const document = parser.parseFromString(input, type); | ||
if (noTextChildNodes.includes(parentNodeName)) { | ||
/* istanbul ignore else */ | ||
if (/\S/.test(nodeText)) { | ||
// eslint-disable-next-line no-console | ||
console.warn( | ||
`A textNode is not allowed inside '${parentNodeName}'. Your text "${nodeText}" will be ignored`, | ||
); | ||
} | ||
if (includeAllNodes) { | ||
const { childNodes } = document.body; | ||
return null; | ||
if (nodeOnly) { | ||
return childNodes; | ||
} | ||
return nodeText; | ||
return [...childNodes].map(node => convertFromNode(node, options)); | ||
} | ||
case 8: { | ||
// html-comment | ||
return null; | ||
} | ||
/* istanbul ignore next */ | ||
default: { | ||
return null; | ||
} | ||
} | ||
} | ||
export function convertFromString(input: string, options: Options = {}): React.ReactNode | Node { | ||
if (!input || typeof input !== 'string') { | ||
return null; | ||
} | ||
const node = document.querySelector(selector) || document.body.childNodes[0]; | ||
const { nodeOnly = false, selector = 'body > *', type = 'text/html' } = options; | ||
try { | ||
const parser = new DOMParser(); | ||
const document = parser.parseFromString(input, type as DOMParserSupportedType); | ||
const node = document.querySelector(selector); | ||
/* c8 ignore next 3 */ | ||
if (!(node instanceof Node)) { | ||
@@ -242,4 +306,4 @@ throw new TypeError('Error parsing input'); | ||
return convertFromNode(node, options); | ||
/* c8 ignore start */ | ||
} catch (error) { | ||
/* istanbul ignore else */ | ||
if (process.env.NODE_ENV !== 'production') { | ||
@@ -252,8 +316,6 @@ // eslint-disable-next-line no-console | ||
return null; | ||
/* c8 ignore stop */ | ||
} | ||
export default function convert( | ||
input: Node | string, | ||
options: Options = {}, | ||
): React.ReactNode | Node { | ||
export default function convert(input: Node | string, options: Options = {}): Output { | ||
if (typeof input === 'string') { | ||
@@ -260,0 +322,0 @@ return convertFromString(input, options); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
129432
26
11
1915
1