@knighted/jsx 1.7.9 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/cjs/internal/dom-create-element.cjs +76 -0
- package/dist/cjs/internal/dom-create-element.d.cts +21 -0
- package/dist/cjs/internal/jsx-types.cjs +2 -0
- package/dist/cjs/internal/jsx-types.d.cts +7 -0
- package/dist/cjs/jsx.cjs +13 -3
- package/dist/cjs/jsx.d.cts +9 -7
- package/dist/cjs/node/debug/index.d.cts +5 -1
- package/dist/cjs/node/index.d.cts +5 -1
- package/dist/cjs/transpile.cjs +255 -0
- package/dist/cjs/transpile.d.cts +12 -0
- package/dist/internal/dom-create-element.d.ts +21 -0
- package/dist/internal/dom-create-element.js +72 -0
- package/dist/internal/jsx-types.d.ts +7 -0
- package/dist/internal/jsx-types.js +1 -0
- package/dist/jsx.d.ts +9 -7
- package/dist/jsx.js +12 -1
- package/dist/lite/debug/index.js +5 -5
- package/dist/lite/index.js +7 -7
- package/dist/lite/node/debug/index.js +8 -8
- package/dist/lite/node/index.js +7 -7
- package/dist/node/debug/index.d.ts +5 -1
- package/dist/node/index.d.ts +5 -1
- package/dist/transpile.d.ts +12 -0
- package/dist/transpile.js +249 -0
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -68,6 +68,32 @@ const button = jsx`
|
|
|
68
68
|
document.body.append(button)
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
### Source transpilation (`transpileJsxSource`)
|
|
72
|
+
|
|
73
|
+
Need to transform raw JSX source text (e.g. code typed in an editor) without Babel? Use `transpileJsxSource`:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { transpileJsxSource } from '@knighted/jsx/transpile'
|
|
77
|
+
|
|
78
|
+
const input = `
|
|
79
|
+
const App = () => {
|
|
80
|
+
return <button>click me</button>
|
|
81
|
+
}
|
|
82
|
+
`
|
|
83
|
+
|
|
84
|
+
const { code } = transpileJsxSource(input)
|
|
85
|
+
// -> const App = () => { return React.createElement("button", null, "click me") }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
By default this emits `React.createElement(...)` and `React.Fragment`. Override them when needed:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
transpileJsxSource(input, {
|
|
92
|
+
createElement: '__jsx',
|
|
93
|
+
fragment: '__fragment',
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
|
|
71
97
|
### React runtime (`reactJsx`)
|
|
72
98
|
|
|
73
99
|
Need to compose React elements instead of DOM nodes? Import the dedicated helper from the `@knighted/jsx/react` subpath (React 18+ and `react-dom` are still required to mount the tree):
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDomCreateElement = exports.Fragment = void 0;
|
|
4
|
+
exports.Fragment = Symbol.for('@knighted/jsx::Fragment');
|
|
5
|
+
const CREATE_ELEMENT_NAMESPACE_PROP = '__jsxNs';
|
|
6
|
+
const resolveChildrenForCreateElement = (props, children) => {
|
|
7
|
+
if (children.length > 0) {
|
|
8
|
+
return children;
|
|
9
|
+
}
|
|
10
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'children')) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
return [props.children];
|
|
14
|
+
};
|
|
15
|
+
const createPropsForComponent = (props, children) => {
|
|
16
|
+
const nextProps = { ...props };
|
|
17
|
+
if (children.length === 1) {
|
|
18
|
+
nextProps.children = children[0];
|
|
19
|
+
}
|
|
20
|
+
else if (children.length > 1) {
|
|
21
|
+
nextProps.children = children;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
delete nextProps.children;
|
|
25
|
+
}
|
|
26
|
+
return nextProps;
|
|
27
|
+
};
|
|
28
|
+
const resolveNamespaceOverride = (props) => {
|
|
29
|
+
if (!Object.prototype.hasOwnProperty.call(props, CREATE_ELEMENT_NAMESPACE_PROP)) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const override = props[CREATE_ELEMENT_NAMESPACE_PROP];
|
|
33
|
+
delete props[CREATE_ELEMENT_NAMESPACE_PROP];
|
|
34
|
+
if (override === 'svg' || override === null) {
|
|
35
|
+
return override;
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`${CREATE_ELEMENT_NAMESPACE_PROP} must be "svg" or null when provided.`);
|
|
38
|
+
};
|
|
39
|
+
const createDomCreateElement = ({ ensureDomAvailable, appendChildValue, setDomProp, isPromiseLike, }) => {
|
|
40
|
+
function createElement(type, props, ...children) {
|
|
41
|
+
ensureDomAvailable();
|
|
42
|
+
const nextProps = props ? { ...props } : {};
|
|
43
|
+
const resolvedChildren = resolveChildrenForCreateElement(nextProps, children);
|
|
44
|
+
if (type === exports.Fragment) {
|
|
45
|
+
const fragment = document.createDocumentFragment();
|
|
46
|
+
resolvedChildren.forEach(child => appendChildValue(fragment, child));
|
|
47
|
+
return fragment;
|
|
48
|
+
}
|
|
49
|
+
if (typeof type === 'function') {
|
|
50
|
+
const result = type(createPropsForComponent(nextProps, resolvedChildren));
|
|
51
|
+
if (isPromiseLike(result)) {
|
|
52
|
+
throw new Error('Async jsx components are not supported.');
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
if (typeof type !== 'string') {
|
|
57
|
+
throw new Error(`Unsupported jsx createElement type: ${String(type)}`);
|
|
58
|
+
}
|
|
59
|
+
delete nextProps.children;
|
|
60
|
+
const namespaceOverride = resolveNamespaceOverride(nextProps);
|
|
61
|
+
const nextNamespace = namespaceOverride ?? (type === 'svg' ? 'svg' : null);
|
|
62
|
+
const domElement = nextNamespace === 'svg'
|
|
63
|
+
? document.createElementNS('http://www.w3.org/2000/svg', type)
|
|
64
|
+
: document.createElement(type);
|
|
65
|
+
Object.entries(nextProps).forEach(([name, value]) => {
|
|
66
|
+
if (name === 'key') {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
setDomProp(domElement, name, value, nextNamespace);
|
|
70
|
+
});
|
|
71
|
+
resolvedChildren.forEach(value => appendChildValue(domElement, value));
|
|
72
|
+
return domElement;
|
|
73
|
+
}
|
|
74
|
+
return createElement;
|
|
75
|
+
};
|
|
76
|
+
exports.createDomCreateElement = createDomCreateElement;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Namespace } from './attribute-resolution.cjs';
|
|
2
|
+
import type { JsxComponent, JsxRenderable } from './jsx-types.cjs';
|
|
3
|
+
export declare const FragmentToken: unique symbol;
|
|
4
|
+
export type JsxFragmentToken = typeof FragmentToken;
|
|
5
|
+
export declare const Fragment: JsxFragmentToken;
|
|
6
|
+
type JsxPropsRecord = Record<string, unknown>;
|
|
7
|
+
export type JsxCreateElement = {
|
|
8
|
+
(type: JsxFragmentToken, props: null, ...children: JsxRenderable[]): DocumentFragment;
|
|
9
|
+
<Props extends JsxPropsRecord>(type: JsxComponent<Props>, props: (Props & {
|
|
10
|
+
children?: JsxRenderable | JsxRenderable[];
|
|
11
|
+
}) | null, ...children: JsxRenderable[]): JsxRenderable;
|
|
12
|
+
(type: string, props: JsxPropsRecord | null, ...children: JsxRenderable[]): JsxRenderable;
|
|
13
|
+
};
|
|
14
|
+
type DomCreateElementHelpers = {
|
|
15
|
+
ensureDomAvailable: () => void;
|
|
16
|
+
appendChildValue: (parent: Node & ParentNode, value: JsxRenderable) => void;
|
|
17
|
+
setDomProp: (element: Element, name: string, value: unknown, namespace: Namespace) => void;
|
|
18
|
+
isPromiseLike: (value: unknown) => value is PromiseLike<unknown>;
|
|
19
|
+
};
|
|
20
|
+
export declare const createDomCreateElement: ({ ensureDomAvailable, appendChildValue, setDomProp, isPromiseLike, }: DomCreateElementHelpers) => JsxCreateElement;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type JsxRenderable = Node | DocumentFragment | string | number | bigint | boolean | null | undefined | Iterable<JsxRenderable>;
|
|
2
|
+
export type JsxComponent<Props = Record<string, unknown>> = {
|
|
3
|
+
(props: Props & {
|
|
4
|
+
children?: JsxRenderable | JsxRenderable[];
|
|
5
|
+
}): JsxRenderable;
|
|
6
|
+
displayName?: string;
|
|
7
|
+
};
|
package/dist/cjs/jsx.cjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.jsx = void 0;
|
|
3
|
+
exports.jsx = exports.createElement = void 0;
|
|
4
4
|
const oxc_parser_1 = require("oxc-parser");
|
|
5
5
|
const shared_js_1 = require("./runtime/shared.cjs");
|
|
6
6
|
const property_information_1 = require("property-information");
|
|
7
7
|
const attribute_resolution_js_1 = require("./internal/attribute-resolution.cjs");
|
|
8
8
|
const event_bindings_js_1 = require("./internal/event-bindings.cjs");
|
|
9
|
+
const dom_create_element_js_1 = require("./internal/dom-create-element.cjs");
|
|
9
10
|
const ensureDomAvailable = () => {
|
|
10
11
|
if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
|
|
11
12
|
throw new Error('The jsx template tag requires a DOM-like environment (document missing).');
|
|
@@ -306,7 +307,13 @@ const evaluateJsxNode = (node, ctx, namespace) => {
|
|
|
306
307
|
}
|
|
307
308
|
return evaluateJsxElement(node, ctx, namespace);
|
|
308
309
|
};
|
|
309
|
-
|
|
310
|
+
exports.createElement = (0, dom_create_element_js_1.createDomCreateElement)({
|
|
311
|
+
ensureDomAvailable,
|
|
312
|
+
appendChildValue,
|
|
313
|
+
setDomProp,
|
|
314
|
+
isPromiseLike,
|
|
315
|
+
});
|
|
316
|
+
const jsxTag = (templates, ...values) => {
|
|
310
317
|
ensureDomAvailable();
|
|
311
318
|
const build = (0, shared_js_1.buildTemplate)(templates, values);
|
|
312
319
|
const result = (0, oxc_parser_1.parseSync)('inline.jsx', build.source, shared_js_1.parserOptions);
|
|
@@ -321,4 +328,7 @@ const jsx = (templates, ...values) => {
|
|
|
321
328
|
};
|
|
322
329
|
return evaluateJsxNode(root, ctx, null);
|
|
323
330
|
};
|
|
324
|
-
exports.jsx =
|
|
331
|
+
exports.jsx = Object.assign(jsxTag, {
|
|
332
|
+
createElement: exports.createElement,
|
|
333
|
+
Fragment: dom_create_element_js_1.Fragment,
|
|
334
|
+
});
|
package/dist/cjs/jsx.d.cts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { type JsxFragmentToken, type JsxCreateElement } from './internal/dom-create-element.cjs';
|
|
2
|
+
import type { JsxRenderable } from './internal/jsx-types.cjs';
|
|
3
|
+
export type { JsxRenderable, JsxComponent } from './internal/jsx-types.cjs';
|
|
4
|
+
export declare const createElement: JsxCreateElement;
|
|
5
|
+
type JsxTaggedTemplate = {
|
|
6
|
+
(templates: TemplateStringsArray, ...values: unknown[]): JsxRenderable;
|
|
7
|
+
createElement: typeof createElement;
|
|
8
|
+
Fragment: JsxFragmentToken;
|
|
7
9
|
};
|
|
8
|
-
export declare const jsx:
|
|
10
|
+
export declare const jsx: JsxTaggedTemplate;
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
export declare const jsx:
|
|
1
|
+
export declare const jsx: {
|
|
2
|
+
(templates: TemplateStringsArray, ...values: unknown[]): import("../../jsx.cjs").JsxRenderable;
|
|
3
|
+
createElement: typeof import("../../jsx.cjs").createElement;
|
|
4
|
+
Fragment: import("../../internal/dom-create-element.cjs").JsxFragmentToken;
|
|
5
|
+
};
|
|
2
6
|
export type { JsxRenderable, JsxComponent } from '../../jsx.cjs';
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
export declare const jsx:
|
|
1
|
+
export declare const jsx: {
|
|
2
|
+
(templates: TemplateStringsArray, ...values: unknown[]): import("../jsx.cjs").JsxRenderable;
|
|
3
|
+
createElement: typeof import("../jsx.cjs").createElement;
|
|
4
|
+
Fragment: import("../internal/dom-create-element.cjs").JsxFragmentToken;
|
|
5
|
+
};
|
|
2
6
|
export type { JsxRenderable, JsxComponent } from '../jsx.cjs';
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.transpileJsxSource = transpileJsxSource;
|
|
7
|
+
const magic_string_1 = __importDefault(require("magic-string"));
|
|
8
|
+
const oxc_parser_1 = require("oxc-parser");
|
|
9
|
+
const normalize_text_js_1 = require("./shared/normalize-text.cjs");
|
|
10
|
+
const createModuleParserOptions = (sourceType) => ({
|
|
11
|
+
lang: 'tsx',
|
|
12
|
+
sourceType,
|
|
13
|
+
range: true,
|
|
14
|
+
preserveParens: true,
|
|
15
|
+
});
|
|
16
|
+
const formatParserError = (error) => {
|
|
17
|
+
let message = `[jsx] ${error.message}`;
|
|
18
|
+
if (error.labels?.length) {
|
|
19
|
+
const label = error.labels[0];
|
|
20
|
+
if (label.message) {
|
|
21
|
+
message += `\n${label.message}`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (error.codeframe) {
|
|
25
|
+
message += `\n${error.codeframe}`;
|
|
26
|
+
}
|
|
27
|
+
if (error.helpMessage) {
|
|
28
|
+
message += `\n${error.helpMessage}`;
|
|
29
|
+
}
|
|
30
|
+
return message;
|
|
31
|
+
};
|
|
32
|
+
const isObjectRecord = (value) => typeof value === 'object' && value !== null;
|
|
33
|
+
const isSourceRange = (value) => Array.isArray(value) &&
|
|
34
|
+
value.length === 2 &&
|
|
35
|
+
typeof value[0] === 'number' &&
|
|
36
|
+
typeof value[1] === 'number';
|
|
37
|
+
const hasSourceRange = (value) => isObjectRecord(value) && isSourceRange(value.range);
|
|
38
|
+
const compareByRangeStartDesc = (first, second) => second.range[0] - first.range[0];
|
|
39
|
+
class SourceJsxReactBuilder {
|
|
40
|
+
source;
|
|
41
|
+
createElementRef;
|
|
42
|
+
fragmentRef;
|
|
43
|
+
constructor(source, createElementRef, fragmentRef) {
|
|
44
|
+
this.source = source;
|
|
45
|
+
this.createElementRef = createElementRef;
|
|
46
|
+
this.fragmentRef = fragmentRef;
|
|
47
|
+
}
|
|
48
|
+
compile(node) {
|
|
49
|
+
return this.compileNode(node);
|
|
50
|
+
}
|
|
51
|
+
compileNode(node) {
|
|
52
|
+
if (node.type === 'JSXFragment') {
|
|
53
|
+
const children = this.compileChildren(node.children);
|
|
54
|
+
return this.buildCreateElement(this.fragmentRef, 'null', children);
|
|
55
|
+
}
|
|
56
|
+
const opening = node.openingElement;
|
|
57
|
+
const tagExpr = this.compileTagName(opening.name);
|
|
58
|
+
const propsExpr = this.compileProps(opening.attributes);
|
|
59
|
+
const children = this.compileChildren(node.children);
|
|
60
|
+
return this.buildCreateElement(tagExpr, propsExpr, children);
|
|
61
|
+
}
|
|
62
|
+
compileChildren(children) {
|
|
63
|
+
const compiled = [];
|
|
64
|
+
children.forEach(child => {
|
|
65
|
+
switch (child.type) {
|
|
66
|
+
case 'JSXText': {
|
|
67
|
+
const normalized = (0, normalize_text_js_1.normalizeJsxText)(child.value);
|
|
68
|
+
if (normalized) {
|
|
69
|
+
compiled.push(JSON.stringify(normalized));
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case 'JSXExpressionContainer': {
|
|
74
|
+
if (child.expression.type === 'JSXEmptyExpression') {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
compiled.push(this.compileExpression(child.expression));
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case 'JSXSpreadChild': {
|
|
81
|
+
compiled.push(this.compileExpression(child.expression));
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case 'JSXElement':
|
|
85
|
+
case 'JSXFragment': {
|
|
86
|
+
compiled.push(this.compileNode(child));
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return compiled;
|
|
92
|
+
}
|
|
93
|
+
compileProps(attributes) {
|
|
94
|
+
const segments = [];
|
|
95
|
+
let staticEntries = [];
|
|
96
|
+
const flushStatics = () => {
|
|
97
|
+
if (!staticEntries.length) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
segments.push(`{ ${staticEntries.join(', ')} }`);
|
|
101
|
+
staticEntries = [];
|
|
102
|
+
};
|
|
103
|
+
attributes.forEach(attribute => {
|
|
104
|
+
if (attribute.type === 'JSXSpreadAttribute') {
|
|
105
|
+
flushStatics();
|
|
106
|
+
const spreadValue = this.compileExpression(attribute.argument);
|
|
107
|
+
segments.push(`(${spreadValue} ?? {})`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const name = this.compileAttributeName(attribute.name);
|
|
111
|
+
let value;
|
|
112
|
+
if (!attribute.value) {
|
|
113
|
+
value = 'true';
|
|
114
|
+
}
|
|
115
|
+
else if (attribute.value.type === 'Literal') {
|
|
116
|
+
value = JSON.stringify(attribute.value.value);
|
|
117
|
+
}
|
|
118
|
+
else if (attribute.value.type === 'JSXExpressionContainer') {
|
|
119
|
+
if (attribute.value.expression.type === 'JSXEmptyExpression') {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
value = this.compileExpression(attribute.value.expression);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
value = 'undefined';
|
|
126
|
+
}
|
|
127
|
+
staticEntries.push(`${JSON.stringify(name)}: ${value}`);
|
|
128
|
+
});
|
|
129
|
+
flushStatics();
|
|
130
|
+
if (!segments.length) {
|
|
131
|
+
return 'null';
|
|
132
|
+
}
|
|
133
|
+
if (segments.length === 1) {
|
|
134
|
+
return segments[0] ?? 'null';
|
|
135
|
+
}
|
|
136
|
+
return `Object.assign({}, ${segments.join(', ')})`;
|
|
137
|
+
}
|
|
138
|
+
compileAttributeName(name) {
|
|
139
|
+
switch (name.type) {
|
|
140
|
+
case 'JSXIdentifier':
|
|
141
|
+
return name.name;
|
|
142
|
+
case 'JSXNamespacedName':
|
|
143
|
+
return `${name.namespace.name}:${name.name.name}`;
|
|
144
|
+
case 'JSXMemberExpression':
|
|
145
|
+
return `${this.compileAttributeName(name.object)}.${name.property.name}`;
|
|
146
|
+
default:
|
|
147
|
+
return '';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
compileMemberExpressionTagName(name) {
|
|
151
|
+
const parts = [];
|
|
152
|
+
let current = name;
|
|
153
|
+
while (current.type === 'JSXMemberExpression') {
|
|
154
|
+
parts.unshift(current.property.name);
|
|
155
|
+
current = current.object;
|
|
156
|
+
}
|
|
157
|
+
parts.unshift(current.name);
|
|
158
|
+
return parts.join('.');
|
|
159
|
+
}
|
|
160
|
+
compileTagName(name) {
|
|
161
|
+
if (!name) {
|
|
162
|
+
throw new Error('[jsx] Encountered JSX element without a tag name.');
|
|
163
|
+
}
|
|
164
|
+
if (name.type === 'JSXIdentifier') {
|
|
165
|
+
if (/^[A-Z]/.test(name.name)) {
|
|
166
|
+
return name.name;
|
|
167
|
+
}
|
|
168
|
+
return JSON.stringify(name.name);
|
|
169
|
+
}
|
|
170
|
+
if (name.type === 'JSXMemberExpression') {
|
|
171
|
+
return this.compileMemberExpressionTagName(name);
|
|
172
|
+
}
|
|
173
|
+
if (name.type === 'JSXNamespacedName') {
|
|
174
|
+
return JSON.stringify(`${name.namespace.name}:${name.name.name}`);
|
|
175
|
+
}
|
|
176
|
+
throw new Error('[jsx] Unsupported JSX tag expression.');
|
|
177
|
+
}
|
|
178
|
+
compileExpression(node) {
|
|
179
|
+
if (node.type === 'JSXElement' || node.type === 'JSXFragment') {
|
|
180
|
+
return this.compileNode(node);
|
|
181
|
+
}
|
|
182
|
+
if (!hasSourceRange(node)) {
|
|
183
|
+
throw new Error('[jsx] Unable to read source range for expression node.');
|
|
184
|
+
}
|
|
185
|
+
const range = node.range;
|
|
186
|
+
const nestedJsxRoots = collectRootJsxNodes(node);
|
|
187
|
+
if (!nestedJsxRoots.length) {
|
|
188
|
+
return this.source.slice(range[0], range[1]);
|
|
189
|
+
}
|
|
190
|
+
const expressionSource = this.source.slice(range[0], range[1]);
|
|
191
|
+
const magic = new magic_string_1.default(expressionSource);
|
|
192
|
+
nestedJsxRoots.sort(compareByRangeStartDesc).forEach(jsxNode => {
|
|
193
|
+
magic.overwrite(jsxNode.range[0] - range[0], jsxNode.range[1] - range[0], this.compileNode(jsxNode));
|
|
194
|
+
});
|
|
195
|
+
return magic.toString();
|
|
196
|
+
}
|
|
197
|
+
buildCreateElement(type, props, children) {
|
|
198
|
+
const args = [type, props];
|
|
199
|
+
if (children.length) {
|
|
200
|
+
args.push(children.join(', '));
|
|
201
|
+
}
|
|
202
|
+
return `${this.createElementRef}(${args.join(', ')})`;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const collectRootJsxNodes = (root) => {
|
|
206
|
+
const nodes = [];
|
|
207
|
+
const isJsxElementOrFragment = (node) => {
|
|
208
|
+
if (!isObjectRecord(node)) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
return node.type === 'JSXElement' || node.type === 'JSXFragment';
|
|
212
|
+
};
|
|
213
|
+
const walk = (value, insideJsx) => {
|
|
214
|
+
if (!isObjectRecord(value)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (Array.isArray(value)) {
|
|
218
|
+
value.forEach(entry => walk(entry, insideJsx));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const node = value;
|
|
222
|
+
const isJsxNode = isJsxElementOrFragment(node);
|
|
223
|
+
if (isJsxNode && hasSourceRange(node) && !insideJsx) {
|
|
224
|
+
nodes.push(node);
|
|
225
|
+
}
|
|
226
|
+
for (const entry of Object.values(node)) {
|
|
227
|
+
walk(entry, insideJsx || isJsxNode);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
walk(root, false);
|
|
231
|
+
return nodes;
|
|
232
|
+
};
|
|
233
|
+
function transpileJsxSource(source, options = {}) {
|
|
234
|
+
const sourceType = options.sourceType ?? 'module';
|
|
235
|
+
const createElementRef = options.createElement ?? 'React.createElement';
|
|
236
|
+
const fragmentRef = options.fragment ?? 'React.Fragment';
|
|
237
|
+
const parsed = (0, oxc_parser_1.parseSync)('transpile-jsx-source.tsx', source, createModuleParserOptions(sourceType));
|
|
238
|
+
const firstError = parsed.errors[0];
|
|
239
|
+
if (firstError) {
|
|
240
|
+
throw new Error(formatParserError(firstError));
|
|
241
|
+
}
|
|
242
|
+
const jsxRoots = collectRootJsxNodes(parsed.program);
|
|
243
|
+
if (!jsxRoots.length) {
|
|
244
|
+
return { code: source, changed: false };
|
|
245
|
+
}
|
|
246
|
+
const builder = new SourceJsxReactBuilder(source, createElementRef, fragmentRef);
|
|
247
|
+
const magic = new magic_string_1.default(source);
|
|
248
|
+
jsxRoots.sort(compareByRangeStartDesc).forEach(node => {
|
|
249
|
+
magic.overwrite(node.range[0], node.range[1], builder.compile(node));
|
|
250
|
+
});
|
|
251
|
+
return {
|
|
252
|
+
code: magic.toString(),
|
|
253
|
+
changed: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type TranspileSourceType = 'module' | 'script';
|
|
2
|
+
export type TranspileJsxSourceOptions = {
|
|
3
|
+
sourceType?: TranspileSourceType;
|
|
4
|
+
createElement?: string;
|
|
5
|
+
fragment?: string;
|
|
6
|
+
};
|
|
7
|
+
export type TranspileJsxSourceResult = {
|
|
8
|
+
code: string;
|
|
9
|
+
changed: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function transpileJsxSource(source: string, options?: TranspileJsxSourceOptions): TranspileJsxSourceResult;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Namespace } from './attribute-resolution.js';
|
|
2
|
+
import type { JsxComponent, JsxRenderable } from './jsx-types.js';
|
|
3
|
+
export declare const FragmentToken: unique symbol;
|
|
4
|
+
export type JsxFragmentToken = typeof FragmentToken;
|
|
5
|
+
export declare const Fragment: JsxFragmentToken;
|
|
6
|
+
type JsxPropsRecord = Record<string, unknown>;
|
|
7
|
+
export type JsxCreateElement = {
|
|
8
|
+
(type: JsxFragmentToken, props: null, ...children: JsxRenderable[]): DocumentFragment;
|
|
9
|
+
<Props extends JsxPropsRecord>(type: JsxComponent<Props>, props: (Props & {
|
|
10
|
+
children?: JsxRenderable | JsxRenderable[];
|
|
11
|
+
}) | null, ...children: JsxRenderable[]): JsxRenderable;
|
|
12
|
+
(type: string, props: JsxPropsRecord | null, ...children: JsxRenderable[]): JsxRenderable;
|
|
13
|
+
};
|
|
14
|
+
type DomCreateElementHelpers = {
|
|
15
|
+
ensureDomAvailable: () => void;
|
|
16
|
+
appendChildValue: (parent: Node & ParentNode, value: JsxRenderable) => void;
|
|
17
|
+
setDomProp: (element: Element, name: string, value: unknown, namespace: Namespace) => void;
|
|
18
|
+
isPromiseLike: (value: unknown) => value is PromiseLike<unknown>;
|
|
19
|
+
};
|
|
20
|
+
export declare const createDomCreateElement: ({ ensureDomAvailable, appendChildValue, setDomProp, isPromiseLike, }: DomCreateElementHelpers) => JsxCreateElement;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export const Fragment = Symbol.for('@knighted/jsx::Fragment');
|
|
2
|
+
const CREATE_ELEMENT_NAMESPACE_PROP = '__jsxNs';
|
|
3
|
+
const resolveChildrenForCreateElement = (props, children) => {
|
|
4
|
+
if (children.length > 0) {
|
|
5
|
+
return children;
|
|
6
|
+
}
|
|
7
|
+
if (!Object.prototype.hasOwnProperty.call(props, 'children')) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
return [props.children];
|
|
11
|
+
};
|
|
12
|
+
const createPropsForComponent = (props, children) => {
|
|
13
|
+
const nextProps = { ...props };
|
|
14
|
+
if (children.length === 1) {
|
|
15
|
+
nextProps.children = children[0];
|
|
16
|
+
}
|
|
17
|
+
else if (children.length > 1) {
|
|
18
|
+
nextProps.children = children;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
delete nextProps.children;
|
|
22
|
+
}
|
|
23
|
+
return nextProps;
|
|
24
|
+
};
|
|
25
|
+
const resolveNamespaceOverride = (props) => {
|
|
26
|
+
if (!Object.prototype.hasOwnProperty.call(props, CREATE_ELEMENT_NAMESPACE_PROP)) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const override = props[CREATE_ELEMENT_NAMESPACE_PROP];
|
|
30
|
+
delete props[CREATE_ELEMENT_NAMESPACE_PROP];
|
|
31
|
+
if (override === 'svg' || override === null) {
|
|
32
|
+
return override;
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`${CREATE_ELEMENT_NAMESPACE_PROP} must be "svg" or null when provided.`);
|
|
35
|
+
};
|
|
36
|
+
export const createDomCreateElement = ({ ensureDomAvailable, appendChildValue, setDomProp, isPromiseLike, }) => {
|
|
37
|
+
function createElement(type, props, ...children) {
|
|
38
|
+
ensureDomAvailable();
|
|
39
|
+
const nextProps = props ? { ...props } : {};
|
|
40
|
+
const resolvedChildren = resolveChildrenForCreateElement(nextProps, children);
|
|
41
|
+
if (type === Fragment) {
|
|
42
|
+
const fragment = document.createDocumentFragment();
|
|
43
|
+
resolvedChildren.forEach(child => appendChildValue(fragment, child));
|
|
44
|
+
return fragment;
|
|
45
|
+
}
|
|
46
|
+
if (typeof type === 'function') {
|
|
47
|
+
const result = type(createPropsForComponent(nextProps, resolvedChildren));
|
|
48
|
+
if (isPromiseLike(result)) {
|
|
49
|
+
throw new Error('Async jsx components are not supported.');
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
if (typeof type !== 'string') {
|
|
54
|
+
throw new Error(`Unsupported jsx createElement type: ${String(type)}`);
|
|
55
|
+
}
|
|
56
|
+
delete nextProps.children;
|
|
57
|
+
const namespaceOverride = resolveNamespaceOverride(nextProps);
|
|
58
|
+
const nextNamespace = namespaceOverride ?? (type === 'svg' ? 'svg' : null);
|
|
59
|
+
const domElement = nextNamespace === 'svg'
|
|
60
|
+
? document.createElementNS('http://www.w3.org/2000/svg', type)
|
|
61
|
+
: document.createElement(type);
|
|
62
|
+
Object.entries(nextProps).forEach(([name, value]) => {
|
|
63
|
+
if (name === 'key') {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
setDomProp(domElement, name, value, nextNamespace);
|
|
67
|
+
});
|
|
68
|
+
resolvedChildren.forEach(value => appendChildValue(domElement, value));
|
|
69
|
+
return domElement;
|
|
70
|
+
}
|
|
71
|
+
return createElement;
|
|
72
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type JsxRenderable = Node | DocumentFragment | string | number | bigint | boolean | null | undefined | Iterable<JsxRenderable>;
|
|
2
|
+
export type JsxComponent<Props = Record<string, unknown>> = {
|
|
3
|
+
(props: Props & {
|
|
4
|
+
children?: JsxRenderable | JsxRenderable[];
|
|
5
|
+
}): JsxRenderable;
|
|
6
|
+
displayName?: string;
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/jsx.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { type JsxFragmentToken, type JsxCreateElement } from './internal/dom-create-element.js';
|
|
2
|
+
import type { JsxRenderable } from './internal/jsx-types.js';
|
|
3
|
+
export type { JsxRenderable, JsxComponent } from './internal/jsx-types.js';
|
|
4
|
+
export declare const createElement: JsxCreateElement;
|
|
5
|
+
type JsxTaggedTemplate = {
|
|
6
|
+
(templates: TemplateStringsArray, ...values: unknown[]): JsxRenderable;
|
|
7
|
+
createElement: typeof createElement;
|
|
8
|
+
Fragment: JsxFragmentToken;
|
|
7
9
|
};
|
|
8
|
-
export declare const jsx:
|
|
10
|
+
export declare const jsx: JsxTaggedTemplate;
|
package/dist/jsx.js
CHANGED
|
@@ -3,6 +3,7 @@ import { buildTemplate, evaluateExpression, extractRootNode, formatTaggedTemplat
|
|
|
3
3
|
import { find as findPropertyInfo, html as htmlProperties, svg as svgProperties, } from 'property-information';
|
|
4
4
|
import { createResolveAttributes, } from './internal/attribute-resolution.js';
|
|
5
5
|
import { parseEventPropName, resolveEventHandlerValue, } from './internal/event-bindings.js';
|
|
6
|
+
import { Fragment, createDomCreateElement, } from './internal/dom-create-element.js';
|
|
6
7
|
const ensureDomAvailable = () => {
|
|
7
8
|
if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
|
|
8
9
|
throw new Error('The jsx template tag requires a DOM-like environment (document missing).');
|
|
@@ -303,7 +304,13 @@ const evaluateJsxNode = (node, ctx, namespace) => {
|
|
|
303
304
|
}
|
|
304
305
|
return evaluateJsxElement(node, ctx, namespace);
|
|
305
306
|
};
|
|
306
|
-
export const
|
|
307
|
+
export const createElement = createDomCreateElement({
|
|
308
|
+
ensureDomAvailable,
|
|
309
|
+
appendChildValue,
|
|
310
|
+
setDomProp,
|
|
311
|
+
isPromiseLike,
|
|
312
|
+
});
|
|
313
|
+
const jsxTag = (templates, ...values) => {
|
|
307
314
|
ensureDomAvailable();
|
|
308
315
|
const build = buildTemplate(templates, values);
|
|
309
316
|
const result = parseSync('inline.jsx', build.source, parserOptions);
|
|
@@ -318,3 +325,7 @@ export const jsx = (templates, ...values) => {
|
|
|
318
325
|
};
|
|
319
326
|
return evaluateJsxNode(root, ctx, null);
|
|
320
327
|
};
|
|
328
|
+
export const jsx = Object.assign(jsxTag, {
|
|
329
|
+
createElement,
|
|
330
|
+
Fragment,
|
|
331
|
+
});
|