Skip to content

Commit afb9a75

Browse files
authored
fix(): do not bun-dle react/react-dom (#294)
* add a proper bun dependency on TroyAlford/basis * add better types for Component prop, and add JSDoc to all props/methods
1 parent 61cf8ab commit afb9a75

4 files changed

Lines changed: 212 additions & 10 deletions

File tree

bun.lockb

5.41 KB
Binary file not shown.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"@types/bun": "^1.1.6",
1616
"@typescript-eslint/eslint-plugin": "^7.15.0",
1717
"@typescript-eslint/parser": "^7.15.0",
18-
"basis": "TroyAlford/basis",
18+
"basis": "github:TroyAlford/basis#v1.1.0",
1919
"concurrently": "^8.2.2",
2020
"cross-env": "^7.0.3",
2121
"eslint": "^8.57.0",
@@ -55,7 +55,7 @@
5555
"repository": "TroyAlford/react-jsx-parser",
5656
"scripts": {
5757
"build": "bun build:types && bun build:code",
58-
"build:code": "bun build --target=browser --outfile=./dist/react-jsx-parser.min.js ./source/index.ts",
58+
"build:code": "bun build --target=browser --outfile=./dist/react-jsx-parser.min.js ./source/index.ts --external react --external react-dom",
5959
"build:types": "bun run tsc -p ./tsconfig.json -d --emitDeclarationOnly",
6060
"develop": "NODE_ENV=production concurrently -n build,ts,demo -c green,cyan,yellow \"bun build:code --watch\" \"bun build:types --watch\" \"bun serve\"",
6161
"lint": "bun eslint --ext .js,.ts,.tsx source/",

source/components/JsxParser.tsx

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as Acorn from 'acorn'
22
import * as AcornJSX from 'acorn-jsx'
3-
import React, { ComponentType, ExoticComponent, Fragment } from 'react'
3+
import React, { Fragment } from 'react'
44
import ATTRIBUTES from '../constants/attributeNames'
55
import { canHaveChildren, canHaveWhitespace } from '../constants/specialTags'
66
import { NullishShortCircuit } from '../errors/NullishShortCircuit'
@@ -14,24 +14,74 @@ function handleNaN<T>(child: T): T | 'NaN' {
1414

1515
type ParsedJSX = React.ReactNode | boolean | string
1616
type ParsedTree = ParsedJSX | ParsedJSX[] | null
17+
18+
/**
19+
* Props for the JsxParser component
20+
*/
1721
export type TProps = {
22+
/** Whether to allow rendering of unrecognized HTML elements. Defaults to true. */
1823
allowUnknownElements?: boolean,
24+
25+
/**
26+
* Whether to auto-close void elements like <img>, <br>, <hr> etc. in HTML style.
27+
* Defaults to false.
28+
*/
1929
autoCloseVoidElements?: boolean,
30+
31+
/** Object containing values that can be referenced in the JSX string */
2032
bindings?: { [key: string]: unknown; },
33+
34+
/**
35+
* Array of attribute names or RegExp patterns to blacklist.
36+
* By default removes 'on*' attributes
37+
*/
2138
blacklistedAttrs?: Array<string | RegExp>,
39+
40+
/**
41+
* Array of HTML tag names to blacklist.
42+
* By default removes 'script' tags
43+
*/
2244
blacklistedTags?: string[],
45+
46+
/** CSS class name(s) to add to the wrapper div */
2347
className?: string,
24-
components?: Record<string, ComponentType | ExoticComponent>,
48+
49+
/** Map of component names to their React component definitions */
50+
components?: Record<
51+
string,
52+
| React.ComponentType // allows for class components
53+
| React.ExoticComponent // allows for forwardRef
54+
| (() => React.ReactNode) // allows for function components
55+
>,
56+
57+
/** If true, only renders custom components defined in the components prop */
2558
componentsOnly?: boolean,
59+
60+
/** If true, disables usage of React.Fragment. May affect whitespace handling */
2661
disableFragments?: boolean,
62+
63+
/** If true, disables automatic generation of key props */
2764
disableKeyGeneration?: boolean,
65+
66+
/** The JSX string to parse and render */
2867
jsx?: string,
68+
69+
/** Callback function when parsing/rendering errors occur */
2970
onError?: (error: Error) => void,
71+
72+
/** If true, shows parsing/rendering warnings in console */
3073
showWarnings?: boolean,
74+
75+
/** Custom error renderer function */
3176
renderError?: (props: { error: string }) => React.ReactNode | null,
77+
78+
/** Whether to wrap output in a div. If false, renders children directly */
3279
renderInWrapper?: boolean,
80+
81+
/** Custom renderer for unrecognized elements */
3382
renderUnrecognized?: (tagName: string) => React.ReactNode | null,
3483
}
84+
3585
type Scope = Record<string, any>
3686

3787
export default class JsxParser extends React.Component<TProps> {
@@ -55,8 +105,14 @@ export default class JsxParser extends React.Component<TProps> {
55105
renderUnrecognized: () => null,
56106
}
57107

108+
/** Stores the parsed React elements */
58109
private ParsedChildren: ParsedTree = null
59110

111+
/**
112+
* Parses a JSX string into React elements
113+
* @param jsx - The JSX string to parse
114+
* @returns The parsed React node(s) or null if parsing fails
115+
*/
60116
#parseJSX = (jsx: string): React.ReactNode | React.ReactNode[] | null => {
61117
const parser = Acorn.Parser.extend(AcornJSX.default({
62118
autoCloseVoidElements: this.props.autoCloseVoidElements,
@@ -80,6 +136,12 @@ export default class JsxParser extends React.Component<TProps> {
80136
return parsed.map(p => this.#parseExpression(p)).filter(Boolean)
81137
}
82138

139+
/**
140+
* Parses a single JSX expression into its corresponding value/element
141+
* @param expression - The JSX expression to parse
142+
* @param scope - Optional scope for variable resolution
143+
* @returns The parsed value/element
144+
*/
83145
#parseExpression = (expression: AcornJSX.Expression, scope?: Scope): any => {
84146
switch (expression.type) {
85147
case 'JSXAttribute':
@@ -204,6 +266,12 @@ export default class JsxParser extends React.Component<TProps> {
204266
}
205267
}
206268

269+
/**
270+
* Parses a chain expression (optional chaining)
271+
* @param expression - The chain expression to parse
272+
* @param scope - Optional scope for variable resolution
273+
* @returns The parsed value
274+
*/
207275
#parseChainExpression = (expression: AcornJSX.ChainExpression, scope?: Scope): any => {
208276
try {
209277
return this.#parseExpression(expression.expression, scope)
@@ -213,6 +281,12 @@ export default class JsxParser extends React.Component<TProps> {
213281
}
214282
}
215283

284+
/**
285+
* Parses a member expression (e.g., obj.prop or obj['prop'])
286+
* @param expression - The member expression to parse
287+
* @param scope - Optional scope for variable resolution
288+
* @returns The resolved member value
289+
*/
216290
#parseMemberExpression = (expression: AcornJSX.MemberExpression, scope?: Scope): any => {
217291
const object = this.#parseExpression(expression.object, scope)
218292

@@ -242,11 +316,22 @@ export default class JsxParser extends React.Component<TProps> {
242316
return member
243317
}
244318

319+
/**
320+
* Parses a JSX element name (simple or member expression)
321+
* @param element - The JSX identifier or member expression
322+
* @returns The parsed element name as a string
323+
*/
245324
#parseName = (element: AcornJSX.JSXIdentifier | AcornJSX.JSXMemberExpression): string => {
246325
if (element.type === 'JSXIdentifier') { return element.name }
247326
return `${this.#parseName(element.object)}.${this.#parseName(element.property)}`
248327
}
249328

329+
/**
330+
* Parses a JSX element into React elements
331+
* @param element - The JSX element to parse
332+
* @param scope - Optional scope for variable resolution
333+
* @returns The parsed React node(s) or null
334+
*/
250335
#parseElement = (
251336
element: AcornJSX.JSXElement | AcornJSX.JSXFragment,
252337
scope?: Scope,
@@ -357,6 +442,10 @@ export default class JsxParser extends React.Component<TProps> {
357442
return React.createElement(component || lowerName, props, children)
358443
}
359444

445+
/**
446+
* Renders the parsed JSX content
447+
* @returns The rendered React elements wrapped in a div (if renderInWrapper is true)
448+
*/
360449
render() {
361450
const jsx = (this.props.jsx || '').trim().replace(/<!DOCTYPE([^>]*)>/g, '')
362451
this.ParsedChildren = this.#parseJSX(jsx)

0 commit comments

Comments
 (0)