@fluixi/compiler 1.0.0-alpha.100
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/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/analyze/expr-shape.cjs +62 -0
- package/dist/analyze/expr-shape.d.cts +14 -0
- package/dist/analyze/expr-shape.d.ts +15 -0
- package/dist/analyze/expr-shape.d.ts.map +1 -0
- package/dist/analyze/expr-shape.js +37 -0
- package/dist/analyze/expr-shape.mjs +41 -0
- package/dist/analyze/intrinsics.cjs +112 -0
- package/dist/analyze/intrinsics.d.cts +80 -0
- package/dist/analyze/intrinsics.d.ts +81 -0
- package/dist/analyze/intrinsics.d.ts.map +1 -0
- package/dist/analyze/intrinsics.js +126 -0
- package/dist/analyze/intrinsics.mjs +91 -0
- package/dist/analyze/paren.cjs +134 -0
- package/dist/analyze/paren.d.cts +23 -0
- package/dist/analyze/paren.d.ts +24 -0
- package/dist/analyze/paren.d.ts.map +1 -0
- package/dist/analyze/paren.js +168 -0
- package/dist/analyze/paren.mjs +113 -0
- package/dist/analyze/props-destructure.cjs +226 -0
- package/dist/analyze/props-destructure.d.cts +83 -0
- package/dist/analyze/props-destructure.d.ts +84 -0
- package/dist/analyze/props-destructure.d.ts.map +1 -0
- package/dist/analyze/props-destructure.js +272 -0
- package/dist/analyze/props-destructure.mjs +205 -0
- package/dist/analyze/reactive-bindings.cjs +210 -0
- package/dist/analyze/reactive-bindings.d.cts +42 -0
- package/dist/analyze/reactive-bindings.d.ts +43 -0
- package/dist/analyze/reactive-bindings.d.ts.map +1 -0
- package/dist/analyze/reactive-bindings.js +191 -0
- package/dist/analyze/reactive-bindings.mjs +187 -0
- package/dist/analyze/reactive-expr.cjs +38 -0
- package/dist/analyze/reactive-expr.d.cts +38 -0
- package/dist/analyze/reactive-expr.d.ts +39 -0
- package/dist/analyze/reactive-expr.d.ts.map +1 -0
- package/dist/analyze/reactive-expr.js +31 -0
- package/dist/analyze/reactive-expr.mjs +17 -0
- package/dist/analyze/static-graph.cjs +831 -0
- package/dist/analyze/static-graph.d.cts +102 -0
- package/dist/analyze/static-graph.d.ts +103 -0
- package/dist/analyze/static-graph.d.ts.map +1 -0
- package/dist/analyze/static-graph.js +871 -0
- package/dist/analyze/static-graph.mjs +813 -0
- package/dist/babel-I6BUWUCU.mjs +636 -0
- package/dist/chunk-3AICWNF7.mjs +92 -0
- package/dist/chunk-6CWJWGE2.mjs +2170 -0
- package/dist/chunk-C3XPDB4Y.mjs +16 -0
- package/dist/chunk-GX5XV6Z7.mjs +190 -0
- package/dist/chunk-HXEXPWJ2.mjs +999 -0
- package/dist/chunk-NU3W67UZ.mjs +584 -0
- package/dist/chunk-SKAVY75W.mjs +11 -0
- package/dist/codegen/backend.cjs +18 -0
- package/dist/codegen/backend.d.cts +35 -0
- package/dist/codegen/backend.d.ts +36 -0
- package/dist/codegen/backend.d.ts.map +1 -0
- package/dist/codegen/backend.js +1 -0
- package/dist/codegen/backend.mjs +0 -0
- package/dist/codegen/backends/imperative.cjs +577 -0
- package/dist/codegen/backends/imperative.d.cts +11 -0
- package/dist/codegen/backends/imperative.d.ts +12 -0
- package/dist/codegen/backends/imperative.d.ts.map +1 -0
- package/dist/codegen/backends/imperative.js +362 -0
- package/dist/codegen/backends/imperative.mjs +554 -0
- package/dist/codegen/contract.cjs +52 -0
- package/dist/codegen/contract.d.cts +24 -0
- package/dist/codegen/contract.d.ts +25 -0
- package/dist/codegen/contract.d.ts.map +1 -0
- package/dist/codegen/contract.js +30 -0
- package/dist/codegen/contract.mjs +31 -0
- package/dist/codegen/partial-template.cjs +176 -0
- package/dist/codegen/partial-template.d.cts +61 -0
- package/dist/codegen/partial-template.d.ts +62 -0
- package/dist/codegen/partial-template.d.ts.map +1 -0
- package/dist/codegen/partial-template.js +0 -0
- package/dist/codegen/partial-template.mjs +153 -0
- package/dist/codegen/serialize-static.cjs +125 -0
- package/dist/codegen/serialize-static.d.cts +38 -0
- package/dist/codegen/serialize-static.d.ts +39 -0
- package/dist/codegen/serialize-static.d.ts.map +1 -0
- package/dist/codegen/serialize-static.js +165 -0
- package/dist/codegen/serialize-static.mjs +104 -0
- package/dist/frontend/babel/build-ir-lit.cjs +1933 -0
- package/dist/frontend/babel/build-ir-lit.d.cts +26 -0
- package/dist/frontend/babel/build-ir-lit.d.ts +27 -0
- package/dist/frontend/babel/build-ir-lit.d.ts.map +1 -0
- package/dist/frontend/babel/build-ir-lit.js +13 -0
- package/dist/frontend/babel/build-ir-lit.mjs +1902 -0
- package/dist/frontend/babel/build-ir.cjs +1948 -0
- package/dist/frontend/babel/build-ir.d.cts +35 -0
- package/dist/frontend/babel/build-ir.d.ts +36 -0
- package/dist/frontend/babel/build-ir.d.ts.map +1 -0
- package/dist/frontend/babel/build-ir.js +124 -0
- package/dist/frontend/babel/build-ir.mjs +1919 -0
- package/dist/frontend/babel/index.cjs +2610 -0
- package/dist/frontend/babel/index.d.cts +33 -0
- package/dist/frontend/babel/index.d.ts +34 -0
- package/dist/frontend/babel/index.d.ts.map +1 -0
- package/dist/frontend/babel/index.js +65 -0
- package/dist/frontend/babel/index.mjs +2580 -0
- package/dist/frontend/babel/lower-template.cjs +1931 -0
- package/dist/frontend/babel/lower-template.d.cts +23 -0
- package/dist/frontend/babel/lower-template.d.ts +24 -0
- package/dist/frontend/babel/lower-template.d.ts.map +1 -0
- package/dist/frontend/babel/lower-template.js +64 -0
- package/dist/frontend/babel/lower-template.mjs +1902 -0
- package/dist/frontend/babel/plugin.cjs +2556 -0
- package/dist/frontend/babel/plugin.d.cts +95 -0
- package/dist/frontend/babel/plugin.d.ts +96 -0
- package/dist/frontend/babel/plugin.d.ts.map +1 -0
- package/dist/frontend/babel/plugin.js +523 -0
- package/dist/frontend/babel/plugin.mjs +2527 -0
- package/dist/frontend/babel/server-functions.cjs +110 -0
- package/dist/frontend/babel/server-functions.d.cts +10 -0
- package/dist/frontend/babel/server-functions.d.ts +11 -0
- package/dist/frontend/babel/server-functions.d.ts.map +1 -0
- package/dist/frontend/babel/server-functions.js +81 -0
- package/dist/frontend/babel/server-functions.mjs +89 -0
- package/dist/frontend/babel/types.cjs +18 -0
- package/dist/frontend/babel/types.d.cts +33 -0
- package/dist/frontend/babel/types.d.ts +34 -0
- package/dist/frontend/babel/types.d.ts.map +1 -0
- package/dist/frontend/babel/types.js +1 -0
- package/dist/frontend/babel/types.mjs +0 -0
- package/dist/frontend/types.cjs +18 -0
- package/dist/frontend/types.d.cts +25 -0
- package/dist/frontend/types.d.ts +26 -0
- package/dist/frontend/types.d.ts.map +1 -0
- package/dist/frontend/types.js +1 -0
- package/dist/frontend/types.mjs +0 -0
- package/dist/frontend/vocabulary.cjs +98 -0
- package/dist/frontend/vocabulary.d.cts +79 -0
- package/dist/frontend/vocabulary.d.ts +80 -0
- package/dist/frontend/vocabulary.d.ts.map +1 -0
- package/dist/frontend/vocabulary.js +124 -0
- package/dist/frontend/vocabulary.mjs +77 -0
- package/dist/index.cjs +1957 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.mjs +1949 -0
- package/dist/integrations.cjs +5705 -0
- package/dist/integrations.d.cts +215 -0
- package/dist/integrations.d.ts +216 -0
- package/dist/integrations.d.ts.map +1 -0
- package/dist/integrations.js +566 -0
- package/dist/integrations.mjs +810 -0
- package/dist/ir/nodes.cjs +18 -0
- package/dist/ir/nodes.d.cts +168 -0
- package/dist/ir/nodes.d.ts +169 -0
- package/dist/ir/nodes.d.ts.map +1 -0
- package/dist/ir/nodes.js +1 -0
- package/dist/ir/nodes.mjs +0 -0
- package/dist/lower/compile-template.cjs +1670 -0
- package/dist/lower/compile-template.d.cts +82 -0
- package/dist/lower/compile-template.d.ts +83 -0
- package/dist/lower/compile-template.d.ts.map +1 -0
- package/dist/lower/compile-template.js +79 -0
- package/dist/lower/compile-template.mjs +1662 -0
- package/dist/lower/entities.cjs +288 -0
- package/dist/lower/entities.d.cts +1 -0
- package/dist/lower/entities.d.ts +2 -0
- package/dist/lower/entities.d.ts.map +1 -0
- package/dist/lower/entities.js +54 -0
- package/dist/lower/entities.mjs +267 -0
- package/dist/lower/jsx.cjs +1101 -0
- package/dist/lower/jsx.d.cts +33 -0
- package/dist/lower/jsx.d.ts +34 -0
- package/dist/lower/jsx.d.ts.map +1 -0
- package/dist/lower/jsx.js +414 -0
- package/dist/lower/jsx.mjs +1078 -0
- package/dist/lower/template.cjs +1354 -0
- package/dist/lower/template.d.cts +53 -0
- package/dist/lower/template.d.ts +54 -0
- package/dist/lower/template.d.ts.map +1 -0
- package/dist/lower/template.js +542 -0
- package/dist/lower/template.mjs +1335 -0
- package/dist/lower/text.cjs +42 -0
- package/dist/lower/text.d.cts +10 -0
- package/dist/lower/text.d.ts +11 -0
- package/dist/lower/text.d.ts.map +1 -0
- package/dist/lower/text.js +31 -0
- package/dist/lower/text.mjs +21 -0
- package/dist/optimize/analyze-static.cjs +112 -0
- package/dist/optimize/analyze-static.d.cts +39 -0
- package/dist/optimize/analyze-static.d.ts +40 -0
- package/dist/optimize/analyze-static.d.ts.map +1 -0
- package/dist/optimize/analyze-static.js +131 -0
- package/dist/optimize/analyze-static.mjs +91 -0
- package/dist/optimize/collapse-strategies.cjs +90 -0
- package/dist/optimize/collapse-strategies.d.cts +30 -0
- package/dist/optimize/collapse-strategies.d.ts +31 -0
- package/dist/optimize/collapse-strategies.d.ts.map +1 -0
- package/dist/optimize/collapse-strategies.js +33 -0
- package/dist/optimize/collapse-strategies.mjs +67 -0
- package/dist/options.cjs +33 -0
- package/dist/options.d.cts +39 -0
- package/dist/options.d.ts +40 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +8 -0
- package/dist/options.mjs +12 -0
- package/dist/resolve/builtins.cjs +73 -0
- package/dist/resolve/builtins.d.cts +68 -0
- package/dist/resolve/builtins.d.ts +69 -0
- package/dist/resolve/builtins.d.ts.map +1 -0
- package/dist/resolve/builtins.js +64 -0
- package/dist/resolve/builtins.mjs +53 -0
- package/dist/resolve/component-globals.cjs +272 -0
- package/dist/resolve/component-globals.d.cts +88 -0
- package/dist/resolve/component-globals.d.ts +89 -0
- package/dist/resolve/component-globals.d.ts.map +1 -0
- package/dist/resolve/component-globals.js +160 -0
- package/dist/resolve/component-globals.mjs +249 -0
- package/dist/resolve/component-resolver.cjs +80 -0
- package/dist/resolve/component-resolver.d.cts +68 -0
- package/dist/resolve/component-resolver.d.ts +69 -0
- package/dist/resolve/component-resolver.d.ts.map +1 -0
- package/dist/resolve/component-resolver.js +95 -0
- package/dist/resolve/component-resolver.mjs +59 -0
- package/dist/resolve/load-directive.cjs +93 -0
- package/dist/resolve/load-directive.d.cts +53 -0
- package/dist/resolve/load-directive.d.ts +54 -0
- package/dist/resolve/load-directive.d.ts.map +1 -0
- package/dist/resolve/load-directive.js +93 -0
- package/dist/resolve/load-directive.mjs +72 -0
- package/dist/resolve/resolve-ir.cjs +108 -0
- package/dist/resolve/resolve-ir.d.cts +53 -0
- package/dist/resolve/resolve-ir.d.ts +54 -0
- package/dist/resolve/resolve-ir.d.ts.map +1 -0
- package/dist/resolve/resolve-ir.js +58 -0
- package/dist/resolve/resolve-ir.mjs +85 -0
- package/dist/resolve/write-globals.cjs +383 -0
- package/dist/resolve/write-globals.d.cts +36 -0
- package/dist/resolve/write-globals.d.ts +37 -0
- package/dist/resolve/write-globals.d.ts.map +1 -0
- package/dist/resolve/write-globals.js +169 -0
- package/dist/resolve/write-globals.mjs +362 -0
- package/dist/server-fns-VZLHMQTC.mjs +79 -0
- package/dist/server-functions-UAZKOLZW.mjs +82 -0
- package/dist/transform/bindings.cjs +71 -0
- package/dist/transform/bindings.d.cts +16 -0
- package/dist/transform/bindings.d.ts +17 -0
- package/dist/transform/bindings.d.ts.map +1 -0
- package/dist/transform/bindings.js +69 -0
- package/dist/transform/bindings.mjs +50 -0
- package/dist/transform/index.cjs +3837 -0
- package/dist/transform/index.d.cts +15 -0
- package/dist/transform/index.d.ts +16 -0
- package/dist/transform/index.d.ts.map +1 -0
- package/dist/transform/index.js +12 -0
- package/dist/transform/index.mjs +4882 -0
- package/dist/transform/intrinsics.cjs +261 -0
- package/dist/transform/intrinsics.d.cts +33 -0
- package/dist/transform/intrinsics.d.ts +34 -0
- package/dist/transform/intrinsics.d.ts.map +1 -0
- package/dist/transform/intrinsics.js +120 -0
- package/dist/transform/intrinsics.mjs +238 -0
- package/dist/transform/props.cjs +441 -0
- package/dist/transform/props.d.cts +45 -0
- package/dist/transform/props.d.ts +46 -0
- package/dist/transform/props.d.ts.map +1 -0
- package/dist/transform/props.js +221 -0
- package/dist/transform/props.mjs +1487 -0
- package/dist/transform/server-fn-id.cjs +35 -0
- package/dist/transform/server-fn-id.d.cts +9 -0
- package/dist/transform/server-fn-id.d.ts +10 -0
- package/dist/transform/server-fn-id.d.ts.map +1 -0
- package/dist/transform/server-fn-id.js +18 -0
- package/dist/transform/server-fn-id.mjs +14 -0
- package/dist/transform/server-fns.cjs +114 -0
- package/dist/transform/server-fns.d.cts +12 -0
- package/dist/transform/server-fns.d.ts +13 -0
- package/dist/transform/server-fns.d.ts.map +1 -0
- package/dist/transform/server-fns.js +94 -0
- package/dist/transform/server-fns.mjs +1160 -0
- package/dist/transform/source-locations.cjs +393 -0
- package/dist/transform/source-locations.d.cts +48 -0
- package/dist/transform/source-locations.d.ts +49 -0
- package/dist/transform/source-locations.d.ts.map +1 -0
- package/dist/transform/source-locations.js +303 -0
- package/dist/transform/source-locations.mjs +370 -0
- package/dist/transform/styles.cjs +606 -0
- package/dist/transform/styles.d.cts +237 -0
- package/dist/transform/styles.d.ts +238 -0
- package/dist/transform/styles.d.ts.map +1 -0
- package/dist/transform/styles.js +743 -0
- package/dist/transform/styles.mjs +671 -0
- package/dist/transform/templates.cjs +3806 -0
- package/dist/transform/templates.d.cts +82 -0
- package/dist/transform/templates.d.ts +83 -0
- package/dist/transform/templates.d.ts.map +1 -0
- package/dist/transform/templates.js +613 -0
- package/dist/transform/templates.mjs +4852 -0
- package/dist/transform-3W45L3EA.mjs +951 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/vite-styles.cjs +223 -0
- package/dist/vite-styles.d.cts +68 -0
- package/dist/vite-styles.d.ts +69 -0
- package/dist/vite-styles.d.ts.map +1 -0
- package/dist/vite-styles.js +241 -0
- package/dist/vite-styles.mjs +214 -0
- package/package.json +141 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lower a parsed Template AST (`@fluixi/template-parser`) into the host-agnostic IR.
|
|
3
|
+
*
|
|
4
|
+
* No parser in here: the `${…}` holes arrive already described as `TemplateHole`,
|
|
5
|
+
* since reading them means reading JavaScript and that belongs to a front-end.
|
|
6
|
+
*
|
|
7
|
+
* Pipeline: pieces → parseTemplate → Template AST → lowerTemplate → IRNode
|
|
8
|
+
*/
|
|
9
|
+
import { type Piece } from '@fluixi/template-parser';
|
|
10
|
+
import type { HolePosition, IRNode } from '../ir/nodes.cjs';
|
|
11
|
+
/**
|
|
12
|
+
* One `${…}` slot, as the front-end describes it. Conclusions about the
|
|
13
|
+
* expression only: never a syntax tree, or the parser dependency creeps back.
|
|
14
|
+
*/
|
|
15
|
+
export interface TemplateHole {
|
|
16
|
+
/** The expression source, verbatim. Anything nested inside it is already compiled. */
|
|
17
|
+
code: string;
|
|
18
|
+
/** Whether it must be wired reactively (see analyze/reactive-expr.ts). */
|
|
19
|
+
reactive: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Set when the hole is a single-expression arrow, `${item => …}`. `each` turns
|
|
22
|
+
* that into `For`'s render function, so it needs the parameters and the body
|
|
23
|
+
* separately rather than the arrow as written.
|
|
24
|
+
*/
|
|
25
|
+
arrow?: {
|
|
26
|
+
params: string[];
|
|
27
|
+
body: string;
|
|
28
|
+
bodyReactive: boolean;
|
|
29
|
+
};
|
|
30
|
+
/** Whether the hole is an object literal: `class=${{…}}` lowers to classList. */
|
|
31
|
+
object?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Where the `${…}` was written. Only kept when the host asked for source locations.
|
|
34
|
+
*
|
|
35
|
+
* A template is emitted as one overwrite, so every hole in it traces back through the
|
|
36
|
+
* source map to the line the template opens at. Without this, every node a template makes
|
|
37
|
+
* reports that one position.
|
|
38
|
+
*/
|
|
39
|
+
at?: HolePosition;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Lower a parsed template to IR. `pieces` are the static runs and hole slots as
|
|
43
|
+
* the parser wants them; `holes` describes each `${…}` in the same order. `used`
|
|
44
|
+
* collects the runtime symbols the emitted code imports.
|
|
45
|
+
*/
|
|
46
|
+
export declare function lowerTemplate(pieces: Piece[], holes: TemplateHole[], used: Set<string>, opts?: {
|
|
47
|
+
svg?: boolean;
|
|
48
|
+
sourceFile?: string;
|
|
49
|
+
positionAt?: (offset: number) => {
|
|
50
|
+
line: number;
|
|
51
|
+
column: number;
|
|
52
|
+
};
|
|
53
|
+
}): IRNode;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lower a parsed Template AST (`@fluixi/template-parser`) into the host-agnostic IR.
|
|
3
|
+
*
|
|
4
|
+
* No parser in here: the `${…}` holes arrive already described as `TemplateHole`,
|
|
5
|
+
* since reading them means reading JavaScript and that belongs to a front-end.
|
|
6
|
+
*
|
|
7
|
+
* Pipeline: pieces → parseTemplate → Template AST → lowerTemplate → IRNode
|
|
8
|
+
*/
|
|
9
|
+
import { type Piece } from '@fluixi/template-parser';
|
|
10
|
+
import type { HolePosition, IRNode } from '../ir/nodes.js';
|
|
11
|
+
/**
|
|
12
|
+
* One `${…}` slot, as the front-end describes it. Conclusions about the
|
|
13
|
+
* expression only: never a syntax tree, or the parser dependency creeps back.
|
|
14
|
+
*/
|
|
15
|
+
export interface TemplateHole {
|
|
16
|
+
/** The expression source, verbatim. Anything nested inside it is already compiled. */
|
|
17
|
+
code: string;
|
|
18
|
+
/** Whether it must be wired reactively (see analyze/reactive-expr.ts). */
|
|
19
|
+
reactive: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Set when the hole is a single-expression arrow, `${item => …}`. `each` turns
|
|
22
|
+
* that into `For`'s render function, so it needs the parameters and the body
|
|
23
|
+
* separately rather than the arrow as written.
|
|
24
|
+
*/
|
|
25
|
+
arrow?: {
|
|
26
|
+
params: string[];
|
|
27
|
+
body: string;
|
|
28
|
+
bodyReactive: boolean;
|
|
29
|
+
};
|
|
30
|
+
/** Whether the hole is an object literal: `class=${{…}}` lowers to classList. */
|
|
31
|
+
object?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Where the `${…}` was written. Only kept when the host asked for source locations.
|
|
34
|
+
*
|
|
35
|
+
* A template is emitted as one overwrite, so every hole in it traces back through the
|
|
36
|
+
* source map to the line the template opens at. Without this, every node a template makes
|
|
37
|
+
* reports that one position.
|
|
38
|
+
*/
|
|
39
|
+
at?: HolePosition;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Lower a parsed template to IR. `pieces` are the static runs and hole slots as
|
|
43
|
+
* the parser wants them; `holes` describes each `${…}` in the same order. `used`
|
|
44
|
+
* collects the runtime symbols the emitted code imports.
|
|
45
|
+
*/
|
|
46
|
+
export declare function lowerTemplate(pieces: Piece[], holes: TemplateHole[], used: Set<string>, opts?: {
|
|
47
|
+
svg?: boolean;
|
|
48
|
+
sourceFile?: string;
|
|
49
|
+
positionAt?: (offset: number) => {
|
|
50
|
+
line: number;
|
|
51
|
+
column: number;
|
|
52
|
+
};
|
|
53
|
+
}): IRNode;
|
|
54
|
+
//# sourceMappingURL=template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/lower/template.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAML,KAAK,KAAK,EAGX,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAsB,MAAM,gBAAgB,CAAC;AAQ/E;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAAC;IAClE,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;OAMG;IACH,EAAE,CAAC,EAAE,YAAY,CAAC;CACnB;AAohBD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,KAAK,EAAE,EACf,KAAK,EAAE,YAAY,EAAE,EACrB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,EACjB,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAO,GACnH,MAAM,CAGR"}
|
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lower a parsed Template AST (`@fluixi/template-parser`) into the host-agnostic IR.
|
|
3
|
+
*
|
|
4
|
+
* No parser in here: the `${…}` holes arrive already described as `TemplateHole`,
|
|
5
|
+
* since reading them means reading JavaScript and that belongs to a front-end.
|
|
6
|
+
*
|
|
7
|
+
* Pipeline: pieces → parseTemplate → Template AST → lowerTemplate → IRNode
|
|
8
|
+
*/
|
|
9
|
+
import { parseTemplate, } from '@fluixi/template-parser';
|
|
10
|
+
import { parseLoadDirective } from '../resolve/load-directive.js';
|
|
11
|
+
import { imperativeBackend, inlineTemplates } from '../codegen/backends/imperative.js';
|
|
12
|
+
import { cleanTemplateText } from './text.js';
|
|
13
|
+
import { decodeEntities } from './entities.js';
|
|
14
|
+
import { paren } from '../analyze/paren.js';
|
|
15
|
+
import { markHole } from '../transform/source-locations.js';
|
|
16
|
+
const DELEGATED = new Set([
|
|
17
|
+
'click', 'dblclick', 'input', 'change', 'submit', 'focus', 'blur',
|
|
18
|
+
'keydown', 'keyup', 'keypress', 'mousedown', 'mouseup',
|
|
19
|
+
]);
|
|
20
|
+
/** Escape a static text run for embedding in a template literal. */
|
|
21
|
+
function escapeTemplate(s) {
|
|
22
|
+
return s.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
23
|
+
}
|
|
24
|
+
/** The tag that carries directives and renders only its content. Never a custom element: no dash. */
|
|
25
|
+
const CONTAINER = 'container';
|
|
26
|
+
function capitalize(s) {
|
|
27
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
28
|
+
}
|
|
29
|
+
class Lowerer {
|
|
30
|
+
holes;
|
|
31
|
+
used;
|
|
32
|
+
sourceFile;
|
|
33
|
+
positionAt;
|
|
34
|
+
constructor(holes, used, sourceFile, positionAt) {
|
|
35
|
+
this.holes = holes;
|
|
36
|
+
this.used = used;
|
|
37
|
+
this.sourceFile = sourceFile;
|
|
38
|
+
this.positionAt = positionAt;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Where a tag is written, for the mark a component node carries.
|
|
42
|
+
*
|
|
43
|
+
* The parser reports offsets, and they are offsets into the file: the pieces it was given
|
|
44
|
+
* carry the positions the front-end recorded. Only built when the host asked for source
|
|
45
|
+
* locations, which is the only time either of these is set.
|
|
46
|
+
*/
|
|
47
|
+
tagAt(node) {
|
|
48
|
+
const start = node.loc?.start;
|
|
49
|
+
if (!this.sourceFile || !this.positionAt || start == null)
|
|
50
|
+
return undefined;
|
|
51
|
+
const { line, column } = this.positionAt(start);
|
|
52
|
+
return { file: this.sourceFile, line, column };
|
|
53
|
+
}
|
|
54
|
+
hole(i) {
|
|
55
|
+
// -1 marks a recovered-from missing binding; emit a harmless undefined.
|
|
56
|
+
return this.holes[i] ?? { code: 'undefined', reactive: false };
|
|
57
|
+
}
|
|
58
|
+
/** Emit a nested IR subtree to code (for `Show` fallback / `For` children). */
|
|
59
|
+
emit(node) {
|
|
60
|
+
const { code, imports, templates } = imperativeBackend.emit(node, {});
|
|
61
|
+
for (const s of imports)
|
|
62
|
+
this.used.add(s);
|
|
63
|
+
// No module scope reachable from a nested subtree, so templates are inlined;
|
|
64
|
+
// `createTemplate` caches by HTML, so this is a lookup rather than a re-parse.
|
|
65
|
+
return inlineTemplates(code, templates);
|
|
66
|
+
}
|
|
67
|
+
// --- content ------------------------------------------------------------
|
|
68
|
+
lowerRoot(children) {
|
|
69
|
+
const nodes = this.lowerChildren(children);
|
|
70
|
+
if (nodes.length === 1)
|
|
71
|
+
return nodes[0];
|
|
72
|
+
return { kind: 'fragment', children: nodes };
|
|
73
|
+
}
|
|
74
|
+
/** Lower a sibling list, handling text-drop and if/else/each restructuring. */
|
|
75
|
+
lowerChildren(children) {
|
|
76
|
+
const out = [];
|
|
77
|
+
for (let i = 0; i < children.length; i++) {
|
|
78
|
+
const child = children[i];
|
|
79
|
+
if (child.kind === 'Element' || child.kind === 'Component') {
|
|
80
|
+
const ifDir = child.attributes.find((a) => a.kind === 'IfDirective');
|
|
81
|
+
if (ifDir && ifDir.kind === 'IfDirective') {
|
|
82
|
+
// Pair with a following `else` sibling (skipping a whitespace-only text).
|
|
83
|
+
let elseIdx = i + 1;
|
|
84
|
+
if (elseIdx < children.length && this.isBlankText(children[elseIdx]))
|
|
85
|
+
elseIdx++;
|
|
86
|
+
const elseNode = children[elseIdx];
|
|
87
|
+
const hasElse = elseNode &&
|
|
88
|
+
(elseNode.kind === 'Element' || elseNode.kind === 'Component') &&
|
|
89
|
+
elseNode.attributes.some((a) => a.kind === 'ElseDirective');
|
|
90
|
+
out.push(this.lowerIf(child, ifDir.hole, hasElse ? elseNode : null));
|
|
91
|
+
if (hasElse)
|
|
92
|
+
i = elseIdx;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const node = this.lowerNode(child);
|
|
97
|
+
if (node)
|
|
98
|
+
out.push(node);
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
isBlankText(n) {
|
|
103
|
+
return n.kind === 'Text' && !n.raw && cleanTemplateText(n.value) === '';
|
|
104
|
+
}
|
|
105
|
+
lowerNode(node) {
|
|
106
|
+
switch (node.kind) {
|
|
107
|
+
case 'Text': {
|
|
108
|
+
if (node.raw)
|
|
109
|
+
return { kind: 'text', value: node.value };
|
|
110
|
+
// Decoded after cleaning, so an entity written as a space is kept as written.
|
|
111
|
+
const cleaned = decodeEntities(cleanTemplateText(node.value));
|
|
112
|
+
return cleaned ? { kind: 'text', value: cleaned } : null;
|
|
113
|
+
}
|
|
114
|
+
case 'Comment':
|
|
115
|
+
return null; // dropped, like JSX comments
|
|
116
|
+
case 'Expression': {
|
|
117
|
+
const h = this.hole(node.hole);
|
|
118
|
+
return { kind: 'expr', code: h.code, reactive: h.reactive, ...(h.at ? { at: h.at } : {}) };
|
|
119
|
+
}
|
|
120
|
+
case 'Fragment':
|
|
121
|
+
return { kind: 'fragment', children: this.lowerChildren(node.children) };
|
|
122
|
+
case 'Element':
|
|
123
|
+
return this.lowerElement(node);
|
|
124
|
+
case 'Component':
|
|
125
|
+
return this.lowerComponent(node);
|
|
126
|
+
default:
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
lowerElement(el) {
|
|
131
|
+
// `<component is=${C} …>…</component>` → `<Dynamic component=${C} …>` (a
|
|
132
|
+
// reactive component swap). Dynamic reads `component` via normalizeAccessor,
|
|
133
|
+
// so it must be an ACCESSOR `() => C` (a bare function would be mis-called).
|
|
134
|
+
const isAttr = el.attributes.find((a) => a.kind === 'Attribute' && a.name === 'is');
|
|
135
|
+
if (el.tag === 'component' && isAttr && isAttr.value && isAttr.value.kind === 'hole') {
|
|
136
|
+
const rest = el.attributes.filter((a) => a !== isAttr);
|
|
137
|
+
const node = this.lowerComponent({ ...el, kind: 'Component', tag: 'Dynamic', tagHole: null, attributes: rest });
|
|
138
|
+
node.props.unshift({ name: 'component', kind: 'attr', expr: `() => ${paren(this.hole(isAttr.value.hole).code, 'arrowBody')}` });
|
|
139
|
+
return node;
|
|
140
|
+
}
|
|
141
|
+
// `each` repeats the element's content, never the element: `<ul each>` is one list.
|
|
142
|
+
const each = this.lowerEach(el);
|
|
143
|
+
const children = each ? [each] : this.lowerChildren(el.children);
|
|
144
|
+
// A `<container>` is only what it holds. It carries the directives and is never rendered.
|
|
145
|
+
if (el.tag === CONTAINER)
|
|
146
|
+
return children.length === 1 ? children[0] : { kind: 'fragment', children };
|
|
147
|
+
const at = this.tagAt(el);
|
|
148
|
+
return {
|
|
149
|
+
kind: 'element',
|
|
150
|
+
tag: el.tag,
|
|
151
|
+
svg: el.namespace === 'svg',
|
|
152
|
+
props: this.lowerAttributes(el.attributes, true),
|
|
153
|
+
children,
|
|
154
|
+
static: false,
|
|
155
|
+
...(at ? { at } : {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
lowerComponent(el) {
|
|
159
|
+
// `<${Comp} />`: the tag is an interpolated expression, use the hole's code
|
|
160
|
+
// as the component identity, so `createComponent(<expr>, …)` is emitted.
|
|
161
|
+
const name = el.tagHole != null ? this.hole(el.tagHole).code : el.tag;
|
|
162
|
+
const props = this.lowerAttributes(el.attributes);
|
|
163
|
+
// Named slots: a child with `slot="header"` becomes `props.header` (a lazy
|
|
164
|
+
// getter, like `children`); the rest stay the default `children`. This is the
|
|
165
|
+
// Solid convention: the component reads `props.header` / `props.children`.
|
|
166
|
+
const { slots, rest } = this.partitionSlots(el.children);
|
|
167
|
+
for (const [slotName, nodes] of slots) {
|
|
168
|
+
const ir = nodes.length === 1 ? nodes[0] : { kind: 'fragment', children: nodes };
|
|
169
|
+
const slotProp = {
|
|
170
|
+
name: slotName,
|
|
171
|
+
kind: 'attr',
|
|
172
|
+
expr: this.emit(ir),
|
|
173
|
+
jsxElement: true,
|
|
174
|
+
};
|
|
175
|
+
// A slot replaces a same-named attribute rather than joining it. `<Card header>`
|
|
176
|
+
// alongside `<span slot="header">` otherwise emits the key twice, valid JS where
|
|
177
|
+
// the last wins, but the first is dead code and esbuild warns on it. Content is
|
|
178
|
+
// the more specific intent, so the slot is what survives.
|
|
179
|
+
const clash = props.findIndex((p) => p.name === slotName);
|
|
180
|
+
if (clash >= 0)
|
|
181
|
+
props[clash] = slotProp;
|
|
182
|
+
else
|
|
183
|
+
props.push(slotProp);
|
|
184
|
+
}
|
|
185
|
+
// `load:` is a directive, not a prop: it says when this component's module is
|
|
186
|
+
// fetched. Parsed with the same helper the JSX path uses so the two cannot drift.
|
|
187
|
+
const loadAttr = el.attributes.find((a) => a.kind === 'LoadDirective');
|
|
188
|
+
const load = loadAttr
|
|
189
|
+
? parseLoadDirective(`load:${loadAttr.strategy}`, loadAttr.modifier)
|
|
190
|
+
: undefined;
|
|
191
|
+
// The parser's positions are relative to the template string, not the file, so the
|
|
192
|
+
// tag itself has none to give. The first hole is the closest thing that does, and for
|
|
193
|
+
// the control-flow components: `<Show when=${…}>`, `<For each=${…}>`, it is the
|
|
194
|
+
// expression driving the subtree anyway.
|
|
195
|
+
// A reactive one: a literal carries a position too now, and it drives nothing.
|
|
196
|
+
const bindAt = props.find((p) => p.at && p.reactive)?.at;
|
|
197
|
+
const at = this.tagAt(el);
|
|
198
|
+
return {
|
|
199
|
+
kind: 'component',
|
|
200
|
+
name,
|
|
201
|
+
props,
|
|
202
|
+
children: this.lowerChildren(rest),
|
|
203
|
+
...(at ? { at } : {}),
|
|
204
|
+
...(bindAt ? { bindAt } : {}),
|
|
205
|
+
...(load ? { load } : {}),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/** Split a component's children into named slots (`slot="x"`) and the rest. */
|
|
209
|
+
partitionSlots(children) {
|
|
210
|
+
const slots = new Map();
|
|
211
|
+
const rest = [];
|
|
212
|
+
for (const child of children) {
|
|
213
|
+
if (child.kind === 'Element' || child.kind === 'Component') {
|
|
214
|
+
const slot = child.attributes.find((a) => a.kind === 'Attribute' && a.name === 'slot');
|
|
215
|
+
if (slot && slot.value && slot.value.kind === 'static') {
|
|
216
|
+
const stripped = { ...child, attributes: child.attributes.filter((a) => a !== slot) };
|
|
217
|
+
const ir = stripped.kind === 'Element' ? this.lowerElement(stripped) : this.lowerComponent(stripped);
|
|
218
|
+
const bucket = slots.get(slot.value.value) ?? [];
|
|
219
|
+
bucket.push(ir);
|
|
220
|
+
slots.set(slot.value.value, bucket);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
rest.push(child);
|
|
225
|
+
}
|
|
226
|
+
return { slots, rest };
|
|
227
|
+
}
|
|
228
|
+
// --- control-flow sugar -------------------------------------------------
|
|
229
|
+
/** `<el if=${cond}>…</el>` (+ optional `<el else>`) → `<Show when fallback>`. */
|
|
230
|
+
lowerIf(el, condHole, elseEl) {
|
|
231
|
+
const cond = this.hole(condHole);
|
|
232
|
+
const props = [
|
|
233
|
+
{ name: 'when', kind: 'attr', expr: cond.code, reactive: cond.reactive, ...(cond.at ? { at: cond.at } : {}) },
|
|
234
|
+
];
|
|
235
|
+
if (elseEl) {
|
|
236
|
+
const elseIR = this.stripAndLower(elseEl, (a) => a.kind === 'ElseDirective');
|
|
237
|
+
props.push({ name: 'fallback', kind: 'attr', expr: this.emit(elseIR), jsxElement: true });
|
|
238
|
+
}
|
|
239
|
+
const child = this.stripAndLower(el, (a) => a.kind === 'IfDirective');
|
|
240
|
+
const at = this.tagAt(el);
|
|
241
|
+
return { kind: 'component', name: 'Show', props, children: [child], ...(at ? { at } : {}), ...(cond.at ? { bindAt: cond.at } : {}) };
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
|
|
245
|
+
* content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
|
|
246
|
+
* it is. Null when the element has no `each`.
|
|
247
|
+
*/
|
|
248
|
+
lowerEach(el) {
|
|
249
|
+
const eachDir = el.attributes.find((a) => a.kind === 'EachDirective');
|
|
250
|
+
if (!eachDir || eachDir.kind !== 'EachDirective')
|
|
251
|
+
return null;
|
|
252
|
+
const each = this.hole(eachDir.hole);
|
|
253
|
+
const props = [{ name: 'each', kind: 'attr', expr: each.code, reactive: each.reactive, ...(each.at ? { at: each.at } : {}) }];
|
|
254
|
+
if (eachDir.key) {
|
|
255
|
+
// `<For by>` wants a KEY FUNCTION, not a string. `key="id"` selects the
|
|
256
|
+
// `id` field: `(item) => item["id"]`; `key=${fn}` passes the function.
|
|
257
|
+
const by = 'static' in eachDir.key
|
|
258
|
+
? `(item) => item[${JSON.stringify(eachDir.key.static)}]`
|
|
259
|
+
: this.hole(eachDir.key.hole).code;
|
|
260
|
+
props.push({ name: 'by', kind: 'attr', expr: by });
|
|
261
|
+
}
|
|
262
|
+
const arrow = this.itemArrow(el);
|
|
263
|
+
let childrenCode;
|
|
264
|
+
if (arrow) {
|
|
265
|
+
// A body that reads signals, `${(it) => it.label + count()}`, stays live: it is handed
|
|
266
|
+
// back as an accessor for the runtime to bind, as it was inside the repeated element.
|
|
267
|
+
const body = arrow.bodyReactive ? `() => ${paren(arrow.body, 'arrowBody')}` : paren(arrow.body, 'arrowBody');
|
|
268
|
+
childrenCode = `(${arrow.params.join(', ')}) => ${body}`;
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
const content = this.lowerChildren(el.children);
|
|
272
|
+
const ir = content.length === 1 ? content[0] : { kind: 'fragment', children: content };
|
|
273
|
+
childrenCode = `() => ${paren(this.emit(ir), 'arrowBody')}`;
|
|
274
|
+
}
|
|
275
|
+
const children = [{ kind: 'expr', code: childrenCode, reactive: false }];
|
|
276
|
+
const at = this.tagAt(el);
|
|
277
|
+
return { kind: 'component', name: 'For', props, children, ...(at ? { at } : {}), ...(each.at ? { bindAt: each.at } : {}) };
|
|
278
|
+
}
|
|
279
|
+
/** The single `${item => expr}` child arrow, if that's the element's content. */
|
|
280
|
+
itemArrow(el) {
|
|
281
|
+
const kids = el.children.filter((c) => !this.isBlankText(c));
|
|
282
|
+
if (kids.length !== 1 || kids[0].kind !== 'Expression')
|
|
283
|
+
return null;
|
|
284
|
+
return this.hole(kids[0].hole).arrow ?? null;
|
|
285
|
+
}
|
|
286
|
+
/** Lower an element to IR, dropping the directive attribute matched by `drop`. */
|
|
287
|
+
stripAndLower(el, drop) {
|
|
288
|
+
const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
|
|
289
|
+
return clone.kind === 'Element' ? this.lowerElement(clone) : this.lowerComponent(clone);
|
|
290
|
+
}
|
|
291
|
+
// --- attributes ---------------------------------------------------------
|
|
292
|
+
/**
|
|
293
|
+
* `element`: a DOM element, where each class:/style: value that moves is its own accessor.
|
|
294
|
+
* A component gets the object as before, rebuilt whole when any value moves.
|
|
295
|
+
*/
|
|
296
|
+
lowerAttributes(attrs, element = false) {
|
|
297
|
+
let anyReactive = false;
|
|
298
|
+
const entry = (name, h) => {
|
|
299
|
+
anyReactive ||= h.reactive;
|
|
300
|
+
if (!element || !h.reactive)
|
|
301
|
+
return `${JSON.stringify(name)}: ${h.code}`;
|
|
302
|
+
const accessor = `() => ${paren(h.code, 'arrowBody')}`;
|
|
303
|
+
// Marked like any other binding hole when positions are on.
|
|
304
|
+
return `${JSON.stringify(name)}: ${h.at ? markHole(accessor, h.at.line, h.at.column) : accessor}`;
|
|
305
|
+
};
|
|
306
|
+
const props = [];
|
|
307
|
+
const classEntries = [];
|
|
308
|
+
let classAt;
|
|
309
|
+
const styleEntries = [];
|
|
310
|
+
let styleAt;
|
|
311
|
+
const usePairs = [];
|
|
312
|
+
for (const a of attrs) {
|
|
313
|
+
switch (a.kind) {
|
|
314
|
+
case 'IfDirective':
|
|
315
|
+
case 'EachDirective':
|
|
316
|
+
case 'ElseDirective':
|
|
317
|
+
break; // handled as control-flow at the parent level
|
|
318
|
+
case 'Attribute':
|
|
319
|
+
props.push(this.plainAttr(a));
|
|
320
|
+
break;
|
|
321
|
+
case 'PropertyBinding':
|
|
322
|
+
props.push({
|
|
323
|
+
name: a.name,
|
|
324
|
+
kind: 'prop',
|
|
325
|
+
expr: this.hole(a.hole).code,
|
|
326
|
+
reactive: this.hole(a.hole).reactive,
|
|
327
|
+
...(this.hole(a.hole).at ? { at: this.hole(a.hole).at } : {}),
|
|
328
|
+
});
|
|
329
|
+
break;
|
|
330
|
+
case 'EventBinding':
|
|
331
|
+
props.push(this.eventProp(a));
|
|
332
|
+
break;
|
|
333
|
+
case 'RefBinding':
|
|
334
|
+
props.push({
|
|
335
|
+
name: 'ref',
|
|
336
|
+
kind: 'ref',
|
|
337
|
+
expr: this.hole(a.hole).code,
|
|
338
|
+
reactive: this.hole(a.hole).reactive,
|
|
339
|
+
// Which variable ends up holding the element, and where it was written.
|
|
340
|
+
...(this.hole(a.hole).at ? { at: this.hole(a.hole).at } : {}),
|
|
341
|
+
});
|
|
342
|
+
break;
|
|
343
|
+
case 'Spread':
|
|
344
|
+
props.push({ name: '', kind: 'spread', expr: this.hole(a.hole).code });
|
|
345
|
+
break;
|
|
346
|
+
case 'ClassDirective': {
|
|
347
|
+
const h = this.hole(a.hole);
|
|
348
|
+
classEntries.push(entry(a.name, h));
|
|
349
|
+
classAt ??= h.at;
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
case 'StyleDirective': {
|
|
353
|
+
const h = this.hole(a.hole);
|
|
354
|
+
styleEntries.push(entry(a.name, h));
|
|
355
|
+
styleAt ??= h.at;
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
case 'BindDirective':
|
|
359
|
+
props.push(...this.bindProps(a.name, this.hole(a.hole)));
|
|
360
|
+
break;
|
|
361
|
+
case 'UseDirective': {
|
|
362
|
+
// `use=${dir}` → [dir]; `use:name` → [name]; `use:name=${opts}` →
|
|
363
|
+
// [name, () => opts]. `name` is an in-scope identifier (like Solid).
|
|
364
|
+
const dir = a.name ?? (a.hole != null ? this.hole(a.hole).code : null);
|
|
365
|
+
if (!dir)
|
|
366
|
+
break;
|
|
367
|
+
// parens so an object literal opts isn't read as a block body
|
|
368
|
+
usePairs.push(a.name != null && a.hole != null ? `[${dir}, () => ${paren(this.hole(a.hole).code, 'arrowBody')}]` : `[${dir}]`);
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// Merge all class:/style: directives into one classList/style prop so they
|
|
374
|
+
// don't collide as duplicate keys in the emitted props object.
|
|
375
|
+
// One prop covers every directive of its kind, so it carries the first hole's
|
|
376
|
+
// position: a real source line beats the stack fallback, which lands in the
|
|
377
|
+
// compiled file.
|
|
378
|
+
if (classEntries.length > 0) {
|
|
379
|
+
props.push({
|
|
380
|
+
name: 'classList',
|
|
381
|
+
kind: 'attr',
|
|
382
|
+
expr: `{ ${classEntries.join(', ')} }`,
|
|
383
|
+
reactive: !element && anyReactive,
|
|
384
|
+
...(classAt ? { at: classAt } : {}),
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
// Each entry that moves is its own accessor, so the runtime updates that key alone.
|
|
388
|
+
if (styleEntries.length > 0) {
|
|
389
|
+
const entries = `{ ${styleEntries.join(', ')} }`;
|
|
390
|
+
// `style=${…}` as well: it goes first, the keys after it, applied in order.
|
|
391
|
+
const base = props.findIndex((p) => p.name === 'style');
|
|
392
|
+
if (base === -1)
|
|
393
|
+
props.push({ name: 'style', kind: 'attr', expr: entries, reactive: !element && anyReactive, ...(styleAt ? { at: styleAt } : {}) });
|
|
394
|
+
else {
|
|
395
|
+
const prop = props[base];
|
|
396
|
+
const value = prop.expr === undefined ? JSON.stringify(prop.literal) : prop.reactive ? `() => ${paren(prop.expr, 'arrowBody')}` : prop.expr;
|
|
397
|
+
props[base] = { name: 'style', kind: 'attr', expr: `[${value}, ${entries}]`, reactive: false, ...(prop.at ? { at: prop.at } : {}) };
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// All use: directives normalize to one `use` prop = an array of [dir, accessor?] pairs.
|
|
401
|
+
if (usePairs.length > 0) {
|
|
402
|
+
props.push({ name: 'use', kind: 'attr', expr: `[${usePairs.join(', ')}]` });
|
|
403
|
+
}
|
|
404
|
+
return props;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Lower an event binding. Plain `@click`/`onClick` (no modifiers) stays a
|
|
408
|
+
* DELEGATED handler (unchanged, byte-identical with JSX). `on:click` or any
|
|
409
|
+
* modifier (`.capture`/`.once`/`.passive`/`.prevent`/`.stop`/`.self`) emits a
|
|
410
|
+
* NATIVE `on:name` prop the runtime attaches via addEventListener.
|
|
411
|
+
*/
|
|
412
|
+
eventProp(a) {
|
|
413
|
+
const dom = a.name.toLowerCase();
|
|
414
|
+
const handler = this.hole(a.hole).code;
|
|
415
|
+
const native = a.syntax === 'colon' || a.modifiers.length > 0;
|
|
416
|
+
if (!native) {
|
|
417
|
+
return {
|
|
418
|
+
name: 'on' + capitalize(a.name),
|
|
419
|
+
kind: 'event',
|
|
420
|
+
event: { name: dom, delegated: DELEGATED.has(dom) },
|
|
421
|
+
expr: handler,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const wrapped = this.wrapHandler(handler, a.modifiers);
|
|
425
|
+
const options = this.eventOptions(a.modifiers);
|
|
426
|
+
const value = options ? `[${wrapped}, ${options}]` : wrapped;
|
|
427
|
+
return { name: 'on:' + dom, kind: 'attr', expr: value };
|
|
428
|
+
}
|
|
429
|
+
/** Wrap a handler for `.prevent`/`.stop`/`.self` (no wrap when none apply). */
|
|
430
|
+
wrapHandler(handler, mods) {
|
|
431
|
+
const guard = mods.includes('self') ? 'if (e.target !== e.currentTarget) return; ' : '';
|
|
432
|
+
const pre = [];
|
|
433
|
+
if (mods.includes('prevent'))
|
|
434
|
+
pre.push('e.preventDefault();');
|
|
435
|
+
if (mods.includes('stop'))
|
|
436
|
+
pre.push('e.stopPropagation();');
|
|
437
|
+
if (!guard && pre.length === 0)
|
|
438
|
+
return handler;
|
|
439
|
+
return `(e) => { ${guard}${pre.join(' ')} return (${handler})(e); }`;
|
|
440
|
+
}
|
|
441
|
+
/** addEventListener options object from `.capture`/`.once`/`.passive`, or null. */
|
|
442
|
+
eventOptions(mods) {
|
|
443
|
+
const opts = [];
|
|
444
|
+
if (mods.includes('capture'))
|
|
445
|
+
opts.push('capture: true');
|
|
446
|
+
if (mods.includes('once'))
|
|
447
|
+
opts.push('once: true');
|
|
448
|
+
if (mods.includes('passive'))
|
|
449
|
+
opts.push('passive: true');
|
|
450
|
+
return opts.length ? `{ ${opts.join(', ')} }` : null;
|
|
451
|
+
}
|
|
452
|
+
/** `bind:value=${sig}` → `value={read()}` + `onInput={e => write(e…value)}`. */
|
|
453
|
+
bindProps(prop, h) {
|
|
454
|
+
const sigCode = h.code;
|
|
455
|
+
const checked = prop === 'checked';
|
|
456
|
+
const evt = checked ? 'change' : 'input';
|
|
457
|
+
const accessor = checked ? 'checked' : 'value';
|
|
458
|
+
// Through `bindPair`, so both spellings of a signal work here: the `[get, set]`
|
|
459
|
+
// pair from `createSignal` and the `{ get, set }` object from `signal`.
|
|
460
|
+
// Parenthesized because the expression may be more than an identifier
|
|
461
|
+
// (`[get, set] as const`, `props.model`, a call).
|
|
462
|
+
this.used.add('bindPair');
|
|
463
|
+
const sig = `bindPair(${sigCode})`;
|
|
464
|
+
return [
|
|
465
|
+
{
|
|
466
|
+
name: prop,
|
|
467
|
+
kind: 'attr',
|
|
468
|
+
expr: `${sig}[0]()`,
|
|
469
|
+
reactive: true,
|
|
470
|
+
directive: `bind:${prop}`,
|
|
471
|
+
...(h.at ? { at: h.at } : {}),
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
name: 'on' + capitalize(evt),
|
|
475
|
+
kind: 'event',
|
|
476
|
+
event: { name: evt, delegated: DELEGATED.has(evt) },
|
|
477
|
+
// delegated handler: `target` is the bound element (currentTarget is the root)
|
|
478
|
+
expr: `(e) => ${sig}[1](e.target.${accessor})`,
|
|
479
|
+
},
|
|
480
|
+
];
|
|
481
|
+
}
|
|
482
|
+
/** A plain attribute (incl. lit `?bool`, mixed values), old `mkProp` behavior. */
|
|
483
|
+
plainAttr(a) {
|
|
484
|
+
const v = a.value;
|
|
485
|
+
// `class=${{...}}` (object literal) -> classList (Solid convention), so it
|
|
486
|
+
// doesn't collide with a static `class="box"` (which stays className), both
|
|
487
|
+
// apply. `class` -> className; `style=${{...}}` stays `style` (runtime merges).
|
|
488
|
+
// `html=${x}` is the raw-HTML escape hatch -> innerHTML.
|
|
489
|
+
let name = a.name;
|
|
490
|
+
if (a.name === 'class') {
|
|
491
|
+
name = v && v.kind === 'hole' && this.hole(v.hole).object ? 'classList' : 'className';
|
|
492
|
+
}
|
|
493
|
+
else if (a.name === 'html') {
|
|
494
|
+
name = 'innerHTML';
|
|
495
|
+
}
|
|
496
|
+
const kind = 'attr';
|
|
497
|
+
const base = { name, kind };
|
|
498
|
+
if (v == null) {
|
|
499
|
+
base.literal = true;
|
|
500
|
+
return base;
|
|
501
|
+
}
|
|
502
|
+
if (v.kind === 'static') {
|
|
503
|
+
base.literal = decodeEntities(v.value);
|
|
504
|
+
return base;
|
|
505
|
+
}
|
|
506
|
+
if (v.kind === 'hole') {
|
|
507
|
+
const h = this.hole(v.hole);
|
|
508
|
+
base.expr = h.code;
|
|
509
|
+
base.reactive = h.reactive;
|
|
510
|
+
if (h.at)
|
|
511
|
+
base.at = h.at;
|
|
512
|
+
return base;
|
|
513
|
+
}
|
|
514
|
+
// mixed: template literal, reactive if any hole is
|
|
515
|
+
let reactive = false;
|
|
516
|
+
let at;
|
|
517
|
+
const body = v.parts
|
|
518
|
+
.map((p) => {
|
|
519
|
+
if ('text' in p)
|
|
520
|
+
return escapeTemplate(decodeEntities(p.text));
|
|
521
|
+
const h = this.hole(p.hole);
|
|
522
|
+
reactive = reactive || h.reactive;
|
|
523
|
+
at ??= h.at;
|
|
524
|
+
return '${' + h.code + '}';
|
|
525
|
+
})
|
|
526
|
+
.join('');
|
|
527
|
+
base.expr = '`' + body + '`';
|
|
528
|
+
base.reactive = reactive;
|
|
529
|
+
if (at)
|
|
530
|
+
base.at = at;
|
|
531
|
+
return base;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Lower a parsed template to IR. `pieces` are the static runs and hole slots as
|
|
536
|
+
* the parser wants them; `holes` describes each `${…}` in the same order. `used`
|
|
537
|
+
* collects the runtime symbols the emitted code imports.
|
|
538
|
+
*/
|
|
539
|
+
export function lowerTemplate(pieces, holes, used, opts = {}) {
|
|
540
|
+
const { root } = parseTemplate(pieces, { svg: opts.svg });
|
|
541
|
+
return new Lowerer(holes, used, opts.sourceFile, opts.positionAt).lowerRoot(root.children);
|
|
542
|
+
}
|