workstar-compiler 0.1.0 → 0.2.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/bin/workstar-compile.mjs +15 -1
- package/dist/src/compat-rules.d.ts +2 -0
- package/dist/src/compat-rules.js +4 -0
- package/dist/src/compat.d.ts +5 -0
- package/dist/src/compat.js +12 -0
- package/dist/src/component-script.d.ts +11 -3
- package/dist/src/component-script.js +93 -24
- package/dist/src/control-elements.d.ts +5 -0
- package/dist/src/control-elements.js +55 -20
- package/dist/src/errors.d.ts +9 -1
- package/dist/src/errors.js +6 -2
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +114 -40
- package/dist/src/project.d.ts +15 -1
- package/dist/src/project.js +56 -4
- package/dist/src/react-compat.d.ts +6 -0
- package/dist/src/react-compat.js +369 -0
- package/dist/src/react-entry-compat.d.ts +2 -0
- package/dist/src/react-entry-compat.js +105 -0
- package/dist/src/react-import-resolution.d.ts +2 -0
- package/dist/src/react-import-resolution.js +15 -0
- package/dist/src/source-map.d.ts +7 -0
- package/dist/src/source-map.js +50 -0
- package/dist/src/source-origin.d.ts +14 -0
- package/dist/src/source-origin.js +41 -0
- package/dist/src/styles.d.ts +5 -0
- package/dist/src/styles.js +41 -18
- package/dist/src/vite.d.ts +4 -0
- package/dist/src/vite.js +150 -13
- package/dist/src/vue-compat.d.ts +2 -0
- package/dist/src/vue-compat.js +168 -0
- package/dist/src/vue-script-compat.d.ts +8 -0
- package/dist/src/vue-script-compat.js +244 -0
- package/package.json +7 -2
package/dist/src/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { parseFragment } from 'parse5';
|
|
2
2
|
import { componentScript } from './component-script.js';
|
|
3
|
-
import { controlAttribute, normalizeControls } from './control-elements.js';
|
|
4
|
-
import { fail } from './errors.js';
|
|
5
|
-
import {
|
|
3
|
+
import { ControlSyntaxError, controlAttribute, normalizeControls, } from './control-elements.js';
|
|
4
|
+
import { ComponentCompileError, fail } from './errors.js';
|
|
5
|
+
import { ComponentCode } from './source-origin.js';
|
|
6
|
+
import { compileStyle, StyleCompileError } from './styles.js';
|
|
6
7
|
export { ComponentCompileError } from './errors.js';
|
|
7
8
|
const pathExpression = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[\d+\])*$/;
|
|
8
9
|
const identifier = /^[A-Za-z_$][\w$]*$/;
|
|
@@ -47,6 +48,30 @@ function escapeTemplate(value) {
|
|
|
47
48
|
.replace(/`/g, '\\`')
|
|
48
49
|
.replace(/\$\{/g, '\\${');
|
|
49
50
|
}
|
|
51
|
+
function sourcePosition(source, offset) {
|
|
52
|
+
let line = 1;
|
|
53
|
+
let column = 1;
|
|
54
|
+
for (let index = 0; index < offset; index++) {
|
|
55
|
+
if (source[index] === '\n') {
|
|
56
|
+
line++;
|
|
57
|
+
column = 1;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
column++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { line, column };
|
|
64
|
+
}
|
|
65
|
+
function offsetAtPosition(source, line, column) {
|
|
66
|
+
let offset = 0;
|
|
67
|
+
for (let currentLine = 1; currentLine < line; currentLine++) {
|
|
68
|
+
const newline = source.indexOf('\n', offset);
|
|
69
|
+
if (newline < 0)
|
|
70
|
+
return source.length;
|
|
71
|
+
offset = newline + 1;
|
|
72
|
+
}
|
|
73
|
+
return Math.min(offset + column - 1, source.length);
|
|
74
|
+
}
|
|
50
75
|
function controlName(node) {
|
|
51
76
|
return node.attrs.find((attribute) => attribute.name === controlAttribute)
|
|
52
77
|
?.value;
|
|
@@ -125,7 +150,7 @@ function scopeMarkupElements(nodes, attribute, filename) {
|
|
|
125
150
|
scopeMarkupElements(elementChildren(node), attribute, filename);
|
|
126
151
|
}
|
|
127
152
|
}
|
|
128
|
-
function eachMarkup(node, filename, locals,
|
|
153
|
+
function eachMarkup(node, filename, locals, source) {
|
|
129
154
|
const attributes = new Map(node.attrs
|
|
130
155
|
.filter((attribute) => attribute.name !== controlAttribute)
|
|
131
156
|
.map((attribute) => [attribute.name, attribute.value]));
|
|
@@ -135,19 +160,24 @@ function eachMarkup(node, filename, locals, sourceText) {
|
|
|
135
160
|
!attributes.has('key')) {
|
|
136
161
|
fail(filename, '<Each> needs each={path}, as="name", and key="field|self".');
|
|
137
162
|
}
|
|
138
|
-
const
|
|
163
|
+
const collection = dynamicAttribute(attributes.get('each'), filename, locals);
|
|
139
164
|
const name = attributes.get('as');
|
|
140
165
|
const key = attributes.get('key');
|
|
141
|
-
if (!
|
|
166
|
+
if (!collection || !identifier.test(name))
|
|
142
167
|
fail(filename, 'Invalid <Each> binding.');
|
|
143
168
|
if (key !== 'self' && !identifier.test(key))
|
|
144
169
|
fail(filename, '<Each> key must be a field name or "self".');
|
|
145
170
|
const nested = new Map(locals);
|
|
146
171
|
nested.set(name, `${name}.value`);
|
|
147
|
-
|
|
172
|
+
if (source.hotState) {
|
|
173
|
+
const parent = locals.get('__workstarContext') ?? '__context';
|
|
174
|
+
const itemKey = key === 'self' ? `${name}.value` : `${name}.value.${key}`;
|
|
175
|
+
nested.set('__workstarContext', `${parent}?.child('each:${node.sourceCodeLocation?.startOffset}')?.child(${itemKey})`);
|
|
176
|
+
}
|
|
177
|
+
const body = childMarkup(elementChildren(node), filename, nested, source);
|
|
148
178
|
const keyExpression = key === 'self' ? name : `${name}.${key}`;
|
|
149
179
|
return ('${__repeat(() => ' +
|
|
150
|
-
|
|
180
|
+
collection +
|
|
151
181
|
', (' +
|
|
152
182
|
name +
|
|
153
183
|
') => ' +
|
|
@@ -208,14 +238,14 @@ function componentMarkup(node, filename, locals, source) {
|
|
|
208
238
|
const props = node.attrs
|
|
209
239
|
.filter((attribute) => attribute !== binding && attribute.name !== controlAttribute)
|
|
210
240
|
.map((attribute) => {
|
|
211
|
-
const name = originalAttributeName(node, attribute.name, source);
|
|
241
|
+
const name = originalAttributeName(node, attribute.name, source.text);
|
|
212
242
|
if (!identifier.test(name)) {
|
|
213
243
|
fail(filename, `<Use> prop ${name} must be a TypeScript identifier.`);
|
|
214
244
|
}
|
|
215
245
|
const value = dynamicAttribute(attribute.value, filename, locals);
|
|
216
246
|
const authored = node.sourceCodeLocation?.attrs?.[attribute.name];
|
|
217
247
|
const raw = authored
|
|
218
|
-
? source.slice(authored.startOffset, authored.endOffset)
|
|
248
|
+
? source.text.slice(authored.startOffset, authored.endOffset)
|
|
219
249
|
: '';
|
|
220
250
|
const output = value ?? (raw.includes('=') ? JSON.stringify(attribute.value) : 'true');
|
|
221
251
|
return `${name}: ${output}`;
|
|
@@ -228,7 +258,11 @@ function componentMarkup(node, filename, locals, source) {
|
|
|
228
258
|
childMarkup(children, filename, locals, source) +
|
|
229
259
|
'`');
|
|
230
260
|
}
|
|
231
|
-
|
|
261
|
+
const context = locals.get('__workstarContext') ?? '__context';
|
|
262
|
+
const hotArgument = source.hotState
|
|
263
|
+
? `, ${context}?.child('use:${node.sourceCodeLocation?.startOffset}')`
|
|
264
|
+
: '';
|
|
265
|
+
return ('${() => ' + renderer + '({' + props.join(', ') + '}' + hotArgument + ')}');
|
|
232
266
|
}
|
|
233
267
|
function elementMarkup(node, filename, locals, source) {
|
|
234
268
|
if (!node.sourceCodeLocation)
|
|
@@ -268,7 +302,12 @@ function elementMarkup(node, filename, locals, source) {
|
|
|
268
302
|
fail(filename, `Use on:event instead of ${name}.`);
|
|
269
303
|
}
|
|
270
304
|
const value = dynamicAttribute(attribute.value, filename, locals);
|
|
271
|
-
if (name
|
|
305
|
+
if (name === 'bind:attrs') {
|
|
306
|
+
if (!value)
|
|
307
|
+
fail(filename, 'bind:attrs needs a record expression.');
|
|
308
|
+
result += '${__attrs(() => ' + value + ')}';
|
|
309
|
+
}
|
|
310
|
+
else if (name.startsWith('on:')) {
|
|
272
311
|
if (!value)
|
|
273
312
|
fail(filename, `${name} needs a handler expression.`);
|
|
274
313
|
result += '${__on(' + JSON.stringify(name.slice(3)) + ', ' + value + ')}';
|
|
@@ -318,13 +357,23 @@ function elementMarkup(node, filename, locals, source) {
|
|
|
318
357
|
`</${tag}>`);
|
|
319
358
|
}
|
|
320
359
|
function nodeMarkup(node, filename, locals, source) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
360
|
+
try {
|
|
361
|
+
if ('tagName' in node)
|
|
362
|
+
return elementMarkup(node, filename, locals, source);
|
|
363
|
+
if ('value' in node)
|
|
364
|
+
return textMarkup(node.value, filename, locals);
|
|
365
|
+
if ('data' in node)
|
|
366
|
+
return `<!--${escapeTemplate(node.data)}-->`;
|
|
367
|
+
return fail(filename, 'Doctype belongs in the document shell.');
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
if (error instanceof ComponentCompileError &&
|
|
371
|
+
!error.position &&
|
|
372
|
+
node.sourceCodeLocation) {
|
|
373
|
+
throw new ComponentCompileError(error.description, filename, source.position(node.sourceCodeLocation.startOffset));
|
|
374
|
+
}
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
328
377
|
}
|
|
329
378
|
/** Compile a typed component and its optional co-located stylesheet. */
|
|
330
379
|
export function compileComponentParts(source, filename = 'Component.workstar', options = {}) {
|
|
@@ -333,16 +382,26 @@ export function compileComponentParts(source, filename = 'Component.workstar', o
|
|
|
333
382
|
normalized = normalizeControls(source);
|
|
334
383
|
}
|
|
335
384
|
catch (error) {
|
|
385
|
+
if (error instanceof ControlSyntaxError) {
|
|
386
|
+
throw new ComponentCompileError(error.message, filename, sourcePosition(source, error.offset));
|
|
387
|
+
}
|
|
336
388
|
fail(filename, error instanceof Error ? error.message : String(error));
|
|
337
389
|
}
|
|
338
390
|
const normalizedSource = normalized.source;
|
|
391
|
+
const markupSource = {
|
|
392
|
+
text: normalizedSource,
|
|
393
|
+
hotState: options.hotState ?? false,
|
|
394
|
+
position: (offset) => sourcePosition(source, normalized.originalOffset(offset)),
|
|
395
|
+
};
|
|
339
396
|
const errors = [];
|
|
340
397
|
const fragment = parseFragment(normalizedSource, {
|
|
341
398
|
sourceCodeLocationInfo: true,
|
|
342
|
-
onParseError: (error) => errors.push(
|
|
399
|
+
onParseError: (error) => errors.push({ code: error.code, startOffset: error.startOffset }),
|
|
343
400
|
});
|
|
344
|
-
if (errors.length > 0)
|
|
345
|
-
|
|
401
|
+
if (errors.length > 0) {
|
|
402
|
+
const error = errors[0];
|
|
403
|
+
throw new ComponentCompileError(error.code, filename, markupSource.position(error.startOffset));
|
|
404
|
+
}
|
|
346
405
|
const content = fragment.childNodes.filter((node) => !('value' in node) || node.value.trim().length > 0);
|
|
347
406
|
const first = content[0];
|
|
348
407
|
const hasScript = first && 'tagName' in first && first.tagName === 'script';
|
|
@@ -350,11 +409,13 @@ export function compileComponentParts(source, filename = 'Component.workstar', o
|
|
|
350
409
|
content.some((node) => 'tagName' in node && node.tagName === 'script')) {
|
|
351
410
|
fail(filename, 'A <script lang="ts"> block must come first.');
|
|
352
411
|
}
|
|
353
|
-
const {
|
|
354
|
-
? componentScript(first, filename, options.componentImports ?? 'generated', options.rewriteRelativeImport)
|
|
412
|
+
const { moduleStatements, setupStatements, props } = hasScript
|
|
413
|
+
? componentScript(first, filename, options.componentImports ?? 'generated', markupSource.position, options.rewriteRelativeImport, options.hotState)
|
|
355
414
|
: {
|
|
356
|
-
|
|
357
|
-
|
|
415
|
+
moduleStatements: [
|
|
416
|
+
{ code: 'export type Props = Record<string, never>;' },
|
|
417
|
+
],
|
|
418
|
+
setupStatements: [],
|
|
358
419
|
props: [],
|
|
359
420
|
};
|
|
360
421
|
const componentBody = hasScript ? content.slice(1) : content;
|
|
@@ -387,27 +448,40 @@ export function compileComponentParts(source, filename = 'Component.workstar', o
|
|
|
387
448
|
}
|
|
388
449
|
}
|
|
389
450
|
catch (error) {
|
|
451
|
+
const cssStart = last.sourceCodeLocation?.startTag?.endOffset;
|
|
452
|
+
if (error instanceof StyleCompileError && cssStart !== undefined) {
|
|
453
|
+
throw new ComponentCompileError(error.message, filename, markupSource.position(cssStart + offsetAtPosition(authoredCss, error.line, error.column)));
|
|
454
|
+
}
|
|
390
455
|
fail(filename, error instanceof Error ? error.message : String(error));
|
|
391
456
|
}
|
|
392
457
|
}
|
|
393
458
|
assertControlElementsPreserved(normalized.count, markup, filename);
|
|
394
|
-
const body = childMarkup(markup, filename, new Map(),
|
|
459
|
+
const body = childMarkup(markup, filename, new Map(), markupSource);
|
|
395
460
|
if (body.trim().length === 0)
|
|
396
461
|
fail(filename, 'The component has no markup.');
|
|
397
462
|
const destructure = props.length > 0 ? ` const { ${props.join(', ')} } = props;\n` : '';
|
|
398
|
-
const code =
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
463
|
+
const code = new ComponentCode();
|
|
464
|
+
code.append('// Generated by workstar-compiler. Edit the source component instead.\n');
|
|
465
|
+
code.append("import { html as __html, attr as __attr, attrs as __attrs, on as __on, repeat as __repeat, textareaValue as __textareaValue } from 'workstar';\n");
|
|
466
|
+
if (options.hotState) {
|
|
467
|
+
code.append("import type { HotContext as __WorkstarHotContext } from 'workstar/dev';\n");
|
|
468
|
+
}
|
|
469
|
+
if (css && options.cssImport) {
|
|
470
|
+
code.append(`import ${JSON.stringify(options.cssImport)};\n`);
|
|
471
|
+
}
|
|
472
|
+
code.appendStatements(moduleStatements, normalized.originalOffset);
|
|
473
|
+
code.append(options.hotState
|
|
474
|
+
? '\nexport function render(props: Props, __context?: __WorkstarHotContext) {\n'
|
|
475
|
+
: '\nexport function render(props: Props) {\n');
|
|
476
|
+
code.append(destructure);
|
|
477
|
+
code.appendStatements(setupStatements, normalized.originalOffset);
|
|
478
|
+
code.append('\n ');
|
|
479
|
+
code.append('return __html`', markup[0]?.sourceCodeLocation
|
|
480
|
+
? normalized.originalOffset(markup[0].sourceCodeLocation.startOffset)
|
|
481
|
+
: undefined);
|
|
482
|
+
code.append(body);
|
|
483
|
+
code.append('`;\n}\n');
|
|
484
|
+
return { code: code.toString(), css, origins: code.origins };
|
|
411
485
|
}
|
|
412
486
|
/** Compile a component to a TypeScript module; use parts to emit its CSS. */
|
|
413
487
|
export function compileComponent(source, filename = 'Component.workstar', options = {}) {
|
package/dist/src/project.d.ts
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
export interface ProjectStyles {
|
|
2
2
|
cssOutputPath?: string;
|
|
3
3
|
}
|
|
4
|
-
|
|
4
|
+
export interface ForeignAuditEntry {
|
|
5
|
+
filename: string;
|
|
6
|
+
supported: boolean;
|
|
7
|
+
reason?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ForeignAudit {
|
|
10
|
+
total: number;
|
|
11
|
+
supported: number;
|
|
12
|
+
entries: ForeignAuditEntry[];
|
|
13
|
+
}
|
|
14
|
+
/** Compile one authored Workstar view without rewriting unrelated generated modules. */
|
|
5
15
|
export declare function compileViewFile(sourcePath: string, outputPath: string, options?: ProjectStyles): Promise<void>;
|
|
16
|
+
/** Compile a supported TSX or Vue SFC through the Workstar runtime. */
|
|
17
|
+
export declare function compileForeignFile(sourcePath: string, outputPath: string, options?: ProjectStyles): Promise<void>;
|
|
18
|
+
/** Report source compatibility without writing generated files or changing the app. */
|
|
19
|
+
export declare function auditForeignDirectory(sourceDirectory: string): Promise<ForeignAudit>;
|
|
6
20
|
/** Compile every view before writing any generated modules. */
|
|
7
21
|
export declare function compileViewDirectory(sourceDirectory: string, outputDirectory: string, options?: ProjectStyles): Promise<string[]>;
|
|
8
22
|
/** Watch a view directory and recompile changed views for local development. */
|
package/dist/src/project.js
CHANGED
|
@@ -2,6 +2,9 @@ import { watch } from 'node:fs';
|
|
|
2
2
|
import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { compileComponentParts } from './index.js';
|
|
5
|
+
import { convertForeignComponent } from './compat.js';
|
|
6
|
+
import { reactComponentExportName } from './react-compat.js';
|
|
7
|
+
import { resolveReactComponentImport } from './react-import-resolution.js';
|
|
5
8
|
async function filesInDirectory(directory, include, prefix = '') {
|
|
6
9
|
const entries = await readdir(join(directory, prefix), {
|
|
7
10
|
withFileTypes: true,
|
|
@@ -18,13 +21,19 @@ function generatedPath(viewPath) {
|
|
|
18
21
|
return `${viewPath.slice(0, -'.workstar'.length)}.ts`;
|
|
19
22
|
}
|
|
20
23
|
function relocatedImport(sourcePath, outputPath, specifier) {
|
|
24
|
+
if (specifier.endsWith('.tsx?workstar')) {
|
|
25
|
+
const sourceTarget = resolve(dirname(sourcePath), specifier.slice(0, -'?workstar'.length));
|
|
26
|
+
const generatedTarget = resolve(dirname(outputPath), relative(dirname(sourcePath), sourceTarget).slice(0, -'.tsx'.length));
|
|
27
|
+
const path = relative(dirname(outputPath), generatedTarget)
|
|
28
|
+
.split(sep)
|
|
29
|
+
.join('/');
|
|
30
|
+
return path.startsWith('.') ? path : `./${path}`;
|
|
31
|
+
}
|
|
21
32
|
const target = resolve(dirname(sourcePath), specifier);
|
|
22
33
|
const path = relative(dirname(outputPath), target).split(sep).join('/');
|
|
23
34
|
return path.startsWith('.') ? path : `./${path}`;
|
|
24
35
|
}
|
|
25
|
-
|
|
26
|
-
export async function compileViewFile(sourcePath, outputPath, options = {}) {
|
|
27
|
-
const source = await readFile(sourcePath, 'utf8');
|
|
36
|
+
async function compileSourceFile(sourcePath, outputPath, source, options, namedExport) {
|
|
28
37
|
const { code, css } = compileComponentParts(source, sourcePath, {
|
|
29
38
|
rewriteRelativeImport: (specifier) => relocatedImport(sourcePath, outputPath, specifier),
|
|
30
39
|
});
|
|
@@ -32,12 +41,55 @@ export async function compileViewFile(sourcePath, outputPath, options = {}) {
|
|
|
32
41
|
throw new Error(`${sourcePath}: pass cssOutputPath to emit component styles.`);
|
|
33
42
|
}
|
|
34
43
|
await mkdir(dirname(outputPath), { recursive: true });
|
|
35
|
-
await writeFile(outputPath, code, 'utf8');
|
|
44
|
+
await writeFile(outputPath, code + (namedExport ? `\nexport { render as ${namedExport} };\n` : ''), 'utf8');
|
|
36
45
|
if (options.cssOutputPath) {
|
|
37
46
|
await mkdir(dirname(options.cssOutputPath), { recursive: true });
|
|
38
47
|
await writeFile(options.cssOutputPath, css, 'utf8');
|
|
39
48
|
}
|
|
40
49
|
}
|
|
50
|
+
/** Compile one authored Workstar view without rewriting unrelated generated modules. */
|
|
51
|
+
export async function compileViewFile(sourcePath, outputPath, options = {}) {
|
|
52
|
+
await compileSourceFile(sourcePath, outputPath, await readFile(sourcePath, 'utf8'), options);
|
|
53
|
+
}
|
|
54
|
+
/** Compile a supported TSX or Vue SFC through the Workstar runtime. */
|
|
55
|
+
export async function compileForeignFile(sourcePath, outputPath, options = {}) {
|
|
56
|
+
const original = await readFile(sourcePath, 'utf8');
|
|
57
|
+
const source = convertForeignComponent(original, sourcePath, {
|
|
58
|
+
resolveReactImport: (specifier) => resolveReactComponentImport(sourcePath, specifier),
|
|
59
|
+
});
|
|
60
|
+
const namedExport = sourcePath.endsWith('.tsx')
|
|
61
|
+
? reactComponentExportName(original, sourcePath)
|
|
62
|
+
: undefined;
|
|
63
|
+
await compileSourceFile(sourcePath, outputPath, source, options, namedExport);
|
|
64
|
+
}
|
|
65
|
+
/** Report source compatibility without writing generated files or changing the app. */
|
|
66
|
+
export async function auditForeignDirectory(sourceDirectory) {
|
|
67
|
+
const paths = await filesInDirectory(sourceDirectory, (name) => /\.(tsx|vue)$/.test(name) &&
|
|
68
|
+
!/\.(test|spec|stories)\.(tsx|vue)$/.test(name));
|
|
69
|
+
const entries = await Promise.all(paths.map(async (filename) => {
|
|
70
|
+
const sourcePath = join(sourceDirectory, filename);
|
|
71
|
+
try {
|
|
72
|
+
const converted = convertForeignComponent(await readFile(sourcePath, 'utf8'), sourcePath, {
|
|
73
|
+
resolveReactImport: (specifier) => resolveReactComponentImport(sourcePath, specifier),
|
|
74
|
+
});
|
|
75
|
+
compileComponentParts(converted, `${sourcePath}.workstar`);
|
|
76
|
+
return { filename, supported: true };
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
80
|
+
return {
|
|
81
|
+
filename,
|
|
82
|
+
supported: false,
|
|
83
|
+
reason: message.replace(sourcePath, filename),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}));
|
|
87
|
+
return {
|
|
88
|
+
total: entries.length,
|
|
89
|
+
supported: entries.filter((entry) => entry.supported).length,
|
|
90
|
+
entries,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
41
93
|
/** Compile every view before writing any generated modules. */
|
|
42
94
|
export async function compileViewDirectory(sourceDirectory, outputDirectory, options = {}) {
|
|
43
95
|
const viewPaths = await filesInDirectory(sourceDirectory, (name) => name.endsWith('.workstar'));
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** The source export used by a named TSX import; undefined means default export. */
|
|
2
|
+
export declare function reactComponentExportName(source: string, filename?: string): string | undefined;
|
|
3
|
+
/** Converts one typed, stateless exported function component; unsupported behavior fails closed. */
|
|
4
|
+
export declare function convertReactComponent(source: string, filename?: string, options?: {
|
|
5
|
+
resolveReactImport?: (specifier: string) => string | undefined;
|
|
6
|
+
}): string;
|