@knighted/jsx 1.1.0 → 1.2.0-rc.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 +43 -4
- package/dist/cjs/jsx.cjs +18 -172
- package/dist/cjs/loader/jsx.cjs +23 -12
- package/dist/cjs/loader/jsx.d.cts +5 -0
- package/dist/cjs/react/index.cjs +5 -0
- package/dist/cjs/react/index.d.cts +2 -0
- package/dist/cjs/react/react-jsx.cjs +142 -0
- package/dist/cjs/react/react-jsx.d.cts +5 -0
- package/dist/cjs/runtime/shared.cjs +169 -0
- package/dist/cjs/runtime/shared.d.cts +38 -0
- package/dist/jsx.js +11 -165
- package/dist/lite/index.js +3 -3
- package/dist/loader/jsx.d.ts +5 -0
- package/dist/loader/jsx.js +23 -12
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +1 -0
- package/dist/react/react-jsx.d.ts +5 -0
- package/dist/react/react-jsx.js +138 -0
- package/dist/runtime/shared.d.ts +38 -0
- package/dist/runtime/shared.js +156 -0
- package/package.json +14 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildTemplate = exports.ensureBinding = exports.sanitizeIdentifier = exports.evaluateExpression = exports.collectPlaceholderNames = exports.normalizeJsxText = exports.walkAst = exports.getIdentifierName = exports.extractRootNode = exports.formatParserError = exports.parserOptions = void 0;
|
|
4
|
+
const OPEN_TAG_RE = /<\s*$/;
|
|
5
|
+
const CLOSE_TAG_RE = /<\/\s*$/;
|
|
6
|
+
const PLACEHOLDER_PREFIX = '__KX_EXPR__';
|
|
7
|
+
let invocationCounter = 0;
|
|
8
|
+
exports.parserOptions = {
|
|
9
|
+
lang: 'jsx',
|
|
10
|
+
sourceType: 'module',
|
|
11
|
+
range: true,
|
|
12
|
+
preserveParens: true,
|
|
13
|
+
};
|
|
14
|
+
const formatParserError = (error) => {
|
|
15
|
+
let message = `[oxc-parser] ${error.message}`;
|
|
16
|
+
if (error.labels?.length) {
|
|
17
|
+
const label = error.labels[0];
|
|
18
|
+
if (label.message) {
|
|
19
|
+
message += `\n${label.message}`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (error.codeframe) {
|
|
23
|
+
message += `\n${error.codeframe}`;
|
|
24
|
+
}
|
|
25
|
+
return message;
|
|
26
|
+
};
|
|
27
|
+
exports.formatParserError = formatParserError;
|
|
28
|
+
const extractRootNode = (program) => {
|
|
29
|
+
for (const statement of program.body) {
|
|
30
|
+
if (statement.type === 'ExpressionStatement') {
|
|
31
|
+
const expression = statement.expression;
|
|
32
|
+
if (expression.type === 'JSXElement' || expression.type === 'JSXFragment') {
|
|
33
|
+
return expression;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
throw new Error('The jsx template must contain a single JSX element or fragment.');
|
|
38
|
+
};
|
|
39
|
+
exports.extractRootNode = extractRootNode;
|
|
40
|
+
const getIdentifierName = (identifier) => {
|
|
41
|
+
switch (identifier.type) {
|
|
42
|
+
case 'JSXIdentifier':
|
|
43
|
+
return identifier.name;
|
|
44
|
+
case 'JSXNamespacedName':
|
|
45
|
+
return `${identifier.namespace.name}:${identifier.name.name}`;
|
|
46
|
+
case 'JSXMemberExpression':
|
|
47
|
+
return `${(0, exports.getIdentifierName)(identifier.object)}.${identifier.property.name}`;
|
|
48
|
+
default:
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
exports.getIdentifierName = getIdentifierName;
|
|
53
|
+
const walkAst = (node, visitor) => {
|
|
54
|
+
if (!node || typeof node !== 'object') {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const candidate = node;
|
|
58
|
+
if (typeof candidate.type !== 'string') {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
visitor(candidate);
|
|
62
|
+
Object.values(candidate).forEach(value => {
|
|
63
|
+
if (!value) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
value.forEach(child => (0, exports.walkAst)(child, visitor));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (typeof value === 'object') {
|
|
71
|
+
(0, exports.walkAst)(value, visitor);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
exports.walkAst = walkAst;
|
|
76
|
+
const normalizeJsxText = (value) => {
|
|
77
|
+
const collapsed = value.replace(/\r/g, '').replace(/\n\s+/g, ' ');
|
|
78
|
+
const trimmed = collapsed.trim();
|
|
79
|
+
return trimmed.length > 0 ? trimmed : '';
|
|
80
|
+
};
|
|
81
|
+
exports.normalizeJsxText = normalizeJsxText;
|
|
82
|
+
const collectPlaceholderNames = (expression, ctx) => {
|
|
83
|
+
const placeholders = new Set();
|
|
84
|
+
(0, exports.walkAst)(expression, node => {
|
|
85
|
+
if (node.type === 'Identifier' && ctx.placeholders.has(node.name)) {
|
|
86
|
+
placeholders.add(node.name);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return Array.from(placeholders);
|
|
90
|
+
};
|
|
91
|
+
exports.collectPlaceholderNames = collectPlaceholderNames;
|
|
92
|
+
const evaluateExpression = (expression, ctx, evaluateJsxNode) => {
|
|
93
|
+
if (expression.type === 'JSXElement' || expression.type === 'JSXFragment') {
|
|
94
|
+
return evaluateJsxNode(expression);
|
|
95
|
+
}
|
|
96
|
+
if (!('range' in expression) || !expression.range) {
|
|
97
|
+
throw new Error('Unable to evaluate expression: missing source range information.');
|
|
98
|
+
}
|
|
99
|
+
const [start, end] = expression.range;
|
|
100
|
+
const source = ctx.source.slice(start, end);
|
|
101
|
+
const placeholders = (0, exports.collectPlaceholderNames)(expression, ctx);
|
|
102
|
+
try {
|
|
103
|
+
const evaluator = new Function(...placeholders, `"use strict"; return (${source});`);
|
|
104
|
+
const args = placeholders.map(name => ctx.placeholders.get(name));
|
|
105
|
+
return evaluator(...args);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
throw new Error(`Failed to evaluate expression ${source}: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
exports.evaluateExpression = evaluateExpression;
|
|
112
|
+
const sanitizeIdentifier = (value) => {
|
|
113
|
+
const cleaned = value.replace(/[^a-zA-Z0-9_$]/g, '');
|
|
114
|
+
if (!cleaned) {
|
|
115
|
+
return 'Component';
|
|
116
|
+
}
|
|
117
|
+
if (!/[A-Za-z_$]/.test(cleaned[0])) {
|
|
118
|
+
return `Component${cleaned}`;
|
|
119
|
+
}
|
|
120
|
+
return cleaned;
|
|
121
|
+
};
|
|
122
|
+
exports.sanitizeIdentifier = sanitizeIdentifier;
|
|
123
|
+
const ensureBinding = (value, bindings, bindingLookup) => {
|
|
124
|
+
const existing = bindingLookup.get(value);
|
|
125
|
+
if (existing) {
|
|
126
|
+
return existing;
|
|
127
|
+
}
|
|
128
|
+
const descriptor = value.displayName || value.name || `Component${bindings.length}`;
|
|
129
|
+
const baseName = (0, exports.sanitizeIdentifier)(descriptor ?? '');
|
|
130
|
+
let candidate = baseName;
|
|
131
|
+
let suffix = 1;
|
|
132
|
+
while (bindings.some(binding => binding.name === candidate)) {
|
|
133
|
+
candidate = `${baseName}${suffix++}`;
|
|
134
|
+
}
|
|
135
|
+
const binding = { name: candidate, value };
|
|
136
|
+
bindings.push(binding);
|
|
137
|
+
bindingLookup.set(value, binding);
|
|
138
|
+
return binding;
|
|
139
|
+
};
|
|
140
|
+
exports.ensureBinding = ensureBinding;
|
|
141
|
+
const buildTemplate = (strings, values) => {
|
|
142
|
+
const raw = strings.raw ?? strings;
|
|
143
|
+
const placeholders = new Map();
|
|
144
|
+
const bindings = [];
|
|
145
|
+
const bindingLookup = new Map();
|
|
146
|
+
let source = raw[0] ?? '';
|
|
147
|
+
const templateId = invocationCounter++;
|
|
148
|
+
let placeholderIndex = 0;
|
|
149
|
+
for (let idx = 0; idx < values.length; idx++) {
|
|
150
|
+
const chunk = raw[idx] ?? '';
|
|
151
|
+
const nextChunk = raw[idx + 1] ?? '';
|
|
152
|
+
const value = values[idx];
|
|
153
|
+
const isTagNamePosition = OPEN_TAG_RE.test(chunk) || CLOSE_TAG_RE.test(chunk);
|
|
154
|
+
if (isTagNamePosition && typeof value === 'function') {
|
|
155
|
+
const binding = (0, exports.ensureBinding)(value, bindings, bindingLookup);
|
|
156
|
+
source += binding.name + nextChunk;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (isTagNamePosition && typeof value === 'string') {
|
|
160
|
+
source += value + nextChunk;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const placeholder = `${PLACEHOLDER_PREFIX}${templateId}_${placeholderIndex++}__`;
|
|
164
|
+
placeholders.set(placeholder, value);
|
|
165
|
+
source += placeholder + nextChunk;
|
|
166
|
+
}
|
|
167
|
+
return { source, placeholders, bindings };
|
|
168
|
+
};
|
|
169
|
+
exports.buildTemplate = buildTemplate;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Expression, JSXElement, JSXFragment, JSXIdentifier, JSXMemberExpression, JSXNamespacedName, Program } from '@oxc-project/types';
|
|
2
|
+
import type { OxcError, ParserOptions } from 'oxc-parser';
|
|
3
|
+
type AnyTemplateFunction = (...args: any[]) => unknown;
|
|
4
|
+
type AnyTemplateConstructor = abstract new (...args: any[]) => unknown;
|
|
5
|
+
export type TemplateComponent = (AnyTemplateFunction | AnyTemplateConstructor) & {
|
|
6
|
+
displayName?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
};
|
|
9
|
+
export type BindingEntry<TComponent extends TemplateComponent> = {
|
|
10
|
+
name: string;
|
|
11
|
+
value: TComponent;
|
|
12
|
+
};
|
|
13
|
+
export type TemplateBuildResult<TComponent extends TemplateComponent> = {
|
|
14
|
+
source: string;
|
|
15
|
+
placeholders: Map<string, unknown>;
|
|
16
|
+
bindings: BindingEntry<TComponent>[];
|
|
17
|
+
};
|
|
18
|
+
export type TemplateContext<TComponent extends TemplateComponent> = {
|
|
19
|
+
source: string;
|
|
20
|
+
placeholders: Map<string, unknown>;
|
|
21
|
+
components: Map<string, TComponent>;
|
|
22
|
+
};
|
|
23
|
+
export declare const parserOptions: ParserOptions;
|
|
24
|
+
export declare const formatParserError: (error: OxcError) => string;
|
|
25
|
+
export declare const extractRootNode: (program: Program) => JSXElement | JSXFragment;
|
|
26
|
+
export declare const getIdentifierName: (identifier: JSXIdentifier | JSXNamespacedName | JSXMemberExpression) => string;
|
|
27
|
+
type AnyOxcNode = {
|
|
28
|
+
type: string;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
};
|
|
31
|
+
export declare const walkAst: (node: unknown, visitor: (target: AnyOxcNode) => void) => void;
|
|
32
|
+
export declare const normalizeJsxText: (value: string) => string;
|
|
33
|
+
export declare const collectPlaceholderNames: <TComponent extends TemplateComponent>(expression: Expression | JSXElement | JSXFragment, ctx: TemplateContext<TComponent>) => string[];
|
|
34
|
+
export declare const evaluateExpression: <TComponent extends TemplateComponent>(expression: Expression | JSXElement | JSXFragment, ctx: TemplateContext<TComponent>, evaluateJsxNode: (node: JSXElement | JSXFragment) => unknown) => unknown;
|
|
35
|
+
export declare const sanitizeIdentifier: (value: string) => string;
|
|
36
|
+
export declare const ensureBinding: <TComponent extends TemplateComponent>(value: TComponent, bindings: BindingEntry<TComponent>[], bindingLookup: Map<TComponent, BindingEntry<TComponent>>) => BindingEntry<TComponent>;
|
|
37
|
+
export declare const buildTemplate: <TComponent extends TemplateComponent>(strings: TemplateStringsArray, values: unknown[]) => TemplateBuildResult<TComponent>;
|
|
38
|
+
export {};
|
package/dist/jsx.js
CHANGED
|
@@ -1,55 +1,10 @@
|
|
|
1
1
|
import { parseSync } from 'oxc-parser';
|
|
2
|
-
|
|
3
|
-
const CLOSE_TAG_RE = /<\/\s*$/;
|
|
4
|
-
const PLACEHOLDER_PREFIX = '__KX_EXPR__';
|
|
5
|
-
let invocationCounter = 0;
|
|
6
|
-
const parserOptions = {
|
|
7
|
-
lang: 'jsx',
|
|
8
|
-
sourceType: 'module',
|
|
9
|
-
range: true,
|
|
10
|
-
preserveParens: true,
|
|
11
|
-
};
|
|
2
|
+
import { buildTemplate, evaluateExpression, extractRootNode, formatParserError, getIdentifierName, normalizeJsxText, parserOptions, } from './runtime/shared.js';
|
|
12
3
|
const ensureDomAvailable = () => {
|
|
13
4
|
if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
|
|
14
5
|
throw new Error('The jsx template tag requires a DOM-like environment (document missing).');
|
|
15
6
|
}
|
|
16
7
|
};
|
|
17
|
-
const formatParserError = (error) => {
|
|
18
|
-
let message = `[oxc-parser] ${error.message}`;
|
|
19
|
-
if (error.labels?.length) {
|
|
20
|
-
const label = error.labels[0];
|
|
21
|
-
if (label.message) {
|
|
22
|
-
message += `\n${label.message}`;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
if (error.codeframe) {
|
|
26
|
-
message += `\n${error.codeframe}`;
|
|
27
|
-
}
|
|
28
|
-
return message;
|
|
29
|
-
};
|
|
30
|
-
const extractRootNode = (program) => {
|
|
31
|
-
for (const statement of program.body) {
|
|
32
|
-
if (statement.type === 'ExpressionStatement') {
|
|
33
|
-
const expression = statement.expression;
|
|
34
|
-
if (expression.type === 'JSXElement' || expression.type === 'JSXFragment') {
|
|
35
|
-
return expression;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
throw new Error('The jsx template must contain a single JSX element or fragment.');
|
|
40
|
-
};
|
|
41
|
-
const getIdentifierName = (identifier) => {
|
|
42
|
-
switch (identifier.type) {
|
|
43
|
-
case 'JSXIdentifier':
|
|
44
|
-
return identifier.name;
|
|
45
|
-
case 'JSXNamespacedName':
|
|
46
|
-
return `${identifier.namespace.name}:${identifier.name.name}`;
|
|
47
|
-
case 'JSXMemberExpression':
|
|
48
|
-
return `${getIdentifierName(identifier.object)}.${identifier.property.name}`;
|
|
49
|
-
default:
|
|
50
|
-
return '';
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
8
|
const isNodeLike = (value) => {
|
|
54
9
|
if (typeof Node === 'undefined') {
|
|
55
10
|
return false;
|
|
@@ -68,11 +23,6 @@ const isPromiseLike = (value) => {
|
|
|
68
23
|
}
|
|
69
24
|
return typeof value.then === 'function';
|
|
70
25
|
};
|
|
71
|
-
const normalizeJsxText = (value) => {
|
|
72
|
-
const collapsed = value.replace(/\r/g, '').replace(/\n\s+/g, ' ');
|
|
73
|
-
const trimmed = collapsed.trim();
|
|
74
|
-
return trimmed.length > 0 ? trimmed : '';
|
|
75
|
-
};
|
|
76
26
|
const setDomProp = (element, name, value) => {
|
|
77
27
|
if (value === false || value === null || value === undefined) {
|
|
78
28
|
return;
|
|
@@ -162,11 +112,12 @@ const appendChildValue = (parent, value) => {
|
|
|
162
112
|
}
|
|
163
113
|
parent.appendChild(document.createTextNode(String(value)));
|
|
164
114
|
};
|
|
165
|
-
const
|
|
115
|
+
const evaluateExpressionWithNamespace = (expression, ctx, namespace) => evaluateExpression(expression, ctx, node => evaluateJsxNode(node, ctx, namespace));
|
|
116
|
+
const resolveAttributes = (attributes, ctx, namespace) => {
|
|
166
117
|
const props = {};
|
|
167
118
|
attributes.forEach(attribute => {
|
|
168
119
|
if (attribute.type === 'JSXSpreadAttribute') {
|
|
169
|
-
const spreadValue =
|
|
120
|
+
const spreadValue = evaluateExpressionWithNamespace(attribute.argument, ctx, namespace);
|
|
170
121
|
if (spreadValue && typeof spreadValue === 'object' && !Array.isArray(spreadValue)) {
|
|
171
122
|
Object.assign(props, spreadValue);
|
|
172
123
|
}
|
|
@@ -185,13 +136,13 @@ const resolveAttributes = (attributes, ctx) => {
|
|
|
185
136
|
if (attribute.value.expression.type === 'JSXEmptyExpression') {
|
|
186
137
|
return;
|
|
187
138
|
}
|
|
188
|
-
props[name] =
|
|
139
|
+
props[name] = evaluateExpressionWithNamespace(attribute.value.expression, ctx, namespace);
|
|
189
140
|
}
|
|
190
141
|
});
|
|
191
142
|
return props;
|
|
192
143
|
};
|
|
193
|
-
const applyDomAttributes = (element, attributes, ctx) => {
|
|
194
|
-
const props = resolveAttributes(attributes, ctx);
|
|
144
|
+
const applyDomAttributes = (element, attributes, ctx, namespace) => {
|
|
145
|
+
const props = resolveAttributes(attributes, ctx, namespace);
|
|
195
146
|
Object.entries(props).forEach(([name, value]) => {
|
|
196
147
|
if (name === 'key') {
|
|
197
148
|
return;
|
|
@@ -218,11 +169,11 @@ const evaluateJsxChildren = (children, ctx, namespace) => {
|
|
|
218
169
|
if (child.expression.type === 'JSXEmptyExpression') {
|
|
219
170
|
break;
|
|
220
171
|
}
|
|
221
|
-
resolved.push(
|
|
172
|
+
resolved.push(evaluateExpressionWithNamespace(child.expression, ctx, namespace));
|
|
222
173
|
break;
|
|
223
174
|
}
|
|
224
175
|
case 'JSXSpreadChild': {
|
|
225
|
-
const spreadValue =
|
|
176
|
+
const spreadValue = evaluateExpressionWithNamespace(child.expression, ctx, namespace);
|
|
226
177
|
if (spreadValue !== undefined && spreadValue !== null) {
|
|
227
178
|
resolved.push(spreadValue);
|
|
228
179
|
}
|
|
@@ -238,7 +189,7 @@ const evaluateJsxChildren = (children, ctx, namespace) => {
|
|
|
238
189
|
return resolved;
|
|
239
190
|
};
|
|
240
191
|
const evaluateComponent = (element, ctx, component, namespace) => {
|
|
241
|
-
const props = resolveAttributes(element.openingElement.attributes, ctx);
|
|
192
|
+
const props = resolveAttributes(element.openingElement.attributes, ctx, namespace);
|
|
242
193
|
const childValues = evaluateJsxChildren(element.children, ctx, namespace);
|
|
243
194
|
if (childValues.length === 1) {
|
|
244
195
|
props.children = childValues[0];
|
|
@@ -267,7 +218,7 @@ const evaluateJsxElement = (element, ctx, namespace) => {
|
|
|
267
218
|
const domElement = nextNamespace === 'svg'
|
|
268
219
|
? document.createElementNS('http://www.w3.org/2000/svg', tagName)
|
|
269
220
|
: document.createElement(tagName);
|
|
270
|
-
applyDomAttributes(domElement, opening.attributes, ctx);
|
|
221
|
+
applyDomAttributes(domElement, opening.attributes, ctx, nextNamespace);
|
|
271
222
|
const childValues = evaluateJsxChildren(element.children, ctx, childNamespace);
|
|
272
223
|
childValues.forEach(value => appendChildValue(domElement, value));
|
|
273
224
|
return domElement;
|
|
@@ -281,111 +232,6 @@ const evaluateJsxNode = (node, ctx, namespace) => {
|
|
|
281
232
|
}
|
|
282
233
|
return evaluateJsxElement(node, ctx, namespace);
|
|
283
234
|
};
|
|
284
|
-
const walkAst = (node, visitor) => {
|
|
285
|
-
if (!node || typeof node !== 'object') {
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
const candidate = node;
|
|
289
|
-
if (typeof candidate.type !== 'string') {
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
visitor(candidate);
|
|
293
|
-
Object.values(candidate).forEach(value => {
|
|
294
|
-
if (!value) {
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
if (Array.isArray(value)) {
|
|
298
|
-
value.forEach(child => walkAst(child, visitor));
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
if (typeof value === 'object') {
|
|
302
|
-
walkAst(value, visitor);
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
};
|
|
306
|
-
const collectPlaceholderNames = (expression, ctx) => {
|
|
307
|
-
const placeholders = new Set();
|
|
308
|
-
walkAst(expression, node => {
|
|
309
|
-
if (node.type === 'Identifier' && ctx.placeholders.has(node.name)) {
|
|
310
|
-
placeholders.add(node.name);
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
return Array.from(placeholders);
|
|
314
|
-
};
|
|
315
|
-
const evaluateExpression = (expression, ctx) => {
|
|
316
|
-
if (expression.type === 'JSXElement' || expression.type === 'JSXFragment') {
|
|
317
|
-
return evaluateJsxNode(expression, ctx, null);
|
|
318
|
-
}
|
|
319
|
-
if (!('range' in expression) || !expression.range) {
|
|
320
|
-
throw new Error('Unable to evaluate expression: missing source range information.');
|
|
321
|
-
}
|
|
322
|
-
const [start, end] = expression.range;
|
|
323
|
-
const source = ctx.source.slice(start, end);
|
|
324
|
-
const placeholders = collectPlaceholderNames(expression, ctx);
|
|
325
|
-
try {
|
|
326
|
-
const evaluator = new Function(...placeholders, `"use strict"; return (${source});`);
|
|
327
|
-
const args = placeholders.map(name => ctx.placeholders.get(name));
|
|
328
|
-
return evaluator(...args);
|
|
329
|
-
}
|
|
330
|
-
catch (error) {
|
|
331
|
-
throw new Error(`Failed to evaluate expression ${source}: ${error.message}`);
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
const sanitizeIdentifier = (value) => {
|
|
335
|
-
const cleaned = value.replace(/[^a-zA-Z0-9_$]/g, '');
|
|
336
|
-
if (!cleaned) {
|
|
337
|
-
return 'Component';
|
|
338
|
-
}
|
|
339
|
-
if (!/[A-Za-z_$]/.test(cleaned[0])) {
|
|
340
|
-
return `Component${cleaned}`;
|
|
341
|
-
}
|
|
342
|
-
return cleaned;
|
|
343
|
-
};
|
|
344
|
-
const ensureBinding = (value, bindings, bindingLookup) => {
|
|
345
|
-
const existing = bindingLookup.get(value);
|
|
346
|
-
if (existing) {
|
|
347
|
-
return existing;
|
|
348
|
-
}
|
|
349
|
-
const descriptor = value.displayName || value.name || `Component${bindings.length}`;
|
|
350
|
-
const baseName = sanitizeIdentifier(descriptor);
|
|
351
|
-
let candidate = baseName;
|
|
352
|
-
let suffix = 1;
|
|
353
|
-
while (bindings.some(binding => binding.name === candidate)) {
|
|
354
|
-
candidate = `${baseName}${suffix++}`;
|
|
355
|
-
}
|
|
356
|
-
const binding = { name: candidate, value };
|
|
357
|
-
bindings.push(binding);
|
|
358
|
-
bindingLookup.set(value, binding);
|
|
359
|
-
return binding;
|
|
360
|
-
};
|
|
361
|
-
const buildTemplate = (strings, values) => {
|
|
362
|
-
const raw = strings.raw ?? strings;
|
|
363
|
-
const placeholders = new Map();
|
|
364
|
-
const bindings = [];
|
|
365
|
-
const bindingLookup = new Map();
|
|
366
|
-
let source = raw[0] ?? '';
|
|
367
|
-
const templateId = invocationCounter++;
|
|
368
|
-
let placeholderIndex = 0;
|
|
369
|
-
for (let idx = 0; idx < values.length; idx++) {
|
|
370
|
-
const chunk = raw[idx] ?? '';
|
|
371
|
-
const nextChunk = raw[idx + 1] ?? '';
|
|
372
|
-
const value = values[idx];
|
|
373
|
-
const isTagNamePosition = OPEN_TAG_RE.test(chunk) || CLOSE_TAG_RE.test(chunk);
|
|
374
|
-
if (isTagNamePosition && typeof value === 'function') {
|
|
375
|
-
const binding = ensureBinding(value, bindings, bindingLookup);
|
|
376
|
-
source += binding.name + nextChunk;
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
if (isTagNamePosition && typeof value === 'string') {
|
|
380
|
-
source += value + nextChunk;
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
const placeholder = `${PLACEHOLDER_PREFIX}${templateId}_${placeholderIndex++}__`;
|
|
384
|
-
placeholders.set(placeholder, value);
|
|
385
|
-
source += placeholder + nextChunk;
|
|
386
|
-
}
|
|
387
|
-
return { source, placeholders, bindings };
|
|
388
|
-
};
|
|
389
235
|
export const jsx = (templates, ...values) => {
|
|
390
236
|
ensureDomAvailable();
|
|
391
237
|
const build = buildTemplate(templates, values);
|
package/dist/lite/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {parseSync}from'oxc-parser';var
|
|
1
|
+
import {parseSync}from'oxc-parser';var R=/<\s*$/,j=/<\/\s*$/,F="__KX_EXPR__",$=0,S={lang:"jsx",sourceType:"module",range:true,preserveParens:true},C=e=>{let n=`[oxc-parser] ${e.message}`;if(e.labels?.length){let t=e.labels[0];t.message&&(n+=`
|
|
2
2
|
${t.message}`);}return e.codeframe&&(n+=`
|
|
3
|
-
${e.codeframe}`),n},
|
|
4
|
-
export{
|
|
3
|
+
${e.codeframe}`),n},b=e=>{for(let n of e.body)if(n.type==="ExpressionStatement"){let t=n.expression;if(t.type==="JSXElement"||t.type==="JSXFragment")return t}throw new Error("The jsx template must contain a single JSX element or fragment.")},d=e=>{switch(e.type){case "JSXIdentifier":return e.name;case "JSXNamespacedName":return `${e.namespace.name}:${e.name.name}`;case "JSXMemberExpression":return `${d(e.object)}.${e.property.name}`;default:return ""}},x=(e,n)=>{if(!e||typeof e!="object")return;let t=e;typeof t.type=="string"&&(n(t),Object.values(t).forEach(r=>{if(r){if(Array.isArray(r)){r.forEach(o=>x(o,n));return}typeof r=="object"&&x(r,n);}}));},w=e=>{let t=e.replace(/\r/g,"").replace(/\n\s+/g," ").trim();return t.length>0?t:""},P=(e,n)=>{let t=new Set;return x(e,r=>{r.type==="Identifier"&&n.placeholders.has(r.name)&&t.add(r.name);}),Array.from(t)},T=(e,n,t)=>{if(e.type==="JSXElement"||e.type==="JSXFragment")return t(e);if(!("range"in e)||!e.range)throw new Error("Unable to evaluate expression: missing source range information.");let[r,o]=e.range,s=n.source.slice(r,o),a=P(e,n);try{let i=new Function(...a,`"use strict"; return (${s});`),p=a.map(c=>n.placeholders.get(c));return i(...p)}catch(i){throw new Error(`Failed to evaluate expression ${s}: ${i.message}`)}},_=e=>{let n=e.replace(/[^a-zA-Z0-9_$]/g,"");return n?/[A-Za-z_$]/.test(n[0])?n:`Component${n}`:"Component"},O=(e,n,t)=>{let r=t.get(e);if(r)return r;let o=e.displayName||e.name||`Component${n.length}`,s=_(o??""),a=s,i=1;for(;n.some(c=>c.name===a);)a=`${s}${i++}`;let p={name:a,value:e};return n.push(p),t.set(e,p),p},X=(e,n)=>{let t=e.raw??e,r=new Map,o=[],s=new Map,a=t[0]??"",i=$++,p=0;for(let c=0;c<n.length;c++){let u=t[c]??"",g=t[c+1]??"",m=n[c],E=R.test(u)||j.test(u);if(E&&typeof m=="function"){let A=O(m,o,s);a+=A.name+g;continue}if(E&&typeof m=="string"){a+=m+g;continue}let h=`${F}${i}_${p++}__`;r.set(h,m),a+=h+g;}return {source:a,placeholders:r,bindings:o}};var M=()=>{if(typeof document>"u"||typeof document.createElement!="function")throw new Error("The jsx template tag requires a DOM-like environment (document missing).")},D=e=>typeof Node>"u"?false:e instanceof Node||e instanceof DocumentFragment,L=e=>!e||typeof e=="string"?false:typeof e[Symbol.iterator]=="function",N=e=>!e||typeof e!="object"&&typeof e!="function"?false:typeof e.then=="function",B=(e,n,t)=>{if(!(t===false||t===null||t===void 0)){if(n==="dangerouslySetInnerHTML"&&typeof t=="object"&&t&&"__html"in t){e.innerHTML=String(t.__html??"");return}if(n==="ref"){if(typeof t=="function"){t(e);return}if(t&&typeof t=="object"){t.current=e;return}}if(n==="style"&&typeof t=="object"&&t!==null){let r=t,o=e.style;if(!o)return;let s=o;Object.entries(r).forEach(([a,i])=>{if(i!=null){if(a.startsWith("--")){o.setProperty(a,String(i));return}s[a]=i;}});return}if(typeof t=="function"&&n.startsWith("on")){let r=n.slice(2).toLowerCase();e.addEventListener(r,t);return}if(n==="class"||n==="className"){let r=Array.isArray(t)?t.filter(Boolean).join(" "):String(t);e.setAttribute("class",r);return}if(n==="htmlFor"){e.setAttribute("for",String(t));return}if(n in e&&!n.includes("-")){e[n]=t;return}e.setAttribute(n,t===true?"":String(t));}},l=(e,n)=>{if(n!=null&&typeof n!="boolean"){if(N(n))throw new Error("Async values are not supported inside jsx template results.");if(Array.isArray(n)){n.forEach(t=>l(e,t));return}if(L(n)){for(let t of n)l(e,t);return}if(D(n)){e.appendChild(n);return}e.appendChild(document.createTextNode(String(n)));}},f=(e,n,t)=>T(e,n,r=>J(r,n,t)),k=(e,n,t)=>{let r={};return e.forEach(o=>{if(o.type==="JSXSpreadAttribute"){let a=f(o.argument,n,t);a&&typeof a=="object"&&!Array.isArray(a)&&Object.assign(r,a);return}let s=d(o.name);if(!o.value){r[s]=true;return}if(o.value.type==="Literal"){r[s]=o.value.value;return}if(o.value.type==="JSXExpressionContainer"){if(o.value.expression.type==="JSXEmptyExpression")return;r[s]=f(o.value.expression,n,t);}}),r},z=(e,n,t,r)=>{let o=k(n,t,r);Object.entries(o).forEach(([s,a])=>{if(s!=="key"){if(s==="children"){l(e,a);return}B(e,s,a);}});},y=(e,n,t)=>{let r=[];return e.forEach(o=>{switch(o.type){case "JSXText":{let s=w(o.value);s&&r.push(s);break}case "JSXExpressionContainer":{if(o.expression.type==="JSXEmptyExpression")break;r.push(f(o.expression,n,t));break}case "JSXSpreadChild":{let s=f(o.expression,n,t);s!=null&&r.push(s);break}case "JSXElement":case "JSXFragment":{r.push(J(o,n,t));break}}}),r},W=(e,n,t,r)=>{let o=k(e.openingElement.attributes,n,r),s=y(e.children,n,r);s.length===1?o.children=s[0]:s.length>1&&(o.children=s);let a=t(o);if(N(a))throw new Error("Async jsx components are not supported.");return a},V=(e,n,t)=>{let r=e.openingElement,o=d(r.name),s=n.components.get(o);if(s)return W(e,n,s,t);if(/[A-Z]/.test(o[0]??""))throw new Error(`Unknown component "${o}". Did you interpolate it with the template literal?`);let a=o==="svg"?"svg":t,i=o==="foreignObject"?null:a,p=a==="svg"?document.createElementNS("http://www.w3.org/2000/svg",o):document.createElement(o);return z(p,r.attributes,n,a),y(e.children,n,i).forEach(u=>l(p,u)),p},J=(e,n,t)=>{if(e.type==="JSXFragment"){let r=document.createDocumentFragment();return y(e.children,n,t).forEach(s=>l(r,s)),r}return V(e,n,t)},H=(e,...n)=>{M();let t=X(e,n),r=parseSync("inline.jsx",t.source,S);if(r.errors.length>0)throw new Error(C(r.errors[0]));let o=b(r.program),s={source:t.source,placeholders:t.placeholders,components:new Map(t.bindings.map(a=>[a.name,a.value]))};return J(o,s,null)};
|
|
4
|
+
export{H as jsx};
|
package/dist/loader/jsx.d.ts
CHANGED
|
@@ -7,8 +7,13 @@ type LoaderContext<TOptions> = {
|
|
|
7
7
|
type LoaderOptions = {
|
|
8
8
|
/**
|
|
9
9
|
* Name of the tagged template function. Defaults to `jsx`.
|
|
10
|
+
* Deprecated in favor of `tags`.
|
|
10
11
|
*/
|
|
11
12
|
tag?: string;
|
|
13
|
+
/**
|
|
14
|
+
* List of tagged template function names to transform. Defaults to `['jsx', 'reactJsx']`.
|
|
15
|
+
*/
|
|
16
|
+
tags?: string[];
|
|
12
17
|
};
|
|
13
18
|
export default function jsxLoader(this: LoaderContext<LoaderOptions>, input: string | Buffer): void;
|
|
14
19
|
export {};
|
package/dist/loader/jsx.js
CHANGED
|
@@ -42,7 +42,7 @@ const TEMPLATE_PARSER_OPTIONS = {
|
|
|
42
42
|
range: true,
|
|
43
43
|
preserveParens: true,
|
|
44
44
|
};
|
|
45
|
-
const
|
|
45
|
+
const DEFAULT_TAGS = ['jsx', 'reactJsx'];
|
|
46
46
|
const escapeTemplateChunk = (chunk) => chunk.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '\\${');
|
|
47
47
|
const formatParserError = (error) => {
|
|
48
48
|
let message = `[jsx-loader] ${error.message}`;
|
|
@@ -181,15 +181,15 @@ const transformTemplateLiteral = (templateSource, resourcePath) => {
|
|
|
181
181
|
const slots = collectSlots(result.program, templateSource);
|
|
182
182
|
return renderTemplateWithSlots(templateSource, slots);
|
|
183
183
|
};
|
|
184
|
-
const
|
|
184
|
+
const getTaggedTemplateName = (node) => {
|
|
185
185
|
if (node.type !== 'TaggedTemplateExpression') {
|
|
186
|
-
return
|
|
186
|
+
return null;
|
|
187
187
|
}
|
|
188
188
|
const tagNode = node.tag;
|
|
189
189
|
if (tagNode.type !== 'Identifier') {
|
|
190
|
-
return
|
|
190
|
+
return null;
|
|
191
191
|
}
|
|
192
|
-
return tagNode.name
|
|
192
|
+
return tagNode.name;
|
|
193
193
|
};
|
|
194
194
|
const TAG_PLACEHOLDER_PREFIX = '__JSX_LOADER_TAG_EXPR_';
|
|
195
195
|
const buildTemplateSource = (quasis, expressions, source, tag) => {
|
|
@@ -301,8 +301,9 @@ const transformSource = (source, config) => {
|
|
|
301
301
|
}
|
|
302
302
|
const taggedTemplates = [];
|
|
303
303
|
walkAst(ast.program, node => {
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
const tagName = getTaggedTemplateName(node);
|
|
305
|
+
if (tagName && config.tags.includes(tagName)) {
|
|
306
|
+
taggedTemplates.push({ node, tagName });
|
|
306
307
|
}
|
|
307
308
|
});
|
|
308
309
|
if (!taggedTemplates.length) {
|
|
@@ -311,10 +312,11 @@ const transformSource = (source, config) => {
|
|
|
311
312
|
const magic = new MagicString(source);
|
|
312
313
|
let mutated = false;
|
|
313
314
|
taggedTemplates
|
|
314
|
-
.sort((a, b) => b.start - a.start)
|
|
315
|
-
.forEach(
|
|
315
|
+
.sort((a, b) => b.node.start - a.node.start)
|
|
316
|
+
.forEach(entry => {
|
|
317
|
+
const { node, tagName } = entry;
|
|
316
318
|
const quasi = node.quasi;
|
|
317
|
-
const templateSource = buildTemplateSource(quasi.quasis, quasi.expressions, source,
|
|
319
|
+
const templateSource = buildTemplateSource(quasi.quasis, quasi.expressions, source, tagName);
|
|
318
320
|
const { code, changed } = transformTemplateLiteral(templateSource.source, config.resourcePath);
|
|
319
321
|
const restored = restoreTemplatePlaceholders(code, templateSource.placeholders);
|
|
320
322
|
const templateChanged = changed || templateSource.mutated;
|
|
@@ -332,11 +334,20 @@ export default function jsxLoader(input) {
|
|
|
332
334
|
const callback = this.async();
|
|
333
335
|
try {
|
|
334
336
|
const options = this.getOptions?.() ?? {};
|
|
335
|
-
const
|
|
337
|
+
const explicitTags = Array.isArray(options.tags)
|
|
338
|
+
? options.tags.filter((value) => typeof value === 'string' && value.length > 0)
|
|
339
|
+
: null;
|
|
340
|
+
const legacyTag = typeof options.tag === 'string' && options.tag.length > 0 ? options.tag : null;
|
|
341
|
+
const tagList = explicitTags?.length
|
|
342
|
+
? explicitTags
|
|
343
|
+
: legacyTag
|
|
344
|
+
? [legacyTag]
|
|
345
|
+
: DEFAULT_TAGS;
|
|
346
|
+
const tags = Array.from(new Set(tagList));
|
|
336
347
|
const source = typeof input === 'string' ? input : input.toString('utf8');
|
|
337
348
|
const output = transformSource(source, {
|
|
338
349
|
resourcePath: this.resourcePath,
|
|
339
|
-
|
|
350
|
+
tags,
|
|
340
351
|
});
|
|
341
352
|
callback(null, output);
|
|
342
353
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { reactJsx } from './react-jsx.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ComponentType, type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
export type ReactJsxComponent<Props = Record<string, unknown>> = ComponentType<Props & {
|
|
3
|
+
children?: ReactNode;
|
|
4
|
+
}>;
|
|
5
|
+
export declare const reactJsx: (templates: TemplateStringsArray, ...values: unknown[]) => ReactElement;
|