@knighted/jsx 1.1.0 → 1.2.0-rc.1

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.
@@ -0,0 +1,83 @@
1
+ const DOM_TEMPLATE = '<!doctype html><html><body></body></html>';
2
+ const GLOBAL_KEYS = [
3
+ 'window',
4
+ 'self',
5
+ 'document',
6
+ 'HTMLElement',
7
+ 'Element',
8
+ 'Node',
9
+ 'DocumentFragment',
10
+ 'customElements',
11
+ 'Text',
12
+ 'Comment',
13
+ 'MutationObserver',
14
+ 'navigator',
15
+ ];
16
+ const hasDom = () => typeof document !== 'undefined' && typeof document.createElement === 'function';
17
+ const assignGlobalTargets = (windowObj) => {
18
+ const target = globalThis;
19
+ const source = windowObj;
20
+ GLOBAL_KEYS.forEach(key => {
21
+ if (target[key] === undefined && source[key] !== undefined) {
22
+ target[key] = source[key];
23
+ }
24
+ });
25
+ };
26
+ const loadLinkedom = async () => {
27
+ const { parseHTML } = await import('linkedom');
28
+ const { window } = parseHTML(DOM_TEMPLATE);
29
+ return window;
30
+ };
31
+ const loadJsdom = async () => {
32
+ const { JSDOM } = await import('jsdom');
33
+ const { window } = new JSDOM(DOM_TEMPLATE);
34
+ return window;
35
+ };
36
+ const parsePreference = () => {
37
+ const value = typeof process !== 'undefined' && process.env?.KNIGHTED_JSX_NODE_SHIM
38
+ ? process.env.KNIGHTED_JSX_NODE_SHIM.toLowerCase()
39
+ : undefined;
40
+ if (value === 'linkedom' || value === 'jsdom') {
41
+ return value;
42
+ }
43
+ return 'auto';
44
+ };
45
+ const selectLoaders = () => {
46
+ const pref = parsePreference();
47
+ if (pref === 'linkedom') {
48
+ return [loadLinkedom, loadJsdom];
49
+ }
50
+ if (pref === 'jsdom') {
51
+ return [loadJsdom, loadLinkedom];
52
+ }
53
+ return [loadLinkedom, loadJsdom];
54
+ };
55
+ const createShimWindow = async () => {
56
+ const errors = [];
57
+ for (const loader of selectLoaders()) {
58
+ try {
59
+ return await loader();
60
+ }
61
+ catch (error) {
62
+ errors.push(error);
63
+ }
64
+ }
65
+ 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
+ throw new AggregateError(errors, help);
67
+ };
68
+ let bootstrapPromise = null;
69
+ export const ensureNodeDom = async () => {
70
+ if (hasDom()) {
71
+ return;
72
+ }
73
+ if (!bootstrapPromise) {
74
+ bootstrapPromise = (async () => {
75
+ const windowObj = await createShimWindow();
76
+ assignGlobalTargets(windowObj);
77
+ })().catch(error => {
78
+ bootstrapPromise = null;
79
+ throw error;
80
+ });
81
+ }
82
+ return bootstrapPromise;
83
+ };
@@ -0,0 +1 @@
1
+ export declare const ensureNodeDom: () => Promise<void>;
@@ -0,0 +1,4 @@
1
+ import { ensureNodeDom } from './bootstrap.cjs';
2
+ import { jsx as baseJsx } from '../jsx.cjs';
3
+ await ensureNodeDom();
4
+ export const jsx = baseJsx;
@@ -0,0 +1,2 @@
1
+ export declare const jsx: (templates: TemplateStringsArray, ...values: unknown[]) => import("../jsx.cjs").JsxRenderable;
2
+ export type { JsxRenderable, JsxComponent } from '../jsx.cjs';
@@ -0,0 +1 @@
1
+ export { reactJsx } from '../../react/react-jsx.cjs';
@@ -0,0 +1,2 @@
1
+ export { reactJsx } from '../../react/react-jsx.cjs';
2
+ export type { ReactJsxComponent } from '../../react/react-jsx.cjs';
@@ -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,2 @@
1
+ export { reactJsx } from './react-jsx.cjs';
2
+ export type { ReactJsxComponent } from './react-jsx.cjs';
@@ -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;
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildTemplate = exports.ensureBinding = exports.sanitizeIdentifier = exports.evaluateExpression = exports.collectPlaceholderNames = exports.normalizeJsxText = exports.walkAst = exports.getIdentifierName = exports.extractRootNode = exports.formatParserError = exports.parserOptions = void 0;
4
+ const OPEN_TAG_RE = /<\s*$/;
5
+ const CLOSE_TAG_RE = /<\/\s*$/;
6
+ const PLACEHOLDER_PREFIX = '__KX_EXPR__';
7
+ let invocationCounter = 0;
8
+ exports.parserOptions = {
9
+ lang: 'jsx',
10
+ sourceType: 'module',
11
+ range: true,
12
+ preserveParens: true,
13
+ };
14
+ const formatParserError = (error) => {
15
+ let message = `[oxc-parser] ${error.message}`;
16
+ if (error.labels?.length) {
17
+ const label = error.labels[0];
18
+ if (label.message) {
19
+ message += `\n${label.message}`;
20
+ }
21
+ }
22
+ if (error.codeframe) {
23
+ message += `\n${error.codeframe}`;
24
+ }
25
+ return message;
26
+ };
27
+ exports.formatParserError = formatParserError;
28
+ const extractRootNode = (program) => {
29
+ for (const statement of program.body) {
30
+ if (statement.type === 'ExpressionStatement') {
31
+ const expression = statement.expression;
32
+ if (expression.type === 'JSXElement' || expression.type === 'JSXFragment') {
33
+ return expression;
34
+ }
35
+ }
36
+ }
37
+ throw new Error('The jsx template must contain a single JSX element or fragment.');
38
+ };
39
+ exports.extractRootNode = extractRootNode;
40
+ const getIdentifierName = (identifier) => {
41
+ switch (identifier.type) {
42
+ case 'JSXIdentifier':
43
+ return identifier.name;
44
+ case 'JSXNamespacedName':
45
+ return `${identifier.namespace.name}:${identifier.name.name}`;
46
+ case 'JSXMemberExpression':
47
+ return `${(0, exports.getIdentifierName)(identifier.object)}.${identifier.property.name}`;
48
+ default:
49
+ return '';
50
+ }
51
+ };
52
+ exports.getIdentifierName = getIdentifierName;
53
+ const walkAst = (node, visitor) => {
54
+ if (!node || typeof node !== 'object') {
55
+ return;
56
+ }
57
+ const candidate = node;
58
+ if (typeof candidate.type !== 'string') {
59
+ return;
60
+ }
61
+ visitor(candidate);
62
+ Object.values(candidate).forEach(value => {
63
+ if (!value) {
64
+ return;
65
+ }
66
+ if (Array.isArray(value)) {
67
+ value.forEach(child => (0, exports.walkAst)(child, visitor));
68
+ return;
69
+ }
70
+ if (typeof value === 'object') {
71
+ (0, exports.walkAst)(value, visitor);
72
+ }
73
+ });
74
+ };
75
+ exports.walkAst = walkAst;
76
+ const normalizeJsxText = (value) => {
77
+ const collapsed = value.replace(/\r/g, '').replace(/\n\s+/g, ' ');
78
+ const trimmed = collapsed.trim();
79
+ return trimmed.length > 0 ? trimmed : '';
80
+ };
81
+ exports.normalizeJsxText = normalizeJsxText;
82
+ const collectPlaceholderNames = (expression, ctx) => {
83
+ const placeholders = new Set();
84
+ (0, exports.walkAst)(expression, node => {
85
+ if (node.type === 'Identifier' && ctx.placeholders.has(node.name)) {
86
+ placeholders.add(node.name);
87
+ }
88
+ });
89
+ return Array.from(placeholders);
90
+ };
91
+ exports.collectPlaceholderNames = collectPlaceholderNames;
92
+ const evaluateExpression = (expression, ctx, evaluateJsxNode) => {
93
+ if (expression.type === 'JSXElement' || expression.type === 'JSXFragment') {
94
+ return evaluateJsxNode(expression);
95
+ }
96
+ if (!('range' in expression) || !expression.range) {
97
+ throw new Error('Unable to evaluate expression: missing source range information.');
98
+ }
99
+ const [start, end] = expression.range;
100
+ const source = ctx.source.slice(start, end);
101
+ const placeholders = (0, exports.collectPlaceholderNames)(expression, ctx);
102
+ try {
103
+ const evaluator = new Function(...placeholders, `"use strict"; return (${source});`);
104
+ const args = placeholders.map(name => ctx.placeholders.get(name));
105
+ return evaluator(...args);
106
+ }
107
+ catch (error) {
108
+ throw new Error(`Failed to evaluate expression ${source}: ${error.message}`);
109
+ }
110
+ };
111
+ exports.evaluateExpression = evaluateExpression;
112
+ const sanitizeIdentifier = (value) => {
113
+ const cleaned = value.replace(/[^a-zA-Z0-9_$]/g, '');
114
+ if (!cleaned) {
115
+ return 'Component';
116
+ }
117
+ if (!/[A-Za-z_$]/.test(cleaned[0])) {
118
+ return `Component${cleaned}`;
119
+ }
120
+ return cleaned;
121
+ };
122
+ exports.sanitizeIdentifier = sanitizeIdentifier;
123
+ const ensureBinding = (value, bindings, bindingLookup) => {
124
+ const existing = bindingLookup.get(value);
125
+ if (existing) {
126
+ return existing;
127
+ }
128
+ const descriptor = value.displayName || value.name || `Component${bindings.length}`;
129
+ const baseName = (0, exports.sanitizeIdentifier)(descriptor ?? '');
130
+ let candidate = baseName;
131
+ let suffix = 1;
132
+ while (bindings.some(binding => binding.name === candidate)) {
133
+ candidate = `${baseName}${suffix++}`;
134
+ }
135
+ const binding = { name: candidate, value };
136
+ bindings.push(binding);
137
+ bindingLookup.set(value, binding);
138
+ return binding;
139
+ };
140
+ exports.ensureBinding = ensureBinding;
141
+ const buildTemplate = (strings, values) => {
142
+ const raw = strings.raw ?? strings;
143
+ const placeholders = new Map();
144
+ const bindings = [];
145
+ const bindingLookup = new Map();
146
+ let source = raw[0] ?? '';
147
+ const templateId = invocationCounter++;
148
+ let placeholderIndex = 0;
149
+ for (let idx = 0; idx < values.length; idx++) {
150
+ const chunk = raw[idx] ?? '';
151
+ const nextChunk = raw[idx + 1] ?? '';
152
+ const value = values[idx];
153
+ const isTagNamePosition = OPEN_TAG_RE.test(chunk) || CLOSE_TAG_RE.test(chunk);
154
+ if (isTagNamePosition && typeof value === 'function') {
155
+ const binding = (0, exports.ensureBinding)(value, bindings, bindingLookup);
156
+ source += binding.name + nextChunk;
157
+ continue;
158
+ }
159
+ if (isTagNamePosition && typeof value === 'string') {
160
+ source += value + nextChunk;
161
+ continue;
162
+ }
163
+ const placeholder = `${PLACEHOLDER_PREFIX}${templateId}_${placeholderIndex++}__`;
164
+ placeholders.set(placeholder, value);
165
+ source += placeholder + nextChunk;
166
+ }
167
+ return { source, placeholders, bindings };
168
+ };
169
+ exports.buildTemplate = buildTemplate;
@@ -0,0 +1,38 @@
1
+ import type { Expression, JSXElement, JSXFragment, JSXIdentifier, JSXMemberExpression, JSXNamespacedName, Program } from '@oxc-project/types';
2
+ import type { OxcError, ParserOptions } from 'oxc-parser';
3
+ type AnyTemplateFunction = (...args: any[]) => unknown;
4
+ type AnyTemplateConstructor = abstract new (...args: any[]) => unknown;
5
+ export type TemplateComponent = (AnyTemplateFunction | AnyTemplateConstructor) & {
6
+ displayName?: string;
7
+ name?: string;
8
+ };
9
+ export type BindingEntry<TComponent extends TemplateComponent> = {
10
+ name: string;
11
+ value: TComponent;
12
+ };
13
+ export type TemplateBuildResult<TComponent extends TemplateComponent> = {
14
+ source: string;
15
+ placeholders: Map<string, unknown>;
16
+ bindings: BindingEntry<TComponent>[];
17
+ };
18
+ export type TemplateContext<TComponent extends TemplateComponent> = {
19
+ source: string;
20
+ placeholders: Map<string, unknown>;
21
+ components: Map<string, TComponent>;
22
+ };
23
+ export declare const parserOptions: ParserOptions;
24
+ export declare const formatParserError: (error: OxcError) => string;
25
+ export declare const extractRootNode: (program: Program) => JSXElement | JSXFragment;
26
+ export declare const getIdentifierName: (identifier: JSXIdentifier | JSXNamespacedName | JSXMemberExpression) => string;
27
+ type AnyOxcNode = {
28
+ type: string;
29
+ [key: string]: unknown;
30
+ };
31
+ export declare const walkAst: (node: unknown, visitor: (target: AnyOxcNode) => void) => void;
32
+ export declare const normalizeJsxText: (value: string) => string;
33
+ export declare const collectPlaceholderNames: <TComponent extends TemplateComponent>(expression: Expression | JSXElement | JSXFragment, ctx: TemplateContext<TComponent>) => string[];
34
+ export declare const evaluateExpression: <TComponent extends TemplateComponent>(expression: Expression | JSXElement | JSXFragment, ctx: TemplateContext<TComponent>, evaluateJsxNode: (node: JSXElement | JSXFragment) => unknown) => unknown;
35
+ export declare const sanitizeIdentifier: (value: string) => string;
36
+ export declare const ensureBinding: <TComponent extends TemplateComponent>(value: TComponent, bindings: BindingEntry<TComponent>[], bindingLookup: Map<TComponent, BindingEntry<TComponent>>) => BindingEntry<TComponent>;
37
+ export declare const buildTemplate: <TComponent extends TemplateComponent>(strings: TemplateStringsArray, values: unknown[]) => TemplateBuildResult<TComponent>;
38
+ export {};