@knighted/jsx 1.5.1 → 1.6.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 +1 -0
- package/dist/cjs/debug/diagnostics.cjs +57 -0
- package/dist/cjs/debug/diagnostics.d.cts +6 -0
- package/dist/cjs/debug/index.cjs +7 -0
- package/dist/cjs/debug/index.d.cts +2 -0
- package/dist/cjs/internal/attribute-resolution.cjs +55 -0
- package/dist/cjs/internal/attribute-resolution.d.cts +15 -0
- package/dist/cjs/internal/dev-environment.cjs +41 -0
- package/dist/cjs/internal/dev-environment.d.cts +4 -0
- package/dist/cjs/internal/event-bindings.cjs +93 -0
- package/dist/cjs/internal/event-bindings.d.cts +22 -0
- package/dist/cjs/internal/template-diagnostics.cjs +171 -0
- package/dist/cjs/internal/template-diagnostics.d.cts +13 -0
- package/dist/cjs/jsx.cjs +9 -110
- package/dist/cjs/loader/jsx.cjs +92 -20
- package/dist/cjs/loader/jsx.d.cts +6 -1
- package/dist/cjs/node/bootstrap.cjs +19 -19
- package/dist/cjs/node/bootstrap.d.cts +2 -1
- package/dist/cjs/node/debug/index.cjs +6 -0
- package/dist/cjs/node/debug/index.d.cts +2 -0
- package/dist/cjs/node/index.cjs +1 -1
- package/dist/cjs/react/react-jsx.cjs +1 -1
- package/dist/cjs/runtime/shared.cjs +41 -22
- package/dist/cjs/runtime/shared.d.cts +5 -2
- package/dist/debug/diagnostics.d.ts +6 -0
- package/dist/debug/diagnostics.js +52 -0
- package/dist/debug/index.d.ts +2 -0
- package/dist/debug/index.js +3 -0
- package/dist/internal/attribute-resolution.d.ts +15 -0
- package/dist/internal/attribute-resolution.js +50 -0
- package/dist/internal/dev-environment.d.ts +4 -0
- package/dist/internal/dev-environment.js +34 -0
- package/dist/internal/event-bindings.d.ts +22 -0
- package/dist/internal/event-bindings.js +87 -0
- package/dist/internal/template-diagnostics.d.ts +13 -0
- package/dist/internal/template-diagnostics.js +167 -0
- package/dist/jsx.js +9 -110
- package/dist/lite/debug/diagnostics.js +1 -0
- package/dist/lite/debug/index.js +8 -0
- package/dist/lite/index.js +8 -4
- package/dist/lite/node/debug/index.js +8 -0
- package/dist/lite/node/index.js +8 -4
- package/dist/lite/node/react/index.js +7 -3
- package/dist/lite/react/index.js +7 -3
- package/dist/loader/jsx.d.ts +6 -1
- package/dist/loader/jsx.js +92 -20
- package/dist/node/bootstrap.d.ts +2 -1
- package/dist/node/bootstrap.js +19 -19
- package/dist/node/debug/index.d.ts +2 -0
- package/dist/node/debug/index.js +6 -0
- package/dist/node/index.js +1 -1
- package/dist/react/react-jsx.js +2 -2
- package/dist/runtime/shared.d.ts +5 -2
- package/dist/runtime/shared.js +39 -21
- package/package.json +40 -8
package/dist/cjs/loader/jsx.cjs
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.default = jsxLoader;
|
|
7
7
|
const magic_string_1 = __importDefault(require("magic-string"));
|
|
8
8
|
const oxc_parser_1 = require("oxc-parser");
|
|
9
|
+
const template_diagnostics_js_1 = require("../internal/template-diagnostics.cjs");
|
|
9
10
|
const createPlaceholderMap = (placeholders) => new Map(placeholders.map(entry => [entry.marker, entry.code]));
|
|
10
11
|
class ReactTemplateBuilder {
|
|
11
12
|
placeholderMap;
|
|
@@ -165,6 +166,9 @@ class ReactTemplateBuilder {
|
|
|
165
166
|
throw new Error('[jsx-loader] Unable to inline complex expressions in react mode.');
|
|
166
167
|
}
|
|
167
168
|
/* c8 ignore next */
|
|
169
|
+
/* v8 ignore next */
|
|
170
|
+
/* istanbul ignore next */
|
|
171
|
+
// Should never happen because OXC always annotates expression ranges.
|
|
168
172
|
throw new Error('[jsx-loader] Unable to compile expression for react mode.');
|
|
169
173
|
}
|
|
170
174
|
buildCreateElement(type, props, children) {
|
|
@@ -276,11 +280,15 @@ const shouldInterpolateName = (name) => /^[A-Z]/.test(name.name);
|
|
|
276
280
|
const addSlot = (slots, source, range) => {
|
|
277
281
|
if (!range) {
|
|
278
282
|
/* c8 ignore next */
|
|
283
|
+
/* v8 ignore next */
|
|
284
|
+
// OXC always provides ranges; guard defends against malformed AST nodes.
|
|
279
285
|
return;
|
|
280
286
|
}
|
|
281
287
|
const [start, end] = range;
|
|
282
288
|
if (start === end) {
|
|
283
289
|
/* c8 ignore next */
|
|
290
|
+
/* v8 ignore next */
|
|
291
|
+
// Zero-length ranges indicate parser bugs and would emit empty slices.
|
|
284
292
|
return;
|
|
285
293
|
}
|
|
286
294
|
slots.push({
|
|
@@ -293,6 +301,9 @@ const collectSlots = (program, source) => {
|
|
|
293
301
|
const slots = [];
|
|
294
302
|
const recordComponentName = (name) => {
|
|
295
303
|
if (!name) {
|
|
304
|
+
/* c8 ignore next */
|
|
305
|
+
/* v8 ignore next */
|
|
306
|
+
// JSX elements emitted by OXC always carry a name; this is defensive.
|
|
296
307
|
return;
|
|
297
308
|
}
|
|
298
309
|
switch (name.type) {
|
|
@@ -362,6 +373,8 @@ const renderTemplateWithSlots = (source, slots) => {
|
|
|
362
373
|
slots.forEach(slot => {
|
|
363
374
|
if (slot.start < cursor) {
|
|
364
375
|
/* c8 ignore next */
|
|
376
|
+
/* v8 ignore next */
|
|
377
|
+
// Slots are generated from non-overlapping JSX ranges; this protects against parser regressions.
|
|
365
378
|
throw new Error('Overlapping JSX expressions detected inside template literal.');
|
|
366
379
|
}
|
|
367
380
|
output += escapeTemplateChunk(source.slice(cursor, slot.start));
|
|
@@ -371,10 +384,12 @@ const renderTemplateWithSlots = (source, slots) => {
|
|
|
371
384
|
output += escapeTemplateChunk(source.slice(cursor));
|
|
372
385
|
return { code: output, changed: slots.length > 0 };
|
|
373
386
|
};
|
|
374
|
-
const transformTemplateLiteral = (templateSource, resourcePath) => {
|
|
387
|
+
const transformTemplateLiteral = (templateSource, resourcePath, tagName, templates, diagnostics) => {
|
|
375
388
|
const result = (0, oxc_parser_1.parseSync)(`${resourcePath}?jsx-template`, templateSource, TEMPLATE_PARSER_OPTIONS);
|
|
376
389
|
if (result.errors.length > 0) {
|
|
377
|
-
throw new Error(
|
|
390
|
+
throw new Error((0, template_diagnostics_js_1.formatTaggedTemplateParserError)(tagName, templates, diagnostics, result.errors[0], {
|
|
391
|
+
label: 'jsx-loader',
|
|
392
|
+
}));
|
|
378
393
|
}
|
|
379
394
|
const slots = collectSlots(result.program, templateSource);
|
|
380
395
|
return renderTemplateWithSlots(templateSource, slots);
|
|
@@ -443,6 +458,25 @@ const normalizeJsxTextSegments = (value, placeholders) => {
|
|
|
443
458
|
return segments;
|
|
444
459
|
};
|
|
445
460
|
const TAG_PLACEHOLDER_PREFIX = '__JSX_LOADER_TAG_EXPR_';
|
|
461
|
+
const materializeTemplateStrings = (quasis) => {
|
|
462
|
+
const cooked = [];
|
|
463
|
+
const raw = [];
|
|
464
|
+
quasis.forEach(quasi => {
|
|
465
|
+
const value = quasi.value;
|
|
466
|
+
const cookedChunk = typeof value.cooked === 'string' ? value.cooked : (value.raw ?? '');
|
|
467
|
+
const rawChunk = typeof value.raw === 'string' ? value.raw : cookedChunk;
|
|
468
|
+
cooked.push(cookedChunk);
|
|
469
|
+
raw.push(rawChunk);
|
|
470
|
+
});
|
|
471
|
+
const templates = cooked;
|
|
472
|
+
Object.defineProperty(templates, 'raw', {
|
|
473
|
+
value: raw,
|
|
474
|
+
writable: false,
|
|
475
|
+
configurable: false,
|
|
476
|
+
enumerable: false,
|
|
477
|
+
});
|
|
478
|
+
return templates;
|
|
479
|
+
};
|
|
446
480
|
const buildTemplateSource = (quasis, expressions, source, tag) => {
|
|
447
481
|
const placeholderMap = new Map();
|
|
448
482
|
const tagPlaceholderMap = new Map();
|
|
@@ -450,6 +484,7 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
|
|
|
450
484
|
let placeholderIndex = 0;
|
|
451
485
|
let trimStartNext = 0;
|
|
452
486
|
let mutated = false;
|
|
487
|
+
const expressionRanges = [];
|
|
453
488
|
const registerMarker = (code, isTag) => {
|
|
454
489
|
if (isTag) {
|
|
455
490
|
const existing = tagPlaceholderMap.get(code);
|
|
@@ -465,10 +500,18 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
|
|
|
465
500
|
placeholderMap.set(marker, code);
|
|
466
501
|
return marker;
|
|
467
502
|
};
|
|
503
|
+
const appendInsertion = (expressionIndex, insertion) => {
|
|
504
|
+
const start = template.length;
|
|
505
|
+
template += insertion;
|
|
506
|
+
const end = template.length;
|
|
507
|
+
expressionRanges.push({ index: expressionIndex, sourceStart: start, sourceEnd: end });
|
|
508
|
+
};
|
|
468
509
|
quasis.forEach((quasi, index) => {
|
|
469
510
|
let chunk = quasi.value.cooked;
|
|
470
511
|
if (typeof chunk !== 'string') {
|
|
471
512
|
/* c8 ignore next */
|
|
513
|
+
/* v8 ignore next */
|
|
514
|
+
// Cooked text is always available for valid templates; fall back shields invalid escape sequences.
|
|
472
515
|
chunk = quasi.value.raw ?? '';
|
|
473
516
|
}
|
|
474
517
|
if (trimStartNext > 0) {
|
|
@@ -480,10 +523,13 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
|
|
|
480
523
|
if (!expression) {
|
|
481
524
|
return;
|
|
482
525
|
}
|
|
526
|
+
const expressionIndex = index;
|
|
483
527
|
const start = expression.start ?? null;
|
|
484
528
|
const end = expression.end ?? null;
|
|
485
529
|
if (start === null || end === null) {
|
|
486
530
|
/* c8 ignore next */
|
|
531
|
+
/* v8 ignore next */
|
|
532
|
+
// Expressions parsed from tagged templates always include start/end ranges.
|
|
487
533
|
throw new Error('Unable to read template expression source range.');
|
|
488
534
|
}
|
|
489
535
|
const nextChunk = quasis[index + 1];
|
|
@@ -493,7 +539,8 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
|
|
|
493
539
|
const code = source.slice(start, end);
|
|
494
540
|
const marker = registerMarker(code, context.type === 'tag');
|
|
495
541
|
const appendMarker = (wrapper) => {
|
|
496
|
-
|
|
542
|
+
const insertion = wrapper ? wrapper(marker) : marker;
|
|
543
|
+
appendInsertion(expressionIndex, insertion);
|
|
497
544
|
};
|
|
498
545
|
switch (context.type) {
|
|
499
546
|
case 'tag':
|
|
@@ -535,15 +582,22 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
|
|
|
535
582
|
marker,
|
|
536
583
|
code,
|
|
537
584
|
})),
|
|
585
|
+
diagnostics: { expressionRanges },
|
|
538
586
|
};
|
|
539
587
|
};
|
|
540
588
|
const restoreTemplatePlaceholders = (code, placeholders) => placeholders.reduce((result, placeholder) => {
|
|
541
589
|
return result.split(placeholder.marker).join(`\${${placeholder.code}}`);
|
|
542
590
|
}, code);
|
|
543
|
-
const
|
|
591
|
+
const createInlineSourceMapComment = (map) => {
|
|
592
|
+
const payload = Buffer.from(JSON.stringify(map), 'utf8').toString('base64');
|
|
593
|
+
return `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${payload}`;
|
|
594
|
+
};
|
|
595
|
+
const compileReactTemplate = (templateSource, placeholders, resourcePath, tagName, templates, diagnostics) => {
|
|
544
596
|
const parsed = (0, oxc_parser_1.parseSync)(`${resourcePath}?jsx-react-template`, templateSource, TEMPLATE_PARSER_OPTIONS);
|
|
545
597
|
if (parsed.errors.length > 0) {
|
|
546
|
-
throw new Error(
|
|
598
|
+
throw new Error((0, template_diagnostics_js_1.formatTaggedTemplateParserError)(tagName, templates, diagnostics, parsed.errors[0], {
|
|
599
|
+
label: 'jsx-loader',
|
|
600
|
+
}));
|
|
547
601
|
}
|
|
548
602
|
const root = extractJsxRoot(parsed.program);
|
|
549
603
|
const builder = new ReactTemplateBuilder(placeholders);
|
|
@@ -554,12 +608,14 @@ const isLoaderPlaceholderIdentifier = (node) => {
|
|
|
554
608
|
(node.type !== 'Identifier' && node.type !== 'JSXIdentifier') ||
|
|
555
609
|
typeof node.name !== 'string') {
|
|
556
610
|
/* c8 ignore next */
|
|
611
|
+
/* v8 ignore next */
|
|
612
|
+
// Visitor only calls this helper with identifier-like nodes; guard prevents crashes on malformed ASTs.
|
|
557
613
|
return false;
|
|
558
614
|
}
|
|
559
615
|
return (node.name.startsWith(TEMPLATE_EXPR_PLACEHOLDER_PREFIX) ||
|
|
560
616
|
node.name.startsWith(TAG_PLACEHOLDER_PREFIX));
|
|
561
617
|
};
|
|
562
|
-
const transformSource = (source, config) => {
|
|
618
|
+
const transformSource = (source, config, options) => {
|
|
563
619
|
const ast = (0, oxc_parser_1.parseSync)(config.resourcePath, source, MODULE_PARSER_OPTIONS);
|
|
564
620
|
if (ast.errors.length > 0) {
|
|
565
621
|
throw new Error(formatParserError(ast.errors[0]));
|
|
@@ -572,7 +628,7 @@ const transformSource = (source, config) => {
|
|
|
572
628
|
}
|
|
573
629
|
});
|
|
574
630
|
if (!taggedTemplates.length) {
|
|
575
|
-
return { code: source,
|
|
631
|
+
return { code: source, mutated: false };
|
|
576
632
|
}
|
|
577
633
|
const magic = new magic_string_1.default(source);
|
|
578
634
|
let mutated = false;
|
|
@@ -584,8 +640,9 @@ const transformSource = (source, config) => {
|
|
|
584
640
|
const mode = config.tagModes.get(tagName) ?? DEFAULT_MODE;
|
|
585
641
|
const quasi = node.quasi;
|
|
586
642
|
const templateSource = buildTemplateSource(quasi.quasis, quasi.expressions, source, tagName);
|
|
643
|
+
const templateStrings = materializeTemplateStrings(quasi.quasis);
|
|
587
644
|
if (mode === 'runtime') {
|
|
588
|
-
const { code, changed } = transformTemplateLiteral(templateSource.source, config.resourcePath);
|
|
645
|
+
const { code, changed } = transformTemplateLiteral(templateSource.source, config.resourcePath, tagName, templateStrings, templateSource.diagnostics);
|
|
589
646
|
const restored = restoreTemplatePlaceholders(code, templateSource.placeholders);
|
|
590
647
|
const templateChanged = changed || templateSource.mutated;
|
|
591
648
|
if (!templateChanged) {
|
|
@@ -598,20 +655,37 @@ const transformSource = (source, config) => {
|
|
|
598
655
|
return;
|
|
599
656
|
}
|
|
600
657
|
if (mode === 'react') {
|
|
601
|
-
const compiled = compileReactTemplate(templateSource.source, templateSource.placeholders, config.resourcePath);
|
|
658
|
+
const compiled = compileReactTemplate(templateSource.source, templateSource.placeholders, config.resourcePath, tagName, templateStrings, templateSource.diagnostics);
|
|
602
659
|
helperKinds.add('react');
|
|
603
660
|
magic.overwrite(node.start, node.end, compiled);
|
|
604
661
|
mutated = true;
|
|
605
662
|
return;
|
|
606
663
|
}
|
|
607
664
|
/* c8 ignore next */
|
|
665
|
+
/* v8 ignore next */
|
|
666
|
+
// Modes are validated during option parsing; this fallback guards future extensions.
|
|
608
667
|
throw new Error(`[jsx-loader] Transformation mode "${mode}" not implemented yet for tag "${tagName}".`);
|
|
609
668
|
});
|
|
669
|
+
const helperSource = Array.from(helperKinds)
|
|
670
|
+
.map(kind => HELPER_SNIPPETS[kind])
|
|
671
|
+
.filter(Boolean)
|
|
672
|
+
.join('\n');
|
|
673
|
+
if (helperSource) {
|
|
674
|
+
magic.append(`\n${helperSource}`);
|
|
675
|
+
mutated = true;
|
|
676
|
+
}
|
|
677
|
+
const code = mutated ? magic.toString() : source;
|
|
678
|
+
const map = options?.sourceMap && mutated
|
|
679
|
+
? magic.generateMap({
|
|
680
|
+
hires: true,
|
|
681
|
+
source: config.resourcePath,
|
|
682
|
+
includeContent: true,
|
|
683
|
+
})
|
|
684
|
+
: undefined;
|
|
610
685
|
return {
|
|
611
|
-
code
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
.filter(Boolean),
|
|
686
|
+
code,
|
|
687
|
+
map,
|
|
688
|
+
mutated,
|
|
615
689
|
};
|
|
616
690
|
};
|
|
617
691
|
function jsxLoader(input) {
|
|
@@ -648,16 +722,14 @@ function jsxLoader(input) {
|
|
|
648
722
|
}
|
|
649
723
|
});
|
|
650
724
|
const source = typeof input === 'string' ? input : input.toString('utf8');
|
|
651
|
-
const
|
|
725
|
+
const enableSourceMap = options.sourceMap === true;
|
|
726
|
+
const { code, map } = transformSource(source, {
|
|
652
727
|
resourcePath: this.resourcePath,
|
|
653
728
|
tags,
|
|
654
729
|
tagModes,
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
callback(null, code);
|
|
730
|
+
}, { sourceMap: enableSourceMap });
|
|
731
|
+
const output = map && enableSourceMap ? `${code}\n${createInlineSourceMapComment(map)}` : code;
|
|
732
|
+
callback(null, output, map);
|
|
661
733
|
}
|
|
662
734
|
catch (error) {
|
|
663
735
|
callback(error);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { type SourceMap } from 'magic-string';
|
|
2
|
+
type LoaderCallback = (error: Error | null, content?: string, map?: SourceMap | null) => void;
|
|
2
3
|
type LoaderContext<TOptions> = {
|
|
3
4
|
resourcePath: string;
|
|
4
5
|
async(): LoaderCallback;
|
|
@@ -24,6 +25,10 @@ type LoaderOptions = {
|
|
|
24
25
|
* Optional per-tag override of the transformation mode. Keys map to tag names.
|
|
25
26
|
*/
|
|
26
27
|
tagModes?: Record<string, LoaderMode | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* When true, generate inline source maps for mutated files.
|
|
30
|
+
*/
|
|
31
|
+
sourceMap?: boolean;
|
|
27
32
|
};
|
|
28
33
|
export default function jsxLoader(this: LoaderContext<LoaderOptions>, input: string | Buffer): void;
|
|
29
34
|
export {};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
3
|
+
let requireOverride = null;
|
|
4
|
+
const resolveRequire = () => requireOverride ?? nodeRequire;
|
|
1
5
|
const DOM_TEMPLATE = '<!doctype html><html><body></body></html>';
|
|
2
6
|
const GLOBAL_KEYS = [
|
|
3
7
|
'window',
|
|
@@ -23,13 +27,13 @@ const assignGlobalTargets = (windowObj) => {
|
|
|
23
27
|
}
|
|
24
28
|
});
|
|
25
29
|
};
|
|
26
|
-
const loadLinkedom =
|
|
27
|
-
const { parseHTML } =
|
|
30
|
+
const loadLinkedom = () => {
|
|
31
|
+
const { parseHTML } = resolveRequire()('linkedom');
|
|
28
32
|
const { window } = parseHTML(DOM_TEMPLATE);
|
|
29
33
|
return window;
|
|
30
34
|
};
|
|
31
|
-
const loadJsdom =
|
|
32
|
-
const { JSDOM } =
|
|
35
|
+
const loadJsdom = () => {
|
|
36
|
+
const { JSDOM } = resolveRequire()('jsdom');
|
|
33
37
|
const { window } = new JSDOM(DOM_TEMPLATE);
|
|
34
38
|
return window;
|
|
35
39
|
};
|
|
@@ -52,11 +56,11 @@ const selectLoaders = () => {
|
|
|
52
56
|
}
|
|
53
57
|
return [loadLinkedom, loadJsdom];
|
|
54
58
|
};
|
|
55
|
-
const createShimWindow =
|
|
59
|
+
const createShimWindow = () => {
|
|
56
60
|
const errors = [];
|
|
57
61
|
for (const loader of selectLoaders()) {
|
|
58
62
|
try {
|
|
59
|
-
return
|
|
63
|
+
return loader();
|
|
60
64
|
}
|
|
61
65
|
catch (error) {
|
|
62
66
|
errors.push(error);
|
|
@@ -65,19 +69,15 @@ const createShimWindow = async () => {
|
|
|
65
69
|
const help = 'Unable to bootstrap a DOM-like environment. Install "linkedom" or "jsdom" (both optional peer dependencies) or set KNIGHTED_JSX_NODE_SHIM to pick one explicitly.';
|
|
66
70
|
throw new AggregateError(errors, help);
|
|
67
71
|
};
|
|
68
|
-
let
|
|
69
|
-
export const ensureNodeDom =
|
|
70
|
-
if (hasDom()) {
|
|
72
|
+
let bootstrapped = false;
|
|
73
|
+
export const ensureNodeDom = () => {
|
|
74
|
+
if (hasDom() || bootstrapped) {
|
|
71
75
|
return;
|
|
72
76
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
throw error;
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
return bootstrapPromise;
|
|
77
|
+
const windowObj = createShimWindow();
|
|
78
|
+
assignGlobalTargets(windowObj);
|
|
79
|
+
bootstrapped = true;
|
|
80
|
+
};
|
|
81
|
+
export const __setNodeRequireForTesting = (mockRequire) => {
|
|
82
|
+
requireOverride = mockRequire;
|
|
83
83
|
};
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export declare const ensureNodeDom: () =>
|
|
1
|
+
export declare const ensureNodeDom: () => void;
|
|
2
|
+
export declare const __setNodeRequireForTesting: (mockRequire: NodeJS.Require | null) => void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { enableJsxDebugDiagnostics } from '../../debug/diagnostics.cjs';
|
|
2
|
+
import { ensureNodeDom } from '../bootstrap.cjs';
|
|
3
|
+
import { jsx as baseJsx } from '../../jsx.cjs';
|
|
4
|
+
enableJsxDebugDiagnostics({ mode: 'always' });
|
|
5
|
+
ensureNodeDom();
|
|
6
|
+
export const jsx = baseJsx;
|
package/dist/cjs/node/index.cjs
CHANGED
|
@@ -127,7 +127,7 @@ const reactJsx = (templates, ...values) => {
|
|
|
127
127
|
const build = (0, shared_js_1.buildTemplate)(templates, values);
|
|
128
128
|
const result = (0, oxc_parser_1.parseSync)('inline.jsx', build.source, shared_js_1.parserOptions);
|
|
129
129
|
if (result.errors.length > 0) {
|
|
130
|
-
throw new Error((0, shared_js_1.
|
|
130
|
+
throw new Error((0, shared_js_1.formatTaggedTemplateParserError)('reactJsx', templates, build.diagnostics, result.errors[0]));
|
|
131
131
|
}
|
|
132
132
|
const root = (0, shared_js_1.extractRootNode)(result.program);
|
|
133
133
|
const ctx = {
|
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildTemplate = exports.ensureBinding = exports.sanitizeIdentifier = exports.evaluateExpression = exports.collectPlaceholderNames = exports.normalizeJsxTextSegments = exports.walkAst = exports.getIdentifierName = exports.extractRootNode = exports.
|
|
3
|
+
exports.buildTemplate = exports.ensureBinding = exports.sanitizeIdentifier = exports.evaluateExpression = exports.collectPlaceholderNames = exports.normalizeJsxTextSegments = exports.walkAst = exports.getIdentifierName = exports.extractRootNode = exports.parserOptions = exports.formatParserError = exports.placeholderPattern = exports.PLACEHOLDER_PREFIX = exports.formatTaggedTemplateParserError = void 0;
|
|
4
|
+
var template_diagnostics_js_1 = require("../internal/template-diagnostics.cjs");
|
|
5
|
+
Object.defineProperty(exports, "formatTaggedTemplateParserError", { enumerable: true, get: function () { return template_diagnostics_js_1.formatTaggedTemplateParserError; } });
|
|
4
6
|
const OPEN_TAG_RE = /<\s*$/;
|
|
5
7
|
const CLOSE_TAG_RE = /<\/\s*$/;
|
|
6
8
|
exports.PLACEHOLDER_PREFIX = '__KX_EXPR__';
|
|
7
9
|
exports.placeholderPattern = new RegExp(`${exports.PLACEHOLDER_PREFIX}\\d+_\\d+__`, 'g');
|
|
8
10
|
let invocationCounter = 0;
|
|
9
|
-
exports.parserOptions = {
|
|
10
|
-
lang: 'jsx',
|
|
11
|
-
sourceType: 'module',
|
|
12
|
-
range: true,
|
|
13
|
-
preserveParens: true,
|
|
14
|
-
};
|
|
15
11
|
const formatParserError = (error) => {
|
|
16
12
|
let message = `[oxc-parser] ${error.message}`;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
message += `\n${label.message}`;
|
|
21
|
-
}
|
|
13
|
+
const primaryLabel = error.labels?.[0];
|
|
14
|
+
if (primaryLabel?.message) {
|
|
15
|
+
message += `\n${primaryLabel.message}`;
|
|
22
16
|
}
|
|
23
17
|
if (error.codeframe) {
|
|
24
18
|
message += `\n${error.codeframe}`;
|
|
25
19
|
}
|
|
20
|
+
if (error.helpMessage) {
|
|
21
|
+
message += `\n${error.helpMessage}`;
|
|
22
|
+
}
|
|
26
23
|
return message;
|
|
27
24
|
};
|
|
28
25
|
exports.formatParserError = formatParserError;
|
|
26
|
+
exports.parserOptions = {
|
|
27
|
+
lang: 'jsx',
|
|
28
|
+
sourceType: 'module',
|
|
29
|
+
range: true,
|
|
30
|
+
preserveParens: true,
|
|
31
|
+
};
|
|
29
32
|
const extractRootNode = (program) => {
|
|
30
33
|
for (const statement of program.body) {
|
|
31
34
|
if (statement.type === 'ExpressionStatement') {
|
|
@@ -183,24 +186,40 @@ const buildTemplate = (strings, values) => {
|
|
|
183
186
|
let source = raw[0] ?? '';
|
|
184
187
|
const templateId = invocationCounter++;
|
|
185
188
|
let placeholderIndex = 0;
|
|
189
|
+
const expressionRanges = [];
|
|
186
190
|
for (let idx = 0; idx < values.length; idx++) {
|
|
187
191
|
const chunk = raw[idx] ?? '';
|
|
188
192
|
const nextChunk = raw[idx + 1] ?? '';
|
|
189
193
|
const value = values[idx];
|
|
190
194
|
const isTagNamePosition = OPEN_TAG_RE.test(chunk) || CLOSE_TAG_RE.test(chunk);
|
|
195
|
+
let insertion;
|
|
191
196
|
if (isTagNamePosition && typeof value === 'function') {
|
|
192
197
|
const binding = (0, exports.ensureBinding)(value, bindings, bindingLookup);
|
|
193
|
-
|
|
194
|
-
continue;
|
|
198
|
+
insertion = binding.name;
|
|
195
199
|
}
|
|
196
|
-
if (isTagNamePosition && typeof value === 'string') {
|
|
197
|
-
|
|
198
|
-
continue;
|
|
200
|
+
else if (isTagNamePosition && typeof value === 'string') {
|
|
201
|
+
insertion = value;
|
|
199
202
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
else {
|
|
204
|
+
const placeholder = `${exports.PLACEHOLDER_PREFIX}${templateId}_${placeholderIndex++}__`;
|
|
205
|
+
placeholders.set(placeholder, value);
|
|
206
|
+
insertion = placeholder;
|
|
207
|
+
}
|
|
208
|
+
const sourceStart = source.length;
|
|
209
|
+
source += insertion;
|
|
210
|
+
const sourceEnd = source.length;
|
|
211
|
+
expressionRanges.push({
|
|
212
|
+
index: idx,
|
|
213
|
+
sourceStart,
|
|
214
|
+
sourceEnd,
|
|
215
|
+
});
|
|
216
|
+
source += nextChunk;
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
source,
|
|
220
|
+
placeholders,
|
|
221
|
+
bindings,
|
|
222
|
+
diagnostics: { expressionRanges },
|
|
223
|
+
};
|
|
205
224
|
};
|
|
206
225
|
exports.buildTemplate = buildTemplate;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { Expression, JSXElement, JSXFragment, JSXIdentifier, JSXMemberExpression, JSXNamespacedName, Program } from '@oxc-project/types';
|
|
2
2
|
import type { OxcError, ParserOptions } from 'oxc-parser';
|
|
3
|
+
import type { TemplateDiagnostics } from '../internal/template-diagnostics.cjs';
|
|
4
|
+
export { formatTaggedTemplateParserError } from '../internal/template-diagnostics.cjs';
|
|
5
|
+
export type { TemplateDiagnostics, TemplateExpressionRange, } from '../internal/template-diagnostics.cjs';
|
|
3
6
|
export declare const PLACEHOLDER_PREFIX = "__KX_EXPR__";
|
|
4
7
|
export declare const placeholderPattern: RegExp;
|
|
8
|
+
export declare const formatParserError: (error: OxcError) => string;
|
|
5
9
|
type AnyTemplateFunction = (...args: any[]) => unknown;
|
|
6
10
|
type AnyTemplateConstructor = abstract new (...args: any[]) => unknown;
|
|
7
11
|
export type TemplateComponent = (AnyTemplateFunction | AnyTemplateConstructor) & {
|
|
@@ -16,6 +20,7 @@ export type TemplateBuildResult<TComponent extends TemplateComponent> = {
|
|
|
16
20
|
source: string;
|
|
17
21
|
placeholders: Map<string, unknown>;
|
|
18
22
|
bindings: BindingEntry<TComponent>[];
|
|
23
|
+
diagnostics: TemplateDiagnostics;
|
|
19
24
|
};
|
|
20
25
|
export type TemplateContext<TComponent extends TemplateComponent> = {
|
|
21
26
|
source: string;
|
|
@@ -23,7 +28,6 @@ export type TemplateContext<TComponent extends TemplateComponent> = {
|
|
|
23
28
|
components: Map<string, TComponent>;
|
|
24
29
|
};
|
|
25
30
|
export declare const parserOptions: ParserOptions;
|
|
26
|
-
export declare const formatParserError: (error: OxcError) => string;
|
|
27
31
|
export declare const extractRootNode: (program: Program) => JSXElement | JSXFragment;
|
|
28
32
|
export declare const getIdentifierName: (identifier: JSXIdentifier | JSXNamespacedName | JSXMemberExpression) => string;
|
|
29
33
|
type AnyOxcNode = {
|
|
@@ -37,4 +41,3 @@ export declare const evaluateExpression: <TComponent extends TemplateComponent>(
|
|
|
37
41
|
export declare const sanitizeIdentifier: (value: string) => string;
|
|
38
42
|
export declare const ensureBinding: <TComponent extends TemplateComponent>(value: TComponent, bindings: BindingEntry<TComponent>[], bindingLookup: Map<TComponent, BindingEntry<TComponent>>) => BindingEntry<TComponent>;
|
|
39
43
|
export declare const buildTemplate: <TComponent extends TemplateComponent>(strings: TemplateStringsArray, values: unknown[]) => TemplateBuildResult<TComponent>;
|
|
40
|
-
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type JsxDiagnosticsMode = 'env' | 'always';
|
|
2
|
+
export type EnableJsxDebugDiagnosticsOptions = {
|
|
3
|
+
mode?: JsxDiagnosticsMode;
|
|
4
|
+
};
|
|
5
|
+
export declare const enableJsxDebugDiagnostics: (options?: EnableJsxDebugDiagnosticsOptions) => void;
|
|
6
|
+
export declare const disableJsxDebugDiagnostics: () => void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { setAttributeDiagnosticsHooks, } from '../internal/attribute-resolution.js';
|
|
2
|
+
import { setEventDiagnosticsHooks, } from '../internal/event-bindings.js';
|
|
3
|
+
import { createDevError, describeValue, emitDevWarning, isDevEnvironment, } from '../internal/dev-environment.js';
|
|
4
|
+
const isAsciiLowercase = (char) => char >= 'a' && char <= 'z';
|
|
5
|
+
let diagnosticsMode = 'env';
|
|
6
|
+
const shouldRunDiagnostics = () => diagnosticsMode === 'always' || (diagnosticsMode === 'env' && isDevEnvironment());
|
|
7
|
+
const shouldForceWarnings = () => diagnosticsMode === 'always';
|
|
8
|
+
const attributeDiagnostics = {
|
|
9
|
+
warnLowercaseEventProp(name) {
|
|
10
|
+
if (!shouldRunDiagnostics()) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (!name.startsWith('on') || name.startsWith('on:') || name.length < 3) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const indicator = name[2] ?? '';
|
|
17
|
+
if (!isAsciiLowercase(indicator)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const suggestion = `${name.slice(0, 2)}${indicator.toUpperCase()}${name.slice(3)}`;
|
|
21
|
+
emitDevWarning(`Use camelCase DOM event props when targeting runtime jsx templates. Received "${name}"; did you mean "${suggestion}"?`, shouldForceWarnings());
|
|
22
|
+
},
|
|
23
|
+
ensureValidDangerouslySetInnerHTML(value) {
|
|
24
|
+
if (!shouldRunDiagnostics()) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
28
|
+
throw createDevError('dangerouslySetInnerHTML expects an object with a string __html field.');
|
|
29
|
+
}
|
|
30
|
+
const html = value.__html;
|
|
31
|
+
if (typeof html !== 'string') {
|
|
32
|
+
throw createDevError(`dangerouslySetInnerHTML.__html must be a string but received ${describeValue(html)}.`);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
const eventDiagnostics = {
|
|
37
|
+
onInvalidHandler(propName, value) {
|
|
38
|
+
if (!shouldRunDiagnostics()) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
throw createDevError(`The "${propName}" prop expects a function, EventListenerObject, or descriptor ({ handler }) but received ${describeValue(value)}.`);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
export const enableJsxDebugDiagnostics = (options) => {
|
|
45
|
+
diagnosticsMode = options?.mode ?? 'env';
|
|
46
|
+
setAttributeDiagnosticsHooks(attributeDiagnostics);
|
|
47
|
+
setEventDiagnosticsHooks(eventDiagnostics);
|
|
48
|
+
};
|
|
49
|
+
export const disableJsxDebugDiagnostics = () => {
|
|
50
|
+
setAttributeDiagnosticsHooks(null);
|
|
51
|
+
setEventDiagnosticsHooks(null);
|
|
52
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Expression, JSXAttribute, JSXElement, JSXFragment, JSXSpreadAttribute } from '@oxc-project/types';
|
|
2
|
+
import type { TemplateComponent, TemplateContext } from '../runtime/shared.js';
|
|
3
|
+
export type AttributeDiagnosticsHooks = {
|
|
4
|
+
warnLowercaseEventProp?: (name: string) => void;
|
|
5
|
+
ensureValidDangerouslySetInnerHTML?: (value: unknown) => void;
|
|
6
|
+
};
|
|
7
|
+
export declare const setAttributeDiagnosticsHooks: (hooks: AttributeDiagnosticsHooks | null) => void;
|
|
8
|
+
export type Namespace = 'svg' | null;
|
|
9
|
+
export type EvaluateExpressionWithNamespace<TComponent extends TemplateComponent> = (expression: Expression | JSXElement | JSXFragment, ctx: TemplateContext<TComponent>, namespace: Namespace) => unknown;
|
|
10
|
+
export type ResolveAttributesDependencies<TComponent extends TemplateComponent> = {
|
|
11
|
+
getIdentifierName: (name: JSXAttribute['name']) => string;
|
|
12
|
+
evaluateExpressionWithNamespace: EvaluateExpressionWithNamespace<TComponent>;
|
|
13
|
+
};
|
|
14
|
+
export type ResolveAttributesFn<TComponent extends TemplateComponent> = (attributes: (JSXAttribute | JSXSpreadAttribute)[], ctx: TemplateContext<TComponent>, namespace: Namespace) => Record<string, unknown>;
|
|
15
|
+
export declare const createResolveAttributes: <TComponent extends TemplateComponent>(deps: ResolveAttributesDependencies<TComponent>) => ResolveAttributesFn<TComponent>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
let attributeDiagnostics = null;
|
|
2
|
+
export const setAttributeDiagnosticsHooks = (hooks) => {
|
|
3
|
+
attributeDiagnostics = hooks;
|
|
4
|
+
};
|
|
5
|
+
const warnLowercaseEventProp = (name) => {
|
|
6
|
+
attributeDiagnostics?.warnLowercaseEventProp?.(name);
|
|
7
|
+
};
|
|
8
|
+
const ensureValidDangerouslySetInnerHTML = (value) => {
|
|
9
|
+
attributeDiagnostics?.ensureValidDangerouslySetInnerHTML?.(value);
|
|
10
|
+
};
|
|
11
|
+
export const createResolveAttributes = (deps) => {
|
|
12
|
+
const { getIdentifierName, evaluateExpressionWithNamespace } = deps;
|
|
13
|
+
return (attributes, ctx, namespace) => {
|
|
14
|
+
const props = {};
|
|
15
|
+
const assignProp = (propName, propValue) => {
|
|
16
|
+
if (propName === 'dangerouslySetInnerHTML') {
|
|
17
|
+
ensureValidDangerouslySetInnerHTML(propValue);
|
|
18
|
+
}
|
|
19
|
+
props[propName] = propValue;
|
|
20
|
+
};
|
|
21
|
+
attributes.forEach(attribute => {
|
|
22
|
+
if (attribute.type === 'JSXSpreadAttribute') {
|
|
23
|
+
const spreadValue = evaluateExpressionWithNamespace(attribute.argument, ctx, namespace);
|
|
24
|
+
if (spreadValue &&
|
|
25
|
+
typeof spreadValue === 'object' &&
|
|
26
|
+
!Array.isArray(spreadValue)) {
|
|
27
|
+
Object.assign(props, spreadValue);
|
|
28
|
+
}
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const name = getIdentifierName(attribute.name);
|
|
32
|
+
warnLowercaseEventProp(name);
|
|
33
|
+
if (!attribute.value) {
|
|
34
|
+
assignProp(name, true);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (attribute.value.type === 'Literal') {
|
|
38
|
+
assignProp(name, attribute.value.value);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (attribute.value.type === 'JSXExpressionContainer') {
|
|
42
|
+
if (attribute.value.expression.type === 'JSXEmptyExpression') {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
assignProp(name, evaluateExpressionWithNamespace(attribute.value.expression, ctx, namespace));
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return props;
|
|
49
|
+
};
|
|
50
|
+
};
|