@knighted/jsx 1.5.2 → 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.
Files changed (49) hide show
  1. package/README.md +1 -0
  2. package/dist/cjs/debug/diagnostics.cjs +57 -0
  3. package/dist/cjs/debug/diagnostics.d.cts +6 -0
  4. package/dist/cjs/debug/index.cjs +7 -0
  5. package/dist/cjs/debug/index.d.cts +2 -0
  6. package/dist/cjs/internal/attribute-resolution.cjs +22 -4
  7. package/dist/cjs/internal/attribute-resolution.d.cts +5 -0
  8. package/dist/cjs/internal/dev-environment.cjs +41 -0
  9. package/dist/cjs/internal/dev-environment.d.cts +4 -0
  10. package/dist/cjs/internal/event-bindings.cjs +11 -2
  11. package/dist/cjs/internal/event-bindings.d.cts +5 -1
  12. package/dist/cjs/internal/template-diagnostics.cjs +171 -0
  13. package/dist/cjs/internal/template-diagnostics.d.cts +13 -0
  14. package/dist/cjs/jsx.cjs +2 -2
  15. package/dist/cjs/loader/jsx.cjs +72 -20
  16. package/dist/cjs/loader/jsx.d.cts +6 -1
  17. package/dist/cjs/node/debug/index.cjs +6 -0
  18. package/dist/cjs/node/debug/index.d.cts +2 -0
  19. package/dist/cjs/react/react-jsx.cjs +1 -1
  20. package/dist/cjs/runtime/shared.cjs +41 -22
  21. package/dist/cjs/runtime/shared.d.cts +5 -2
  22. package/dist/debug/diagnostics.d.ts +6 -0
  23. package/dist/debug/diagnostics.js +52 -0
  24. package/dist/debug/index.d.ts +2 -0
  25. package/dist/debug/index.js +3 -0
  26. package/dist/internal/attribute-resolution.d.ts +5 -0
  27. package/dist/internal/attribute-resolution.js +20 -3
  28. package/dist/internal/dev-environment.d.ts +4 -0
  29. package/dist/internal/dev-environment.js +34 -0
  30. package/dist/internal/event-bindings.d.ts +5 -1
  31. package/dist/internal/event-bindings.js +9 -1
  32. package/dist/internal/template-diagnostics.d.ts +13 -0
  33. package/dist/internal/template-diagnostics.js +167 -0
  34. package/dist/jsx.js +3 -3
  35. package/dist/lite/debug/diagnostics.js +1 -0
  36. package/dist/lite/debug/index.js +8 -0
  37. package/dist/lite/index.js +8 -4
  38. package/dist/lite/node/debug/index.js +8 -0
  39. package/dist/lite/node/index.js +8 -4
  40. package/dist/lite/node/react/index.js +7 -3
  41. package/dist/lite/react/index.js +7 -3
  42. package/dist/loader/jsx.d.ts +6 -1
  43. package/dist/loader/jsx.js +72 -20
  44. package/dist/node/debug/index.d.ts +2 -0
  45. package/dist/node/debug/index.js +6 -0
  46. package/dist/react/react-jsx.js +2 -2
  47. package/dist/runtime/shared.d.ts +5 -2
  48. package/dist/runtime/shared.js +39 -21
  49. package/package.json +39 -7
package/README.md CHANGED
@@ -19,6 +19,7 @@ A runtime JSX template tag backed by the [`oxc-parser`](https://github.com/oxc-p
19
19
  - [Loader integration](#loader-integration)
20
20
  - [Node / SSR usage](#node--ssr-usage)
21
21
  - [Browser usage](#browser-usage)
22
+ - [Development diagnostics](docs/development-diagnostics.md)
22
23
  - [TypeScript plugin](docs/ts-plugin.md)
23
24
  - [TypeScript guide](docs/typescript.md)
24
25
  - [Component testing](docs/testing.md)
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.disableJsxDebugDiagnostics = exports.enableJsxDebugDiagnostics = void 0;
4
+ const attribute_resolution_js_1 = require("../internal/attribute-resolution.cjs");
5
+ const event_bindings_js_1 = require("../internal/event-bindings.cjs");
6
+ const dev_environment_js_1 = require("../internal/dev-environment.cjs");
7
+ const isAsciiLowercase = (char) => char >= 'a' && char <= 'z';
8
+ let diagnosticsMode = 'env';
9
+ const shouldRunDiagnostics = () => diagnosticsMode === 'always' || (diagnosticsMode === 'env' && (0, dev_environment_js_1.isDevEnvironment)());
10
+ const shouldForceWarnings = () => diagnosticsMode === 'always';
11
+ const attributeDiagnostics = {
12
+ warnLowercaseEventProp(name) {
13
+ if (!shouldRunDiagnostics()) {
14
+ return;
15
+ }
16
+ if (!name.startsWith('on') || name.startsWith('on:') || name.length < 3) {
17
+ return;
18
+ }
19
+ const indicator = name[2] ?? '';
20
+ if (!isAsciiLowercase(indicator)) {
21
+ return;
22
+ }
23
+ const suggestion = `${name.slice(0, 2)}${indicator.toUpperCase()}${name.slice(3)}`;
24
+ (0, dev_environment_js_1.emitDevWarning)(`Use camelCase DOM event props when targeting runtime jsx templates. Received "${name}"; did you mean "${suggestion}"?`, shouldForceWarnings());
25
+ },
26
+ ensureValidDangerouslySetInnerHTML(value) {
27
+ if (!shouldRunDiagnostics()) {
28
+ return;
29
+ }
30
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
31
+ throw (0, dev_environment_js_1.createDevError)('dangerouslySetInnerHTML expects an object with a string __html field.');
32
+ }
33
+ const html = value.__html;
34
+ if (typeof html !== 'string') {
35
+ throw (0, dev_environment_js_1.createDevError)(`dangerouslySetInnerHTML.__html must be a string but received ${(0, dev_environment_js_1.describeValue)(html)}.`);
36
+ }
37
+ },
38
+ };
39
+ const eventDiagnostics = {
40
+ onInvalidHandler(propName, value) {
41
+ if (!shouldRunDiagnostics()) {
42
+ return;
43
+ }
44
+ throw (0, dev_environment_js_1.createDevError)(`The "${propName}" prop expects a function, EventListenerObject, or descriptor ({ handler }) but received ${(0, dev_environment_js_1.describeValue)(value)}.`);
45
+ },
46
+ };
47
+ const enableJsxDebugDiagnostics = (options) => {
48
+ diagnosticsMode = options?.mode ?? 'env';
49
+ (0, attribute_resolution_js_1.setAttributeDiagnosticsHooks)(attributeDiagnostics);
50
+ (0, event_bindings_js_1.setEventDiagnosticsHooks)(eventDiagnostics);
51
+ };
52
+ exports.enableJsxDebugDiagnostics = enableJsxDebugDiagnostics;
53
+ const disableJsxDebugDiagnostics = () => {
54
+ (0, attribute_resolution_js_1.setAttributeDiagnosticsHooks)(null);
55
+ (0, event_bindings_js_1.setEventDiagnosticsHooks)(null);
56
+ };
57
+ exports.disableJsxDebugDiagnostics = disableJsxDebugDiagnostics;
@@ -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,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.jsx = void 0;
4
+ const diagnostics_js_1 = require("./diagnostics.cjs");
5
+ (0, diagnostics_js_1.enableJsxDebugDiagnostics)({ mode: 'always' });
6
+ var jsx_js_1 = require("../jsx.cjs");
7
+ Object.defineProperty(exports, "jsx", { enumerable: true, get: function () { return jsx_js_1.jsx; } });
@@ -0,0 +1,2 @@
1
+ export { jsx } from '../jsx.cjs';
2
+ export type { JsxRenderable, JsxComponent } from '../jsx.cjs';
@@ -1,10 +1,27 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createResolveAttributes = void 0;
3
+ exports.createResolveAttributes = exports.setAttributeDiagnosticsHooks = void 0;
4
+ let attributeDiagnostics = null;
5
+ const setAttributeDiagnosticsHooks = (hooks) => {
6
+ attributeDiagnostics = hooks;
7
+ };
8
+ exports.setAttributeDiagnosticsHooks = setAttributeDiagnosticsHooks;
9
+ const warnLowercaseEventProp = (name) => {
10
+ attributeDiagnostics?.warnLowercaseEventProp?.(name);
11
+ };
12
+ const ensureValidDangerouslySetInnerHTML = (value) => {
13
+ attributeDiagnostics?.ensureValidDangerouslySetInnerHTML?.(value);
14
+ };
4
15
  const createResolveAttributes = (deps) => {
5
16
  const { getIdentifierName, evaluateExpressionWithNamespace } = deps;
6
17
  return (attributes, ctx, namespace) => {
7
18
  const props = {};
19
+ const assignProp = (propName, propValue) => {
20
+ if (propName === 'dangerouslySetInnerHTML') {
21
+ ensureValidDangerouslySetInnerHTML(propValue);
22
+ }
23
+ props[propName] = propValue;
24
+ };
8
25
  attributes.forEach(attribute => {
9
26
  if (attribute.type === 'JSXSpreadAttribute') {
10
27
  const spreadValue = evaluateExpressionWithNamespace(attribute.argument, ctx, namespace);
@@ -16,19 +33,20 @@ const createResolveAttributes = (deps) => {
16
33
  return;
17
34
  }
18
35
  const name = getIdentifierName(attribute.name);
36
+ warnLowercaseEventProp(name);
19
37
  if (!attribute.value) {
20
- props[name] = true;
38
+ assignProp(name, true);
21
39
  return;
22
40
  }
23
41
  if (attribute.value.type === 'Literal') {
24
- props[name] = attribute.value.value;
42
+ assignProp(name, attribute.value.value);
25
43
  return;
26
44
  }
27
45
  if (attribute.value.type === 'JSXExpressionContainer') {
28
46
  if (attribute.value.expression.type === 'JSXEmptyExpression') {
29
47
  return;
30
48
  }
31
- props[name] = evaluateExpressionWithNamespace(attribute.value.expression, ctx, namespace);
49
+ assignProp(name, evaluateExpressionWithNamespace(attribute.value.expression, ctx, namespace));
32
50
  }
33
51
  });
34
52
  return props;
@@ -1,5 +1,10 @@
1
1
  import type { Expression, JSXAttribute, JSXElement, JSXFragment, JSXSpreadAttribute } from '@oxc-project/types';
2
2
  import type { TemplateComponent, TemplateContext } from '../runtime/shared.cjs';
3
+ export type AttributeDiagnosticsHooks = {
4
+ warnLowercaseEventProp?: (name: string) => void;
5
+ ensureValidDangerouslySetInnerHTML?: (value: unknown) => void;
6
+ };
7
+ export declare const setAttributeDiagnosticsHooks: (hooks: AttributeDiagnosticsHooks | null) => void;
3
8
  export type Namespace = 'svg' | null;
4
9
  export type EvaluateExpressionWithNamespace<TComponent extends TemplateComponent> = (expression: Expression | JSXElement | JSXFragment, ctx: TemplateContext<TComponent>, namespace: Namespace) => unknown;
5
10
  export type ResolveAttributesDependencies<TComponent extends TemplateComponent> = {
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.emitDevWarning = exports.createDevError = exports.describeValue = exports.isDevEnvironment = void 0;
4
+ const DEV_PREFIX = '@knighted/jsx';
5
+ const formatDevMessage = (message) => `${DEV_PREFIX}: ${message}`;
6
+ const isDevEnvironment = () => typeof process !== 'undefined' && process.env?.KNIGHTED_JSX_DEBUG === '1';
7
+ exports.isDevEnvironment = isDevEnvironment;
8
+ const describeValue = (value) => {
9
+ if (value === null) {
10
+ return 'null';
11
+ }
12
+ if (value === undefined) {
13
+ return 'undefined';
14
+ }
15
+ if (typeof value === 'function') {
16
+ return value.name ? `function ${value.name}` : 'function';
17
+ }
18
+ if (Array.isArray(value)) {
19
+ return 'array';
20
+ }
21
+ if (typeof value === 'object') {
22
+ const ctor = value.constructor;
23
+ if (ctor && typeof ctor.name === 'string' && ctor.name && ctor.name !== 'Object') {
24
+ return `${ctor.name} instance`;
25
+ }
26
+ return 'object';
27
+ }
28
+ return typeof value;
29
+ };
30
+ exports.describeValue = describeValue;
31
+ const createDevError = (message) => new Error(formatDevMessage(message));
32
+ exports.createDevError = createDevError;
33
+ const emitDevWarning = (message, force = false) => {
34
+ if (!force && !(0, exports.isDevEnvironment)()) {
35
+ return;
36
+ }
37
+ if (typeof console !== 'undefined' && typeof console.warn === 'function') {
38
+ console.warn(formatDevMessage(message));
39
+ }
40
+ };
41
+ exports.emitDevWarning = emitDevWarning;
@@ -0,0 +1,4 @@
1
+ export declare const isDevEnvironment: () => boolean;
2
+ export declare const describeValue: (value: unknown) => string;
3
+ export declare const createDevError: (message: string) => Error;
4
+ export declare const emitDevWarning: (message: string, force?: boolean) => void;
@@ -1,6 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveEventHandlerValue = exports.parseEventPropName = void 0;
3
+ exports.resolveEventHandlerValue = exports.parseEventPropName = exports.setEventDiagnosticsHooks = void 0;
4
+ let eventDiagnostics = null;
5
+ const setEventDiagnosticsHooks = (hooks) => {
6
+ eventDiagnostics = hooks;
7
+ };
8
+ exports.setEventDiagnosticsHooks = setEventDiagnosticsHooks;
4
9
  const captureSuffix = 'Capture';
5
10
  const stripCaptureSuffix = (rawName) => {
6
11
  if (rawName.endsWith(captureSuffix)) {
@@ -54,11 +59,15 @@ const isEventHandlerDescriptor = (value) => {
54
59
  }
55
60
  return isEventListenerObject(handler);
56
61
  };
57
- const resolveEventHandlerValue = (value) => {
62
+ const throwInvalidHandlerError = (propName, value) => {
63
+ eventDiagnostics?.onInvalidHandler?.(propName, value);
64
+ };
65
+ const resolveEventHandlerValue = (propName, value) => {
58
66
  if (typeof value === 'function' || isEventListenerObject(value)) {
59
67
  return { listener: value };
60
68
  }
61
69
  if (!isEventHandlerDescriptor(value)) {
70
+ throwInvalidHandlerError(propName, value);
62
71
  return null;
63
72
  }
64
73
  const descriptor = value;
@@ -1,3 +1,7 @@
1
+ export type EventDiagnosticsHooks = {
2
+ onInvalidHandler?: (propName: string, value: unknown) => void;
3
+ };
4
+ export declare const setEventDiagnosticsHooks: (hooks: EventDiagnosticsHooks | null) => void;
1
5
  export type ParsedEventBinding = {
2
6
  eventName: string;
3
7
  capture: boolean;
@@ -15,4 +19,4 @@ export type ResolvedEventHandler = {
15
19
  listener: EventListenerOrEventListenerObject;
16
20
  options?: AddEventListenerOptions;
17
21
  };
18
- export declare const resolveEventHandlerValue: (value: unknown) => ResolvedEventHandler | null;
22
+ export declare const resolveEventHandlerValue: (propName: string, value: unknown) => ResolvedEventHandler | null;
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatTaggedTemplateParserError = void 0;
4
+ const DEFAULT_LABEL = 'oxc-parser';
5
+ const buildTemplateDisplaySource = (templates) => {
6
+ const raw = templates.raw ?? templates;
7
+ let source = raw[0] ?? '';
8
+ const spans = [];
9
+ for (let idx = 0; idx < raw.length - 1; idx++) {
10
+ const label = '${expr#' + idx + '}';
11
+ const templateStart = source.length;
12
+ source += label;
13
+ const templateEnd = source.length;
14
+ spans.push({ index: idx, templateStart, templateEnd, label });
15
+ source += raw[idx + 1] ?? '';
16
+ }
17
+ return { source, spans };
18
+ };
19
+ const combineExpressionSpans = (diagnostics, templateSpans) => {
20
+ const templateSpanMap = new Map();
21
+ templateSpans.forEach(span => {
22
+ templateSpanMap.set(span.index, span);
23
+ });
24
+ return diagnostics.expressionRanges
25
+ .map(span => {
26
+ const templateSpan = templateSpanMap.get(span.index);
27
+ if (!templateSpan) {
28
+ return null;
29
+ }
30
+ const sourceLength = Math.max(0, span.sourceEnd - span.sourceStart);
31
+ const templateLength = Math.max(0, templateSpan.templateEnd - templateSpan.templateStart);
32
+ return {
33
+ sourceStart: span.sourceStart,
34
+ sourceEnd: span.sourceEnd,
35
+ templateStart: templateSpan.templateStart,
36
+ templateEnd: templateSpan.templateEnd,
37
+ delta: templateLength - sourceLength,
38
+ };
39
+ })
40
+ .filter((span) => Boolean(span))
41
+ .sort((a, b) => a.sourceStart - b.sourceStart);
42
+ };
43
+ const mapIndexToTemplate = (index, spans, templateLength) => {
44
+ if (!Number.isFinite(index) || index <= 0) {
45
+ return 0;
46
+ }
47
+ let delta = 0;
48
+ for (const span of spans) {
49
+ if (index < span.sourceStart) {
50
+ break;
51
+ }
52
+ if (index < span.sourceEnd) {
53
+ const relative = Math.max(0, index - span.sourceStart);
54
+ const templateSpanLength = Math.max(0, span.templateEnd - span.templateStart);
55
+ if (templateSpanLength === 0) {
56
+ return span.templateStart;
57
+ }
58
+ const clamped = Math.min(relative, Math.max(0, templateSpanLength - 1));
59
+ return span.templateStart + clamped;
60
+ }
61
+ delta += span.delta;
62
+ }
63
+ const mapped = index + delta;
64
+ if (mapped <= 0) {
65
+ return 0;
66
+ }
67
+ if (mapped >= templateLength) {
68
+ return templateLength;
69
+ }
70
+ return mapped;
71
+ };
72
+ const getLineAndColumnFromIndex = (source, index) => {
73
+ const limit = Math.max(0, Math.min(index, source.length));
74
+ let line = 1;
75
+ let column = 1;
76
+ for (let idx = 0; idx < limit; idx++) {
77
+ if (source.charCodeAt(idx) === 10) {
78
+ line++;
79
+ column = 1;
80
+ continue;
81
+ }
82
+ column++;
83
+ }
84
+ return { line, column };
85
+ };
86
+ const computeLineOffsets = (lines) => {
87
+ const offsets = [];
88
+ let cursor = 0;
89
+ lines.forEach((line, index) => {
90
+ offsets.push(cursor);
91
+ cursor += line.length;
92
+ if (index < lines.length - 1) {
93
+ cursor += 1;
94
+ }
95
+ });
96
+ return offsets;
97
+ };
98
+ const createPointerLine = (lineNumber, lineText, lineStartOffset, rangeStart, rangeEnd, startLine, endLine) => {
99
+ const lineEndOffset = lineStartOffset + lineText.length;
100
+ const overlapStart = Math.max(rangeStart, lineStartOffset);
101
+ const overlapEnd = Math.min(rangeEnd, lineEndOffset);
102
+ if (overlapEnd > overlapStart) {
103
+ const pointerStart = Math.max(0, overlapStart - lineStartOffset);
104
+ const pointerWidth = Math.max(1, overlapEnd - overlapStart);
105
+ return ' '.repeat(pointerStart) + '^'.repeat(pointerWidth);
106
+ }
107
+ if (lineText.length === 0 && lineNumber >= startLine && lineNumber <= endLine) {
108
+ return '^';
109
+ }
110
+ if (lineNumber === startLine) {
111
+ const caretPos = Math.max(0, rangeStart - lineStartOffset);
112
+ return ' '.repeat(Math.min(caretPos, lineText.length)) + '^';
113
+ }
114
+ return '';
115
+ };
116
+ const buildCodeFrame = (source, start, end, startLine, endLine) => {
117
+ if (!source.length) {
118
+ return '';
119
+ }
120
+ const lines = source.split('\n');
121
+ const offsets = computeLineOffsets(lines);
122
+ const frameStart = Math.max(1, startLine - 1);
123
+ const frameEnd = Math.min(lines.length, endLine + 1);
124
+ const gutterWidth = String(frameEnd).length;
125
+ const frame = [];
126
+ for (let lineNumber = frameStart; lineNumber <= frameEnd; lineNumber++) {
127
+ const lineText = lines[lineNumber - 1] ?? '';
128
+ const gutter = String(lineNumber).padStart(gutterWidth, ' ');
129
+ frame.push(`${gutter} | ${lineText}`);
130
+ const pointer = createPointerLine(lineNumber, lineText, offsets[lineNumber - 1] ?? 0, start, end, startLine, endLine);
131
+ if (pointer) {
132
+ frame.push(`${' '.repeat(gutterWidth)} | ${pointer}`);
133
+ }
134
+ }
135
+ return frame.join('\n');
136
+ };
137
+ const formatTaggedTemplateParserError = (tagName, templates, diagnostics, error, options) => {
138
+ const label = options?.label ?? DEFAULT_LABEL;
139
+ const fallback = `[${label}] ${error.message}`;
140
+ const primaryLabel = error.labels?.[0];
141
+ if (!primaryLabel) {
142
+ return fallback;
143
+ }
144
+ const { source: templateSource, spans } = buildTemplateDisplaySource(templates);
145
+ const combinedSpans = combineExpressionSpans(diagnostics, spans);
146
+ const mapIndex = (value) => {
147
+ const numeric = typeof value === 'number' ? value : 0;
148
+ return mapIndexToTemplate(numeric, combinedSpans, templateSource.length);
149
+ };
150
+ const startIndex = mapIndex(primaryLabel.start);
151
+ let endIndex = mapIndex(primaryLabel.end);
152
+ if (endIndex <= startIndex) {
153
+ endIndex = Math.min(templateSource.length, startIndex + 1);
154
+ }
155
+ const startLocation = getLineAndColumnFromIndex(templateSource, startIndex);
156
+ const endLocation = getLineAndColumnFromIndex(templateSource, Math.max(startIndex, endIndex - 1));
157
+ const codeframe = buildCodeFrame(templateSource, startIndex, endIndex, startLocation.line, endLocation.line);
158
+ let message = `[${label}] ${error.message}`;
159
+ message += `\n--> ${tagName} template:${startLocation.line}:${startLocation.column}`;
160
+ if (primaryLabel.message) {
161
+ message += `\n${primaryLabel.message}`;
162
+ }
163
+ if (codeframe) {
164
+ message += `\n${codeframe}`;
165
+ }
166
+ if (error.helpMessage) {
167
+ message += `\n${error.helpMessage}`;
168
+ }
169
+ return message;
170
+ };
171
+ exports.formatTaggedTemplateParserError = formatTaggedTemplateParserError;
@@ -0,0 +1,13 @@
1
+ import type { OxcError } from 'oxc-parser';
2
+ export type TemplateExpressionRange = {
3
+ index: number;
4
+ sourceStart: number;
5
+ sourceEnd: number;
6
+ };
7
+ export type TemplateDiagnostics = {
8
+ expressionRanges: TemplateExpressionRange[];
9
+ };
10
+ export type TaggedTemplateFormatOptions = {
11
+ label?: string;
12
+ };
13
+ export declare const formatTaggedTemplateParserError: (tagName: string, templates: TemplateStringsArray, diagnostics: TemplateDiagnostics, error: OxcError, options?: TaggedTemplateFormatOptions) => string;
package/dist/cjs/jsx.cjs CHANGED
@@ -116,7 +116,7 @@ const setDomProp = (element, name, value, namespace) => {
116
116
  }
117
117
  const eventBinding = (0, event_bindings_js_1.parseEventPropName)(name);
118
118
  if (eventBinding) {
119
- const handlerValue = (0, event_bindings_js_1.resolveEventHandlerValue)(value);
119
+ const handlerValue = (0, event_bindings_js_1.resolveEventHandlerValue)(name, value);
120
120
  if (handlerValue) {
121
121
  let options = handlerValue.options ? { ...handlerValue.options } : undefined;
122
122
  if (eventBinding.capture) {
@@ -311,7 +311,7 @@ const jsx = (templates, ...values) => {
311
311
  const build = (0, shared_js_1.buildTemplate)(templates, values);
312
312
  const result = (0, oxc_parser_1.parseSync)('inline.jsx', build.source, shared_js_1.parserOptions);
313
313
  if (result.errors.length > 0) {
314
- throw new Error((0, shared_js_1.formatParserError)(result.errors[0]));
314
+ throw new Error((0, shared_js_1.formatTaggedTemplateParserError)('jsx', templates, build.diagnostics, result.errors[0]));
315
315
  }
316
316
  const root = (0, shared_js_1.extractRootNode)(result.program);
317
317
  const ctx = {
@@ -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;
@@ -383,10 +384,12 @@ const renderTemplateWithSlots = (source, slots) => {
383
384
  output += escapeTemplateChunk(source.slice(cursor));
384
385
  return { code: output, changed: slots.length > 0 };
385
386
  };
386
- const transformTemplateLiteral = (templateSource, resourcePath) => {
387
+ const transformTemplateLiteral = (templateSource, resourcePath, tagName, templates, diagnostics) => {
387
388
  const result = (0, oxc_parser_1.parseSync)(`${resourcePath}?jsx-template`, templateSource, TEMPLATE_PARSER_OPTIONS);
388
389
  if (result.errors.length > 0) {
389
- throw new Error(formatParserError(result.errors[0]));
390
+ throw new Error((0, template_diagnostics_js_1.formatTaggedTemplateParserError)(tagName, templates, diagnostics, result.errors[0], {
391
+ label: 'jsx-loader',
392
+ }));
390
393
  }
391
394
  const slots = collectSlots(result.program, templateSource);
392
395
  return renderTemplateWithSlots(templateSource, slots);
@@ -455,6 +458,25 @@ const normalizeJsxTextSegments = (value, placeholders) => {
455
458
  return segments;
456
459
  };
457
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
+ };
458
480
  const buildTemplateSource = (quasis, expressions, source, tag) => {
459
481
  const placeholderMap = new Map();
460
482
  const tagPlaceholderMap = new Map();
@@ -462,6 +484,7 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
462
484
  let placeholderIndex = 0;
463
485
  let trimStartNext = 0;
464
486
  let mutated = false;
487
+ const expressionRanges = [];
465
488
  const registerMarker = (code, isTag) => {
466
489
  if (isTag) {
467
490
  const existing = tagPlaceholderMap.get(code);
@@ -477,6 +500,12 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
477
500
  placeholderMap.set(marker, code);
478
501
  return marker;
479
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
+ };
480
509
  quasis.forEach((quasi, index) => {
481
510
  let chunk = quasi.value.cooked;
482
511
  if (typeof chunk !== 'string') {
@@ -494,6 +523,7 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
494
523
  if (!expression) {
495
524
  return;
496
525
  }
526
+ const expressionIndex = index;
497
527
  const start = expression.start ?? null;
498
528
  const end = expression.end ?? null;
499
529
  if (start === null || end === null) {
@@ -509,7 +539,8 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
509
539
  const code = source.slice(start, end);
510
540
  const marker = registerMarker(code, context.type === 'tag');
511
541
  const appendMarker = (wrapper) => {
512
- template += wrapper ? wrapper(marker) : marker;
542
+ const insertion = wrapper ? wrapper(marker) : marker;
543
+ appendInsertion(expressionIndex, insertion);
513
544
  };
514
545
  switch (context.type) {
515
546
  case 'tag':
@@ -551,15 +582,22 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
551
582
  marker,
552
583
  code,
553
584
  })),
585
+ diagnostics: { expressionRanges },
554
586
  };
555
587
  };
556
588
  const restoreTemplatePlaceholders = (code, placeholders) => placeholders.reduce((result, placeholder) => {
557
589
  return result.split(placeholder.marker).join(`\${${placeholder.code}}`);
558
590
  }, code);
559
- const compileReactTemplate = (templateSource, placeholders, resourcePath) => {
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) => {
560
596
  const parsed = (0, oxc_parser_1.parseSync)(`${resourcePath}?jsx-react-template`, templateSource, TEMPLATE_PARSER_OPTIONS);
561
597
  if (parsed.errors.length > 0) {
562
- throw new Error(formatParserError(parsed.errors[0]));
598
+ throw new Error((0, template_diagnostics_js_1.formatTaggedTemplateParserError)(tagName, templates, diagnostics, parsed.errors[0], {
599
+ label: 'jsx-loader',
600
+ }));
563
601
  }
564
602
  const root = extractJsxRoot(parsed.program);
565
603
  const builder = new ReactTemplateBuilder(placeholders);
@@ -577,7 +615,7 @@ const isLoaderPlaceholderIdentifier = (node) => {
577
615
  return (node.name.startsWith(TEMPLATE_EXPR_PLACEHOLDER_PREFIX) ||
578
616
  node.name.startsWith(TAG_PLACEHOLDER_PREFIX));
579
617
  };
580
- const transformSource = (source, config) => {
618
+ const transformSource = (source, config, options) => {
581
619
  const ast = (0, oxc_parser_1.parseSync)(config.resourcePath, source, MODULE_PARSER_OPTIONS);
582
620
  if (ast.errors.length > 0) {
583
621
  throw new Error(formatParserError(ast.errors[0]));
@@ -590,7 +628,7 @@ const transformSource = (source, config) => {
590
628
  }
591
629
  });
592
630
  if (!taggedTemplates.length) {
593
- return { code: source, helpers: [] };
631
+ return { code: source, mutated: false };
594
632
  }
595
633
  const magic = new magic_string_1.default(source);
596
634
  let mutated = false;
@@ -602,8 +640,9 @@ const transformSource = (source, config) => {
602
640
  const mode = config.tagModes.get(tagName) ?? DEFAULT_MODE;
603
641
  const quasi = node.quasi;
604
642
  const templateSource = buildTemplateSource(quasi.quasis, quasi.expressions, source, tagName);
643
+ const templateStrings = materializeTemplateStrings(quasi.quasis);
605
644
  if (mode === 'runtime') {
606
- const { code, changed } = transformTemplateLiteral(templateSource.source, config.resourcePath);
645
+ const { code, changed } = transformTemplateLiteral(templateSource.source, config.resourcePath, tagName, templateStrings, templateSource.diagnostics);
607
646
  const restored = restoreTemplatePlaceholders(code, templateSource.placeholders);
608
647
  const templateChanged = changed || templateSource.mutated;
609
648
  if (!templateChanged) {
@@ -616,7 +655,7 @@ const transformSource = (source, config) => {
616
655
  return;
617
656
  }
618
657
  if (mode === 'react') {
619
- const compiled = compileReactTemplate(templateSource.source, templateSource.placeholders, config.resourcePath);
658
+ const compiled = compileReactTemplate(templateSource.source, templateSource.placeholders, config.resourcePath, tagName, templateStrings, templateSource.diagnostics);
620
659
  helperKinds.add('react');
621
660
  magic.overwrite(node.start, node.end, compiled);
622
661
  mutated = true;
@@ -627,11 +666,26 @@ const transformSource = (source, config) => {
627
666
  // Modes are validated during option parsing; this fallback guards future extensions.
628
667
  throw new Error(`[jsx-loader] Transformation mode "${mode}" not implemented yet for tag "${tagName}".`);
629
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;
630
685
  return {
631
- code: mutated ? magic.toString() : source,
632
- helpers: Array.from(helperKinds)
633
- .map(kind => HELPER_SNIPPETS[kind])
634
- .filter(Boolean),
686
+ code,
687
+ map,
688
+ mutated,
635
689
  };
636
690
  };
637
691
  function jsxLoader(input) {
@@ -668,16 +722,14 @@ function jsxLoader(input) {
668
722
  }
669
723
  });
670
724
  const source = typeof input === 'string' ? input : input.toString('utf8');
671
- const { code, helpers } = transformSource(source, {
725
+ const enableSourceMap = options.sourceMap === true;
726
+ const { code, map } = transformSource(source, {
672
727
  resourcePath: this.resourcePath,
673
728
  tags,
674
729
  tagModes,
675
- });
676
- if (helpers.length) {
677
- callback(null, `${code}\n${helpers.join('\n')}`);
678
- return;
679
- }
680
- callback(null, code);
730
+ }, { sourceMap: enableSourceMap });
731
+ const output = map && enableSourceMap ? `${code}\n${createInlineSourceMapComment(map)}` : code;
732
+ callback(null, output, map);
681
733
  }
682
734
  catch (error) {
683
735
  callback(error);
@@ -1,4 +1,5 @@
1
- type LoaderCallback = (err: Error | null, content?: string) => void;
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 {};