@knighted/jsx 1.10.0 → 1.12.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.
@@ -120,6 +120,165 @@ const collectImportMetadata = (body) => {
120
120
  });
121
121
  return imports;
122
122
  };
123
+ const toIdentifierName = (value) => {
124
+ if (!isObjectRecord(value)) {
125
+ return null;
126
+ }
127
+ if (value.type !== 'Identifier') {
128
+ return null;
129
+ }
130
+ return typeof value.name === 'string' ? value.name : null;
131
+ };
132
+ const toVariableInitializerKind = (value) => {
133
+ if (!isObjectRecord(value) || typeof value.type !== 'string') {
134
+ return null;
135
+ }
136
+ if (value.type === 'ArrowFunctionExpression') {
137
+ return 'arrow-function';
138
+ }
139
+ if (value.type === 'FunctionExpression') {
140
+ return 'function-expression';
141
+ }
142
+ if (value.type === 'ClassExpression') {
143
+ return 'class-expression';
144
+ }
145
+ return 'other';
146
+ };
147
+ const pushTopLevelDeclarationMetadata = ({ declaration, exportKind, statementRange, declarations, }) => {
148
+ if (declaration.type === 'FunctionDeclaration') {
149
+ const name = toIdentifierName(declaration.id);
150
+ if (!name) {
151
+ return;
152
+ }
153
+ declarations.push({
154
+ name,
155
+ kind: 'function',
156
+ exportKind,
157
+ range: toSourceRange(declaration),
158
+ statementRange,
159
+ initializerKind: null,
160
+ });
161
+ return;
162
+ }
163
+ if (declaration.type === 'ClassDeclaration') {
164
+ const name = toIdentifierName(declaration.id);
165
+ if (!name) {
166
+ return;
167
+ }
168
+ declarations.push({
169
+ name,
170
+ kind: 'class',
171
+ exportKind,
172
+ range: toSourceRange(declaration),
173
+ statementRange,
174
+ initializerKind: null,
175
+ });
176
+ return;
177
+ }
178
+ if (!Array.isArray(declaration.declarations)) {
179
+ return;
180
+ }
181
+ for (const declarator of declaration.declarations) {
182
+ if (!isObjectRecord(declarator)) {
183
+ continue;
184
+ }
185
+ const name = toIdentifierName(declarator.id);
186
+ if (!name) {
187
+ continue;
188
+ }
189
+ declarations.push({
190
+ name,
191
+ kind: 'variable',
192
+ exportKind,
193
+ range: toSourceRange(declarator),
194
+ statementRange,
195
+ initializerKind: toVariableInitializerKind(declarator.init),
196
+ });
197
+ }
198
+ };
199
+ const collectTopLevelDeclarationMetadata = (body) => {
200
+ if (!Array.isArray(body)) {
201
+ return [];
202
+ }
203
+ const declarations = [];
204
+ for (const statement of body) {
205
+ if (!isObjectRecord(statement) || typeof statement.type !== 'string') {
206
+ continue;
207
+ }
208
+ const statementRange = toSourceRange(statement);
209
+ if (statement.type === 'ExportNamedDeclaration') {
210
+ if (!isObjectRecord(statement.declaration)) {
211
+ continue;
212
+ }
213
+ pushTopLevelDeclarationMetadata({
214
+ declaration: statement.declaration,
215
+ exportKind: 'named',
216
+ statementRange,
217
+ declarations,
218
+ });
219
+ continue;
220
+ }
221
+ if (statement.type === 'ExportDefaultDeclaration') {
222
+ if (!isObjectRecord(statement.declaration)) {
223
+ continue;
224
+ }
225
+ pushTopLevelDeclarationMetadata({
226
+ declaration: statement.declaration,
227
+ exportKind: 'default',
228
+ statementRange,
229
+ declarations,
230
+ });
231
+ continue;
232
+ }
233
+ pushTopLevelDeclarationMetadata({
234
+ declaration: statement,
235
+ exportKind: 'none',
236
+ statementRange,
237
+ declarations,
238
+ });
239
+ }
240
+ return declarations;
241
+ };
242
+ const unwrapExpressionNode = (value) => {
243
+ let current = value;
244
+ while (isObjectRecord(current) && typeof current.type === 'string') {
245
+ if (current.type === 'ParenthesizedExpression') {
246
+ current = current.expression;
247
+ continue;
248
+ }
249
+ if (current.type === 'TSAsExpression' ||
250
+ current.type === 'TSSatisfiesExpression' ||
251
+ current.type === 'TSInstantiationExpression' ||
252
+ current.type === 'TSNonNullExpression' ||
253
+ current.type === 'TSTypeAssertion') {
254
+ current = current.expression;
255
+ continue;
256
+ }
257
+ break;
258
+ }
259
+ return current;
260
+ };
261
+ const isJsxExpressionNode = (value) => {
262
+ const unwrapped = unwrapExpressionNode(value);
263
+ if (!isObjectRecord(unwrapped) || typeof unwrapped.type !== 'string') {
264
+ return false;
265
+ }
266
+ return unwrapped.type === 'JSXElement' || unwrapped.type === 'JSXFragment';
267
+ };
268
+ const collectTopLevelJsxExpressionMetadata = (body) => {
269
+ if (!Array.isArray(body)) {
270
+ return false;
271
+ }
272
+ for (const statement of body) {
273
+ if (!isObjectRecord(statement) || statement.type !== 'ExpressionStatement') {
274
+ continue;
275
+ }
276
+ if (isJsxExpressionNode(statement.expression)) {
277
+ return true;
278
+ }
279
+ }
280
+ return false;
281
+ };
123
282
  const ensureSupportedOptions = (options) => {
124
283
  if (options.sourceType !== undefined &&
125
284
  options.sourceType !== 'module' &&
@@ -136,6 +295,14 @@ const ensureSupportedOptions = (options) => {
136
295
  options.typescriptStripBackend !== 'transpile-manual') {
137
296
  throw new Error(`[jsx] Unsupported typescriptStripBackend "${String(options.typescriptStripBackend)}". Use "oxc-transform" or "transpile-manual".`);
138
297
  }
298
+ if (options.collectTopLevelDeclarations !== undefined &&
299
+ typeof options.collectTopLevelDeclarations !== 'boolean') {
300
+ throw new Error(`[jsx] Unsupported collectTopLevelDeclarations value "${String(options.collectTopLevelDeclarations)}". Use true or false.`);
301
+ }
302
+ if (options.collectTopLevelJsxExpression !== undefined &&
303
+ typeof options.collectTopLevelJsxExpression !== 'boolean') {
304
+ throw new Error(`[jsx] Unsupported collectTopLevelJsxExpression value "${String(options.collectTopLevelJsxExpression)}". Use true or false.`);
305
+ }
139
306
  };
140
307
  function transformJsxSource(source, options = {}) {
141
308
  const internalOptions = options;
@@ -146,12 +313,20 @@ function transformJsxSource(source, options = {}) {
146
313
  const parsed = (0, oxc_parser_1.parseSync)('transform-jsx-source.tsx', source, createParserOptions(sourceType));
147
314
  const parserDiagnostics = parsed.errors.map(error => toDiagnostic('parser', error));
148
315
  const imports = collectImportMetadata(parsed.program.body);
316
+ const declarations = internalOptions.collectTopLevelDeclarations
317
+ ? collectTopLevelDeclarationMetadata(parsed.program.body)
318
+ : undefined;
319
+ const hasTopLevelJsxExpression = internalOptions.collectTopLevelJsxExpression
320
+ ? collectTopLevelJsxExpressionMetadata(parsed.program.body)
321
+ : undefined;
149
322
  if (parserDiagnostics.length) {
150
323
  return {
151
324
  code: source,
152
325
  changed: false,
153
326
  imports,
154
327
  diagnostics: parserDiagnostics,
328
+ declarations,
329
+ hasTopLevelJsxExpression,
155
330
  };
156
331
  }
157
332
  const transpileBaseOptions = {
@@ -167,6 +342,8 @@ function transformJsxSource(source, options = {}) {
167
342
  changed: result.changed,
168
343
  imports,
169
344
  diagnostics: parserDiagnostics,
345
+ declarations,
346
+ hasTopLevelJsxExpression,
170
347
  };
171
348
  }
172
349
  if (typescriptStripBackend === 'transpile-manual') {
@@ -179,6 +356,8 @@ function transformJsxSource(source, options = {}) {
179
356
  changed: result.changed,
180
357
  imports,
181
358
  diagnostics: parserDiagnostics,
359
+ declarations,
360
+ hasTopLevelJsxExpression,
182
361
  };
183
362
  }
184
363
  const transformed = (0, oxc_transform_1.transformSync)('transform-jsx-source.tsx', source, {
@@ -196,6 +375,8 @@ function transformJsxSource(source, options = {}) {
196
375
  changed: fallbackCode !== source,
197
376
  imports,
198
377
  diagnostics,
378
+ declarations,
379
+ hasTopLevelJsxExpression,
199
380
  };
200
381
  }
201
382
  const jsxResult = (0, transpile_js_1.transpileJsxSource)(transformed.code, transpileBaseOptions);
@@ -204,5 +385,7 @@ function transformJsxSource(source, options = {}) {
204
385
  changed: jsxResult.code !== source,
205
386
  imports,
206
387
  diagnostics,
388
+ declarations,
389
+ hasTopLevelJsxExpression,
207
390
  };
208
391
  }
@@ -23,12 +23,28 @@ export type TransformImport = {
23
23
  bindings: TransformImportBinding[];
24
24
  range: SourceRange | null;
25
25
  };
26
- export type TransformJsxSourceOptions = TranspileJsxSourceOptions;
26
+ export type TransformTopLevelDeclarationKind = 'function' | 'class' | 'variable';
27
+ export type TransformTopLevelDeclarationExportKind = 'none' | 'named' | 'default';
28
+ export type TransformVariableInitializerKind = 'arrow-function' | 'function-expression' | 'class-expression' | 'other' | null;
29
+ export type TransformTopLevelDeclaration = {
30
+ name: string;
31
+ kind: TransformTopLevelDeclarationKind;
32
+ exportKind: TransformTopLevelDeclarationExportKind;
33
+ range: SourceRange | null;
34
+ statementRange: SourceRange | null;
35
+ initializerKind: TransformVariableInitializerKind;
36
+ };
37
+ export type TransformJsxSourceOptions = TranspileJsxSourceOptions & {
38
+ collectTopLevelDeclarations?: boolean;
39
+ collectTopLevelJsxExpression?: boolean;
40
+ };
27
41
  export type TransformJsxSourceResult = {
28
42
  code: string;
29
43
  changed: boolean;
30
44
  imports: TransformImport[];
31
45
  diagnostics: TransformDiagnostic[];
46
+ declarations?: TransformTopLevelDeclaration[];
47
+ hasTopLevelJsxExpression?: boolean;
32
48
  };
33
49
  export declare function transformJsxSource(source: string, options?: TransformJsxSourceOptions): TransformJsxSourceResult;
34
50
  export {};
@@ -23,12 +23,28 @@ export type TransformImport = {
23
23
  bindings: TransformImportBinding[];
24
24
  range: SourceRange | null;
25
25
  };
26
- export type TransformJsxSourceOptions = TranspileJsxSourceOptions;
26
+ export type TransformTopLevelDeclarationKind = 'function' | 'class' | 'variable';
27
+ export type TransformTopLevelDeclarationExportKind = 'none' | 'named' | 'default';
28
+ export type TransformVariableInitializerKind = 'arrow-function' | 'function-expression' | 'class-expression' | 'other' | null;
29
+ export type TransformTopLevelDeclaration = {
30
+ name: string;
31
+ kind: TransformTopLevelDeclarationKind;
32
+ exportKind: TransformTopLevelDeclarationExportKind;
33
+ range: SourceRange | null;
34
+ statementRange: SourceRange | null;
35
+ initializerKind: TransformVariableInitializerKind;
36
+ };
37
+ export type TransformJsxSourceOptions = TranspileJsxSourceOptions & {
38
+ collectTopLevelDeclarations?: boolean;
39
+ collectTopLevelJsxExpression?: boolean;
40
+ };
27
41
  export type TransformJsxSourceResult = {
28
42
  code: string;
29
43
  changed: boolean;
30
44
  imports: TransformImport[];
31
45
  diagnostics: TransformDiagnostic[];
46
+ declarations?: TransformTopLevelDeclaration[];
47
+ hasTopLevelJsxExpression?: boolean;
32
48
  };
33
49
  export declare function transformJsxSource(source: string, options?: TransformJsxSourceOptions): TransformJsxSourceResult;
34
50
  export {};
package/dist/transform.js CHANGED
@@ -117,6 +117,165 @@ const collectImportMetadata = (body) => {
117
117
  });
118
118
  return imports;
119
119
  };
120
+ const toIdentifierName = (value) => {
121
+ if (!isObjectRecord(value)) {
122
+ return null;
123
+ }
124
+ if (value.type !== 'Identifier') {
125
+ return null;
126
+ }
127
+ return typeof value.name === 'string' ? value.name : null;
128
+ };
129
+ const toVariableInitializerKind = (value) => {
130
+ if (!isObjectRecord(value) || typeof value.type !== 'string') {
131
+ return null;
132
+ }
133
+ if (value.type === 'ArrowFunctionExpression') {
134
+ return 'arrow-function';
135
+ }
136
+ if (value.type === 'FunctionExpression') {
137
+ return 'function-expression';
138
+ }
139
+ if (value.type === 'ClassExpression') {
140
+ return 'class-expression';
141
+ }
142
+ return 'other';
143
+ };
144
+ const pushTopLevelDeclarationMetadata = ({ declaration, exportKind, statementRange, declarations, }) => {
145
+ if (declaration.type === 'FunctionDeclaration') {
146
+ const name = toIdentifierName(declaration.id);
147
+ if (!name) {
148
+ return;
149
+ }
150
+ declarations.push({
151
+ name,
152
+ kind: 'function',
153
+ exportKind,
154
+ range: toSourceRange(declaration),
155
+ statementRange,
156
+ initializerKind: null,
157
+ });
158
+ return;
159
+ }
160
+ if (declaration.type === 'ClassDeclaration') {
161
+ const name = toIdentifierName(declaration.id);
162
+ if (!name) {
163
+ return;
164
+ }
165
+ declarations.push({
166
+ name,
167
+ kind: 'class',
168
+ exportKind,
169
+ range: toSourceRange(declaration),
170
+ statementRange,
171
+ initializerKind: null,
172
+ });
173
+ return;
174
+ }
175
+ if (!Array.isArray(declaration.declarations)) {
176
+ return;
177
+ }
178
+ for (const declarator of declaration.declarations) {
179
+ if (!isObjectRecord(declarator)) {
180
+ continue;
181
+ }
182
+ const name = toIdentifierName(declarator.id);
183
+ if (!name) {
184
+ continue;
185
+ }
186
+ declarations.push({
187
+ name,
188
+ kind: 'variable',
189
+ exportKind,
190
+ range: toSourceRange(declarator),
191
+ statementRange,
192
+ initializerKind: toVariableInitializerKind(declarator.init),
193
+ });
194
+ }
195
+ };
196
+ const collectTopLevelDeclarationMetadata = (body) => {
197
+ if (!Array.isArray(body)) {
198
+ return [];
199
+ }
200
+ const declarations = [];
201
+ for (const statement of body) {
202
+ if (!isObjectRecord(statement) || typeof statement.type !== 'string') {
203
+ continue;
204
+ }
205
+ const statementRange = toSourceRange(statement);
206
+ if (statement.type === 'ExportNamedDeclaration') {
207
+ if (!isObjectRecord(statement.declaration)) {
208
+ continue;
209
+ }
210
+ pushTopLevelDeclarationMetadata({
211
+ declaration: statement.declaration,
212
+ exportKind: 'named',
213
+ statementRange,
214
+ declarations,
215
+ });
216
+ continue;
217
+ }
218
+ if (statement.type === 'ExportDefaultDeclaration') {
219
+ if (!isObjectRecord(statement.declaration)) {
220
+ continue;
221
+ }
222
+ pushTopLevelDeclarationMetadata({
223
+ declaration: statement.declaration,
224
+ exportKind: 'default',
225
+ statementRange,
226
+ declarations,
227
+ });
228
+ continue;
229
+ }
230
+ pushTopLevelDeclarationMetadata({
231
+ declaration: statement,
232
+ exportKind: 'none',
233
+ statementRange,
234
+ declarations,
235
+ });
236
+ }
237
+ return declarations;
238
+ };
239
+ const unwrapExpressionNode = (value) => {
240
+ let current = value;
241
+ while (isObjectRecord(current) && typeof current.type === 'string') {
242
+ if (current.type === 'ParenthesizedExpression') {
243
+ current = current.expression;
244
+ continue;
245
+ }
246
+ if (current.type === 'TSAsExpression' ||
247
+ current.type === 'TSSatisfiesExpression' ||
248
+ current.type === 'TSInstantiationExpression' ||
249
+ current.type === 'TSNonNullExpression' ||
250
+ current.type === 'TSTypeAssertion') {
251
+ current = current.expression;
252
+ continue;
253
+ }
254
+ break;
255
+ }
256
+ return current;
257
+ };
258
+ const isJsxExpressionNode = (value) => {
259
+ const unwrapped = unwrapExpressionNode(value);
260
+ if (!isObjectRecord(unwrapped) || typeof unwrapped.type !== 'string') {
261
+ return false;
262
+ }
263
+ return unwrapped.type === 'JSXElement' || unwrapped.type === 'JSXFragment';
264
+ };
265
+ const collectTopLevelJsxExpressionMetadata = (body) => {
266
+ if (!Array.isArray(body)) {
267
+ return false;
268
+ }
269
+ for (const statement of body) {
270
+ if (!isObjectRecord(statement) || statement.type !== 'ExpressionStatement') {
271
+ continue;
272
+ }
273
+ if (isJsxExpressionNode(statement.expression)) {
274
+ return true;
275
+ }
276
+ }
277
+ return false;
278
+ };
120
279
  const ensureSupportedOptions = (options) => {
121
280
  if (options.sourceType !== undefined &&
122
281
  options.sourceType !== 'module' &&
@@ -133,6 +292,14 @@ const ensureSupportedOptions = (options) => {
133
292
  options.typescriptStripBackend !== 'transpile-manual') {
134
293
  throw new Error(`[jsx] Unsupported typescriptStripBackend "${String(options.typescriptStripBackend)}". Use "oxc-transform" or "transpile-manual".`);
135
294
  }
295
+ if (options.collectTopLevelDeclarations !== undefined &&
296
+ typeof options.collectTopLevelDeclarations !== 'boolean') {
297
+ throw new Error(`[jsx] Unsupported collectTopLevelDeclarations value "${String(options.collectTopLevelDeclarations)}". Use true or false.`);
298
+ }
299
+ if (options.collectTopLevelJsxExpression !== undefined &&
300
+ typeof options.collectTopLevelJsxExpression !== 'boolean') {
301
+ throw new Error(`[jsx] Unsupported collectTopLevelJsxExpression value "${String(options.collectTopLevelJsxExpression)}". Use true or false.`);
302
+ }
136
303
  };
137
304
  export function transformJsxSource(source, options = {}) {
138
305
  const internalOptions = options;
@@ -143,12 +310,20 @@ export function transformJsxSource(source, options = {}) {
143
310
  const parsed = parseSync('transform-jsx-source.tsx', source, createParserOptions(sourceType));
144
311
  const parserDiagnostics = parsed.errors.map(error => toDiagnostic('parser', error));
145
312
  const imports = collectImportMetadata(parsed.program.body);
313
+ const declarations = internalOptions.collectTopLevelDeclarations
314
+ ? collectTopLevelDeclarationMetadata(parsed.program.body)
315
+ : undefined;
316
+ const hasTopLevelJsxExpression = internalOptions.collectTopLevelJsxExpression
317
+ ? collectTopLevelJsxExpressionMetadata(parsed.program.body)
318
+ : undefined;
146
319
  if (parserDiagnostics.length) {
147
320
  return {
148
321
  code: source,
149
322
  changed: false,
150
323
  imports,
151
324
  diagnostics: parserDiagnostics,
325
+ declarations,
326
+ hasTopLevelJsxExpression,
152
327
  };
153
328
  }
154
329
  const transpileBaseOptions = {
@@ -164,6 +339,8 @@ export function transformJsxSource(source, options = {}) {
164
339
  changed: result.changed,
165
340
  imports,
166
341
  diagnostics: parserDiagnostics,
342
+ declarations,
343
+ hasTopLevelJsxExpression,
167
344
  };
168
345
  }
169
346
  if (typescriptStripBackend === 'transpile-manual') {
@@ -176,6 +353,8 @@ export function transformJsxSource(source, options = {}) {
176
353
  changed: result.changed,
177
354
  imports,
178
355
  diagnostics: parserDiagnostics,
356
+ declarations,
357
+ hasTopLevelJsxExpression,
179
358
  };
180
359
  }
181
360
  const transformed = transformSync('transform-jsx-source.tsx', source, {
@@ -193,6 +372,8 @@ export function transformJsxSource(source, options = {}) {
193
372
  changed: fallbackCode !== source,
194
373
  imports,
195
374
  diagnostics,
375
+ declarations,
376
+ hasTopLevelJsxExpression,
196
377
  };
197
378
  }
198
379
  const jsxResult = transpileJsxSource(transformed.code, transpileBaseOptions);
@@ -201,5 +382,7 @@ export function transformJsxSource(source, options = {}) {
201
382
  changed: jsxResult.code !== source,
202
383
  imports,
203
384
  diagnostics,
385
+ declarations,
386
+ hasTopLevelJsxExpression,
204
387
  };
205
388
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/jsx",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "Runtime JSX tagged template that renders DOM or React trees anywhere with or without a build step.",
5
5
  "keywords": [
6
6
  "jsx runtime",
@@ -173,7 +173,7 @@
173
173
  "jsdom": "^27.2.0",
174
174
  "lint-staged": "^16.2.7",
175
175
  "lit": "^3.2.1",
176
- "next": "^16.1.6",
176
+ "next": "^16.1.7",
177
177
  "oxlint": "^1.51.0",
178
178
  "prettier": "^3.7.3",
179
179
  "react": "^19.0.0",