@jxsuite/compiler 8.4.0 → 8.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +179 -166
- package/dist/compiler.js +118 -105
- package/package.json +8 -7
- package/src/compiler.ts +3 -0
- package/src/erased.ts +60 -0
- package/src/shared.ts +64 -2
- package/src/site/layout-resolver.ts +5 -1
- package/src/site/pages-discovery.ts +4 -2
- package/src/site/validate-command.ts +48 -3
- package/src/targets/compile-class.ts +4 -1
- package/src/targets/compile-client.ts +3 -2
- package/src/targets/compile-element.ts +12 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/compiler",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.5.0",
|
|
4
4
|
"description": "Jx static HTML compiler, island detector, and site builder",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -52,10 +52,11 @@
|
|
|
52
52
|
"upgrade": "bun update --latest && bun install"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@jxsuite/create": "^1.
|
|
56
|
-
"@jxsuite/
|
|
57
|
-
"@jxsuite/
|
|
58
|
-
"@jxsuite/
|
|
55
|
+
"@jxsuite/create": "^1.7.0",
|
|
56
|
+
"@jxsuite/erase": "^0.1.0",
|
|
57
|
+
"@jxsuite/runtime": "^6.2.2",
|
|
58
|
+
"@jxsuite/schema": "^5.5.0",
|
|
59
|
+
"@jxsuite/site": "^2.5.1",
|
|
59
60
|
"@vue/reactivity": "3.5.44",
|
|
60
61
|
"esbuild": "^0.28.2",
|
|
61
62
|
"lit-html": "^3.3.3",
|
|
@@ -63,8 +64,8 @@
|
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|
|
65
66
|
"@happy-dom/global-registrator": "^21.0.4",
|
|
66
|
-
"@jxsuite/connector": "^0.8.
|
|
67
|
-
"@jxsuite/parser": "^4.3.
|
|
67
|
+
"@jxsuite/connector": "^0.8.7",
|
|
68
|
+
"@jxsuite/parser": "^4.3.3",
|
|
68
69
|
"@jxsuite/test-support": "^0.0.0",
|
|
69
70
|
"@types/mdast": "^4.0.4"
|
|
70
71
|
}
|
package/src/compiler.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* @version 3.0.0
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { erasedDocument } from "./erased.ts";
|
|
16
17
|
import { readFileSync } from "node:fs";
|
|
17
18
|
import {
|
|
18
19
|
DEFAULT_LIT_HTML_SRC,
|
|
@@ -109,6 +110,8 @@ export async function compile(sourcePath: string | JxDocument, opts: CompileOpti
|
|
|
109
110
|
} else {
|
|
110
111
|
raw = sourcePath;
|
|
111
112
|
}
|
|
113
|
+
// Function bodies as JavaScript before anything analyses them (spec.md §23.1).
|
|
114
|
+
raw = erasedDocument(raw, isString(sourcePath) ? sourcePath : "the document");
|
|
112
115
|
|
|
113
116
|
// Route 0: .class.json schema-defined class → JS class module
|
|
114
117
|
if (isClassDef(raw)) {
|
package/src/erased.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documents as the compiler reads them: with the TypeScript in their function bodies erased, once,
|
|
3
|
+
* as each is read (`spec.md` §23.1, `compiler.md` §2.3). Everything after this, the analysis that
|
|
4
|
+
* classifies a body and every emitter, sees JavaScript, so none of it changes.
|
|
5
|
+
*
|
|
6
|
+
* A body that was already JavaScript and reads differently as TypeScript (`a < b > (c)`) is said
|
|
7
|
+
* once per place, on stderr, and the build goes on with the TypeScript reading.
|
|
8
|
+
*
|
|
9
|
+
* @docs framework/concepts/functions
|
|
10
|
+
*/
|
|
11
|
+
import { eraseClass, eraseDocument } from "@jxsuite/erase";
|
|
12
|
+
import type { EraseWarning } from "@jxsuite/erase";
|
|
13
|
+
import type { JsonValue, JxClassDef, JxDocument } from "@jxsuite/schema/types";
|
|
14
|
+
|
|
15
|
+
/** Every warning already printed, so a document read twice says each thing once. */
|
|
16
|
+
const said = new Set<string>();
|
|
17
|
+
|
|
18
|
+
/** Print what erasing found that did not stop it. */
|
|
19
|
+
function report(warnings: readonly EraseWarning[]): void {
|
|
20
|
+
for (const { path, pointer, problem } of warnings) {
|
|
21
|
+
const line = `${path}${pointer} (line ${problem.line}, column ${problem.column}): ${problem.message}`;
|
|
22
|
+
if (!said.has(line)) {
|
|
23
|
+
said.add(line);
|
|
24
|
+
console.warn(`Warning: ${line}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A document with its function bodies as JavaScript.
|
|
31
|
+
*
|
|
32
|
+
* @param document The document as read.
|
|
33
|
+
* @param path Where it was read from, which names it in a message.
|
|
34
|
+
* @throws {import("@jxsuite/erase").EraseError} When a body uses TypeScript that cannot be erased.
|
|
35
|
+
*/
|
|
36
|
+
export function erasedDocument<T extends JsonValue | JxDocument | JxClassDef>(
|
|
37
|
+
document: T,
|
|
38
|
+
path: string,
|
|
39
|
+
): T {
|
|
40
|
+
const warnings: EraseWarning[] = [];
|
|
41
|
+
const erased = eraseDocument(document, path, warnings);
|
|
42
|
+
report(warnings);
|
|
43
|
+
return erased;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A class definition with its members' bodies as JavaScript.
|
|
48
|
+
*
|
|
49
|
+
* @param definition The class definition as read.
|
|
50
|
+
* @param path Where it was read from.
|
|
51
|
+
*/
|
|
52
|
+
export function erasedClass<T extends JsonValue | JxDocument | JxClassDef>(
|
|
53
|
+
definition: T,
|
|
54
|
+
path: string,
|
|
55
|
+
): T {
|
|
56
|
+
const warnings: EraseWarning[] = [];
|
|
57
|
+
const erased = eraseClass(definition, path, warnings);
|
|
58
|
+
report(warnings);
|
|
59
|
+
return erased;
|
|
60
|
+
}
|
package/src/shared.ts
CHANGED
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
isTemplateString,
|
|
50
50
|
paramNames,
|
|
51
51
|
tagNameCandidates,
|
|
52
|
+
maskCode,
|
|
52
53
|
} from "@jxsuite/schema/guards";
|
|
53
54
|
import type { TagNameSlot } from "@jxsuite/schema/guards";
|
|
54
55
|
import { resolveShadowMode, styleScopePrefix } from "./shadow.ts";
|
|
@@ -604,6 +605,62 @@ const STATE_ASSIGNMENT_RE =
|
|
|
604
605
|
const STATE_MUTATING_METHOD_RE =
|
|
605
606
|
/\bstate\.([A-Za-z_$][\w$]*)\s*\.\s*(?:push|pop|shift|unshift|splice|sort|reverse|fill|copyWithin)\s*\(/g;
|
|
606
607
|
|
|
608
|
+
/** A character that continues an identifier, or a `.` that makes `state` someone's member. */
|
|
609
|
+
const JOINS_STATE = /[\w$.]/u;
|
|
610
|
+
|
|
611
|
+
/** A character that continues an identifier. */
|
|
612
|
+
const CONTINUES_NAME = /[\w$]/u;
|
|
613
|
+
|
|
614
|
+
/** Whether `state` at `at` in `read` is the one to move: standalone, and a member read unless bare. */
|
|
615
|
+
function movesState(read: string, at: number, bare: boolean): boolean {
|
|
616
|
+
if (at > 0 && JOINS_STATE.test(read.charAt(at - 1))) {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
let next = at + "state".length;
|
|
620
|
+
if (bare) {
|
|
621
|
+
return !CONTINUES_NAME.test(read.charAt(next));
|
|
622
|
+
}
|
|
623
|
+
while (/\s/u.test(read.charAt(next))) {
|
|
624
|
+
next += 1;
|
|
625
|
+
}
|
|
626
|
+
return read.charAt(next) === ".";
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Rewrite `state` in emitted code to what holds it there (`this.state`, `s`): the one way every
|
|
631
|
+
* target does it (`spec.md` §23.1). Only a standalone `state` moves, so `prevstate.n` stays itself,
|
|
632
|
+
* and in a body only code moves, so a string or a comment that mentions `state.` is left as
|
|
633
|
+
* written.
|
|
634
|
+
*
|
|
635
|
+
* @param text The body, or an expression.
|
|
636
|
+
* @param to What `state` becomes.
|
|
637
|
+
* @param options `bare` moves `state` alone too, not only `state.member`; `code: false` reads text
|
|
638
|
+
* that is not a body (an attribute's template), where nothing is a string or a comment.
|
|
639
|
+
*/
|
|
640
|
+
export function rewriteState(
|
|
641
|
+
text: string,
|
|
642
|
+
to: string,
|
|
643
|
+
options: { bare?: boolean; code?: boolean } = {},
|
|
644
|
+
): string {
|
|
645
|
+
// Most text never names `state`, and every emitter calls this on every body and template.
|
|
646
|
+
if (!text.includes("state")) {
|
|
647
|
+
return text;
|
|
648
|
+
}
|
|
649
|
+
const bare = options.bare === true;
|
|
650
|
+
const read = options.code === false ? text : maskCode(text);
|
|
651
|
+
/* A scan rather than a lookbehind pattern: every emitter runs this on every body and template,
|
|
652
|
+
and JavaScriptCore runs `(?<!…)` on its slow path, which a compile benchmark measured. */
|
|
653
|
+
let out = "";
|
|
654
|
+
let last = 0;
|
|
655
|
+
for (let at = read.indexOf("state"); at !== -1; at = read.indexOf("state", at + 1)) {
|
|
656
|
+
if (movesState(read, at, bare)) {
|
|
657
|
+
out += text.slice(last, at) + to;
|
|
658
|
+
last = at + "state".length;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return out + text.slice(last);
|
|
662
|
+
}
|
|
663
|
+
|
|
607
664
|
/**
|
|
608
665
|
* Collect every state key a handler writes to, across all the forms a body can take: a string body,
|
|
609
666
|
* a structured body (spec §20), and an `$expression` node with a mutating operator.
|
|
@@ -618,11 +675,16 @@ const STATE_MUTATING_METHOD_RE =
|
|
|
618
675
|
*/
|
|
619
676
|
function collectAssignedStateKeys(node: JxDocumentValue | undefined, into: Set<string>) {
|
|
620
677
|
if (isString(node)) {
|
|
621
|
-
|
|
678
|
+
if (!node.includes("state.")) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
// Code only: an assignment a comment or a string mentions writes nothing (spec.md §23.1).
|
|
682
|
+
const code = maskCode(node);
|
|
683
|
+
for (const m of code.matchAll(STATE_ASSIGNMENT_RE)) {
|
|
622
684
|
// SAFETY: the pattern's one capture group is outside any optional part, so a match has it.
|
|
623
685
|
into.add(m[1] as string);
|
|
624
686
|
}
|
|
625
|
-
for (const m of
|
|
687
|
+
for (const m of code.matchAll(STATE_MUTATING_METHOD_RE)) {
|
|
626
688
|
// SAFETY: the pattern's one capture group is outside any optional part, so a match has it.
|
|
627
689
|
into.add(m[1] as string);
|
|
628
690
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* and saying which file was wrong when it is.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { erasedDocument } from "../erased.ts";
|
|
11
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
12
13
|
import { parseNativeDocument } from "@jxsuite/schema/jxn";
|
|
13
14
|
import { errorMessage } from "@jxsuite/schema/parse";
|
|
@@ -27,7 +28,10 @@ export function nodeLayoutLoader(projectRoot: string): (layoutRef: string) => Jx
|
|
|
27
28
|
throw new Error(`Layout not found: ${layoutRef} (resolved to ${layoutPath})`);
|
|
28
29
|
}
|
|
29
30
|
try {
|
|
30
|
-
return
|
|
31
|
+
return erasedDocument(
|
|
32
|
+
parseNativeDocument(readFileSync(layoutPath, "utf8"), layoutPath),
|
|
33
|
+
layoutPath,
|
|
34
|
+
);
|
|
31
35
|
} catch (error) {
|
|
32
36
|
throw new Error(`Invalid layout at ${layoutPath}: ${errorMessage(error)}`, {
|
|
33
37
|
cause: error,
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* genuinely needs a filesystem: the walk, the `$layout` peek, and `$paths` expansion.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import { erasedDocument } from "../erased.ts";
|
|
19
20
|
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
20
21
|
import { isJsonObject, isString } from "@jxsuite/schema/guards";
|
|
21
22
|
import { isNativeDocumentPath, parseNativeDocument } from "@jxsuite/schema/jxn";
|
|
@@ -82,7 +83,7 @@ export async function readPageDocument(
|
|
|
82
83
|
): Promise<JxDocument> {
|
|
83
84
|
const source = readFileSync(filePath, "utf8");
|
|
84
85
|
if (isNativeDocumentPath(filePath)) {
|
|
85
|
-
return parseNativeDocument(source, filePath);
|
|
86
|
+
return erasedDocument(parseNativeDocument(source, filePath), filePath);
|
|
86
87
|
}
|
|
87
88
|
const ext = extname(filePath).toLowerCase();
|
|
88
89
|
const entry = registry?.byExtension(ext, "parse");
|
|
@@ -93,7 +94,8 @@ export async function readPageDocument(
|
|
|
93
94
|
`e.g. "@jxsuite/parser".`,
|
|
94
95
|
);
|
|
95
96
|
}
|
|
96
|
-
|
|
97
|
+
// A format's page holds function bodies too (spec.md §23.1).
|
|
98
|
+
return erasedDocument(await entry.parseDocument(source), filePath);
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
/**
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
21
21
|
import { isJxnPath, isNativeDocumentPath, lintJxn, parseJxn } from "@jxsuite/schema/jxn";
|
|
22
22
|
import { errorMessage } from "@jxsuite/schema/parse";
|
|
23
|
+
import { eraseDocument } from "@jxsuite/erase";
|
|
24
|
+
import type { EraseWarning } from "@jxsuite/erase";
|
|
23
25
|
import { createRequire } from "node:module";
|
|
24
26
|
import { join, relative, resolve } from "node:path";
|
|
25
27
|
import { generateClassSchema } from "@jxsuite/schema";
|
|
@@ -65,8 +67,11 @@ export interface ProjectTreeLintFinding {
|
|
|
65
67
|
file: string;
|
|
66
68
|
/** The node's path inside the document. */
|
|
67
69
|
path: (string | number)[];
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Which lint: `popover`, `dialog`, `accessibility`, `notation` for a `.jxn` file's text, or
|
|
72
|
+
* `function-body` for a body whose meaning reading it as TypeScript changes (`spec.md` §23.1).
|
|
73
|
+
*/
|
|
74
|
+
source: "popover" | "dialog" | "accessibility" | "notation" | "function-body";
|
|
70
75
|
rule: string;
|
|
71
76
|
message: string;
|
|
72
77
|
detail: string;
|
|
@@ -221,6 +226,12 @@ export async function validateProjectTree(
|
|
|
221
226
|
continue;
|
|
222
227
|
}
|
|
223
228
|
const doc = read.document;
|
|
229
|
+
const bodies = checkBodies(doc, relative(root, file));
|
|
230
|
+
lint.push(...bodies.warnings);
|
|
231
|
+
if (bodies.failure !== null) {
|
|
232
|
+
issues.push(bodies.failure);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
224
235
|
if (validateDoc(doc)) {
|
|
225
236
|
/*
|
|
226
237
|
* Only a well-formed document is judged; a schema error is the report for the rest.
|
|
@@ -241,7 +252,11 @@ export async function validateProjectTree(
|
|
|
241
252
|
for (const file of walkClassFiles(root)) {
|
|
242
253
|
checked += 1;
|
|
243
254
|
const doc = readJson(file);
|
|
244
|
-
|
|
255
|
+
const bodies = checkBodies(doc, relative(root, file));
|
|
256
|
+
lint.push(...bodies.warnings);
|
|
257
|
+
if (bodies.failure !== null) {
|
|
258
|
+
issues.push(bodies.failure);
|
|
259
|
+
} else if (!validateClass(doc)) {
|
|
245
260
|
issues.push({ errors: validateClass.errors ?? [], file: relative(root, file) });
|
|
246
261
|
}
|
|
247
262
|
}
|
|
@@ -315,6 +330,36 @@ function readDocumentFile(file: string, name: string): ReadDocumentFile {
|
|
|
315
330
|
}
|
|
316
331
|
}
|
|
317
332
|
|
|
333
|
+
/** What checking a document's function bodies found: an issue that stops it, and warnings. */
|
|
334
|
+
interface BodyCheck {
|
|
335
|
+
failure: ProjectTreeIssue | null;
|
|
336
|
+
warnings: ProjectTreeLintFinding[];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Whether every function body in a document is TypeScript that can be erased (`spec.md` §23.1): a
|
|
341
|
+
* body that cannot is an issue, as it stops `jx build`, and one that reads differently as
|
|
342
|
+
* TypeScript than as the JavaScript it already was is a warning.
|
|
343
|
+
*/
|
|
344
|
+
function checkBodies(document: JsonValue, name: string): BodyCheck {
|
|
345
|
+
const found: EraseWarning[] = [];
|
|
346
|
+
try {
|
|
347
|
+
eraseDocument(document, name, found);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
return { failure: { errors: [{ message: errorMessage(error) }], file: name }, warnings: [] };
|
|
350
|
+
}
|
|
351
|
+
const warnings = found.map(({ pointer, problem }): ProjectTreeLintFinding => ({
|
|
352
|
+
detail: problem.message,
|
|
353
|
+
file: name,
|
|
354
|
+
message: `${pointer} (line ${problem.line}, column ${problem.column}): ${problem.message}`,
|
|
355
|
+
path: pointer.split("/").slice(1),
|
|
356
|
+
rule: "typescript-reading",
|
|
357
|
+
severity: "warn",
|
|
358
|
+
source: "function-body",
|
|
359
|
+
}));
|
|
360
|
+
return { failure: null, warnings };
|
|
361
|
+
}
|
|
362
|
+
|
|
318
363
|
/** A `.jxn` file's document, as the JSON value it is. */
|
|
319
364
|
function readJxnValue(text: string): JsonValue {
|
|
320
365
|
const document = parseJxn(text);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Schema 2020-12 class definition.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { erasedClass } from "../erased.ts";
|
|
8
9
|
import type {
|
|
9
10
|
JxClassCtorDef,
|
|
10
11
|
JxClassDef,
|
|
@@ -20,7 +21,9 @@ import { isString } from "@jxsuite/schema/guards";
|
|
|
20
21
|
* @param {JxClassDef} classDef - Parsed .class.json content (must have $prototype: "Class")
|
|
21
22
|
* @returns {string} JavaScript module source code
|
|
22
23
|
*/
|
|
23
|
-
export function compileClassJson(
|
|
24
|
+
export function compileClassJson(authored: JxClassDef) {
|
|
25
|
+
// Member bodies as JavaScript before anything splices them (spec.md §23.1).
|
|
26
|
+
const classDef = erasedClass(authored, authored.title ?? "a class");
|
|
24
27
|
const className = classDef.title;
|
|
25
28
|
if (!className) {
|
|
26
29
|
throw new Error("compileClassJson: missing title (class name)");
|
|
@@ -774,7 +774,8 @@ function emitLitMapTemplate(def: JxMutableNode | null | undefined, preformatted
|
|
|
774
774
|
* has: a repeater handler is emitted inline and needs inline source.
|
|
775
775
|
*/
|
|
776
776
|
const body = mapRefsToLit(val.body as string);
|
|
777
|
-
|
|
777
|
+
// The newline ends a last-line comment before the brace does.
|
|
778
|
+
attrs += ` @${eventName}=\${(e) => { ${body}\n}}`;
|
|
778
779
|
} else if (isNonNullObject(val) && "$expression" in val) {
|
|
779
780
|
/*
|
|
780
781
|
* SAFETY: an `on*` member carrying `$expression` is a Shape 5 expression entry, whose
|
|
@@ -1005,7 +1006,7 @@ function emitClientModule(
|
|
|
1005
1006
|
const argNames = def.args ?? ["state"];
|
|
1006
1007
|
const callArgs = argNames.map((a: string) => (a === "state" ? "state" : "e")).join(", ");
|
|
1007
1008
|
lines.push(
|
|
1008
|
-
` ${objectKey(key)}: (e) => { const fn = (${argNames.join(", ")}) => { ${def.body}
|
|
1009
|
+
` ${objectKey(key)}: (e) => { const fn = (${argNames.join(", ")}) => { ${def.body}\n}; fn(${
|
|
1009
1010
|
callArgs
|
|
1010
1011
|
}); },`,
|
|
1011
1012
|
);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* state and lit-html for rendering.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { erasedDocument } from "../erased.ts";
|
|
8
9
|
import { RESERVED_KEYS, camelToKebab } from "@jxsuite/runtime";
|
|
9
10
|
import {
|
|
10
11
|
ATTR_HELPER,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
escapeHtml,
|
|
27
28
|
isMutating,
|
|
28
29
|
isSchemaOnly,
|
|
30
|
+
rewriteState,
|
|
29
31
|
serverCallHelperSource,
|
|
30
32
|
srcExportName,
|
|
31
33
|
srcImportBinding,
|
|
@@ -144,8 +146,10 @@ export async function compileElement(
|
|
|
144
146
|
}
|
|
145
147
|
doc = await entry.parseDocument(readFileSync(filePath, "utf8"));
|
|
146
148
|
}
|
|
149
|
+
// Function bodies as JavaScript before anything analyses them (spec.md §23.1).
|
|
150
|
+
doc = erasedDocument(doc, filePath);
|
|
147
151
|
} else {
|
|
148
|
-
doc = srcPath;
|
|
152
|
+
doc = erasedDocument(srcPath, srcPath.tagName ?? "a component");
|
|
149
153
|
filePath = null;
|
|
150
154
|
if (doc.tagName && visited.has(doc.tagName)) {
|
|
151
155
|
return;
|
|
@@ -665,7 +669,7 @@ export function emitElementModule(
|
|
|
665
669
|
for (const [key, template] of templateEntries) {
|
|
666
670
|
lines.push(
|
|
667
671
|
"",
|
|
668
|
-
` ${refAccessor("this.state", escapeToken(key))} = computed(() => ${templateValueSource(template
|
|
672
|
+
` ${refAccessor("this.state", escapeToken(key))} = computed(() => ${templateValueSource(rewriteState(template, "this.state", { code: false }))});`,
|
|
669
673
|
);
|
|
670
674
|
}
|
|
671
675
|
|
|
@@ -686,7 +690,7 @@ export function emitElementModule(
|
|
|
686
690
|
);
|
|
687
691
|
} else {
|
|
688
692
|
lines.push(` ${refAccessor("this.state", escapeToken(key))} = computed(() => {`);
|
|
689
|
-
const body = (isString(def.body) ? def.body : ""
|
|
693
|
+
const body = rewriteState(isString(def.body) ? def.body : "", "this.state");
|
|
690
694
|
lines.push(` ${body}`, " });");
|
|
691
695
|
}
|
|
692
696
|
}
|
|
@@ -839,7 +843,7 @@ export function emitElementModule(
|
|
|
839
843
|
for (const [cssProp, value] of dynamicStyles) {
|
|
840
844
|
const expr = value.replaceAll(
|
|
841
845
|
/\$\{([^}]+)\}/g,
|
|
842
|
-
(_: string, e: string) => `\${${e
|
|
846
|
+
(_: string, e: string) => `\${${rewriteState(e, "this.state")}}`,
|
|
843
847
|
);
|
|
844
848
|
lines.push(` this.style['${cssProp}'] = \`${expr}\`;`);
|
|
845
849
|
}
|
|
@@ -1521,7 +1525,7 @@ function mapRefToExpr(ref: string) {
|
|
|
1521
1525
|
* @returns {string}
|
|
1522
1526
|
*/
|
|
1523
1527
|
function toLitExpr(str: string) {
|
|
1524
|
-
return str
|
|
1528
|
+
return rewriteState(str, "s", { code: false });
|
|
1525
1529
|
}
|
|
1526
1530
|
|
|
1527
1531
|
/**
|
|
@@ -1576,9 +1580,10 @@ function inlineHandlerBody(def: JxFunctionDef) {
|
|
|
1576
1580
|
// Body needs no textual rewriting, and a declared `event` stops silently resolving to the
|
|
1577
1581
|
// Deprecated `window.event` global (any other declared name threw ReferenceError outright).
|
|
1578
1582
|
const args = declared.map((name: string) => (name === "state" ? "s" : "e")).join(", ");
|
|
1579
|
-
|
|
1583
|
+
// The newline ends a last-line comment before the brace does.
|
|
1584
|
+
return `((${declared.join(", ")}) => { ${body}\n})(${args});`;
|
|
1580
1585
|
}
|
|
1581
|
-
return body
|
|
1586
|
+
return `${rewriteState(body, "s", { bare: true })}\n`;
|
|
1582
1587
|
}
|
|
1583
1588
|
|
|
1584
1589
|
/**
|