@knighted/jsx 1.0.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 +85 -4
- package/dist/cjs/jsx.cjs +18 -172
- package/dist/cjs/loader/jsx.cjs +363 -0
- package/dist/cjs/loader/jsx.d.cts +19 -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 +19 -0
- package/dist/loader/jsx.js +357 -0
- 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 +30 -2
|
@@ -0,0 +1,363 @@
|
|
|
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.default = jsxLoader;
|
|
7
|
+
const magic_string_1 = __importDefault(require("magic-string"));
|
|
8
|
+
const oxc_parser_1 = require("oxc-parser");
|
|
9
|
+
const stripTrailingWhitespace = (value) => value.replace(/\s+$/g, '');
|
|
10
|
+
const stripLeadingWhitespace = (value) => value.replace(/^\s+/g, '');
|
|
11
|
+
const getTemplateExpressionContext = (left, right) => {
|
|
12
|
+
const trimmedLeft = stripTrailingWhitespace(left);
|
|
13
|
+
const trimmedRight = stripLeadingWhitespace(right);
|
|
14
|
+
if (trimmedLeft.endsWith('<') || trimmedLeft.endsWith('</')) {
|
|
15
|
+
return { type: 'tag' };
|
|
16
|
+
}
|
|
17
|
+
if (/{\s*\.\.\.$/.test(trimmedLeft) && trimmedRight.startsWith('}')) {
|
|
18
|
+
return { type: 'spread' };
|
|
19
|
+
}
|
|
20
|
+
const attrStringMatch = trimmedLeft.match(/=\s*(["'])$/);
|
|
21
|
+
if (attrStringMatch) {
|
|
22
|
+
const quoteChar = attrStringMatch[1];
|
|
23
|
+
if (trimmedRight.startsWith(quoteChar)) {
|
|
24
|
+
return { type: 'attributeString', quote: quoteChar };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (trimmedLeft.endsWith('={') && trimmedRight.startsWith('}')) {
|
|
28
|
+
return { type: 'attributeExisting' };
|
|
29
|
+
}
|
|
30
|
+
if (/=\s*$/.test(trimmedLeft)) {
|
|
31
|
+
return { type: 'attributeUnquoted' };
|
|
32
|
+
}
|
|
33
|
+
if (trimmedLeft.endsWith('{') && trimmedRight.startsWith('}')) {
|
|
34
|
+
return { type: 'childExisting' };
|
|
35
|
+
}
|
|
36
|
+
return { type: 'childText' };
|
|
37
|
+
};
|
|
38
|
+
const TEMPLATE_EXPR_PLACEHOLDER_PREFIX = '__JSX_LOADER_TEMPLATE_EXPR_';
|
|
39
|
+
const MODULE_PARSER_OPTIONS = {
|
|
40
|
+
lang: 'tsx',
|
|
41
|
+
sourceType: 'module',
|
|
42
|
+
range: true,
|
|
43
|
+
preserveParens: true,
|
|
44
|
+
};
|
|
45
|
+
const TEMPLATE_PARSER_OPTIONS = {
|
|
46
|
+
lang: 'tsx',
|
|
47
|
+
sourceType: 'module',
|
|
48
|
+
range: true,
|
|
49
|
+
preserveParens: true,
|
|
50
|
+
};
|
|
51
|
+
const DEFAULT_TAGS = ['jsx', 'reactJsx'];
|
|
52
|
+
const escapeTemplateChunk = (chunk) => chunk.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '\\${');
|
|
53
|
+
const formatParserError = (error) => {
|
|
54
|
+
let message = `[jsx-loader] ${error.message}`;
|
|
55
|
+
if (error.labels?.length) {
|
|
56
|
+
const label = error.labels[0];
|
|
57
|
+
if (label.message) {
|
|
58
|
+
message += `\n${label.message}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (error.codeframe) {
|
|
62
|
+
message += `\n${error.codeframe}`;
|
|
63
|
+
}
|
|
64
|
+
return message;
|
|
65
|
+
};
|
|
66
|
+
const walkAst = (node, visitor) => {
|
|
67
|
+
if (!node || typeof node !== 'object') {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const current = node;
|
|
71
|
+
if (typeof current.type === 'string') {
|
|
72
|
+
visitor(current);
|
|
73
|
+
}
|
|
74
|
+
for (const value of Object.values(current)) {
|
|
75
|
+
if (!value) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (Array.isArray(value)) {
|
|
79
|
+
value.forEach(child => walkAst(child, visitor));
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (typeof value === 'object') {
|
|
83
|
+
walkAst(value, visitor);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const shouldInterpolateName = (name) => /^[A-Z]/.test(name.name);
|
|
88
|
+
const addSlot = (slots, source, range) => {
|
|
89
|
+
if (!range) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const [start, end] = range;
|
|
93
|
+
if (start === end) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
slots.push({
|
|
97
|
+
start,
|
|
98
|
+
end,
|
|
99
|
+
code: source.slice(start, end),
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
const collectSlots = (program, source) => {
|
|
103
|
+
const slots = [];
|
|
104
|
+
const recordComponentName = (name) => {
|
|
105
|
+
if (!name) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
switch (name.type) {
|
|
109
|
+
case 'JSXIdentifier': {
|
|
110
|
+
if (!shouldInterpolateName(name)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
addSlot(slots, source, name.range);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case 'JSXMemberExpression': {
|
|
117
|
+
addSlot(slots, source, name.range);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
default:
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
walkAst(program, node => {
|
|
125
|
+
switch (node.type) {
|
|
126
|
+
case 'JSXExpressionContainer': {
|
|
127
|
+
const expression = node.expression;
|
|
128
|
+
if (expression.type === 'JSXEmptyExpression') {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
if (isLoaderPlaceholderIdentifier(expression)) {
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
addSlot(slots, source, (expression.range ?? node.range));
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case 'JSXSpreadAttribute': {
|
|
138
|
+
const argument = node.argument;
|
|
139
|
+
if (isLoaderPlaceholderIdentifier(argument)) {
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
addSlot(slots, source, argument?.range);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case 'JSXSpreadChild': {
|
|
146
|
+
const expression = node.expression;
|
|
147
|
+
if (isLoaderPlaceholderIdentifier(expression)) {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
addSlot(slots, source, expression?.range);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case 'JSXElement': {
|
|
154
|
+
const opening = node.openingElement;
|
|
155
|
+
recordComponentName(opening.name);
|
|
156
|
+
const closing = node.closingElement;
|
|
157
|
+
if (closing?.name) {
|
|
158
|
+
recordComponentName(closing.name);
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
default:
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
return slots.sort((a, b) => a.start - b.start);
|
|
167
|
+
};
|
|
168
|
+
const renderTemplateWithSlots = (source, slots) => {
|
|
169
|
+
let cursor = 0;
|
|
170
|
+
let output = '';
|
|
171
|
+
slots.forEach(slot => {
|
|
172
|
+
if (slot.start < cursor) {
|
|
173
|
+
throw new Error('Overlapping JSX expressions detected inside template literal.');
|
|
174
|
+
}
|
|
175
|
+
output += escapeTemplateChunk(source.slice(cursor, slot.start));
|
|
176
|
+
output += `\${${slot.code}}`;
|
|
177
|
+
cursor = slot.end;
|
|
178
|
+
});
|
|
179
|
+
output += escapeTemplateChunk(source.slice(cursor));
|
|
180
|
+
return { code: output, changed: slots.length > 0 };
|
|
181
|
+
};
|
|
182
|
+
const transformTemplateLiteral = (templateSource, resourcePath) => {
|
|
183
|
+
const result = (0, oxc_parser_1.parseSync)(`${resourcePath}?jsx-template`, templateSource, TEMPLATE_PARSER_OPTIONS);
|
|
184
|
+
if (result.errors.length > 0) {
|
|
185
|
+
throw new Error(formatParserError(result.errors[0]));
|
|
186
|
+
}
|
|
187
|
+
const slots = collectSlots(result.program, templateSource);
|
|
188
|
+
return renderTemplateWithSlots(templateSource, slots);
|
|
189
|
+
};
|
|
190
|
+
const getTaggedTemplateName = (node) => {
|
|
191
|
+
if (node.type !== 'TaggedTemplateExpression') {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const tagNode = node.tag;
|
|
195
|
+
if (tagNode.type !== 'Identifier') {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
return tagNode.name;
|
|
199
|
+
};
|
|
200
|
+
const TAG_PLACEHOLDER_PREFIX = '__JSX_LOADER_TAG_EXPR_';
|
|
201
|
+
const buildTemplateSource = (quasis, expressions, source, tag) => {
|
|
202
|
+
const placeholderMap = new Map();
|
|
203
|
+
const tagPlaceholderMap = new Map();
|
|
204
|
+
let template = '';
|
|
205
|
+
let placeholderIndex = 0;
|
|
206
|
+
let trimStartNext = 0;
|
|
207
|
+
let mutated = false;
|
|
208
|
+
const registerMarker = (code, isTag) => {
|
|
209
|
+
if (isTag) {
|
|
210
|
+
const existing = tagPlaceholderMap.get(code);
|
|
211
|
+
if (existing) {
|
|
212
|
+
return existing;
|
|
213
|
+
}
|
|
214
|
+
const marker = `${TAG_PLACEHOLDER_PREFIX}${tagPlaceholderMap.size}__`;
|
|
215
|
+
tagPlaceholderMap.set(code, marker);
|
|
216
|
+
placeholderMap.set(marker, code);
|
|
217
|
+
return marker;
|
|
218
|
+
}
|
|
219
|
+
const marker = `${TEMPLATE_EXPR_PLACEHOLDER_PREFIX}${placeholderIndex++}__`;
|
|
220
|
+
placeholderMap.set(marker, code);
|
|
221
|
+
return marker;
|
|
222
|
+
};
|
|
223
|
+
quasis.forEach((quasi, index) => {
|
|
224
|
+
let chunk = quasi.value.cooked;
|
|
225
|
+
if (typeof chunk !== 'string') {
|
|
226
|
+
chunk = quasi.value.raw ?? '';
|
|
227
|
+
}
|
|
228
|
+
if (trimStartNext > 0) {
|
|
229
|
+
chunk = chunk.slice(trimStartNext);
|
|
230
|
+
trimStartNext = 0;
|
|
231
|
+
}
|
|
232
|
+
template += chunk;
|
|
233
|
+
const expression = expressions[index];
|
|
234
|
+
if (!expression) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const start = expression.start ?? null;
|
|
238
|
+
const end = expression.end ?? null;
|
|
239
|
+
if (start === null || end === null) {
|
|
240
|
+
throw new Error('Unable to read template expression source range.');
|
|
241
|
+
}
|
|
242
|
+
const nextChunk = quasis[index + 1];
|
|
243
|
+
const nextValue = nextChunk?.value;
|
|
244
|
+
const rightText = nextValue?.cooked ?? nextValue?.raw ?? '';
|
|
245
|
+
const context = getTemplateExpressionContext(chunk, rightText);
|
|
246
|
+
const code = source.slice(start, end);
|
|
247
|
+
const marker = registerMarker(code, context.type === 'tag');
|
|
248
|
+
const appendMarker = (wrapper) => {
|
|
249
|
+
template += wrapper ? wrapper(marker) : marker;
|
|
250
|
+
};
|
|
251
|
+
switch (context.type) {
|
|
252
|
+
case 'tag':
|
|
253
|
+
case 'spread':
|
|
254
|
+
case 'attributeExisting':
|
|
255
|
+
case 'childExisting': {
|
|
256
|
+
appendMarker();
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
case 'attributeString': {
|
|
260
|
+
const quoteChar = context.quote;
|
|
261
|
+
if (!template.endsWith(quoteChar)) {
|
|
262
|
+
throw new Error(`[jsx-loader] Expected attribute quote ${quoteChar} before template expression inside ${tag}\`\` block.`);
|
|
263
|
+
}
|
|
264
|
+
template = template.slice(0, -1);
|
|
265
|
+
appendMarker(identifier => `{${identifier}}`);
|
|
266
|
+
mutated = true;
|
|
267
|
+
if (rightText.startsWith(quoteChar)) {
|
|
268
|
+
trimStartNext = 1;
|
|
269
|
+
}
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
case 'attributeUnquoted': {
|
|
273
|
+
appendMarker(identifier => `{${identifier}}`);
|
|
274
|
+
mutated = true;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case 'childText': {
|
|
278
|
+
appendMarker(identifier => `{${identifier}}`);
|
|
279
|
+
mutated = true;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
return {
|
|
285
|
+
source: template,
|
|
286
|
+
mutated,
|
|
287
|
+
placeholders: Array.from(placeholderMap.entries()).map(([marker, code]) => ({
|
|
288
|
+
marker,
|
|
289
|
+
code,
|
|
290
|
+
})),
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
const restoreTemplatePlaceholders = (code, placeholders) => placeholders.reduce((result, placeholder) => {
|
|
294
|
+
return result.split(placeholder.marker).join(`\${${placeholder.code}}`);
|
|
295
|
+
}, code);
|
|
296
|
+
const isLoaderPlaceholderIdentifier = (node) => {
|
|
297
|
+
if (node?.type !== 'Identifier' || typeof node.name !== 'string') {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
return (node.name.startsWith(TEMPLATE_EXPR_PLACEHOLDER_PREFIX) ||
|
|
301
|
+
node.name.startsWith(TAG_PLACEHOLDER_PREFIX));
|
|
302
|
+
};
|
|
303
|
+
const transformSource = (source, config) => {
|
|
304
|
+
const ast = (0, oxc_parser_1.parseSync)(config.resourcePath, source, MODULE_PARSER_OPTIONS);
|
|
305
|
+
if (ast.errors.length > 0) {
|
|
306
|
+
throw new Error(formatParserError(ast.errors[0]));
|
|
307
|
+
}
|
|
308
|
+
const taggedTemplates = [];
|
|
309
|
+
walkAst(ast.program, node => {
|
|
310
|
+
const tagName = getTaggedTemplateName(node);
|
|
311
|
+
if (tagName && config.tags.includes(tagName)) {
|
|
312
|
+
taggedTemplates.push({ node, tagName });
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
if (!taggedTemplates.length) {
|
|
316
|
+
return source;
|
|
317
|
+
}
|
|
318
|
+
const magic = new magic_string_1.default(source);
|
|
319
|
+
let mutated = false;
|
|
320
|
+
taggedTemplates
|
|
321
|
+
.sort((a, b) => b.node.start - a.node.start)
|
|
322
|
+
.forEach(entry => {
|
|
323
|
+
const { node, tagName } = entry;
|
|
324
|
+
const quasi = node.quasi;
|
|
325
|
+
const templateSource = buildTemplateSource(quasi.quasis, quasi.expressions, source, tagName);
|
|
326
|
+
const { code, changed } = transformTemplateLiteral(templateSource.source, config.resourcePath);
|
|
327
|
+
const restored = restoreTemplatePlaceholders(code, templateSource.placeholders);
|
|
328
|
+
const templateChanged = changed || templateSource.mutated;
|
|
329
|
+
if (!templateChanged) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const tagSource = source.slice(node.tag.start, node.tag.end);
|
|
333
|
+
const replacement = `${tagSource}\`${restored}\``;
|
|
334
|
+
magic.overwrite(node.start, node.end, replacement);
|
|
335
|
+
mutated = true;
|
|
336
|
+
});
|
|
337
|
+
return mutated ? magic.toString() : source;
|
|
338
|
+
};
|
|
339
|
+
function jsxLoader(input) {
|
|
340
|
+
const callback = this.async();
|
|
341
|
+
try {
|
|
342
|
+
const options = this.getOptions?.() ?? {};
|
|
343
|
+
const explicitTags = Array.isArray(options.tags)
|
|
344
|
+
? options.tags.filter((value) => typeof value === 'string' && value.length > 0)
|
|
345
|
+
: null;
|
|
346
|
+
const legacyTag = typeof options.tag === 'string' && options.tag.length > 0 ? options.tag : null;
|
|
347
|
+
const tagList = explicitTags?.length
|
|
348
|
+
? explicitTags
|
|
349
|
+
: legacyTag
|
|
350
|
+
? [legacyTag]
|
|
351
|
+
: DEFAULT_TAGS;
|
|
352
|
+
const tags = Array.from(new Set(tagList));
|
|
353
|
+
const source = typeof input === 'string' ? input : input.toString('utf8');
|
|
354
|
+
const output = transformSource(source, {
|
|
355
|
+
resourcePath: this.resourcePath,
|
|
356
|
+
tags,
|
|
357
|
+
});
|
|
358
|
+
callback(null, output);
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
callback(error);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type LoaderCallback = (err: Error | null, content?: string) => void;
|
|
2
|
+
type LoaderContext<TOptions> = {
|
|
3
|
+
resourcePath: string;
|
|
4
|
+
async(): LoaderCallback;
|
|
5
|
+
getOptions?: () => Partial<TOptions>;
|
|
6
|
+
};
|
|
7
|
+
type LoaderOptions = {
|
|
8
|
+
/**
|
|
9
|
+
* Name of the tagged template function. Defaults to `jsx`.
|
|
10
|
+
* Deprecated in favor of `tags`.
|
|
11
|
+
*/
|
|
12
|
+
tag?: string;
|
|
13
|
+
/**
|
|
14
|
+
* List of tagged template function names to transform. Defaults to `['jsx', 'reactJsx']`.
|
|
15
|
+
*/
|
|
16
|
+
tags?: string[];
|
|
17
|
+
};
|
|
18
|
+
export default function jsxLoader(this: LoaderContext<LoaderOptions>, input: string | Buffer): void;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reactJsx = void 0;
|
|
4
|
+
var react_jsx_js_1 = require("./react-jsx.cjs");
|
|
5
|
+
Object.defineProperty(exports, "reactJsx", { enumerable: true, get: function () { return react_jsx_js_1.reactJsx; } });
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reactJsx = void 0;
|
|
4
|
+
const oxc_parser_1 = require("oxc-parser");
|
|
5
|
+
const shared_js_1 = require("../runtime/shared.cjs");
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const isIterable = (value) => {
|
|
8
|
+
if (!value || typeof value === 'string') {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return typeof value[Symbol.iterator] === 'function';
|
|
12
|
+
};
|
|
13
|
+
const isPromiseLike = (value) => {
|
|
14
|
+
if (!value || (typeof value !== 'object' && typeof value !== 'function')) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return typeof value.then === 'function';
|
|
18
|
+
};
|
|
19
|
+
const appendReactChild = (bucket, value) => {
|
|
20
|
+
if (value === null || value === undefined) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (typeof value === 'boolean') {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (isPromiseLike(value)) {
|
|
27
|
+
throw new Error('Async values are not supported inside reactJsx template results.');
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
value.forEach(entry => appendReactChild(bucket, entry));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (isIterable(value)) {
|
|
34
|
+
for (const entry of value) {
|
|
35
|
+
appendReactChild(bucket, entry);
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
bucket.push(value);
|
|
40
|
+
};
|
|
41
|
+
const evaluateExpressionForReact = (expression, ctx) => (0, shared_js_1.evaluateExpression)(expression, ctx, node => evaluateReactJsxNode(node, ctx));
|
|
42
|
+
const resolveAttributes = (attributes, ctx) => {
|
|
43
|
+
const props = {};
|
|
44
|
+
attributes.forEach(attribute => {
|
|
45
|
+
if (attribute.type === 'JSXSpreadAttribute') {
|
|
46
|
+
const spreadValue = evaluateExpressionForReact(attribute.argument, ctx);
|
|
47
|
+
if (spreadValue && typeof spreadValue === 'object' && !Array.isArray(spreadValue)) {
|
|
48
|
+
Object.assign(props, spreadValue);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const name = (0, shared_js_1.getIdentifierName)(attribute.name);
|
|
53
|
+
if (!attribute.value) {
|
|
54
|
+
props[name] = true;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (attribute.value.type === 'Literal') {
|
|
58
|
+
props[name] = attribute.value.value;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (attribute.value.type === 'JSXExpressionContainer') {
|
|
62
|
+
if (attribute.value.expression.type === 'JSXEmptyExpression') {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
props[name] = evaluateExpressionForReact(attribute.value.expression, ctx);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return props;
|
|
69
|
+
};
|
|
70
|
+
const evaluateReactJsxChildren = (children, ctx) => {
|
|
71
|
+
const resolved = [];
|
|
72
|
+
children.forEach(child => {
|
|
73
|
+
switch (child.type) {
|
|
74
|
+
case 'JSXText': {
|
|
75
|
+
const text = (0, shared_js_1.normalizeJsxText)(child.value);
|
|
76
|
+
if (text) {
|
|
77
|
+
resolved.push(text);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case 'JSXExpressionContainer': {
|
|
82
|
+
if (child.expression.type === 'JSXEmptyExpression') {
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
appendReactChild(resolved, evaluateExpressionForReact(child.expression, ctx));
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case 'JSXSpreadChild': {
|
|
89
|
+
const spreadValue = evaluateExpressionForReact(child.expression, ctx);
|
|
90
|
+
if (spreadValue !== undefined && spreadValue !== null) {
|
|
91
|
+
appendReactChild(resolved, spreadValue);
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case 'JSXElement':
|
|
96
|
+
case 'JSXFragment': {
|
|
97
|
+
resolved.push(evaluateReactJsxNode(child, ctx));
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
return resolved;
|
|
103
|
+
};
|
|
104
|
+
const createReactElement = (type, props, children) => {
|
|
105
|
+
return (0, react_1.createElement)(type, props, ...children);
|
|
106
|
+
};
|
|
107
|
+
const evaluateReactJsxElement = (element, ctx) => {
|
|
108
|
+
const opening = element.openingElement;
|
|
109
|
+
const tagName = (0, shared_js_1.getIdentifierName)(opening.name);
|
|
110
|
+
const component = ctx.components.get(tagName);
|
|
111
|
+
const props = resolveAttributes(opening.attributes, ctx);
|
|
112
|
+
const childValues = evaluateReactJsxChildren(element.children, ctx);
|
|
113
|
+
if (component) {
|
|
114
|
+
return createReactElement(component, props, childValues);
|
|
115
|
+
}
|
|
116
|
+
if (/[A-Z]/.test(tagName[0] ?? '')) {
|
|
117
|
+
throw new Error(`Unknown component "${tagName}". Did you interpolate it with the template literal?`);
|
|
118
|
+
}
|
|
119
|
+
return createReactElement(tagName, props, childValues);
|
|
120
|
+
};
|
|
121
|
+
const evaluateReactJsxNode = (node, ctx) => {
|
|
122
|
+
if (node.type === 'JSXFragment') {
|
|
123
|
+
const children = evaluateReactJsxChildren(node.children, ctx);
|
|
124
|
+
return (0, react_1.createElement)(react_1.Fragment, null, ...children);
|
|
125
|
+
}
|
|
126
|
+
return evaluateReactJsxElement(node, ctx);
|
|
127
|
+
};
|
|
128
|
+
const reactJsx = (templates, ...values) => {
|
|
129
|
+
const build = (0, shared_js_1.buildTemplate)(templates, values);
|
|
130
|
+
const result = (0, oxc_parser_1.parseSync)('inline.jsx', build.source, shared_js_1.parserOptions);
|
|
131
|
+
if (result.errors.length > 0) {
|
|
132
|
+
throw new Error((0, shared_js_1.formatParserError)(result.errors[0]));
|
|
133
|
+
}
|
|
134
|
+
const root = (0, shared_js_1.extractRootNode)(result.program);
|
|
135
|
+
const ctx = {
|
|
136
|
+
source: build.source,
|
|
137
|
+
placeholders: build.placeholders,
|
|
138
|
+
components: new Map(build.bindings.map(binding => [binding.name, binding.value])),
|
|
139
|
+
};
|
|
140
|
+
return evaluateReactJsxNode(root, ctx);
|
|
141
|
+
};
|
|
142
|
+
exports.reactJsx = reactJsx;
|
|
@@ -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;
|