@knighted/jsx 1.6.3-rc.1 → 1.7.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/dist/cjs/loader/dom-template-builder.cjs +217 -0
- package/dist/cjs/loader/dom-template-builder.d.cts +12 -0
- package/dist/cjs/loader/helpers/dom-snippets.cjs +149 -0
- package/dist/cjs/loader/helpers/dom-snippets.d.cts +2 -0
- package/dist/cjs/loader/helpers/format-import-specifier.cjs +30 -0
- package/dist/cjs/loader/helpers/format-import-specifier.d.cts +13 -0
- package/dist/cjs/loader/helpers/materialize-slice.cjs +37 -0
- package/dist/cjs/loader/helpers/materialize-slice.d.cts +1 -0
- package/dist/cjs/loader/helpers/parse-range-key.cjs +14 -0
- package/dist/cjs/loader/helpers/parse-range-key.d.cts +1 -0
- package/dist/cjs/loader/helpers/rewrite-imports-without-tags.cjs +62 -0
- package/dist/cjs/loader/helpers/rewrite-imports-without-tags.d.cts +3 -0
- package/dist/cjs/loader/jsx.cjs +57 -33
- package/dist/cjs/loader/jsx.d.cts +3 -1
- package/dist/cjs/loader/modes.cjs +17 -0
- package/dist/cjs/loader/modes.d.cts +3 -0
- package/dist/cjs/runtime/shared.cjs +3 -13
- package/dist/cjs/shared/normalize-text.cjs +22 -0
- package/dist/cjs/shared/normalize-text.d.cts +1 -0
- package/dist/lite/debug/index.js +7 -7
- package/dist/lite/index.js +7 -7
- package/dist/lite/node/debug/index.js +7 -7
- package/dist/lite/node/index.js +7 -7
- package/dist/lite/node/react/index.js +5 -5
- package/dist/lite/react/index.js +5 -5
- package/dist/loader/dom-template-builder.d.ts +12 -0
- package/dist/loader/dom-template-builder.js +213 -0
- package/dist/loader/helpers/dom-snippets.d.ts +2 -0
- package/dist/loader/helpers/dom-snippets.js +146 -0
- package/dist/loader/helpers/format-import-specifier.d.ts +13 -0
- package/dist/loader/helpers/format-import-specifier.js +26 -0
- package/dist/loader/helpers/materialize-slice.d.ts +1 -0
- package/dist/loader/helpers/materialize-slice.js +33 -0
- package/dist/loader/helpers/parse-range-key.d.ts +1 -0
- package/dist/loader/helpers/parse-range-key.js +10 -0
- package/dist/loader/helpers/rewrite-imports-without-tags.d.ts +3 -0
- package/dist/loader/helpers/rewrite-imports-without-tags.js +58 -0
- package/dist/loader/jsx.d.ts +3 -1
- package/dist/loader/jsx.js +55 -31
- package/dist/loader/modes.d.ts +3 -0
- package/dist/loader/modes.js +13 -0
- package/dist/runtime/shared.js +3 -13
- package/dist/shared/normalize-text.d.ts +1 -0
- package/dist/shared/normalize-text.js +18 -0
- package/package.json +6 -6
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { parseRangeKey } from './parse-range-key.js';
|
|
2
|
+
export const materializeSlice = (start, end, source, replacements) => {
|
|
3
|
+
const exact = replacements.get(`${start}:${end}`);
|
|
4
|
+
if (exact !== undefined) {
|
|
5
|
+
return exact;
|
|
6
|
+
}
|
|
7
|
+
const nested = [];
|
|
8
|
+
replacements.forEach((code, key) => {
|
|
9
|
+
const range = parseRangeKey(key);
|
|
10
|
+
if (!range)
|
|
11
|
+
return;
|
|
12
|
+
const [rStart, rEnd] = range;
|
|
13
|
+
if (rStart >= start && rEnd <= end) {
|
|
14
|
+
nested.push({ start: rStart, end: rEnd, code });
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
if (!nested.length) {
|
|
18
|
+
return source.slice(start, end);
|
|
19
|
+
}
|
|
20
|
+
nested.sort((a, b) => a.start - b.start);
|
|
21
|
+
let cursor = start;
|
|
22
|
+
let output = '';
|
|
23
|
+
nested.forEach(entry => {
|
|
24
|
+
if (entry.start < cursor) {
|
|
25
|
+
throw new Error(`[jsx-loader] Overlapping replacement ranges detected (${entry.start}:${entry.end}) within ${start}:${end}. Nested replacements must not overlap.`);
|
|
26
|
+
}
|
|
27
|
+
output += source.slice(cursor, entry.start);
|
|
28
|
+
output += entry.code;
|
|
29
|
+
cursor = entry.end;
|
|
30
|
+
});
|
|
31
|
+
output += source.slice(cursor, end);
|
|
32
|
+
return output;
|
|
33
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const parseRangeKey: (key: string) => [number, number] | null;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const parseRangeKey = (key) => {
|
|
2
|
+
const [start, end] = key.split(':').map(entry => Number.parseInt(entry, 10));
|
|
3
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
if (end < start) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
return [start, end];
|
|
10
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { formatImportSpecifier } from './format-import-specifier.js';
|
|
2
|
+
export const rewriteImportsWithoutTags = (program, magic, inlineTagNames, originalSource) => {
|
|
3
|
+
if (!inlineTagNames.size) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
let mutated = false;
|
|
7
|
+
program.body.forEach(node => {
|
|
8
|
+
if (node.type !== 'ImportDeclaration') {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const specifiers = node.specifiers;
|
|
12
|
+
const kept = [];
|
|
13
|
+
let removed = false;
|
|
14
|
+
specifiers.forEach(spec => {
|
|
15
|
+
const localName = spec.local?.name;
|
|
16
|
+
if (!localName) {
|
|
17
|
+
kept.push(spec);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const shouldDrop = inlineTagNames.has(localName);
|
|
21
|
+
if (shouldDrop) {
|
|
22
|
+
removed = true;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
kept.push(spec);
|
|
26
|
+
});
|
|
27
|
+
if (!removed) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (!kept.length) {
|
|
31
|
+
magic.remove(node.start, node.end);
|
|
32
|
+
mutated = true;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const keyword = node.importKind === 'type' ? 'import type' : 'import';
|
|
36
|
+
const bindings = [];
|
|
37
|
+
const defaultSpec = kept.find(spec => spec.type === 'ImportDefaultSpecifier');
|
|
38
|
+
const namespaceSpec = kept.find(spec => spec.type === 'ImportNamespaceSpecifier');
|
|
39
|
+
const namedSpecs = kept.filter(spec => spec.type === 'ImportSpecifier');
|
|
40
|
+
if (defaultSpec) {
|
|
41
|
+
bindings.push(formatImportSpecifier(defaultSpec));
|
|
42
|
+
}
|
|
43
|
+
if (namespaceSpec) {
|
|
44
|
+
bindings.push(formatImportSpecifier(namespaceSpec));
|
|
45
|
+
}
|
|
46
|
+
if (namedSpecs.length) {
|
|
47
|
+
bindings.push(`{ ${namedSpecs.map(formatImportSpecifier).join(', ')} }`);
|
|
48
|
+
}
|
|
49
|
+
const sourceLiteral = node.source;
|
|
50
|
+
const sourceText = sourceLiteral.raw
|
|
51
|
+
? sourceLiteral.raw
|
|
52
|
+
: originalSource.slice(sourceLiteral.start ?? 0, sourceLiteral.end ?? 0);
|
|
53
|
+
const rewritten = `${keyword} ${bindings.join(', ')} from ${sourceText}`;
|
|
54
|
+
magic.overwrite(node.start, node.end, rewritten);
|
|
55
|
+
mutated = true;
|
|
56
|
+
});
|
|
57
|
+
return mutated;
|
|
58
|
+
};
|
package/dist/loader/jsx.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { type SourceMap } from 'magic-string';
|
|
2
|
+
import { type LoaderMode } from './modes.js';
|
|
2
3
|
type LoaderCallback = (error: Error | null, content?: string, map?: SourceMap | null) => void;
|
|
3
4
|
type LoaderContext<TOptions> = {
|
|
4
5
|
resourcePath: string;
|
|
6
|
+
target?: string;
|
|
7
|
+
emitWarning?: (warning: Error | string) => void;
|
|
5
8
|
async(): LoaderCallback;
|
|
6
9
|
getOptions?: () => Partial<TOptions>;
|
|
7
10
|
};
|
|
8
|
-
type LoaderMode = 'runtime' | 'react';
|
|
9
11
|
type LoaderOptions = {
|
|
10
12
|
/**
|
|
11
13
|
* Name of the tagged template function. Defaults to `jsx`.
|
package/dist/loader/jsx.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import MagicString from 'magic-string';
|
|
2
2
|
import { parseSync } from 'oxc-parser';
|
|
3
3
|
import { formatTaggedTemplateParserError, } from '../internal/template-diagnostics.js';
|
|
4
|
+
import { compileDomTemplate, DOM_HELPER_SNIPPETS } from './dom-template-builder.js';
|
|
5
|
+
import { materializeSlice } from './helpers/materialize-slice.js';
|
|
6
|
+
import { rewriteImportsWithoutTags } from './helpers/rewrite-imports-without-tags.js';
|
|
7
|
+
import { DEFAULT_MODE, parseLoaderMode } from './modes.js';
|
|
8
|
+
import { normalizeJsxText } from '../shared/normalize-text.js';
|
|
4
9
|
const createPlaceholderMap = (placeholders) => new Map(placeholders.map(entry => [entry.marker, entry.code]));
|
|
5
10
|
class ReactTemplateBuilder {
|
|
6
11
|
placeholderMap;
|
|
@@ -217,23 +222,13 @@ const TEMPLATE_PARSER_OPTIONS = {
|
|
|
217
222
|
preserveParens: true,
|
|
218
223
|
};
|
|
219
224
|
const DEFAULT_TAGS = ['jsx', 'reactJsx'];
|
|
220
|
-
const
|
|
225
|
+
const WEB_TARGETS = new Set(['web', 'webworker', 'electron-renderer', 'node-webkit']);
|
|
226
|
+
const isWebTarget = (target) => target ? WEB_TARGETS.has(target) : false;
|
|
221
227
|
const HELPER_SNIPPETS = {
|
|
222
228
|
react: `const __jsxReactMergeProps = (...sources) => Object.assign({}, ...sources)
|
|
223
229
|
const __jsxReact = (type, props, ...children) => React.createElement(type, props, ...children)
|
|
224
230
|
`,
|
|
225
|
-
|
|
226
|
-
const parseLoaderMode = (value) => {
|
|
227
|
-
if (typeof value !== 'string') {
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
switch (value) {
|
|
231
|
-
case 'runtime':
|
|
232
|
-
case 'react':
|
|
233
|
-
return value;
|
|
234
|
-
default:
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
231
|
+
...DOM_HELPER_SNIPPETS,
|
|
237
232
|
};
|
|
238
233
|
const escapeTemplateChunk = (chunk) => chunk.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '\\${');
|
|
239
234
|
const formatParserError = (error) => {
|
|
@@ -410,19 +405,8 @@ const extractJsxRoot = (program) => {
|
|
|
410
405
|
throw new Error('[jsx-loader] Expected the template to contain a single JSX root node.');
|
|
411
406
|
};
|
|
412
407
|
const normalizeJsxTextSegments = (value, placeholders) => {
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
const trailingWhitespace = value.match(/\s*$/)?.[0] ?? '';
|
|
416
|
-
const trimStart = /\n/.test(leadingWhitespace);
|
|
417
|
-
const trimEnd = /\n/.test(trailingWhitespace);
|
|
418
|
-
let normalized = collapsed;
|
|
419
|
-
if (trimStart) {
|
|
420
|
-
normalized = normalized.replace(/^\s+/, '');
|
|
421
|
-
}
|
|
422
|
-
if (trimEnd) {
|
|
423
|
-
normalized = normalized.replace(/\s+$/, '');
|
|
424
|
-
}
|
|
425
|
-
if (normalized.length === 0 || normalized.trim().length === 0) {
|
|
408
|
+
const normalized = normalizeJsxText(value);
|
|
409
|
+
if (!normalized) {
|
|
426
410
|
return [];
|
|
427
411
|
}
|
|
428
412
|
const segments = [];
|
|
@@ -471,7 +455,7 @@ const materializeTemplateStrings = (quasis) => {
|
|
|
471
455
|
});
|
|
472
456
|
return templates;
|
|
473
457
|
};
|
|
474
|
-
const buildTemplateSource = (quasis, expressions, source, tag) => {
|
|
458
|
+
const buildTemplateSource = (quasis, expressions, source, tag, replacements) => {
|
|
475
459
|
const placeholderMap = new Map();
|
|
476
460
|
const tagPlaceholderMap = new Map();
|
|
477
461
|
let template = '';
|
|
@@ -530,7 +514,7 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
|
|
|
530
514
|
const nextValue = nextChunk?.value;
|
|
531
515
|
const rightText = nextValue?.cooked ?? nextValue?.raw ?? '';
|
|
532
516
|
const context = getTemplateExpressionContext(chunk, rightText);
|
|
533
|
-
const code =
|
|
517
|
+
const code = materializeSlice(start, end, source, replacements);
|
|
534
518
|
const marker = registerMarker(code, context.type === 'tag');
|
|
535
519
|
const appendMarker = (wrapper) => {
|
|
536
520
|
const insertion = wrapper ? wrapper(marker) : marker;
|
|
@@ -627,13 +611,15 @@ const transformSource = (source, config, options) => {
|
|
|
627
611
|
const magic = new MagicString(source);
|
|
628
612
|
let mutated = false;
|
|
629
613
|
const helperKinds = new Set();
|
|
614
|
+
const replacements = new Map();
|
|
615
|
+
const inlineTags = new Set();
|
|
630
616
|
taggedTemplates
|
|
631
617
|
.sort((a, b) => b.node.start - a.node.start)
|
|
632
618
|
.forEach(entry => {
|
|
633
619
|
const { node, tagName } = entry;
|
|
634
620
|
const mode = config.tagModes.get(tagName) ?? DEFAULT_MODE;
|
|
635
621
|
const quasi = node.quasi;
|
|
636
|
-
const templateSource = buildTemplateSource(quasi.quasis, quasi.expressions, source, tagName);
|
|
622
|
+
const templateSource = buildTemplateSource(quasi.quasis, quasi.expressions, source, tagName, replacements);
|
|
637
623
|
const templateStrings = materializeTemplateStrings(quasi.quasis);
|
|
638
624
|
if (mode === 'runtime') {
|
|
639
625
|
const { code, changed } = transformTemplateLiteral(templateSource.source, config.resourcePath, tagName, templateStrings, templateSource.diagnostics);
|
|
@@ -645,6 +631,7 @@ const transformSource = (source, config, options) => {
|
|
|
645
631
|
const tagSource = source.slice(node.tag.start, node.tag.end);
|
|
646
632
|
const replacement = `${tagSource}\`${restored}\``;
|
|
647
633
|
magic.overwrite(node.start, node.end, replacement);
|
|
634
|
+
replacements.set(`${node.start}:${node.end}`, replacement);
|
|
648
635
|
mutated = true;
|
|
649
636
|
return;
|
|
650
637
|
}
|
|
@@ -652,7 +639,18 @@ const transformSource = (source, config, options) => {
|
|
|
652
639
|
const compiled = compileReactTemplate(templateSource.source, templateSource.placeholders, config.resourcePath, tagName, templateStrings, templateSource.diagnostics);
|
|
653
640
|
helperKinds.add('react');
|
|
654
641
|
magic.overwrite(node.start, node.end, compiled);
|
|
642
|
+
replacements.set(`${node.start}:${node.end}`, compiled);
|
|
643
|
+
mutated = true;
|
|
644
|
+
inlineTags.add(tagName);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (mode === 'dom') {
|
|
648
|
+
const result = compileDomTemplate(templateSource.source, templateSource.placeholders, config.resourcePath, tagName, templateStrings, templateSource.diagnostics);
|
|
649
|
+
result.helpers.forEach(helper => helperKinds.add(helper));
|
|
650
|
+
magic.overwrite(node.start, node.end, result.code);
|
|
651
|
+
replacements.set(`${node.start}:${node.end}`, result.code);
|
|
655
652
|
mutated = true;
|
|
653
|
+
inlineTags.add(tagName);
|
|
656
654
|
return;
|
|
657
655
|
}
|
|
658
656
|
/* c8 ignore next */
|
|
@@ -660,12 +658,22 @@ const transformSource = (source, config, options) => {
|
|
|
660
658
|
// Modes are validated during option parsing; this fallback guards future extensions.
|
|
661
659
|
throw new Error(`[jsx-loader] Transformation mode "${mode}" not implemented yet for tag "${tagName}".`);
|
|
662
660
|
});
|
|
661
|
+
if (rewriteImportsWithoutTags(ast.program, magic, inlineTags, source)) {
|
|
662
|
+
mutated = true;
|
|
663
|
+
}
|
|
663
664
|
const helperSource = Array.from(helperKinds)
|
|
664
665
|
.map(kind => HELPER_SNIPPETS[kind])
|
|
665
666
|
.filter(Boolean)
|
|
666
667
|
.join('\n');
|
|
667
668
|
if (helperSource) {
|
|
668
|
-
|
|
669
|
+
const helperBlock = `${helperSource.trimEnd()}\n\n`;
|
|
670
|
+
const shebangIndex = source.startsWith('#!') ? source.indexOf('\n') : -1;
|
|
671
|
+
if (shebangIndex >= 0) {
|
|
672
|
+
magic.appendLeft(shebangIndex + 1, helperBlock);
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
magic.prepend(helperBlock);
|
|
676
|
+
}
|
|
669
677
|
mutated = true;
|
|
670
678
|
}
|
|
671
679
|
const code = mutated ? magic.toString() : source;
|
|
@@ -686,6 +694,8 @@ export default function jsxLoader(input) {
|
|
|
686
694
|
const callback = this.async();
|
|
687
695
|
try {
|
|
688
696
|
const options = this.getOptions?.() ?? {};
|
|
697
|
+
const warn = this.emitWarning?.bind(this);
|
|
698
|
+
const webTarget = isWebTarget(this.target);
|
|
689
699
|
const explicitTags = Array.isArray(options.tags)
|
|
690
700
|
? options.tags.filter((value) => typeof value === 'string' && value.length > 0)
|
|
691
701
|
: null;
|
|
@@ -699,6 +709,9 @@ export default function jsxLoader(input) {
|
|
|
699
709
|
const configuredTagModes = options.tagModes && typeof options.tagModes === 'object'
|
|
700
710
|
? options.tagModes
|
|
701
711
|
: undefined;
|
|
712
|
+
const userSpecifiedMode = parseLoaderMode(options.mode);
|
|
713
|
+
const defaultMode = userSpecifiedMode ?? DEFAULT_MODE;
|
|
714
|
+
const userConfiguredTags = new Set();
|
|
702
715
|
if (configuredTagModes) {
|
|
703
716
|
Object.entries(configuredTagModes).forEach(([tagName, mode]) => {
|
|
704
717
|
const parsed = parseLoaderMode(mode);
|
|
@@ -706,15 +719,26 @@ export default function jsxLoader(input) {
|
|
|
706
719
|
return;
|
|
707
720
|
}
|
|
708
721
|
tagModes.set(tagName, parsed);
|
|
722
|
+
userConfiguredTags.add(tagName);
|
|
709
723
|
});
|
|
710
724
|
}
|
|
711
|
-
const defaultMode = parseLoaderMode(options.mode) ?? DEFAULT_MODE;
|
|
712
725
|
const tags = Array.from(new Set([...tagList, ...tagModes.keys()]));
|
|
713
726
|
tags.forEach(tagName => {
|
|
714
727
|
if (!tagModes.has(tagName)) {
|
|
715
728
|
tagModes.set(tagName, defaultMode);
|
|
716
729
|
}
|
|
717
730
|
});
|
|
731
|
+
/**
|
|
732
|
+
* If targeting the web and runtime mode is only implied (not explicitly requested),
|
|
733
|
+
* keep the runtime output but surface a warning so users can opt into react mode when
|
|
734
|
+
* bundling for the browser.
|
|
735
|
+
*/
|
|
736
|
+
if (webTarget && userSpecifiedMode === null) {
|
|
737
|
+
const hasImplicitRuntime = tags.some(tagName => tagModes.get(tagName) === 'runtime' && !userConfiguredTags.has(tagName));
|
|
738
|
+
if (hasImplicitRuntime) {
|
|
739
|
+
warn?.(new Error('[jsx-loader] Web target detected while defaulting to runtime mode; the shipped parser expects a Node-like environment. Set mode: "react" (or configure per-tag) when bundling client code, or provide a browser-safe runtime parser if you intentionally need runtime output.'));
|
|
740
|
+
}
|
|
741
|
+
}
|
|
718
742
|
const source = typeof input === 'string' ? input : input.toString('utf8');
|
|
719
743
|
const enableSourceMap = options.sourceMap === true;
|
|
720
744
|
const { code, map } = transformSource(source, {
|
package/dist/runtime/shared.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeJsxText } from '../shared/normalize-text.js';
|
|
1
2
|
export { formatTaggedTemplateParserError } from '../internal/template-diagnostics.js';
|
|
2
3
|
const OPEN_TAG_RE = /<\s*$/;
|
|
3
4
|
const CLOSE_TAG_RE = /<\/\s*$/;
|
|
@@ -70,19 +71,8 @@ export const walkAst = (node, visitor) => {
|
|
|
70
71
|
});
|
|
71
72
|
};
|
|
72
73
|
export const normalizeJsxTextSegments = (value, placeholders) => {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
const trailingWhitespace = value.match(/\s*$/)?.[0] ?? '';
|
|
76
|
-
const trimStart = /\n/.test(leadingWhitespace);
|
|
77
|
-
const trimEnd = /\n/.test(trailingWhitespace);
|
|
78
|
-
let normalized = collapsed;
|
|
79
|
-
if (trimStart) {
|
|
80
|
-
normalized = normalized.replace(/^\s+/, '');
|
|
81
|
-
}
|
|
82
|
-
if (trimEnd) {
|
|
83
|
-
normalized = normalized.replace(/\s+$/, '');
|
|
84
|
-
}
|
|
85
|
-
if (normalized.length === 0 || normalized.trim().length === 0) {
|
|
74
|
+
const normalized = normalizeJsxText(value);
|
|
75
|
+
if (!normalized) {
|
|
86
76
|
return [];
|
|
87
77
|
}
|
|
88
78
|
const segments = [];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const normalizeJsxText: (value: string) => string | null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const normalizeJsxText = (value) => {
|
|
2
|
+
const collapsed = value.replace(/\r/g, '').replace(/\n\s+/g, ' ');
|
|
3
|
+
const leadingWhitespace = value.match(/^\s*/)?.[0] ?? '';
|
|
4
|
+
const trailingWhitespace = value.match(/\s*$/)?.[0] ?? '';
|
|
5
|
+
const trimStart = /\n/.test(leadingWhitespace);
|
|
6
|
+
const trimEnd = /\n/.test(trailingWhitespace);
|
|
7
|
+
let normalized = collapsed;
|
|
8
|
+
if (trimStart) {
|
|
9
|
+
normalized = normalized.replace(/^\s+/, '');
|
|
10
|
+
}
|
|
11
|
+
if (trimEnd) {
|
|
12
|
+
normalized = normalized.replace(/\s+$/, '');
|
|
13
|
+
}
|
|
14
|
+
if (normalized.length === 0 || normalized.trim().length === 0) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return normalized;
|
|
18
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knighted/jsx",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Runtime JSX tagged template that renders DOM or React trees anywhere without a build step.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jsx runtime",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"./package.json": "./package.json"
|
|
109
109
|
},
|
|
110
110
|
"engines": {
|
|
111
|
-
"node": ">=22.
|
|
111
|
+
"node": ">=22.21.1"
|
|
112
112
|
},
|
|
113
113
|
"engineStrict": true,
|
|
114
114
|
"scripts": {
|
|
@@ -119,9 +119,9 @@
|
|
|
119
119
|
"check-types:lib": "tsc --noEmit --project tsconfig.json",
|
|
120
120
|
"check-types:demo": "tsc --noEmit --project examples/browser/tsconfig.json",
|
|
121
121
|
"check-types:test": "tsc --noEmit --project tsconfig.vitest.json",
|
|
122
|
-
"clean:deps": "rimraf
|
|
123
|
-
"clean:dist": "rimraf
|
|
124
|
-
"clean": "npm run clean:
|
|
122
|
+
"clean:deps": "rimraf node_modules",
|
|
123
|
+
"clean:dist": "rimraf dist",
|
|
124
|
+
"clean": "npm run clean:dist && npm run clean:deps",
|
|
125
125
|
"lint": "eslint src test",
|
|
126
126
|
"pretest": "npm run build",
|
|
127
127
|
"cycles": "madge src --circular --extensions ts,tsx,js,jsx --ts-config tsconfig.json",
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
},
|
|
144
144
|
"devDependencies": {
|
|
145
145
|
"@eslint/js": "^9.39.1",
|
|
146
|
-
"@knighted/duel": "^4.0.0
|
|
146
|
+
"@knighted/duel": "^4.0.0",
|
|
147
147
|
"@oxc-project/types": "^0.105.0",
|
|
148
148
|
"@playwright/test": "^1.57.0",
|
|
149
149
|
"@rspack/core": "^1.0.5",
|