@azerothjs/compiler 0.3.0-alpha.3 → 0.4.0-beta.2
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 +163 -0
- package/dist/codegen.d.ts +45 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/codegen.js +286 -0
- package/dist/codegen.js.map +1 -0
- package/dist/compile.d.ts +47 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +209 -0
- package/dist/compile.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -14
- package/dist/index.js.map +1 -1
- package/dist/parser.d.ts +37 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +318 -0
- package/dist/parser.js.map +1 -0
- package/dist/scanner.d.ts +110 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +443 -0
- package/dist/scanner.js.map +1 -0
- package/dist/sourcemap.d.ts +64 -0
- package/dist/sourcemap.d.ts.map +1 -0
- package/dist/sourcemap.js +109 -0
- package/dist/sourcemap.js.map +1 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/vite.d.ts +33 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +65 -0
- package/dist/vite.js.map +1 -0
- package/package.json +32 -9
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# @azerothjs/compiler
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `.azeroth` single-file-component compiler. A `.azeroth` file is a JavaScript
|
|
6
|
+
or TypeScript module written with AzerothJS markup, a JSX-style syntax. The
|
|
7
|
+
compiler locates the markup regions inside otherwise ordinary code and rewrites
|
|
8
|
+
them into `h()` hyperscript calls with fine-grained reactive bindings, leaving
|
|
9
|
+
the rest of the module byte-for-byte. For example:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
<h1>Count: {count()}</h1>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
compiles to:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
h('h1', {}, 'Count: ', () => (count()))
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
It has no runtime dependencies and is used both as a build-time tool (the Vite
|
|
22
|
+
plugin) and as the shared language core that the editor tooling reuses.
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
The compiler does not have a type system, symbol table, or semantic analyzer. It
|
|
27
|
+
treats a `.azeroth` file as code with embedded markup and only transforms the
|
|
28
|
+
markup, which is why the rest of the module passes through unchanged. The
|
|
29
|
+
pipeline, in the order it runs:
|
|
30
|
+
|
|
31
|
+
1. Scanner: finds markup regions inside arbitrary JavaScript, correctly skipping
|
|
32
|
+
strings, template literals, comments, and regular expressions, and detecting
|
|
33
|
+
whether a `<` begins markup or is a comparison or generic.
|
|
34
|
+
2. Parser: turns a markup region into an AST (`parseMarkup`).
|
|
35
|
+
3. Codegen: turns the AST into `h()` and component-call source, adding the
|
|
36
|
+
reactive wrapping (`() => (...)`) around dynamic expressions.
|
|
37
|
+
4. Compile: orchestrates scan, parse, codegen, and splice back into the module,
|
|
38
|
+
producing the output plus a source map.
|
|
39
|
+
|
|
40
|
+
The scanner and parser are exported because the editor tooling
|
|
41
|
+
(`@azerothjs/language-service`) reuses them as its single source of truth for
|
|
42
|
+
markup behavior, rather than re-implementing a second parser that could drift.
|
|
43
|
+
|
|
44
|
+
## Components
|
|
45
|
+
|
|
46
|
+
| File | Role |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `scanner.ts` | Markup-region detection and the low-level skip helpers. |
|
|
49
|
+
| `parser.ts` | `parseMarkup` and `CompileError`. |
|
|
50
|
+
| `codegen.ts` | `generate`, `walkComponentTags`, the `ExpressionCompiler` hook. |
|
|
51
|
+
| `compile.ts` | `compile`: the end-to-end entry point and `CompileResult`. |
|
|
52
|
+
| `sourcemap.ts` | Source-map construction (VLQ encoding, mappings). |
|
|
53
|
+
| `vite.ts` | `azeroth`: the Vite plugin and its options. |
|
|
54
|
+
| `types.ts` | The markup AST node types and `Span`. |
|
|
55
|
+
|
|
56
|
+
### Public API
|
|
57
|
+
|
|
58
|
+
- `compile(source, filename?)` returns the compiled module and its source map.
|
|
59
|
+
- `parseMarkup(source, start)` parses one markup region to an AST node.
|
|
60
|
+
- `generate(node, compileExpression)` emits source for a markup AST node.
|
|
61
|
+
- `walkComponentTags(...)` visits component tags in a region.
|
|
62
|
+
- The scanner helpers (`findMarkupStart`, `skipString`, `skipTemplate`,
|
|
63
|
+
`skipBalanced`, `skipRegex`, `isIdentStart`, `isIdentPart`, `isWhitespace`,
|
|
64
|
+
and the comment skippers) are exported for tooling that needs the same
|
|
65
|
+
scanning behavior.
|
|
66
|
+
- `azeroth(options?)` is the Vite plugin.
|
|
67
|
+
|
|
68
|
+
## Building
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
npm run build -w @azerothjs/compiler
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Several other packages (the language service and server, and anything importing
|
|
75
|
+
the AST or scanner) depend on the compiler being built first; the repository root
|
|
76
|
+
`npm run build` builds in dependency order.
|
|
77
|
+
|
|
78
|
+
## Testing
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
npx vitest run test/compiler
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Configuration
|
|
85
|
+
|
|
86
|
+
The Vite plugin (`azeroth`) compiles `.azeroth` files as part of a Vite build.
|
|
87
|
+
Add it to a project's `vite.config`:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { azeroth } from '@azerothjs/compiler';
|
|
91
|
+
|
|
92
|
+
export default {
|
|
93
|
+
plugins: [azeroth()]
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Authoring idiom and reactivity rules
|
|
98
|
+
|
|
99
|
+
A component is a plain function that returns markup; there is no
|
|
100
|
+
`defineComponent` wrapper. Props are the function's first argument, read where
|
|
101
|
+
they are used so they stay reactive:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
function Greeting(props: { name: string })
|
|
105
|
+
{
|
|
106
|
+
return <p>Hello {props.name}</p>;
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
How the compiler emits each dynamic `{expr}` decides whether it is reactive:
|
|
111
|
+
|
|
112
|
+
- An event handler (`onClick={…}`), a function literal, a bare reference
|
|
113
|
+
(`draft`, `props.x`), or an array/object literal is passed through verbatim.
|
|
114
|
+
- Any other expression is wrapped in a getter, `() => (expr)`, so the runtime
|
|
115
|
+
re-applies it when the signals it reads change. `value={draft()}` becomes
|
|
116
|
+
`value: () => (draft())`.
|
|
117
|
+
|
|
118
|
+
`classList()` and `styleMap()` (from `@azerothjs/core`) return a getter, so a
|
|
119
|
+
`class={classList({ active: isActive })}` ends up wrapped as a
|
|
120
|
+
getter-returning-a-getter. The renderer resolves a reactive value by calling
|
|
121
|
+
through while it is still a function, so this works without special-casing in
|
|
122
|
+
the compiler - see `DECISIONS.md` (entry 1) for why the fix lives in the
|
|
123
|
+
renderer rather than here.
|
|
124
|
+
|
|
125
|
+
An attribute-less, child-less component tag compiles to a zero-argument call,
|
|
126
|
+
`<Comp/>` -> `Comp()`, so a prop-less component never has to declare an unused
|
|
127
|
+
props parameter (`DECISIONS.md`, entry 2).
|
|
128
|
+
|
|
129
|
+
## Type checking
|
|
130
|
+
|
|
131
|
+
The compiler only transforms markup; it does not type-check. `tsc` cannot parse
|
|
132
|
+
`.azeroth`, so use `azeroth-tsc` (from `@azerothjs/language-server`) to gate a
|
|
133
|
+
build, the same way `vue-tsc` does for Vue:
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
npx azeroth-tsc # check every .azeroth file under the cwd
|
|
137
|
+
npx azeroth-tsc -p tsconfig.json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
It compiles each file to its virtual TypeScript module, type-checks it against
|
|
141
|
+
the project's tsconfig, and prints `tsc`-style diagnostics mapped back to the
|
|
142
|
+
original `.azeroth` positions, exiting non-zero on the first error. In the
|
|
143
|
+
editor, `@azerothjs/language-service` provides the same analysis live.
|
|
144
|
+
|
|
145
|
+
## Examples
|
|
146
|
+
|
|
147
|
+
`examples/Showcase.azeroth` is a single comprehensive `.azeroth` file (a function
|
|
148
|
+
component and a class component) used to exercise both the compiler and the
|
|
149
|
+
editor tooling. Compile a string directly:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { compile } from '@azerothjs/compiler';
|
|
153
|
+
|
|
154
|
+
const { code, map } = compile('export default () => <h1>Hi {name()}</h1>;', 'App.azeroth');
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Contributing
|
|
158
|
+
|
|
159
|
+
The scanner is the trickiest part: it must distinguish markup from comparison and
|
|
160
|
+
generic syntax and skip every JavaScript token kind, because a mistake there
|
|
161
|
+
mis-compiles otherwise valid code. Keep its behavior covered by tests under
|
|
162
|
+
`test/compiler`, and remember that the language service depends on the exported
|
|
163
|
+
scanner and parser behaving exactly as the compiler uses them.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { MarkupElement, MarkupFragment } from './types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Recompiles arbitrary JS that may contain nested markup.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* // The identity compiler leaves plain expressions untouched.
|
|
8
|
+
* const identity: ExpressionCompiler = (code) => code;
|
|
9
|
+
* identity('count()'); // 'count()'
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export type ExpressionCompiler = (code: string) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Generates code for a markup element or fragment.
|
|
15
|
+
*
|
|
16
|
+
* @param node - The element/fragment AST node
|
|
17
|
+
* @param compileExpression - Recompiles nested-markup-bearing JS
|
|
18
|
+
*
|
|
19
|
+
* @returns A source string: `h(...)`, `Component({...})`, or for a fragment,
|
|
20
|
+
* an array `[...]`.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const id = (code: string) => code;
|
|
25
|
+
* const { node } = parseMarkup('<h1>Hi</h1>', 0);
|
|
26
|
+
* generate(node, id); // "h('h1', { }, 'Hi')"
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function generate(node: MarkupElement | MarkupFragment, compileExpression: ExpressionCompiler): string;
|
|
30
|
+
/**
|
|
31
|
+
* Visits the markup element tree (not expression holes) and calls `visit`
|
|
32
|
+
* with the tag of every component (capitalised/dotted) element. Used by
|
|
33
|
+
* compile() to auto-import the built-in components a file references. Holes
|
|
34
|
+
* are handled by the caller's recursion.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const { node } = parseMarkup('<Show><p>hi</p></Show>', 0);
|
|
39
|
+
* const tags: string[] = [];
|
|
40
|
+
* walkComponentTags(node, (tag) => tags.push(tag));
|
|
41
|
+
* tags; // ['Show'] (the lowercase <p> host element is skipped)
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function walkComponentTags(node: MarkupElement | MarkupFragment, visit: (tag: string) => void): void;
|
|
45
|
+
//# sourceMappingURL=codegen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../src/codegen.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACR,aAAa,EACb,cAAc,EAGjB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;GASG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;AA2P1D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,cAAc,EAAE,iBAAiB,EAAE,kBAAkB,GAAG,MAAM,CA4B5G;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,cAAc,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAa1G"}
|
package/dist/codegen.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// Turns a markup AST node into an h() (or component-call) source string. The
|
|
2
|
+
// reactivity rule for how each expression is emitted:
|
|
3
|
+
//
|
|
4
|
+
// - Event handlers (on*): passed through verbatim.
|
|
5
|
+
// - Function / bare-identifier expressions: passed through verbatim - they
|
|
6
|
+
// are already a getter, handler, or component reference.
|
|
7
|
+
// - Any other dynamic expression: wrapped as `() => (expr)` so h() treats it
|
|
8
|
+
// as reactive.
|
|
9
|
+
// - Static "string" attributes: kept as string literals.
|
|
10
|
+
//
|
|
11
|
+
// Expression holes can contain nested markup; we recompile their source via
|
|
12
|
+
// the `compileExpression` callback the caller passes in (which loops back to
|
|
13
|
+
// compile.ts), so `{cond && <p/>}` works.
|
|
14
|
+
/**
|
|
15
|
+
* True when `code` is an arrow/function literal - pass it through unwrapped.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* isFunctionLiteral('(item) => item.name'); // true
|
|
20
|
+
* isFunctionLiteral('count() + 1'); // false
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
function isFunctionLiteral(code) {
|
|
24
|
+
const t = code.trim();
|
|
25
|
+
if (/^async\s+function\b/.test(t) || /^function\b/.test(t)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
// Arrow: `x => ...`, `(...) => ...`, `async x => ...`, `async (...) => ...`.
|
|
29
|
+
return /^(async\s+)?(\([^]*?\)|[A-Za-z_$][\w$]*)\s*=>/.test(t);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* True when `code` is a single bare identifier or dotted path (e.g. `draft`, `props.x`).
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* isBareReference('props.value'); // true
|
|
37
|
+
* isBareReference('a + b'); // false
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
function isBareReference(code) {
|
|
41
|
+
return /^[A-Za-z_$][\w$]*(\s*\.\s*[A-Za-z_$][\w$]*)*$/.test(code.trim());
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* True when `code` is an array or object literal (`[...]` / `{...}`).
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* isCollectionLiteral('[resource]'); // true
|
|
49
|
+
* isCollectionLiteral('count()'); // false
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
function isCollectionLiteral(code) {
|
|
53
|
+
const t = code.trim();
|
|
54
|
+
return t.startsWith('[') || t.startsWith('{');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Decides how a dynamic `{expr}` is emitted. Passed through verbatim when it's
|
|
58
|
+
* already the right shape for the runtime:
|
|
59
|
+
* - an event handler,
|
|
60
|
+
* - a function literal (handler / render fn / key fn),
|
|
61
|
+
* - a bare reference (a signal getter, a component, props.x),
|
|
62
|
+
* - an array/object literal (e.g. `on={[res]}`, a props bag).
|
|
63
|
+
* Everything else is a computed value, wrapped in a getter so h() (and
|
|
64
|
+
* reactive props like `when`/`each`) stay fine-grained.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* wrapDynamic('a + b', false); // '() => (a + b)' (computed -> wrapped)
|
|
69
|
+
* wrapDynamic('count', false); // 'count' (bare reference -> verbatim)
|
|
70
|
+
* wrapDynamic('save()', true); // 'save()' (event handler -> verbatim)
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
function wrapDynamic(code, isEventHandler) {
|
|
74
|
+
const compiled = code.trim();
|
|
75
|
+
if (isEventHandler ||
|
|
76
|
+
isFunctionLiteral(compiled) ||
|
|
77
|
+
isBareReference(compiled) ||
|
|
78
|
+
isCollectionLiteral(compiled)) {
|
|
79
|
+
return compiled;
|
|
80
|
+
}
|
|
81
|
+
return `() => (${compiled})`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Quotes an object key when it isn't a valid bare identifier.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* objectKey('class'); // 'class' (bare identifier, unquoted)
|
|
89
|
+
* objectKey('data-id'); // "'data-id'" (hyphen -> quoted)
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
function objectKey(name) {
|
|
93
|
+
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : `'${name}'`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* True for an `on*` event attribute name (`on` + uppercase letter).
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* isEventName('onClick'); // true
|
|
101
|
+
* isEventName('online'); // false (third char is lowercase)
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
function isEventName(name) {
|
|
105
|
+
return name.length > 2 && name.startsWith('on') && name[2] === name[2].toUpperCase();
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Escapes a literal string for emission inside single quotes.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* quoteString('a\'b'); // "'a\\'b'" (the inner quote is escaped)
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
function quoteString(value) {
|
|
116
|
+
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, '\\\'').replace(/\n/g, '\\n')}'`;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Emits one attribute as a `key: value` entry (or a spread).
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* const id = (code: string) => code;
|
|
124
|
+
* generateAttribute(
|
|
125
|
+
* { kind: 'attribute', name: 'id', value: { kind: 'static', value: 'app' }, spread: false },
|
|
126
|
+
* id
|
|
127
|
+
* );
|
|
128
|
+
* // "id: 'app'"
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
function generateAttribute(attr, compileExpression) {
|
|
132
|
+
if (attr.spread) {
|
|
133
|
+
return `...${compileExpression(attr.value.code)}`;
|
|
134
|
+
}
|
|
135
|
+
const name = attr.name;
|
|
136
|
+
const key = objectKey(name);
|
|
137
|
+
if (attr.value.kind === 'none') {
|
|
138
|
+
return `${key}: true`;
|
|
139
|
+
}
|
|
140
|
+
if (attr.value.kind === 'static') {
|
|
141
|
+
return `${key}: ${quoteString(attr.value.value)}`;
|
|
142
|
+
}
|
|
143
|
+
// Expression value.
|
|
144
|
+
const compiled = compileExpression(attr.value.code);
|
|
145
|
+
return `${key}: ${wrapDynamic(compiled, isEventName(name))}`;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Builds the props object literal for an element/component.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* const id = (code: string) => code;
|
|
153
|
+
* generateProps(
|
|
154
|
+
* [{ kind: 'attribute', name: 'id', value: { kind: 'static', value: 'app' }, spread: false }],
|
|
155
|
+
* id
|
|
156
|
+
* );
|
|
157
|
+
* // "{ id: 'app' }"
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
function generateProps(attrs, compileExpression, extra) {
|
|
161
|
+
const parts = attrs.map(a => generateAttribute(a, compileExpression));
|
|
162
|
+
if (extra) {
|
|
163
|
+
parts.push(extra);
|
|
164
|
+
}
|
|
165
|
+
return `{ ${parts.join(', ')} }`;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Emits a single child as an argument to h() / an array element.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* const id = (code: string) => code;
|
|
173
|
+
* generateChild({ kind: 'text', value: 'Hi', start: 0, end: 2 }, id); // "'Hi'"
|
|
174
|
+
* generateChild({ kind: 'expression', code: 'a + b', start: 0, end: 5 }, id); // '() => (a + b)'
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
function generateChild(child, compileExpression) {
|
|
178
|
+
if (child.kind === 'text') {
|
|
179
|
+
return quoteString(child.value);
|
|
180
|
+
}
|
|
181
|
+
if (child.kind === 'expression') {
|
|
182
|
+
const compiled = compileExpression(child.code);
|
|
183
|
+
return wrapDynamic(compiled, false);
|
|
184
|
+
}
|
|
185
|
+
return generate(child, compileExpression);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Generates the `children` entry for a component from its markup children. A
|
|
189
|
+
* lone function-hole child becomes the function itself (e.g.
|
|
190
|
+
* `<For>{(item) => ...}</For>`); anything else becomes a thunk returning the
|
|
191
|
+
* child (or an array of children).
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* const id = (code: string) => code;
|
|
196
|
+
* // A lone function hole becomes the render function itself.
|
|
197
|
+
* generateComponentChildren([{ kind: 'expression', code: '(x) => x', start: 0, end: 8 }], id);
|
|
198
|
+
* // 'children: (x) => x'
|
|
199
|
+
* // Plain text becomes a thunk.
|
|
200
|
+
* generateComponentChildren([{ kind: 'text', value: 'Hi', start: 0, end: 2 }], id);
|
|
201
|
+
* // "children: () => 'Hi'"
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
function generateComponentChildren(children, compileExpression) {
|
|
205
|
+
if (children.length === 0) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
if (children.length === 1) {
|
|
209
|
+
const only = children[0];
|
|
210
|
+
if (only.kind === 'expression') {
|
|
211
|
+
const compiled = compileExpression(only.code).trim();
|
|
212
|
+
// A function child IS the render function (For/Match/etc.).
|
|
213
|
+
if (isFunctionLiteral(compiled)) {
|
|
214
|
+
return `children: ${compiled}`;
|
|
215
|
+
}
|
|
216
|
+
return `children: () => (${compiled})`;
|
|
217
|
+
}
|
|
218
|
+
return `children: () => ${generateChild(only, compileExpression)}`;
|
|
219
|
+
}
|
|
220
|
+
const items = children.map(c => generateChild(c, compileExpression));
|
|
221
|
+
return `children: () => [${items.join(', ')}]`;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Generates code for a markup element or fragment.
|
|
225
|
+
*
|
|
226
|
+
* @param node - The element/fragment AST node
|
|
227
|
+
* @param compileExpression - Recompiles nested-markup-bearing JS
|
|
228
|
+
*
|
|
229
|
+
* @returns A source string: `h(...)`, `Component({...})`, or for a fragment,
|
|
230
|
+
* an array `[...]`.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```ts
|
|
234
|
+
* const id = (code: string) => code;
|
|
235
|
+
* const { node } = parseMarkup('<h1>Hi</h1>', 0);
|
|
236
|
+
* generate(node, id); // "h('h1', { }, 'Hi')"
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
export function generate(node, compileExpression) {
|
|
240
|
+
if (node.kind === 'fragment') {
|
|
241
|
+
const items = node.children.map(c => generateChild(c, compileExpression));
|
|
242
|
+
return `[${items.join(', ')}]`;
|
|
243
|
+
}
|
|
244
|
+
if (node.isComponent) {
|
|
245
|
+
const childrenEntry = generateComponentChildren(node.children, compileExpression);
|
|
246
|
+
// No attributes and no children -> a zero-argument call. Emitting
|
|
247
|
+
// `Comp({ })` instead would force every prop-less component to declare
|
|
248
|
+
// a props parameter (and the language service would flag the call as
|
|
249
|
+
// "Expected 0 arguments, but got 1").
|
|
250
|
+
if (node.attributes.length === 0 && childrenEntry === null) {
|
|
251
|
+
return `${node.tag}()`;
|
|
252
|
+
}
|
|
253
|
+
const props = generateProps(node.attributes, compileExpression, childrenEntry ?? undefined);
|
|
254
|
+
return `${node.tag}(${props})`;
|
|
255
|
+
}
|
|
256
|
+
// Host element -> h('tag', props, ...children).
|
|
257
|
+
const props = generateProps(node.attributes, compileExpression);
|
|
258
|
+
const children = node.children.map(c => generateChild(c, compileExpression));
|
|
259
|
+
const args = [`'${node.tag}'`, props, ...children];
|
|
260
|
+
return `h(${args.join(', ')})`;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Visits the markup element tree (not expression holes) and calls `visit`
|
|
264
|
+
* with the tag of every component (capitalised/dotted) element. Used by
|
|
265
|
+
* compile() to auto-import the built-in components a file references. Holes
|
|
266
|
+
* are handled by the caller's recursion.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* const { node } = parseMarkup('<Show><p>hi</p></Show>', 0);
|
|
271
|
+
* const tags: string[] = [];
|
|
272
|
+
* walkComponentTags(node, (tag) => tags.push(tag));
|
|
273
|
+
* tags; // ['Show'] (the lowercase <p> host element is skipped)
|
|
274
|
+
* ```
|
|
275
|
+
*/
|
|
276
|
+
export function walkComponentTags(node, visit) {
|
|
277
|
+
if (node.kind === 'element' && node.isComponent) {
|
|
278
|
+
visit(node.tag);
|
|
279
|
+
}
|
|
280
|
+
for (const child of node.children) {
|
|
281
|
+
if (child.kind === 'element' || child.kind === 'fragment') {
|
|
282
|
+
walkComponentTags(child, visit);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=codegen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codegen.js","sourceRoot":"","sources":["../src/codegen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type SourceMapV3 } from './sourcemap.ts';
|
|
2
|
+
/** Result of compiling a `.azeroth` source string. */
|
|
3
|
+
export interface CompileResult {
|
|
4
|
+
/** The compiled JS/TS source. */
|
|
5
|
+
code: string;
|
|
6
|
+
/** Source map (original `.azeroth` -> compiled), or `null` when the file
|
|
7
|
+
* contained no markup (output is identical to input). */
|
|
8
|
+
map: SourceMapV3 | null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Compiles a `.azeroth` source string: markup -> h() calls, with the `h`
|
|
12
|
+
* import auto-injected when needed.
|
|
13
|
+
*
|
|
14
|
+
* @param source - The `.azeroth` module source
|
|
15
|
+
*
|
|
16
|
+
* @returns The compiled JS/TS and its source map
|
|
17
|
+
*
|
|
18
|
+
* Without compile: author UI as nested h() calls by hand, wrapping every
|
|
19
|
+
* dynamic hole in a getter yourself:
|
|
20
|
+
*
|
|
21
|
+
* import { h } from '@azerothjs/core';
|
|
22
|
+
* export default () =>
|
|
23
|
+
* h('h1', { }, 'Hello ', () => (name())); // hand-write h() + getters, easy to get wrong
|
|
24
|
+
*
|
|
25
|
+
* With compile: write the markup in a `.azeroth` file and compile() emits the
|
|
26
|
+
* equivalent h() calls (and the `h` import) for you:
|
|
27
|
+
*
|
|
28
|
+
* const { code } = compile('export default () => <h1>Hello {name()}</h1>;');
|
|
29
|
+
* // code -> import { h } from '@azerothjs/core'; ... h('h1', { }, 'Hello ', () => (name()))
|
|
30
|
+
* // write JSX-like markup; the getters and the import are generated
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const { code } = compile(`
|
|
35
|
+
* export default function Hi() {
|
|
36
|
+
* return <h1>Hello {name()}</h1>;
|
|
37
|
+
* }
|
|
38
|
+
* `);
|
|
39
|
+
* // code becomes:
|
|
40
|
+
* // import { h } from '@azerothjs/core';
|
|
41
|
+
* // export default function Hi() {
|
|
42
|
+
* // return h('h1', { }, 'Hello ', () => (name()));
|
|
43
|
+
* // }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function compile(source: string, filename?: string): CompileResult;
|
|
47
|
+
//# sourceMappingURL=compile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":"AAiBA,OAAO,EAIH,KAAK,WAAW,EAEnB,MAAM,gBAAgB,CAAC;AAexB,sDAAsD;AACtD,MAAM,WAAW,aAAa;IAE1B,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IAEb;8DAC0D;IAC1D,GAAG,EAAE,WAAW,GAAG,IAAI,CAAC;CAC3B;AA8BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,SAAmB,GAAG,aAAa,CAmFlF"}
|