@aihu/compiler 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -8
- package/bin/aihu-compile +0 -0
- package/dist/index.d.ts +63 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -7
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ npm install @aihu/compiler
|
|
|
21
21
|
bun add @aihu/compiler
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
<sub><i>Auto-generated against `@aihu/compiler@0.
|
|
24
|
+
<sub><i>Auto-generated against `@aihu/compiler@0.6.0`.</i></sub>
|
|
25
25
|
|
|
26
26
|
<!-- END_AUTOGEN: install -->
|
|
27
27
|
|
|
@@ -32,12 +32,12 @@ bun add @aihu/compiler
|
|
|
32
32
|
|
|
33
33
|
| | |
|
|
34
34
|
|---|---|
|
|
35
|
-
| **Version** | `0.
|
|
35
|
+
| **Version** | `0.6.0` |
|
|
36
36
|
| **Tier** | D — Compiler — Single-File Component (.aihu) → Web Component |
|
|
37
37
|
| **Published files** | 4 entries |
|
|
38
38
|
| **License** | MIT |
|
|
39
39
|
|
|
40
|
-
<sub><i>Auto-generated against `@aihu/compiler@0.
|
|
40
|
+
<sub><i>Auto-generated against `@aihu/compiler@0.6.0`.</i></sub>
|
|
41
41
|
|
|
42
42
|
<!-- END_AUTOGEN: stats -->
|
|
43
43
|
|
|
@@ -50,7 +50,7 @@ bun add @aihu/compiler
|
|
|
50
50
|
|---|---|---|
|
|
51
51
|
| `.` | `./dist/index.js` | `—` |
|
|
52
52
|
|
|
53
|
-
<sub><i>Auto-generated against `@aihu/compiler@0.
|
|
53
|
+
<sub><i>Auto-generated against `@aihu/compiler@0.6.0`.</i></sub>
|
|
54
54
|
|
|
55
55
|
<!-- END_AUTOGEN: exports -->
|
|
56
56
|
|
|
@@ -62,9 +62,8 @@ bun add @aihu/compiler
|
|
|
62
62
|
**Peer dependencies:**
|
|
63
63
|
|
|
64
64
|
- `vite` — `>=5.0.0`
|
|
65
|
-
- `@aihu/css-engine` — `>=0.2.4`
|
|
66
65
|
|
|
67
|
-
<sub><i>Auto-generated against `@aihu/compiler@0.
|
|
66
|
+
<sub><i>Auto-generated against `@aihu/compiler@0.6.0`.</i></sub>
|
|
68
67
|
|
|
69
68
|
<!-- END_AUTOGEN: deps -->
|
|
70
69
|
|
|
@@ -78,7 +77,7 @@ bun add @aihu/compiler
|
|
|
78
77
|
- [Macro Vocabulary spec](../../docs/superpowers/specs/2026-05-02-spec-macro-vocabulary.md)
|
|
79
78
|
- [Aihu framework root](../../README.md)
|
|
80
79
|
|
|
81
|
-
<sub><i>Auto-generated against `@aihu/compiler@0.
|
|
80
|
+
<sub><i>Auto-generated against `@aihu/compiler@0.6.0`.</i></sub>
|
|
82
81
|
|
|
83
82
|
<!-- END_AUTOGEN: see-also -->
|
|
84
83
|
|
|
@@ -89,6 +88,6 @@ bun add @aihu/compiler
|
|
|
89
88
|
|
|
90
89
|
MIT — see [LICENSE](../../LICENSE).
|
|
91
90
|
|
|
92
|
-
<sub><i>Auto-generated against `@aihu/compiler@0.
|
|
91
|
+
<sub><i>Auto-generated against `@aihu/compiler@0.6.0`.</i></sub>
|
|
93
92
|
|
|
94
93
|
<!-- END_AUTOGEN: license -->
|
package/bin/aihu-compile
CHANGED
|
Binary file
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
interface VitePlugin {
|
|
3
3
|
readonly name: string;
|
|
4
4
|
enforce?: 'pre' | 'post';
|
|
5
|
+
resolveId?: (source: string, importer?: string) => string | null | undefined | Promise<string | null | undefined>;
|
|
6
|
+
load?: (id: string) => string | null | undefined | Promise<string | null | undefined>;
|
|
5
7
|
transform?: (code: string, id: string) => Promise<{
|
|
6
8
|
code: string;
|
|
7
9
|
map: null;
|
|
@@ -42,6 +44,15 @@ interface AihuCompilerPluginOptions {
|
|
|
42
44
|
* `defineElement(tag, Ctor, { shadowMode: '...' })`.
|
|
43
45
|
*/
|
|
44
46
|
shadowMode?: 'open' | 'closed' | 'none';
|
|
47
|
+
/**
|
|
48
|
+
* Build target threaded to the compiler binary (`--target`). Defaults to the
|
|
49
|
+
* compiler's `universal` target (current behaviour). Set to `'client'` for a
|
|
50
|
+
* browser bundle that must NOT ship the server `__agentBinding` (policy) and
|
|
51
|
+
* instead gets the policy-free `@agent` opaque-ID dispatcher + the per-instance
|
|
52
|
+
* `_registerAgentDispatcher` wiring the capability bridge reads after mount.
|
|
53
|
+
* See `examples/agent-driven-demo`.
|
|
54
|
+
*/
|
|
55
|
+
target?: 'client' | 'server' | 'universal';
|
|
45
56
|
}
|
|
46
57
|
/**
|
|
47
58
|
* Inject `{ shadowMode: '...' }` as the third argument to the emitted
|
|
@@ -132,6 +143,7 @@ declare function _buildStaticIsland(compiledCode: string, elementTag: string): s
|
|
|
132
143
|
*/
|
|
133
144
|
declare function transform(source: string, id: string, options?: {
|
|
134
145
|
sidecarOut?: string;
|
|
146
|
+
target?: 'client' | 'server' | 'universal';
|
|
135
147
|
}): {
|
|
136
148
|
code: string;
|
|
137
149
|
map: null;
|
|
@@ -181,6 +193,56 @@ declare function transform(source: string, id: string, options?: {
|
|
|
181
193
|
* @internal
|
|
182
194
|
*/
|
|
183
195
|
declare function _foldCssEngineStyles(compiledCode: string, css: string): string;
|
|
196
|
+
/**
|
|
197
|
+
* Virtual-module prefix used by the `shadowMode === 'none'` branch to route
|
|
198
|
+
* per-SFC utility CSS through Vite's built-in CSS pipeline. The plugin
|
|
199
|
+
* (`aihuCompilerPlugin`) implements `resolveId` + `load` for ids matching
|
|
200
|
+
* `VIRTUAL_UTILITY_PREFIX + '<hash>.css'`, returning the stored CSS body so
|
|
201
|
+
* Vite hoists it into the bundle CSS asset (`dist/assets/*.css`) — NOT into
|
|
202
|
+
* `host.adoptedStyleSheets`, which is a no-op when there is no shadow root.
|
|
203
|
+
*
|
|
204
|
+
* The trailing `.css` extension is mandatory: Vite's built-in CSS plugin keys
|
|
205
|
+
* off the extension to know it should run the CSS pipeline on the module.
|
|
206
|
+
*
|
|
207
|
+
* @internal
|
|
208
|
+
*/
|
|
209
|
+
declare const VIRTUAL_UTILITY_PREFIX = "\0virtual:aihu-utility/";
|
|
210
|
+
/**
|
|
211
|
+
* Stable short hash for keying the virtual-CSS module per source-SFC id.
|
|
212
|
+
*
|
|
213
|
+
* djb2-style; collisions are tolerable here because (a) each entry stores its
|
|
214
|
+
* own CSS body, so a hash collision would only matter if two distinct SFCs
|
|
215
|
+
* hashed to the same key AND were processed concurrently; (b) collisions are
|
|
216
|
+
* recoverable — Vite would simply load the wrong CSS for one SFC; we still
|
|
217
|
+
* keyed on the unhashed id internally to avoid that. The hash only appears in
|
|
218
|
+
* the bundled asset URL.
|
|
219
|
+
*
|
|
220
|
+
* @internal
|
|
221
|
+
*/
|
|
222
|
+
declare function _hashIdForUtilityCss(id: string): string;
|
|
223
|
+
/**
|
|
224
|
+
* Bug 6 — `shadowMode === 'none'` branch.
|
|
225
|
+
*
|
|
226
|
+
* Routes utility CSS to Vite's CSS pipeline (which folds CSS imports into the
|
|
227
|
+
* bundled `dist/assets/*.css` asset) instead of to `host.adoptedStyleSheets`
|
|
228
|
+
* (a no-op on an element with no shadow root). Returns a prelude `import` that
|
|
229
|
+
* the plugin's `resolveId` + `load` hooks resolve to the stored CSS body.
|
|
230
|
+
*
|
|
231
|
+
* The `__style__` shadow path is NOT invoked here — utility CSS for a
|
|
232
|
+
* cascade-mode component MUST hit the global stylesheet, not a per-element
|
|
233
|
+
* stylesheet that would be silently dropped by `HTMLElement`'s setter.
|
|
234
|
+
*
|
|
235
|
+
* Authored `@style` blocks still emit through the Rust codegen's `<style>`
|
|
236
|
+
* node and are unaffected. (If a component opts into `shadowMode: 'none'` and
|
|
237
|
+
* authors an `@style` block, the codegen still wires it through the
|
|
238
|
+
* non-shadow path — that is the runtime's contract, not this hook's.)
|
|
239
|
+
*
|
|
240
|
+
* @internal
|
|
241
|
+
*/
|
|
242
|
+
declare function _foldCssEngineStylesGlobal(compiledCode: string, css: string, id: string): {
|
|
243
|
+
code: string;
|
|
244
|
+
virtualId: string;
|
|
245
|
+
} | null;
|
|
184
246
|
/** Top-level AST export — one per .aihu SFC. */
|
|
185
247
|
interface SfcAst {
|
|
186
248
|
/** Resolved custom-element tag name (meta.name → route.name → file stem). */
|
|
@@ -284,5 +346,5 @@ declare function compileToAst(source: string, id?: string): SfcAst;
|
|
|
284
346
|
declare function _injectAutoWiring(code: string): string;
|
|
285
347
|
declare function aihuCompilerPlugin(options?: AihuCompilerPluginOptions): VitePlugin;
|
|
286
348
|
//#endregion
|
|
287
|
-
export { AihuCompilerPluginOptions, SfcAst, SfcAttr, SfcMacroValue, SfcMeta, SfcNode, SfcStyleBlock, _buildDeferredHydration, _buildStaticIsland, _classifyIsland, _foldCssEngineStyles, _injectAutoWiring, _injectShadowMode, aihuCompilerPlugin, compileToAst, transform };
|
|
349
|
+
export { AihuCompilerPluginOptions, SfcAst, SfcAttr, SfcMacroValue, SfcMeta, SfcNode, SfcStyleBlock, VIRTUAL_UTILITY_PREFIX, _buildDeferredHydration, _buildStaticIsland, _classifyIsland, _foldCssEngineStyles, _foldCssEngineStylesGlobal, _hashIdForUtilityCss, _injectAutoWiring, _injectShadowMode, aihuCompilerPlugin, compileToAst, transform };
|
|
288
350
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../js/index.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../js/index.ts"],"mappings":";UAgCU,UAAA;EAAA,SACC,IAAA;EACT,OAAA;EACA,SAAA,IACE,MAAA,UACA,QAAA,0CAC+B,OAAA;EACjC,IAAA,IAAQ,EAAA,yCAA2C,OAAA;EACnD,SAAA,IACE,IAAA,UACA,EAAA,aACG,OAAA;IAAU,IAAA;IAAc,GAAA;EAAA;IAAiB,IAAA;IAAc,GAAA;EAAA;AAAA;;;;UAM7C,yBAAA;EAVoC;;;;;;;;;;EAqBnD,OAAA;EAXe;;;;;;;;;AAmDjB;;;;;AAgCA;;EAtDE,UAAA;EAsD8B;;AAgHhC;;;;;AA2GA;EAvQE,MAAA;AAAA;;;AA4TF;;;;;;;iBAhTgB,iBAAA,CAAkB,IAAA,UAAc,IAAA;;;;;;AAqYhD;;;;;AAiEA;;;;;AAcA;;;;;AA2BA;;iBA/cgB,eAAA,CAAgB,YAAA;;;;;;;;;AAwehC;;;;;;;;;;iBAxXgB,uBAAA,CAAwB,YAAA,UAAsB,UAAA;;;;;;;;AAqY9D;;;;;AAOA;;;;;AAMA;;;;;;;;;iBAvSgB,kBAAA,CAAmB,YAAA,UAAsB,UAAA;;;;;;;;;iBAqDzC,SAAA,CACd,MAAA,UACA,EAAA,UACA,OAAA;EAAY,UAAA;EAAqB,MAAA;AAAA;EAC9B,IAAA;EAAc,GAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;AAgQnB;;;;;;;;;;;;;;;;AAKA;;;;;iBApLgB,oBAAA,CAAqB,YAAA,UAAsB,GAAA;;;;;;AAoM3D;;;;;;;;cAnIa,sBAAA;AAuJb;;;;;AAyOA;;;;;;;AAzOA,iBAzIgB,oBAAA,CAAqB,EAAA;;;;;;;;;;;;;;;;;;;;iBA2BrB,0BAAA,CACd,YAAA,UACA,GAAA,UACA,EAAA;EACG,IAAA;EAAc,SAAA;AAAA;;UAqBF,MAAA;;EAEf,GAAA;;EAEA,UAAA;;EAEA,KAAA,EAAO,aAAA;;EAEP,QAAA,EAAU,OAAA;;EAEV,IAAA,EAAM,OAAA;AAAA;AAAA,UAGS,aAAA;;EAEf,OAAA;;EAEA,KAAA;AAAA;AAAA,UAGe,OAAA;;EAEf,IAAA;AAAA;;KAIU,OAAA;EACN,IAAA;EAAiB,GAAA;EAAa,KAAA,EAAO,OAAA;EAAW,QAAA,EAAU,OAAA;AAAA;EAC1D,IAAA;EAAsB,IAAA;EAAc,KAAA,EAAO,OAAA;EAAW,QAAA,EAAU,OAAA;AAAA;EAChE,IAAA;EAAc,KAAA;AAAA;EACd,IAAA;EAAuB,IAAA;AAAA;EACvB,IAAA;EAAiB,QAAA,EAAU,KAAA;IAAQ,IAAA;IAAc,IAAA,EAAM,OAAA;EAAA;AAAA;EAEvD,IAAA;EACA,IAAA;EACA,IAAA;EACA,GAAA;EACA,GAAA;EACA,IAAA,EAAM,OAAA;EACN,SAAA,EAAW,OAAA;AAAA;EAEX,IAAA;EAAmB,IAAA;AAAA;;KAGb,OAAA;EACN,IAAA;EAAgB,IAAA;EAAc,KAAA;AAAA;EAC9B,IAAA;EAAiB,IAAA;EAAc,IAAA;AAAA;EAC/B,IAAA;EAAe,IAAA;EAAc,KAAA,EAAO,aAAA;AAAA;AAAA,KAE9B,aAAA;EACN,IAAA;EAAgB,KAAA;AAAA;EAChB,IAAA;EAAe,IAAA;AAAA;EACf,IAAA;AAAA;;;;;;;;;;;;iBAaU,YAAA,CAAa,MAAA,UAAgB,EAAA,YAAc,MAAA;;;;;;;;iBAoB3C,iBAAA,CAAkB,IAAA;AAAA,iBAyOlB,kBAAA,CAAmB,OAAA,GAAU,yBAAA,GAA4B,UAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{execFileSync as e}from"node:child_process";import{basename as t,dirname as n,resolve as r}from"node:path";import{fileURLToPath as i}from"node:url";const a=process.platform===`win32`?`.exe
|
|
1
|
+
import{execFileSync as e}from"node:child_process";import{basename as t,dirname as n,resolve as r}from"node:path";import{fileURLToPath as i}from"node:url";const a=process.platform===`win32`?`.exe`:``;function o(){return process.env.SCRIBE_COMPILE_BIN??r(n(i(import.meta.url)),`../bin/aihu-compile${a}`)}function s(e,t){return e.replace(/(defineElement\(\s*['"][^'"]+['"]\s*,\s*defineComponent\([^]*\))\s*\)/,(e,n)=>`${n}, { shadowMode: '${t}' })`)}function c(e){return/\b(?:signal|computed|effect|setSignal|onMount|onCleanup)\s*\(/.test(e)?`interactive`:`static`}function l(e){let t=/defineElement\(\s*['"]([^'"]+)['"]/m.exec(e);return t?t[1]??null:null}function u(e,t){let n=e.replace(/import\s*\{([^}]*)\}\s*from\s*'@aihu\/runtime'/,(e,t)=>{let n=t.split(`,`).map(e=>e.trim()).filter(Boolean);return n.includes(`_hmrReplace`)||n.push(`_hmrReplace`),`import { ${n.join(`, `)} } from '@aihu/runtime'`}).replace(/\bdefineComponent\(/,`defineComponent(__aihu_setup__ = `),r=`
|
|
2
2
|
export { __aihu_setup__ as default }
|
|
3
3
|
|
|
4
4
|
if (typeof __DEV__ !== 'undefined' && __DEV__ && import.meta.hot) {
|
|
@@ -36,10 +36,10 @@ function __aihu_wrap_defer__<T extends typeof HTMLElement>(Ctor: T): T {
|
|
|
36
36
|
mount(__aihu_setup__({ host: root, element: this }), root)
|
|
37
37
|
}
|
|
38
38
|
})
|
|
39
|
-
`)}`}function p(n,r,i){let a=[`--stdin`,`--tag`,t(r,`.aihu`),`--path`,r];return i?.sidecarOut&&a.push(`--sidecar-out`,i.sidecarOut),{code:e(o,a,{input:n,encoding:`utf8`}),map:null}}function m(e){return e.replace(/\\/g,`\\\\`).replace(/`/g,"\\`").replace(/\$\{/g,"\\${")}function h(e,t){if(!t.trim())return e;let n=m(t),r=/(__style__\.replaceSync\(`)[^]*?(`\);)/;if(r.test(e))return e.replace(r,(e,t,r)=>`${t}${n}${r}`);if(/defineComponent\(\s*\((_ctx|ctx)\)\s*=>\s*\{/.exec(e)==null)return e;let i=e.split(`
|
|
39
|
+
`)}`}function p(n,r,i){let a=[`--stdin`,`--tag`,t(r,`.aihu`),`--path`,r];return i?.sidecarOut&&a.push(`--sidecar-out`,i.sidecarOut),i?.target&&a.push(`--target`,i.target),{code:e(o(),a,{input:n,encoding:`utf8`}),map:null}}function m(e){return e.replace(/\\/g,`\\\\`).replace(/`/g,"\\`").replace(/\$\{/g,"\\${")}function h(e,t){if(!t.trim())return e;let n=m(t),r=/(__style__\.replaceSync\(`)[^]*?(`\);)/;if(r.test(e))return e.replace(r,(e,t,r)=>`${t}${n}${r}`);if(/defineComponent\(\s*\((_ctx|ctx)\)\s*=>\s*\{/.exec(e)==null)return e;let i=e.split(`
|
|
40
40
|
`),a=-1;for(let e=i.length-1;e>=0;e--){let t=(i[e]??``).trim();if(t.startsWith(`import `)||t.startsWith(`import{`)){a=e;break}}let o=`const __style__ = new CSSStyleSheet();\n__style__.replaceSync(\`${n}\`);`;a===-1?i.unshift(o):i.splice(a+1,0,o);let s=i.join(`
|
|
41
41
|
`);return s=s.replace(/defineComponent\(\s*\((?:_ctx|ctx)\)\s*=>\s*\{/,`defineComponent((ctx) => {
|
|
42
|
-
(ctx.host as ShadowRoot).adoptedStyleSheets = [__style__];`),s}function g(n,r){let i=[`--stdin`,`--tag`,r?t(r,`.aihu`):`Component`,`--ast-json`];r&&i.push(`--path`,r);let a=e(o,i,{input:n,encoding:`utf8`});return JSON.parse(a)}function
|
|
42
|
+
(ctx.host as ShadowRoot).adoptedStyleSheets = [__style__];`),s}const g=`\0virtual:aihu-utility/`;function _(e){let t=5381;for(let n=0;n<e.length;n++)t=(t*33^e.charCodeAt(n))>>>0;return t.toString(36)}function v(e,t,n){if(!t.trim())return null;let r=`${g}${_(n)}.css`;return{code:`import ${JSON.stringify(r)};\n`+e,virtualId:r}}function y(n,r){let i=[`--stdin`,`--tag`,r?t(r,`.aihu`):`Component`,`--ast-json`];r&&i.push(`--path`,r);let a=e(o(),i,{input:n,encoding:`utf8`});return JSON.parse(a)}function b(e){let t;t=e.includes(`from '@aihu/arbor'`)?e.replace(/import\s*\{([^}]*)\}\s*from\s*'@aihu\/arbor'/,(e,t)=>{let n=t.split(`,`).map(e=>e.trim()).filter(Boolean);return n.includes(`mount`)||n.push(`mount`),`import { ${n.join(`, `)} } from '@aihu/arbor'`}):`import { mount } from '@aihu/arbor'\n${e}`,/import\s+\{[^}]*\}\s+from\s+'@aihu\/signals'/.test(t)?t=t.replace(/import\s*\{([^}]*)\}\s*from\s*'@aihu\/signals'/,(e,t)=>{if(e.startsWith(`import type`))return e;let n=t.split(`,`).map(e=>e.trim()).filter(Boolean);return n.includes(`signal`)||n.push(`signal`),`import { ${n.join(`, `)} } from '@aihu/signals'`}):/import.*from\s*'@aihu\/signals'/.test(t)?/import\s+type\s+\{[^}]*\}\s+from\s+'@aihu\/signals'/.test(t)&&!t.match(/import\s+\{[^}]*\}\s+from\s+'@aihu\/signals'/)&&(t=t.replace(/(import\s+type\s+\{[^}]*\}\s+from\s+'@aihu\/signals')/,(e,t)=>`${t}\nimport { signal } from '@aihu/signals'`)):t=t.replace(/import\s*\{[^}]*\}\s*from\s*'@aihu\/arbor'/,e=>`${e}\nimport { signal } from '@aihu/signals'`),t=t.replace(/import\s*\{([^}]*)\}\s*from\s*'@aihu\/runtime'/,(e,t)=>{let n=t.split(`,`).map(e=>e.trim()).filter(Boolean);return n.includes(`_setMount`)||n.push(`_setMount`),n.includes(`_setSignal`)||n.push(`_setSignal`),`import { ${n.join(`, `)} } from '@aihu/runtime'`});let n=t.split(`
|
|
43
43
|
`),r=-1;for(let e=n.length-1;e>=0;e--){let t=(n[e]??``).trim();if(t.startsWith(`import `)||t.startsWith(`import{`)){r=e;break}}return r!==-1&&(n.splice(r+1,0,`_setMount(mount)`,`_setSignal(signal)`,``),t=n.join(`
|
|
44
|
-
`)),t}let
|
|
44
|
+
`)),t}let x,S=!1;async function C(e,t){if(x===null)return``;if(process.env.SCRIBE_COMPILE_BIN??(process.env.SCRIBE_COMPILE_BIN=o()),x===void 0)try{x=await import(`@aihu/css-engine`)}catch{return x=null,``}try{return x.compileSfc(e,t)}catch(e){if(!S){S=!0;let t=e instanceof Error?e.message:String(e);console.warn(`[@aihu/compiler] @aihu/css-engine is installed but compileSfc() failed; utility classes will not emit. Original error: ${t}\nHint: ensure the native css-core binary is installed (install/upgrade @aihu/css-engine + its per-platform optional dep, or run \`cargo build --release -p aihu-css-core\` in a dev clone).`)}return``}}function w(e){let t=e?.islands!==!1,n=e?.shadowMode,r=e?.target,i=new Map;return{name:`aihu-compiler`,enforce:`pre`,resolveId(e){return e.startsWith(`\0virtual:aihu-utility/`)?e:null},load(e){return e.startsWith(`\0virtual:aihu-utility/`)?i.get(e)??null:null},transform(e,a){let o=a.split(`?`)[0];if(o.endsWith(`.aihu`))return(async()=>{let a=`${o}.ts`,m=p(e,o,r?{sidecarOut:a,target:r}:{sidecarOut:a}),g=n==null?m.code:s(m.code,n),_=await C(e,o);if(_)if(n===`none`){let e=v(g,_,o);e&&(i.set(e.virtualId,_),g=e.code)}else g=h(g,_);let y=l(g),x;t&&y!==null&&c(g)===`static`?x=f(g,y):y===null?(x=g,x=b(x)):(x=u(g,y),x=d(x,y),x=b(x));try{let e=await import(`vite`);return`transformWithEsbuild`in e&&typeof e.transformWithEsbuild==`function`?{code:(await e.transformWithEsbuild(x,`component.ts`,{target:`esnext`,sourcemap:!1})).code,map:null}:{code:x,moduleType:`ts`,map:null}}catch{return{code:x,map:null}}})()}}}export{g as VIRTUAL_UTILITY_PREFIX,d as _buildDeferredHydration,f as _buildStaticIsland,c as _classifyIsland,h as _foldCssEngineStyles,v as _foldCssEngineStylesGlobal,_ as _hashIdForUtilityCss,b as _injectAutoWiring,s as _injectShadowMode,w as aihuCompilerPlugin,y as compileToAst,p as transform};
|
|
45
45
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../js/index.ts"],"sourcesContent":["/**\n * @aihu/compiler — TypeScript wrapper around the aihu-compile Rust binary.\n *\n * Exports:\n * transform(source, id) — compile a single .aihu file to TypeScript\n * aihuCompilerPlugin() — Vite plugin that wires transform() into the build\n */\nimport { execFileSync } from 'node:child_process'\nimport { basename, dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n// Binary resolution: env var override, fallback to the bin/ directory written\n// by the postinstall hook (packages/compiler/bin/aihu-compile[.exe]).\nconst ext = process.platform === 'win32' ? '.exe' : ''\nconst binPath: string =\n process.env.SCRIBE_COMPILE_BIN ??\n resolve(dirname(fileURLToPath(import.meta.url)), `../bin/aihu-compile${ext}`)\n\n// Minimal VitePlugin interface — avoids importing from 'vite' at compile time.\n// Structurally compatible with Vite's Plugin type.\ninterface VitePlugin {\n readonly name: string\n enforce?: 'pre' | 'post'\n transform?: (\n code: string,\n id: string,\n ) => Promise<{ code: string; map: null }> | { code: string; map: null } | null | undefined\n}\n\n/**\n * Options for `aihuCompilerPlugin()` (Plan 3.3 — Islands).\n */\nexport interface AihuCompilerPluginOptions {\n /**\n * When `true` (default), components classified as `'static'` by\n * `_classifyIsland()` are emitted with a minimal HTML-only registration\n * shim that ships **zero** `@aihu/runtime` and `@aihu/signals` JS to\n * the browser. Components classified as `'interactive'` retain the\n * full runtime path.\n *\n * Setting `islands: false` opts every component back into the unified\n * runtime path (Plan 3.2 baseline behaviour).\n */\n islands?: boolean\n\n /**\n * Project-wide shadow-DOM mode applied to every `.aihu` SFC compiled\n * by this plugin instance. When set, the plugin post-processes the\n * compiled JS to inject `, { shadowMode: '<mode>' }` as the third arg\n * to the emitted `defineElement(tag, defineComponent(...))` call.\n *\n * - `'open'` — default browser behaviour (shadow root, externally readable).\n * - `'closed'` — shadow root, externally hidden.\n * - `'none'` — **no shadow root.** The component mounts into its own\n * element. Required for global utility-class CSS frameworks\n * like Tailwind, UnoCSS, Pico that rely on the cascade.\n *\n * Per-component override is not yet supported via SFC syntax (post-v1).\n * For per-component control today, hand-author the component with\n * `defineElement(tag, Ctor, { shadowMode: '...' })`.\n */\n shadowMode?: 'open' | 'closed' | 'none'\n}\n\n/**\n * Inject `{ shadowMode: '...' }` as the third argument to the emitted\n * `defineElement('tag', defineComponent(...))` call. The compiler emits\n * exactly two arguments today; this rewrites the closing of the\n * defineElement call to include the options object. Idempotent — leaves\n * code untouched when the closer is not in the expected shape.\n *\n * @internal\n */\nexport function _injectShadowMode(code: string, mode: 'open' | 'closed' | 'none'): string {\n // Match the trailing `))` that closes `defineElement(tag, defineComponent(setup))`.\n // The compiler always emits this exact two-paren close as the final tokens of\n // the defineElement call — we anchor on it and append the options object.\n // biome-ignore lint/correctness/noEmptyCharacterClassInRegex: [^] is valid JS — matches any char including newlines\n const re = /(defineElement\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*defineComponent\\([^]*\\))\\s*\\)/\n const replaced = code.replace(re, (_m, inner: string) => `${inner}, { shadowMode: '${mode}' })`)\n return replaced\n}\n\n/**\n * Classify the compiled output of a single `.aihu` module as either a\n * **static** island (no reactive state — purely declarative DOM) or an\n * **interactive** island (uses the signals reactivity system).\n *\n * The heuristic is intentionally conservative: any source-level reference\n * to a primitive that requires the `defineComponent` owner context flips\n * the file to `'interactive'`. False positives (e.g. a string literal\n * containing `signal(`) are tolerable — they only forfeit the static-island\n * optimisation. False negatives are forbidden: a static-classified file\n * MUST NOT depend on the signals runtime at execution time.\n *\n * Owner-requiring primitives covered:\n * - `signal(`, `computed(`, `effect(`, `setSignal(` (signals runtime)\n * - `onMount(`, `onCleanup(` (lifecycle hooks — throw `no owner` outside\n * `defineComponent` because they push into the active owner's mount/\n * cleanup queues)\n *\n * Plan 3.3 / acceptance criterion 1.\n *\n * @internal\n */\nexport function _classifyIsland(compiledCode: string): 'static' | 'interactive' {\n // Match call sites of the reactive + lifecycle primitives. Use word-boundary\n // anchors so identifiers like `mySignal(` or `__effect(` do not trip the\n // heuristic. The `(` is required so that bare imports of the names in an\n // unused `import { signal }` line do not flip an otherwise-static module.\n return /\\b(?:signal|computed|effect|setSignal|onMount|onCleanup)\\s*\\(/.test(compiledCode)\n ? 'interactive'\n : 'static'\n}\n\n/**\n * Extract the custom element tag name from compiler-emitted code.\n * The compiler always emits `defineElement('tag-name', ...)` as the\n * first call — pull the first string literal argument.\n * Returns `null` if no `defineElement` call is found.\n * @internal\n */\nfunction _extractElementTag(code: string): string | null {\n const m = /defineElement\\(\\s*['\"]([^'\"]+)['\"]/m.exec(code)\n return m ? (m[1] ?? null) : null\n}\n\n/**\n * Instrument a compiled `.aihu` module with HMR support.\n *\n * The compiler always emits:\n *\n * import { defineComponent, defineElement } from '@aihu/runtime'\n * defineElement('tag', defineComponent((_ctx) => { ... }))\n *\n * This function:\n *\n * 1. Adds `_hmrReplace` to the `@aihu/runtime` import.\n * 2. Prepends a module-level slot variable `__aihu_setup__`.\n * 3. Rewrites the single `defineComponent(` call so the setup function\n * is captured via an assignment expression:\n * `defineComponent(__aihu_setup__ = ` (valid JS; assignment has\n * lower precedence than arrow fn, so `defineComponent` still\n * receives the function as its argument).\n * 4. Appends `export { __aihu_setup__ as default }` so that Vite's\n * `import.meta.hot.accept` callback receives the new setup via\n * `newModule.default` on hot reload.\n * 5. Appends the `import.meta.hot.accept` block, gated on `__DEV__`.\n *\n * The `__DEV__` guard ensures production bundlers (where they replace\n * `__DEV__` with `false`) dead-code-eliminate the entire HMR block.\n *\n * @internal\n */\nfunction _buildHmrCode(compiledCode: string, elementTag: string): string {\n // Step 1 — add _hmrReplace to the @aihu/runtime import.\n const withImport = compiledCode.replace(\n /import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/runtime'/,\n (_m, imports: string) => {\n const parts = imports\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (!parts.includes('_hmrReplace')) parts.push('_hmrReplace')\n return `import { ${parts.join(', ')} } from '@aihu/runtime'`\n },\n )\n\n // Step 2+3 — prepend slot variable and rewrite the defineComponent call.\n // Compiler emits exactly one `defineComponent(` followed by a function expr.\n // Rewrite: defineComponent(fn) → defineComponent(__aihu_setup__ = fn)\n // Assignment expression evaluates to `fn`, so defineComponent still\n // receives the setup function as its first argument unchanged.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const preamble = `let __aihu_setup__: ((ctx: any) => any) | undefined\\n`\n\n const patchedBody = withImport.replace(/\\bdefineComponent\\(/, 'defineComponent(__aihu_setup__ = ')\n\n const tag = JSON.stringify(elementTag)\n // Step 4+5 — postamble with default export and HMR acceptance.\n const postamble = `\nexport { __aihu_setup__ as default }\n\nif (typeof __DEV__ !== 'undefined' && __DEV__ && import.meta.hot) {\n import.meta.hot.accept((newModule) => {\n if (!newModule) return\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const newSetup = (newModule as any)['default']\n if (typeof newSetup !== 'function') return\n document.querySelectorAll(${tag}).forEach((el) => {\n _hmrReplace(el as HTMLElement, newSetup)\n })\n })\n}\n`\n\n return preamble + patchedBody + postamble\n}\n\n/**\n * Rewrite an interactive-island module so its `connectedCallback` waits\n * for the element to scroll into view before mounting. Plan 3.3 — applied\n * only when the consumer adds `defer` to the custom element tag (e.g.\n * `<my-counter defer>`); the runtime helper checks the attribute and\n * either mounts immediately or registers an `IntersectionObserver`.\n *\n * Implementation: the helper is added as a `_hydrateOnVisible` import\n * from `@aihu/runtime`, and the compiler-emitted `defineElement(...)`\n * call is wrapped in a `defineElement` that intercepts `connectedCallback`\n * to honour the `defer` attribute.\n *\n * The whole indirection is tree-shaken when no `.aihu` module reaches\n * this branch, because `_hydrateOnVisible` is exported from its own\n * sibling module inside `@aihu/runtime`.\n *\n * @internal\n */\nexport function _buildDeferredHydration(compiledCode: string, elementTag: string): string {\n // Add _hydrateOnVisible to the @aihu/runtime import.\n const withImport = compiledCode.replace(\n /import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/runtime'/,\n (_m, imports: string) => {\n const parts = imports\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (!parts.includes('_hydrateOnVisible')) parts.push('_hydrateOnVisible')\n return `import { ${parts.join(', ')} } from '@aihu/runtime'`\n },\n )\n\n // Wrap the class returned by defineComponent BEFORE defineElement\n // consumes it. The HTML spec caches lifecycle callbacks at\n // customElements.define() time, so we MUST mutate the prototype\n // before that call — not after. We accomplish this with a synchronous\n // helper invoked between defineComponent and defineElement.\n //\n // Source pattern (compiler-emitted):\n // defineElement('tag', defineComponent((_ctx) => { ... }))\n //\n // After this rewrite:\n // defineElement('tag', __aihu_wrap_defer__(defineComponent((_ctx) => { ... })))\n //\n // …with __aihu_wrap_defer__ defined in the appended preamble.\n const patched = withImport.replace(\n /defineElement\\(\\s*('[^']+'|\"[^\"]+\")\\s*,\\s*defineComponent\\(/,\n (_m, tagLit: string) => `defineElement(${tagLit}, __aihu_wrap_defer__(defineComponent(`,\n )\n // Match the closing `))` of the defineElement call. The HMR pass may\n // have inserted `__aihu_setup__ = ` before the inner function, but\n // the trailing `))` shape is unchanged. Replace exactly one occurrence\n // by anchoring on end-of-string trim; bail if the shape does not match.\n if (patched === withImport) {\n // The expected `defineElement(<tag>, defineComponent(` shape was not\n // present (e.g. compiler output changed). Skip defer wrapping rather\n // than emit broken code.\n return compiledCode\n }\n // Add a trailing `)` to balance the extra `(` from __aihu_wrap_defer__.\n // Source shape after _buildHmrCode is:\n // defineElement('tag', defineComponent(__aihu_setup__ = (_ctx) => {...}))\n // export { __aihu_setup__ as default }\n // if (typeof __DEV__ !== ...) { ... }\n // We must close BEFORE the export line. Match the first `))` followed\n // by a newline and `export` (or end-of-string for the unwrapped case).\n let balanced = patched.replace(/\\)\\s*\\)\\s*\\nexport\\s/, ')))\\nexport ')\n if (balanced === patched) {\n // No HMR postamble — the `))` is at end-of-string.\n balanced = patched.replace(/\\)\\s*\\)\\s*$/, ')))\\n')\n }\n if (balanced === patched) {\n // Could not find the matching `))` — bail out.\n return compiledCode\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const helper = `\n// Plan 3.3 (Islands) — defer attribute support. Wraps the constructor\n// returned by defineComponent so instances bearing the \\`defer\\` attribute\n// hydrate lazily via IntersectionObserver. Bare instances retain the\n// eager Plan 3.2 hydration path.\nfunction __aihu_wrap_defer__<T extends typeof HTMLElement>(Ctor: T): T {\n const orig = (Ctor.prototype as unknown as { connectedCallback?: () => void }).connectedCallback\n if (typeof orig !== 'function') return Ctor\n ;(Ctor.prototype as unknown as { connectedCallback: () => void }).connectedCallback = function (this: HTMLElement) {\n if (this.hasAttribute('defer')) {\n _hydrateOnVisible(this, () => orig.call(this))\n } else {\n orig.call(this)\n }\n }\n return Ctor\n}\n`\n void elementTag\n return helper + balanced\n}\n\n/**\n * Build a static-island shim for a compiled module.\n *\n * The compiled module emitted by the Rust codegen has the shape:\n *\n * import { branch, leaf, slot } from '@aihu/arbor'\n * import { defineComponent, defineElement } from '@aihu/runtime'\n * defineElement('tag', defineComponent((_ctx) => { return <tree> }))\n *\n * For a static island we know `<tree>` contains no `signal(`/`computed(`\n * calls. We can therefore:\n *\n * 1. Drop the `@aihu/runtime` import (saves ~600 B gz of defineComponent\n * + defineElement + bootstrap glue).\n * 2. Replace `defineElement(tag, defineComponent(setup))` with a tiny\n * inline class that mounts the tree directly via `mount()` (which the\n * arbor barrel already exports).\n * 3. Tag the file with a `// SCRIBE_STATIC_ISLAND` comment so consumers\n * can audit which routes shipped zero-JS-runtime.\n *\n * Falls back to the original code if the regex shape does not match\n * (defensive: a future compiler change must opt back into static-island\n * emission explicitly rather than silently break).\n *\n * @internal\n */\nexport function _buildStaticIsland(compiledCode: string, elementTag: string): string {\n // Confirm the shape we expect: a single defineElement(...) call wrapping\n // a single defineComponent(...) call. Bail out otherwise.\n const callRe = /defineElement\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*defineComponent\\(/\n if (!callRe.test(compiledCode)) return compiledCode\n\n // Strip the `@aihu/runtime` import line entirely — static islands\n // don't reference defineComponent/defineElement after the rewrite.\n const withoutRuntimeImport = compiledCode.replace(\n /^\\s*import\\s*\\{[^}]*\\}\\s*from\\s*'@aihu\\/runtime'\\s*;?\\s*$/m,\n '',\n )\n\n // Ensure `mount` is imported from @aihu/arbor (it already exposes\n // branch/leaf/slot, so we just append `mount` to the existing list).\n const withArborMount = withoutRuntimeImport.replace(\n /import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/arbor'/,\n (_m, imports: string) => {\n const parts = imports\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (!parts.includes('mount')) parts.push('mount')\n return `import { ${parts.join(', ')} } from '@aihu/arbor'`\n },\n )\n\n // Replace `defineElement('tag', defineComponent((_ctx) => { ... }))`\n // with an inline `customElements.define` whose connectedCallback mounts\n // the static tree. The setup function is captured verbatim by replacing\n // the wrapping calls with anonymous-IIFE bookends.\n const tagJson = JSON.stringify(elementTag)\n const rewritten = withArborMount\n .replace(\n /defineElement\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*defineComponent\\(/,\n `customElements.define(${tagJson}, class extends HTMLElement {\\n connectedCallback() {\\n const root = this.attachShadow({ mode: 'open' })\\n const __aihu_setup__ = (`,\n )\n .replace(\n /\\)\\s*\\)\\s*$/,\n `)\\n mount(__aihu_setup__({ host: root, element: this }), root)\\n }\\n})\\n`,\n )\n\n return `// SCRIBE_STATIC_ISLAND — zero @aihu/runtime references\\n${rewritten}`\n}\n\n/**\n * Compile a .aihu source string to TypeScript.\n * map is null — source maps are deferred to v1 (OQ-C8)\n *\n * B3b — when `sidecarOut` is provided, also writes the per-SFC `.aihu.ts`\n * sidecar at that path. Callers (e.g. the Vite plugin) typically pass\n * `<source-id>.ts` so `tsc --noEmit` discovers per-SFC template expressions.\n */\nexport function transform(\n source: string,\n id: string,\n options?: { sidecarOut?: string },\n): { code: string; map: null } {\n const stem = basename(id, '.aihu')\n const args = ['--stdin', '--tag', stem, '--path', id]\n if (options?.sidecarOut) {\n args.push('--sidecar-out', options.sidecarOut)\n }\n const code = execFileSync(binPath, args, {\n input: source,\n encoding: 'utf8',\n })\n return {\n code,\n map: null, // source maps deferred to v1 (OQ-C8)\n }\n}\n\n/**\n * Escape a CSS string for safe interpolation inside a JS template literal.\n * The Rust codegen places the authored `@style` body raw inside a backtick\n * literal, so it already assumes no backticks in `@style`. css-engine output\n * (theme tokens + utility rules) likewise never contains backticks, but we\n * escape `\\`, `` ` `` and `${` defensively so a future token value can't\n * break out of the literal.\n *\n * @internal\n */\nfunction _escapeForTemplateLiteral(css: string): string {\n return css.replace(/\\\\/g, '\\\\\\\\').replace(/`/g, '\\\\`').replace(/\\$\\{/g, '\\\\${')\n}\n\n/**\n * Fold css-engine-produced scoped CSS into a compiled `.aihu` module.\n *\n * The Rust codegen emits the authored `@style` block (when present) as:\n *\n * const __style__ = new CSSStyleSheet();\n * __style__.replaceSync(`<authored css>`);\n * defineElement('tag', defineComponent((ctx) => {\n * (ctx.host as ShadowRoot).adoptedStyleSheets = [__style__];\n * return ...\n * }))\n *\n * css-engine's `compileSfc` output is the COMPLETE per-SFC stylesheet:\n * `:host` theme tokens, the variant-resolved utility-class rules, AND the\n * folded authored `@style` block (under an `authored @style` CSS comment).\n * So it is authoritative — we adopt it as the single shadow `<style>` and\n * the authored `@style` keeps emitting through it (acceptance: \"@style still\n * emits correctly alongside\").\n *\n * Two shapes are handled:\n *\n * 1. **SFC has an `@style` block** — the Rust codegen already declared\n * `__style__` with the raw `@style` body. We REPLACE that body with the\n * css-engine output (which already CONTAINS the `@style` block) so the\n * `@style` rules are not duplicated. The existing `adoptedStyleSheets`\n * assignment is reused unchanged.\n *\n * 2. **SFC has NO `@style` block** — there is no `__style__`. We inject a\n * fresh `__style__` declaration after the last import and an\n * `adoptedStyleSheets` assignment as the first statement of the setup\n * function. The compiler emits the setup param as `_ctx` in this case;\n * we rename it to `ctx` so the injected `ctx.host` reference resolves.\n *\n * Runs on the RAW compiled output BEFORE the island / HMR / auto-wiring\n * transforms so those passes operate on the folded module uniformly:\n * - The static-island shim calls `__aihu_setup__({ host: root, ... })`\n * where `root` is the shadow root, so `ctx.host` is valid there too.\n * - The HMR / defer passes only touch the `defineElement(...)` wrapper and\n * the runtime import; they do not disturb `__style__` or the setup body.\n *\n * No-ops (returns input unchanged) when `css` is empty/whitespace.\n *\n * @internal\n */\nexport function _foldCssEngineStyles(compiledCode: string, css: string): string {\n if (!css.trim()) return compiledCode\n const escaped = _escapeForTemplateLiteral(css)\n\n // Shape 1 — an authored @style block already declared __style__. css-engine\n // output already includes that @style block, so REPLACE the replaceSync body\n // (between the backticks) wholesale to avoid duplicating the @style rules.\n // The codegen emits `__style__.replaceSync(`<body>`);` as a single statement;\n // match the body non-greedily up to the closing backtick + paren.\n // biome-ignore lint/correctness/noEmptyCharacterClassInRegex: [^] matches any char incl. newlines\n const styleBodyRe = /(__style__\\.replaceSync\\(`)[^]*?(`\\);)/\n if (styleBodyRe.test(compiledCode)) {\n // Use a function replacer so any `$` in the CSS isn't read as a\n // replacement-pattern backreference.\n return compiledCode.replace(styleBodyRe, (_m, open: string, close: string) => {\n return `${open}${escaped}${close}`\n })\n }\n\n // Shape 2 — no @style block. Inject a fresh stylesheet + adoption.\n // Bail (no-op) if the expected defineComponent setup shape is absent.\n const setupRe = /defineComponent\\(\\s*\\((_ctx|ctx)\\)\\s*=>\\s*\\{/\n const m = setupRe.exec(compiledCode)\n if (m == null) return compiledCode\n\n // Inject the module-level stylesheet declaration after the last import line.\n const lines = compiledCode.split('\\n')\n let lastImportIdx = -1\n for (let i = lines.length - 1; i >= 0; i--) {\n const t = (lines[i] ?? '').trim()\n if (t.startsWith('import ') || t.startsWith('import{')) {\n lastImportIdx = i\n break\n }\n }\n const decl = `const __style__ = new CSSStyleSheet();\\n__style__.replaceSync(\\`${escaped}\\`);`\n if (lastImportIdx !== -1) {\n lines.splice(lastImportIdx + 1, 0, decl)\n } else {\n lines.unshift(decl)\n }\n let withDecl = lines.join('\\n')\n\n // Rename the setup param to `ctx` (codegen emits `_ctx` when no @style/ctx\n // usage) and inject the adoption as the first statement of the setup body.\n withDecl = withDecl.replace(\n /defineComponent\\(\\s*\\((?:_ctx|ctx)\\)\\s*=>\\s*\\{/,\n 'defineComponent((ctx) => {\\n (ctx.host as ShadowRoot).adoptedStyleSheets = [__style__];',\n )\n return withDecl\n}\n\n// ─── v1.0.10a — compiler AST-export hook ─────────────────────────────────────\n//\n// Thin TS wrapper over the `aihu-compile --ast-json` flag. Returns the parsed\n// `.aihu` SFC AST in a stable, serializable shape consumed by the CSS engine's\n// AST scanner (`css-2-ast-scanner`). Mirrors the typed contract in\n// `docs/superpowers/specs/compiler-ast-export-hook.md` §4.\n\n/** Top-level AST export — one per .aihu SFC. */\nexport interface SfcAst {\n /** Resolved custom-element tag name (meta.name → route.name → file stem). */\n tag: string\n /** AST schema version — bumped on any breaking shape change (semver-tied). */\n astVersion: 1\n /** The @style block, if the SFC declared one. */\n style: SfcStyleBlock | null\n /** Parsed template tree. null when the SFC has no @template block. */\n template: SfcNode[] | null\n /** SFC-level metadata. */\n meta: SfcMeta\n}\n\nexport interface SfcStyleBlock {\n /** Verbatim CSS body of the @style block (braces stripped, $global token removed). */\n content: string\n /** 'scoped' (default) or 'global' (@style { $global ... }). */\n scope: 'scoped' | 'global'\n}\n\nexport interface SfcMeta {\n /** From @meta { name } / @route { name } / file stem — never null after resolution. */\n name: string\n}\n\n/** Discriminated union mirroring Rust `TemplateNode`. */\nexport type SfcNode =\n | { kind: 'element'; tag: string; attrs: SfcAttr[]; children: SfcNode[] }\n | { kind: 'macroElement'; name: string; attrs: SfcAttr[]; children: SfcNode[] }\n | { kind: 'text'; value: string }\n | { kind: 'interpolation'; expr: string }\n | { kind: 'ifBlock'; branches: Array<{ cond: string; body: SfcNode[] }> }\n | {\n kind: 'eachBlock'\n list: string\n item: string\n idx: string | null\n key: string | null\n body: SfcNode[]\n emptyBody: SfcNode[] | null\n }\n | { kind: 'htmlBlock'; expr: string }\n\n/** Discriminated union mirroring Rust `Attr` — the three class-forms key on `kind`. */\nexport type SfcAttr =\n | { kind: 'static'; name: string; value: string } // Form A\n | { kind: 'binding'; name: string; expr: string } // Form B\n | { kind: 'macro'; name: string; value: SfcMacroValue } // Form C (and on:/bind:/emit:/if/each/…)\n\nexport type SfcMacroValue =\n | { form: 'quoted'; value: string }\n | { form: 'curly'; expr: string }\n | { form: 'boolean' }\n\n/**\n * Parse a .aihu source string to its structured AST.\n *\n * Thin wrapper over the Rust binary (mirrors `transform()`): spawns\n * `aihu-compile --stdin --tag <stem> --ast-json`, feeds `source` on stdin, and\n * `JSON.parse`s stdout. `id` is optional and only used to derive the tag stem\n * and the `--path` arg (for `@route` C500 checks), identical to `transform()`.\n *\n * Throws on parse failure — the Rust binary exits non-zero and `execFileSync`\n * surfaces the diagnostic (same error path as `transform()`).\n */\nexport function compileToAst(source: string, id?: string): SfcAst {\n const stem = id ? basename(id, '.aihu') : 'Component'\n const args = ['--stdin', '--tag', stem, '--ast-json']\n if (id) {\n args.push('--path', id)\n }\n const json = execFileSync(binPath, args, {\n input: source,\n encoding: 'utf8',\n })\n return JSON.parse(json) as SfcAst\n}\n\n/**\n * Inject `_setMount(mount)` + `_setSignal(signal)` auto-wiring into a compiled\n * `.aihu` module. Adds the necessary symbols to existing imports and inserts\n * the boot calls right after the last `import` statement.\n *\n * @internal\n */\nexport function _injectAutoWiring(code: string): string {\n // 1. Add `mount` to the @aihu/arbor import (or create it).\n let result: string\n if (code.includes(\"from '@aihu/arbor'\")) {\n result = code.replace(\n /import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/arbor'/,\n (_m: string, imports: string) => {\n const parts = imports\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (!parts.includes('mount')) parts.push('mount')\n return `import { ${parts.join(', ')} } from '@aihu/arbor'`\n },\n )\n } else {\n result = `import { mount } from '@aihu/arbor'\\n${code}`\n }\n\n // 2. Add `signal` to the non-type @aihu/signals import (or create it).\n // Note: `import\\s+\\{` does NOT match `import type {` (the regex needs `{` immediately\n // after whitespace, whereas `import type {` has `type` in between). No negation guard\n // is needed — the replace callback below already skips `import type` lines.\n if (/import\\s+\\{[^}]*\\}\\s+from\\s+'@aihu\\/signals'/.test(result)) {\n // There IS a value import from signals — add `signal` if missing.\n result = result.replace(\n /import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/signals'/,\n (_m: string, imports: string) => {\n // Skip type-only imports\n if (_m.startsWith('import type')) return _m\n const parts = imports\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (!parts.includes('signal')) parts.push('signal')\n return `import { ${parts.join(', ')} } from '@aihu/signals'`\n },\n )\n } else if (!/import.*from\\s*'@aihu\\/signals'/.test(result)) {\n // No signals import at all — insert after arbor import\n result = result.replace(\n /import\\s*\\{[^}]*\\}\\s*from\\s*'@aihu\\/arbor'/,\n (m: string) => `${m}\\nimport { signal } from '@aihu/signals'`,\n )\n }\n // If only `import type { Signal }` exists, insert value import after it\n else if (\n /import\\s+type\\s+\\{[^}]*\\}\\s+from\\s+'@aihu\\/signals'/.test(result) &&\n !result.match(/import\\s+\\{[^}]*\\}\\s+from\\s+'@aihu\\/signals'/)\n ) {\n result = result.replace(\n /(import\\s+type\\s+\\{[^}]*\\}\\s+from\\s+'@aihu\\/signals')/,\n (_m: string, typeImport: string) => `${typeImport}\\nimport { signal } from '@aihu/signals'`,\n )\n }\n\n // 3. Add `_setMount`, `_setSignal` to the @aihu/runtime import.\n result = result.replace(\n /import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/runtime'/,\n (_m: string, imports: string) => {\n const parts = imports\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (!parts.includes('_setMount')) parts.push('_setMount')\n if (!parts.includes('_setSignal')) parts.push('_setSignal')\n return `import { ${parts.join(', ')} } from '@aihu/runtime'`\n },\n )\n\n // 4. Insert boot calls after the last `import` statement.\n const lines = result.split('\\n')\n let lastImportIdx = -1\n for (let i = lines.length - 1; i >= 0; i--) {\n const t = (lines[i] ?? '').trim()\n if (t.startsWith('import ') || t.startsWith('import{')) {\n lastImportIdx = i\n break\n }\n }\n if (lastImportIdx !== -1) {\n lines.splice(lastImportIdx + 1, 0, '_setMount(mount)', '_setSignal(signal)', '')\n result = lines.join('\\n')\n }\n\n return result\n}\n\n/**\n * Vite plugin that compiles .aihu files to TypeScript during build and dev.\n *\n * Use `enforce: 'pre'` so the hook fires before Vite/Rollup's built-in\n * parsers attempt to process the raw .aihu content as JavaScript.\n *\n * @example\n * // vite.config.ts\n * import { aihuCompilerPlugin } from '@aihu/compiler'\n * export default { plugins: [aihuCompilerPlugin()] }\n *\n * **Known Limitation — Bun + Rollup4 ESM incompatibility (v0):**\n *\n * `bun vite build` fails in the `fixtures/vite-counter` fixture with two\n * cascading errors:\n *\n * 1. **Missing devDependency:** `vite` is declared only as an optional\n * `peerDependency` in `packages/compiler/package.json`. Bun does not\n * install optional peers automatically, so `bun vite build` exits\n * immediately with `Cannot find package 'vite'`.\n *\n * 2. **Bun + Rollup4 bridge:** Even with Vite installed, Bun processes\n * `vite.config.ts` through its own internal bundler before handing off\n * to Rollup4. When `@aihu/compiler` is resolved from the workspace\n * symlink (`dist/index.js`), Bun's ESM loader evaluates the module at\n * config-load time. The subprocess call inside `transform()` depends on\n * the Rust binary being at `../bin/aihu-compile` relative to `dist/`\n * (written by the postinstall hook). In a dev workspace where postinstall\n * has not run, this path does not exist and `execFileSync` throws. Bun surfaces\n * the error as a config-load failure, not a per-file transform error,\n * causing the entire build to abort before any `.aihu` file is\n * processed.\n *\n * **Workaround (v0):** Use `bun run integrate.ts` directly from\n * `packages/compiler/fixtures/vite-counter/`. This script calls\n * `transform()` from `@aihu/compiler` without involving Vite or Rollup.\n * Preconditions: (1) `cargo build --release` in `packages/compiler/`,\n * (2) `bun install` at the repo root.\n *\n * **v1 resolution:** Add `vite` as a `devDependency` in\n * `packages/compiler/package.json`; add a WASM or pre-built binary\n * strategy so the Rust binary is bundled with the npm package and does not\n * require a separate `cargo build --release` step.\n */\n/**\n * Minimal structural type for the `@aihu/css-engine` module surface this\n * plugin uses. Declared locally so the compiler never type-imports the\n * css-engine package (which would create a compile-time edge against an\n * optional peer that may be absent).\n *\n * @internal\n */\ninterface CssEngineModule {\n compileSfc(source: string, id?: string): string\n}\n\n// Memoised resolution of the optional `@aihu/css-engine` peer. `undefined`\n// = not yet attempted; `null` = attempted and unavailable (no-op path);\n// a module object = available. The dynamic import is attempted once per\n// process — repeated absence does not re-pay the resolution cost.\nlet _cssEngine: CssEngineModule | null | undefined\n\n// Whether we've already surfaced a one-shot warning that css-engine resolved\n// but `compileSfc` threw (typically: native css-core binary unresolvable in\n// the consumer's install — e.g. lockfile pins the per-platform placeholder\n// version). The transform stays non-fatal, but going fully silent leaves users\n// chasing \"why did my utility classes never emit?\". One warn per process.\nlet _cssEngineWarned = false\n\n// The optional-peer module specifier, held in a VARIABLE so TypeScript never\n// statically resolves `@aihu/css-engine`'s declarations at typecheck time.\n// css-engine depends on @aihu/compiler for its AST, so the two form a\n// circular package relationship; under CI's frozen install + moon build\n// ordering, css-engine's `dist`/`.d.ts` are not guaranteed to exist when\n// `compiler:typecheck` runs. A literal `import('@aihu/css-engine')` makes the\n// compiler emit TS2307 in that window (the `as` cast affects the RESULT type\n// only, not whether TS attempts module resolution). Resolving through this\n// variable keeps the import fully dynamic — no compile-time edge on the peer.\nconst _CSS_ENGINE_SPECIFIER = '@aihu/css-engine'\n\n/**\n * Lazily resolve `@aihu/css-engine` and compile a `.aihu` source's utility\n * classes to scoped CSS. Returns `''` when css-engine is not installed\n * (the optional-peer no-op path) or when compilation fails for any reason —\n * a CSS-engine failure MUST NOT break an otherwise-valid `.aihu` build.\n *\n * Sets `process.env.SCRIBE_COMPILE_BIN` to this plugin's resolved compiler\n * binary before calling `compileSfc`: css-engine re-derives the SFC AST via\n * its own bundled copy of `compileToAst`, whose binary path is resolved\n * relative to the css-engine package — which does NOT ship the compiler\n * binary. Pointing it at our `binPath` guarantees the AST css-engine parses\n * is produced by the exact same compiler this build uses.\n *\n * @internal\n */\nasync function _maybeCompileUtilityCss(source: string, id: string): Promise<string> {\n if (_cssEngine === null) return ''\n if (_cssEngine === undefined) {\n try {\n // Guarded, lazy, OPTIONAL — see the plugin transform for the rationale.\n // Importing via the `_CSS_ENGINE_SPECIFIER` variable (not a string\n // literal) keeps this fully dynamic: TS does NOT resolve the peer's\n // `.d.ts` at typecheck time, so `compiler:typecheck` passes even when\n // css-engine's `dist` has not been built (the CI build-order window).\n _cssEngine = (await import(_CSS_ENGINE_SPECIFIER)) as unknown as CssEngineModule\n } catch {\n _cssEngine = null\n return ''\n }\n }\n try {\n // Ensure css-engine's bundled `compileToAst` spawns the SAME compiler\n // binary this plugin uses (it has no compiler binary of its own).\n if (process.env.SCRIBE_COMPILE_BIN == null) {\n process.env.SCRIBE_COMPILE_BIN = binPath\n }\n return _cssEngine.compileSfc(source, id)\n } catch (err) {\n // A css-engine compile failure is non-fatal: fall back to the no-op\n // path (utility classes don't emit) rather than aborting the build.\n // BUT — silently swallowing this means a user who clearly intends\n // css-engine to be active (the peer resolved) will never know their\n // utility classes are inert. Surface a one-shot warning with the\n // underlying error + an install/upgrade hint. Idempotent per process.\n if (!_cssEngineWarned) {\n _cssEngineWarned = true\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(\n `[@aihu/compiler] @aihu/css-engine is installed but compileSfc() failed; ` +\n `utility classes will not emit. Original error: ${msg}\\n` +\n `Hint: ensure the native css-core binary is installed ` +\n `(install/upgrade @aihu/css-engine + its per-platform optional dep, ` +\n `or run \\`cargo build --release -p aihu-css-core\\` in a dev clone).`,\n )\n }\n return ''\n }\n}\n\nexport function aihuCompilerPlugin(options?: AihuCompilerPluginOptions): VitePlugin {\n const islandsEnabled = options?.islands !== false\n const shadowMode = options?.shadowMode\n return {\n name: 'aihu-compiler',\n enforce: 'pre',\n transform(code, id) {\n // Strip Vite query strings (e.g. `?import`, `?t=...`) before checking the extension.\n const rawId = id.split('?')[0]!\n if (!rawId.endsWith('.aihu')) return\n return (async () => {\n // B3b — write per-SFC `.aihu.ts` sidecar adjacent to source so\n // `tsc --noEmit` over `**/*.aihu.ts` type-checks template\n // expressions end-to-end (Architect spec §7 path (i)).\n const sidecarOut = `${rawId}.ts`\n const result = transform(code, rawId, { sidecarOut })\n let compiled = shadowMode != null ? _injectShadowMode(result.code, shadowMode) : result.code\n\n // ── css-engine hook (optional, lazy, no circular dep) ──────────────\n // @aihu/css-engine depends on @aihu/compiler (for its AST), so the\n // compiler MUST NOT hard-depend on it. It is declared an OPTIONAL\n // peerDependency and pulled in ONLY via this guarded dynamic import:\n // when present, we compile the SFC's utility classes to scoped CSS\n // and fold it into the component's shadow `<style>`; when absent the\n // import throws and we no-op (utility classes simply don't emit —\n // the pre-hook behaviour). This keeps css-engine an opt-in enhancement\n // with zero dependency cycle.\n const utilityCss = await _maybeCompileUtilityCss(code, rawId)\n if (utilityCss) {\n compiled = _foldCssEngineStyles(compiled, utilityCss)\n }\n\n const elementTag = _extractElementTag(compiled)\n\n let out: string\n\n // Plan 3.3 — static-island fast path. Bypasses HMR injection because\n // a component with no signals has no setup state to hot-replace.\n // Static islands strip @aihu/runtime entirely — do NOT inject auto-wiring\n // (it would reference _setMount/_setSignal as undefined identifiers).\n if (islandsEnabled && elementTag !== null && _classifyIsland(compiled) === 'static') {\n out = _buildStaticIsland(compiled, elementTag)\n } else if (elementTag !== null) {\n // Inject HMR instrumentation. The injected block is gated on\n // `typeof __DEV__ !== 'undefined' && __DEV__` so production\n // bundlers dead-code-eliminate it when they set __DEV__ = false.\n out = _buildHmrCode(compiled, elementTag)\n // Plan 3.3 — interactive islands also gain `defer` attribute\n // support so individual instances can opt into lazy hydration.\n out = _buildDeferredHydration(out, elementTag)\n // Inject auto-wiring so consumers don't need a manual main.ts bootstrap.\n out = _injectAutoWiring(out)\n } else {\n out = compiled\n // Inject auto-wiring so consumers don't need a manual main.ts bootstrap.\n out = _injectAutoWiring(out)\n }\n\n // The Rust compiler emits TypeScript (type casts, import type, etc.) and\n // the injected HMR / defer helpers also contain TS generics and casts.\n // Vite does NOT re-run its TS-strip step when a plugin returns code for a\n // non-.ts ID, so we must strip types ourselves before returning.\n //\n // Priority: always try transformWithEsbuild first — it strips types to\n // plain JS in both Vite 5 (via esbuild) and Vite 8 (deprecated wrapper).\n // Using moduleType:'ts' only as a last resort because `import('vite')`\n // resolves to the root node_modules vite (which may be v8 even when a\n // consumer project runs v5), causing v5's Rollup to receive raw TypeScript\n // and fail on import-type / as-casts.\n try {\n const vite = await import('vite')\n if ('transformWithEsbuild' in vite && typeof vite.transformWithEsbuild === 'function') {\n const stripped = await vite.transformWithEsbuild(out, 'component.ts', {\n target: 'esnext',\n sourcemap: false,\n })\n return { code: stripped.code, map: null }\n }\n // Fallback for future Vite versions where esbuild is fully removed:\n // return TS and let Rolldown strip types natively.\n // biome-ignore lint/suspicious/noExplicitAny: moduleType is rolldown API\n return { code: out, moduleType: 'ts', map: null } as any\n } catch {\n // If running outside Vite (e.g. tests, standalone transform), return as-is.\n return { code: out, map: null }\n }\n })()\n },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../js/index.ts"],"sourcesContent":["/**\n * @aihu/compiler — TypeScript wrapper around the aihu-compile Rust binary.\n *\n * Exports:\n * transform(source, id) — compile a single .aihu file to TypeScript\n * aihuCompilerPlugin() — Vite plugin that wires transform() into the build\n */\nimport { execFileSync } from 'node:child_process'\nimport { basename, dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n// Binary resolution: env var override, fallback to the bin/ directory written\n// by the postinstall hook (packages/compiler/bin/aihu-compile[.exe]).\n//\n// Bug 6 fix — resolveBinPath() is CALL-TIME, not module-load-time. The Vite\n// plugin's `_maybeCompileUtilityCss` sets `process.env.SCRIBE_COMPILE_BIN` so\n// that css-engine's bundled copy of `compileToAst` spawns THIS compiler's\n// binary. Prior to this fix `binPath` was a module-scope const captured at\n// import time, so the env-var assignment was always too late and `compileSfc`\n// failed with ENOENT on the (non-existent) `packages/css-engine/bin/aihu-compile`\n// path. Re-reading on every call is essentially free (string concatenation +\n// a single env lookup) and makes the SCRIBE_COMPILE_BIN handshake actually work.\nconst ext = process.platform === 'win32' ? '.exe' : ''\nfunction resolveBinPath(): string {\n return (\n process.env.SCRIBE_COMPILE_BIN ??\n resolve(dirname(fileURLToPath(import.meta.url)), `../bin/aihu-compile${ext}`)\n )\n}\n\n// Minimal VitePlugin interface — avoids importing from 'vite' at compile time.\n// Structurally compatible with Vite's Plugin type.\ninterface VitePlugin {\n readonly name: string\n enforce?: 'pre' | 'post'\n resolveId?: (\n source: string,\n importer?: string,\n ) => string | null | undefined | Promise<string | null | undefined>\n load?: (id: string) => string | null | undefined | Promise<string | null | undefined>\n transform?: (\n code: string,\n id: string,\n ) => Promise<{ code: string; map: null }> | { code: string; map: null } | null | undefined\n}\n\n/**\n * Options for `aihuCompilerPlugin()` (Plan 3.3 — Islands).\n */\nexport interface AihuCompilerPluginOptions {\n /**\n * When `true` (default), components classified as `'static'` by\n * `_classifyIsland()` are emitted with a minimal HTML-only registration\n * shim that ships **zero** `@aihu/runtime` and `@aihu/signals` JS to\n * the browser. Components classified as `'interactive'` retain the\n * full runtime path.\n *\n * Setting `islands: false` opts every component back into the unified\n * runtime path (Plan 3.2 baseline behaviour).\n */\n islands?: boolean\n\n /**\n * Project-wide shadow-DOM mode applied to every `.aihu` SFC compiled\n * by this plugin instance. When set, the plugin post-processes the\n * compiled JS to inject `, { shadowMode: '<mode>' }` as the third arg\n * to the emitted `defineElement(tag, defineComponent(...))` call.\n *\n * - `'open'` — default browser behaviour (shadow root, externally readable).\n * - `'closed'` — shadow root, externally hidden.\n * - `'none'` — **no shadow root.** The component mounts into its own\n * element. Required for global utility-class CSS frameworks\n * like Tailwind, UnoCSS, Pico that rely on the cascade.\n *\n * Per-component override is not yet supported via SFC syntax (post-v1).\n * For per-component control today, hand-author the component with\n * `defineElement(tag, Ctor, { shadowMode: '...' })`.\n */\n shadowMode?: 'open' | 'closed' | 'none'\n\n /**\n * Build target threaded to the compiler binary (`--target`). Defaults to the\n * compiler's `universal` target (current behaviour). Set to `'client'` for a\n * browser bundle that must NOT ship the server `__agentBinding` (policy) and\n * instead gets the policy-free `@agent` opaque-ID dispatcher + the per-instance\n * `_registerAgentDispatcher` wiring the capability bridge reads after mount.\n * See `examples/agent-driven-demo`.\n */\n target?: 'client' | 'server' | 'universal'\n}\n\n/**\n * Inject `{ shadowMode: '...' }` as the third argument to the emitted\n * `defineElement('tag', defineComponent(...))` call. The compiler emits\n * exactly two arguments today; this rewrites the closing of the\n * defineElement call to include the options object. Idempotent — leaves\n * code untouched when the closer is not in the expected shape.\n *\n * @internal\n */\nexport function _injectShadowMode(code: string, mode: 'open' | 'closed' | 'none'): string {\n // Match the trailing `))` that closes `defineElement(tag, defineComponent(setup))`.\n // The compiler always emits this exact two-paren close as the final tokens of\n // the defineElement call — we anchor on it and append the options object.\n // biome-ignore lint/correctness/noEmptyCharacterClassInRegex: [^] is valid JS — matches any char including newlines\n const re = /(defineElement\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*defineComponent\\([^]*\\))\\s*\\)/\n const replaced = code.replace(re, (_m, inner: string) => `${inner}, { shadowMode: '${mode}' })`)\n return replaced\n}\n\n/**\n * Classify the compiled output of a single `.aihu` module as either a\n * **static** island (no reactive state — purely declarative DOM) or an\n * **interactive** island (uses the signals reactivity system).\n *\n * The heuristic is intentionally conservative: any source-level reference\n * to a primitive that requires the `defineComponent` owner context flips\n * the file to `'interactive'`. False positives (e.g. a string literal\n * containing `signal(`) are tolerable — they only forfeit the static-island\n * optimisation. False negatives are forbidden: a static-classified file\n * MUST NOT depend on the signals runtime at execution time.\n *\n * Owner-requiring primitives covered:\n * - `signal(`, `computed(`, `effect(`, `setSignal(` (signals runtime)\n * - `onMount(`, `onCleanup(` (lifecycle hooks — throw `no owner` outside\n * `defineComponent` because they push into the active owner's mount/\n * cleanup queues)\n *\n * Plan 3.3 / acceptance criterion 1.\n *\n * @internal\n */\nexport function _classifyIsland(compiledCode: string): 'static' | 'interactive' {\n // Match call sites of the reactive + lifecycle primitives. Use word-boundary\n // anchors so identifiers like `mySignal(` or `__effect(` do not trip the\n // heuristic. The `(` is required so that bare imports of the names in an\n // unused `import { signal }` line do not flip an otherwise-static module.\n return /\\b(?:signal|computed|effect|setSignal|onMount|onCleanup)\\s*\\(/.test(compiledCode)\n ? 'interactive'\n : 'static'\n}\n\n/**\n * Extract the custom element tag name from compiler-emitted code.\n * The compiler always emits `defineElement('tag-name', ...)` as the\n * first call — pull the first string literal argument.\n * Returns `null` if no `defineElement` call is found.\n * @internal\n */\nfunction _extractElementTag(code: string): string | null {\n const m = /defineElement\\(\\s*['\"]([^'\"]+)['\"]/m.exec(code)\n return m ? (m[1] ?? null) : null\n}\n\n/**\n * Instrument a compiled `.aihu` module with HMR support.\n *\n * The compiler always emits:\n *\n * import { defineComponent, defineElement } from '@aihu/runtime'\n * defineElement('tag', defineComponent((_ctx) => { ... }))\n *\n * This function:\n *\n * 1. Adds `_hmrReplace` to the `@aihu/runtime` import.\n * 2. Prepends a module-level slot variable `__aihu_setup__`.\n * 3. Rewrites the single `defineComponent(` call so the setup function\n * is captured via an assignment expression:\n * `defineComponent(__aihu_setup__ = ` (valid JS; assignment has\n * lower precedence than arrow fn, so `defineComponent` still\n * receives the function as its argument).\n * 4. Appends `export { __aihu_setup__ as default }` so that Vite's\n * `import.meta.hot.accept` callback receives the new setup via\n * `newModule.default` on hot reload.\n * 5. Appends the `import.meta.hot.accept` block, gated on `__DEV__`.\n *\n * The `__DEV__` guard ensures production bundlers (where they replace\n * `__DEV__` with `false`) dead-code-eliminate the entire HMR block.\n *\n * @internal\n */\nfunction _buildHmrCode(compiledCode: string, elementTag: string): string {\n // Step 1 — add _hmrReplace to the @aihu/runtime import.\n const withImport = compiledCode.replace(\n /import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/runtime'/,\n (_m, imports: string) => {\n const parts = imports\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (!parts.includes('_hmrReplace')) parts.push('_hmrReplace')\n return `import { ${parts.join(', ')} } from '@aihu/runtime'`\n },\n )\n\n // Step 2+3 — prepend slot variable and rewrite the defineComponent call.\n // Compiler emits exactly one `defineComponent(` followed by a function expr.\n // Rewrite: defineComponent(fn) → defineComponent(__aihu_setup__ = fn)\n // Assignment expression evaluates to `fn`, so defineComponent still\n // receives the setup function as its first argument unchanged.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const preamble = `let __aihu_setup__: ((ctx: any) => any) | undefined\\n`\n\n const patchedBody = withImport.replace(/\\bdefineComponent\\(/, 'defineComponent(__aihu_setup__ = ')\n\n const tag = JSON.stringify(elementTag)\n // Step 4+5 — postamble with default export and HMR acceptance.\n const postamble = `\nexport { __aihu_setup__ as default }\n\nif (typeof __DEV__ !== 'undefined' && __DEV__ && import.meta.hot) {\n import.meta.hot.accept((newModule) => {\n if (!newModule) return\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const newSetup = (newModule as any)['default']\n if (typeof newSetup !== 'function') return\n document.querySelectorAll(${tag}).forEach((el) => {\n _hmrReplace(el as HTMLElement, newSetup)\n })\n })\n}\n`\n\n return preamble + patchedBody + postamble\n}\n\n/**\n * Rewrite an interactive-island module so its `connectedCallback` waits\n * for the element to scroll into view before mounting. Plan 3.3 — applied\n * only when the consumer adds `defer` to the custom element tag (e.g.\n * `<my-counter defer>`); the runtime helper checks the attribute and\n * either mounts immediately or registers an `IntersectionObserver`.\n *\n * Implementation: the helper is added as a `_hydrateOnVisible` import\n * from `@aihu/runtime`, and the compiler-emitted `defineElement(...)`\n * call is wrapped in a `defineElement` that intercepts `connectedCallback`\n * to honour the `defer` attribute.\n *\n * The whole indirection is tree-shaken when no `.aihu` module reaches\n * this branch, because `_hydrateOnVisible` is exported from its own\n * sibling module inside `@aihu/runtime`.\n *\n * @internal\n */\nexport function _buildDeferredHydration(compiledCode: string, elementTag: string): string {\n // Add _hydrateOnVisible to the @aihu/runtime import.\n const withImport = compiledCode.replace(\n /import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/runtime'/,\n (_m, imports: string) => {\n const parts = imports\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (!parts.includes('_hydrateOnVisible')) parts.push('_hydrateOnVisible')\n return `import { ${parts.join(', ')} } from '@aihu/runtime'`\n },\n )\n\n // Wrap the class returned by defineComponent BEFORE defineElement\n // consumes it. The HTML spec caches lifecycle callbacks at\n // customElements.define() time, so we MUST mutate the prototype\n // before that call — not after. We accomplish this with a synchronous\n // helper invoked between defineComponent and defineElement.\n //\n // Source pattern (compiler-emitted):\n // defineElement('tag', defineComponent((_ctx) => { ... }))\n //\n // After this rewrite:\n // defineElement('tag', __aihu_wrap_defer__(defineComponent((_ctx) => { ... })))\n //\n // …with __aihu_wrap_defer__ defined in the appended preamble.\n const patched = withImport.replace(\n /defineElement\\(\\s*('[^']+'|\"[^\"]+\")\\s*,\\s*defineComponent\\(/,\n (_m, tagLit: string) => `defineElement(${tagLit}, __aihu_wrap_defer__(defineComponent(`,\n )\n // Match the closing `))` of the defineElement call. The HMR pass may\n // have inserted `__aihu_setup__ = ` before the inner function, but\n // the trailing `))` shape is unchanged. Replace exactly one occurrence\n // by anchoring on end-of-string trim; bail if the shape does not match.\n if (patched === withImport) {\n // The expected `defineElement(<tag>, defineComponent(` shape was not\n // present (e.g. compiler output changed). Skip defer wrapping rather\n // than emit broken code.\n return compiledCode\n }\n // Add a trailing `)` to balance the extra `(` from __aihu_wrap_defer__.\n // Source shape after _buildHmrCode is:\n // defineElement('tag', defineComponent(__aihu_setup__ = (_ctx) => {...}))\n // export { __aihu_setup__ as default }\n // if (typeof __DEV__ !== ...) { ... }\n // We must close BEFORE the export line. Match the first `))` followed\n // by a newline and `export` (or end-of-string for the unwrapped case).\n let balanced = patched.replace(/\\)\\s*\\)\\s*\\nexport\\s/, ')))\\nexport ')\n if (balanced === patched) {\n // No HMR postamble — the `))` is at end-of-string.\n balanced = patched.replace(/\\)\\s*\\)\\s*$/, ')))\\n')\n }\n if (balanced === patched) {\n // Could not find the matching `))` — bail out.\n return compiledCode\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const helper = `\n// Plan 3.3 (Islands) — defer attribute support. Wraps the constructor\n// returned by defineComponent so instances bearing the \\`defer\\` attribute\n// hydrate lazily via IntersectionObserver. Bare instances retain the\n// eager Plan 3.2 hydration path.\nfunction __aihu_wrap_defer__<T extends typeof HTMLElement>(Ctor: T): T {\n const orig = (Ctor.prototype as unknown as { connectedCallback?: () => void }).connectedCallback\n if (typeof orig !== 'function') return Ctor\n ;(Ctor.prototype as unknown as { connectedCallback: () => void }).connectedCallback = function (this: HTMLElement) {\n if (this.hasAttribute('defer')) {\n _hydrateOnVisible(this, () => orig.call(this))\n } else {\n orig.call(this)\n }\n }\n return Ctor\n}\n`\n void elementTag\n return helper + balanced\n}\n\n/**\n * Build a static-island shim for a compiled module.\n *\n * The compiled module emitted by the Rust codegen has the shape:\n *\n * import { branch, leaf, slot } from '@aihu/arbor'\n * import { defineComponent, defineElement } from '@aihu/runtime'\n * defineElement('tag', defineComponent((_ctx) => { return <tree> }))\n *\n * For a static island we know `<tree>` contains no `signal(`/`computed(`\n * calls. We can therefore:\n *\n * 1. Drop the `@aihu/runtime` import (saves ~600 B gz of defineComponent\n * + defineElement + bootstrap glue).\n * 2. Replace `defineElement(tag, defineComponent(setup))` with a tiny\n * inline class that mounts the tree directly via `mount()` (which the\n * arbor barrel already exports).\n * 3. Tag the file with a `// SCRIBE_STATIC_ISLAND` comment so consumers\n * can audit which routes shipped zero-JS-runtime.\n *\n * Falls back to the original code if the regex shape does not match\n * (defensive: a future compiler change must opt back into static-island\n * emission explicitly rather than silently break).\n *\n * @internal\n */\nexport function _buildStaticIsland(compiledCode: string, elementTag: string): string {\n // Confirm the shape we expect: a single defineElement(...) call wrapping\n // a single defineComponent(...) call. Bail out otherwise.\n const callRe = /defineElement\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*defineComponent\\(/\n if (!callRe.test(compiledCode)) return compiledCode\n\n // Strip the `@aihu/runtime` import line entirely — static islands\n // don't reference defineComponent/defineElement after the rewrite.\n const withoutRuntimeImport = compiledCode.replace(\n /^\\s*import\\s*\\{[^}]*\\}\\s*from\\s*'@aihu\\/runtime'\\s*;?\\s*$/m,\n '',\n )\n\n // Ensure `mount` is imported from @aihu/arbor (it already exposes\n // branch/leaf/slot, so we just append `mount` to the existing list).\n const withArborMount = withoutRuntimeImport.replace(\n /import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/arbor'/,\n (_m, imports: string) => {\n const parts = imports\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (!parts.includes('mount')) parts.push('mount')\n return `import { ${parts.join(', ')} } from '@aihu/arbor'`\n },\n )\n\n // Replace `defineElement('tag', defineComponent((_ctx) => { ... }))`\n // with an inline `customElements.define` whose connectedCallback mounts\n // the static tree. The setup function is captured verbatim by replacing\n // the wrapping calls with anonymous-IIFE bookends.\n const tagJson = JSON.stringify(elementTag)\n const rewritten = withArborMount\n .replace(\n /defineElement\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*defineComponent\\(/,\n `customElements.define(${tagJson}, class extends HTMLElement {\\n connectedCallback() {\\n const root = this.attachShadow({ mode: 'open' })\\n const __aihu_setup__ = (`,\n )\n .replace(\n /\\)\\s*\\)\\s*$/,\n `)\\n mount(__aihu_setup__({ host: root, element: this }), root)\\n }\\n})\\n`,\n )\n\n return `// SCRIBE_STATIC_ISLAND — zero @aihu/runtime references\\n${rewritten}`\n}\n\n/**\n * Compile a .aihu source string to TypeScript.\n * map is null — source maps are deferred to v1 (OQ-C8)\n *\n * B3b — when `sidecarOut` is provided, also writes the per-SFC `.aihu.ts`\n * sidecar at that path. Callers (e.g. the Vite plugin) typically pass\n * `<source-id>.ts` so `tsc --noEmit` discovers per-SFC template expressions.\n */\nexport function transform(\n source: string,\n id: string,\n options?: { sidecarOut?: string; target?: 'client' | 'server' | 'universal' },\n): { code: string; map: null } {\n const stem = basename(id, '.aihu')\n const args = ['--stdin', '--tag', stem, '--path', id]\n if (options?.sidecarOut) {\n args.push('--sidecar-out', options.sidecarOut)\n }\n // T6 (go-public demo) — thread the build target so a client bundle gets the\n // policy-free `@agent` dispatcher (and the per-instance registration the\n // capability bridge needs) instead of the server `__agentBinding`. Defaults to\n // the compiler's `universal` target when omitted (existing behaviour).\n if (options?.target) {\n args.push('--target', options.target)\n }\n const code = execFileSync(resolveBinPath(), args, {\n input: source,\n encoding: 'utf8',\n })\n return {\n code,\n map: null, // source maps deferred to v1 (OQ-C8)\n }\n}\n\n/**\n * Escape a CSS string for safe interpolation inside a JS template literal.\n * The Rust codegen places the authored `@style` body raw inside a backtick\n * literal, so it already assumes no backticks in `@style`. css-engine output\n * (theme tokens + utility rules) likewise never contains backticks, but we\n * escape `\\`, `` ` `` and `${` defensively so a future token value can't\n * break out of the literal.\n *\n * @internal\n */\nfunction _escapeForTemplateLiteral(css: string): string {\n return css.replace(/\\\\/g, '\\\\\\\\').replace(/`/g, '\\\\`').replace(/\\$\\{/g, '\\\\${')\n}\n\n/**\n * Fold css-engine-produced scoped CSS into a compiled `.aihu` module.\n *\n * The Rust codegen emits the authored `@style` block (when present) as:\n *\n * const __style__ = new CSSStyleSheet();\n * __style__.replaceSync(`<authored css>`);\n * defineElement('tag', defineComponent((ctx) => {\n * (ctx.host as ShadowRoot).adoptedStyleSheets = [__style__];\n * return ...\n * }))\n *\n * css-engine's `compileSfc` output is the COMPLETE per-SFC stylesheet:\n * `:host` theme tokens, the variant-resolved utility-class rules, AND the\n * folded authored `@style` block (under an `authored @style` CSS comment).\n * So it is authoritative — we adopt it as the single shadow `<style>` and\n * the authored `@style` keeps emitting through it (acceptance: \"@style still\n * emits correctly alongside\").\n *\n * Two shapes are handled:\n *\n * 1. **SFC has an `@style` block** — the Rust codegen already declared\n * `__style__` with the raw `@style` body. We REPLACE that body with the\n * css-engine output (which already CONTAINS the `@style` block) so the\n * `@style` rules are not duplicated. The existing `adoptedStyleSheets`\n * assignment is reused unchanged.\n *\n * 2. **SFC has NO `@style` block** — there is no `__style__`. We inject a\n * fresh `__style__` declaration after the last import and an\n * `adoptedStyleSheets` assignment as the first statement of the setup\n * function. The compiler emits the setup param as `_ctx` in this case;\n * we rename it to `ctx` so the injected `ctx.host` reference resolves.\n *\n * Runs on the RAW compiled output BEFORE the island / HMR / auto-wiring\n * transforms so those passes operate on the folded module uniformly:\n * - The static-island shim calls `__aihu_setup__({ host: root, ... })`\n * where `root` is the shadow root, so `ctx.host` is valid there too.\n * - The HMR / defer passes only touch the `defineElement(...)` wrapper and\n * the runtime import; they do not disturb `__style__` or the setup body.\n *\n * No-ops (returns input unchanged) when `css` is empty/whitespace.\n *\n * @internal\n */\nexport function _foldCssEngineStyles(compiledCode: string, css: string): string {\n if (!css.trim()) return compiledCode\n const escaped = _escapeForTemplateLiteral(css)\n\n // Shape 1 — an authored @style block already declared __style__. css-engine\n // output already includes that @style block, so REPLACE the replaceSync body\n // (between the backticks) wholesale to avoid duplicating the @style rules.\n // The codegen emits `__style__.replaceSync(`<body>`);` as a single statement;\n // match the body non-greedily up to the closing backtick + paren.\n // biome-ignore lint/correctness/noEmptyCharacterClassInRegex: [^] matches any char incl. newlines\n const styleBodyRe = /(__style__\\.replaceSync\\(`)[^]*?(`\\);)/\n if (styleBodyRe.test(compiledCode)) {\n // Use a function replacer so any `$` in the CSS isn't read as a\n // replacement-pattern backreference.\n return compiledCode.replace(styleBodyRe, (_m, open: string, close: string) => {\n return `${open}${escaped}${close}`\n })\n }\n\n // Shape 2 — no @style block. Inject a fresh stylesheet + adoption.\n // Bail (no-op) if the expected defineComponent setup shape is absent.\n const setupRe = /defineComponent\\(\\s*\\((_ctx|ctx)\\)\\s*=>\\s*\\{/\n const m = setupRe.exec(compiledCode)\n if (m == null) return compiledCode\n\n // Inject the module-level stylesheet declaration after the last import line.\n const lines = compiledCode.split('\\n')\n let lastImportIdx = -1\n for (let i = lines.length - 1; i >= 0; i--) {\n const t = (lines[i] ?? '').trim()\n if (t.startsWith('import ') || t.startsWith('import{')) {\n lastImportIdx = i\n break\n }\n }\n const decl = `const __style__ = new CSSStyleSheet();\\n__style__.replaceSync(\\`${escaped}\\`);`\n if (lastImportIdx !== -1) {\n lines.splice(lastImportIdx + 1, 0, decl)\n } else {\n lines.unshift(decl)\n }\n let withDecl = lines.join('\\n')\n\n // Rename the setup param to `ctx` (codegen emits `_ctx` when no @style/ctx\n // usage) and inject the adoption as the first statement of the setup body.\n withDecl = withDecl.replace(\n /defineComponent\\(\\s*\\((?:_ctx|ctx)\\)\\s*=>\\s*\\{/,\n 'defineComponent((ctx) => {\\n (ctx.host as ShadowRoot).adoptedStyleSheets = [__style__];',\n )\n return withDecl\n}\n\n/**\n * Virtual-module prefix used by the `shadowMode === 'none'` branch to route\n * per-SFC utility CSS through Vite's built-in CSS pipeline. The plugin\n * (`aihuCompilerPlugin`) implements `resolveId` + `load` for ids matching\n * `VIRTUAL_UTILITY_PREFIX + '<hash>.css'`, returning the stored CSS body so\n * Vite hoists it into the bundle CSS asset (`dist/assets/*.css`) — NOT into\n * `host.adoptedStyleSheets`, which is a no-op when there is no shadow root.\n *\n * The trailing `.css` extension is mandatory: Vite's built-in CSS plugin keys\n * off the extension to know it should run the CSS pipeline on the module.\n *\n * @internal\n */\nexport const VIRTUAL_UTILITY_PREFIX = '\\0virtual:aihu-utility/'\n\n/**\n * Stable short hash for keying the virtual-CSS module per source-SFC id.\n *\n * djb2-style; collisions are tolerable here because (a) each entry stores its\n * own CSS body, so a hash collision would only matter if two distinct SFCs\n * hashed to the same key AND were processed concurrently; (b) collisions are\n * recoverable — Vite would simply load the wrong CSS for one SFC; we still\n * keyed on the unhashed id internally to avoid that. The hash only appears in\n * the bundled asset URL.\n *\n * @internal\n */\nexport function _hashIdForUtilityCss(id: string): string {\n let h = 5381\n for (let i = 0; i < id.length; i++) {\n h = ((h * 33) ^ id.charCodeAt(i)) >>> 0\n }\n return h.toString(36)\n}\n\n/**\n * Bug 6 — `shadowMode === 'none'` branch.\n *\n * Routes utility CSS to Vite's CSS pipeline (which folds CSS imports into the\n * bundled `dist/assets/*.css` asset) instead of to `host.adoptedStyleSheets`\n * (a no-op on an element with no shadow root). Returns a prelude `import` that\n * the plugin's `resolveId` + `load` hooks resolve to the stored CSS body.\n *\n * The `__style__` shadow path is NOT invoked here — utility CSS for a\n * cascade-mode component MUST hit the global stylesheet, not a per-element\n * stylesheet that would be silently dropped by `HTMLElement`'s setter.\n *\n * Authored `@style` blocks still emit through the Rust codegen's `<style>`\n * node and are unaffected. (If a component opts into `shadowMode: 'none'` and\n * authors an `@style` block, the codegen still wires it through the\n * non-shadow path — that is the runtime's contract, not this hook's.)\n *\n * @internal\n */\nexport function _foldCssEngineStylesGlobal(\n compiledCode: string,\n css: string,\n id: string,\n): { code: string; virtualId: string } | null {\n if (!css.trim()) return null\n const hash = _hashIdForUtilityCss(id)\n const virtualId = `${VIRTUAL_UTILITY_PREFIX}${hash}.css`\n // Prepend the CSS import as a side-effect-only import so Vite's CSS plugin\n // hoists it into the bundle. We use the NULL-byte virtual id form\n // (Rollup/Vite convention for \"owned by this plugin\"); other plugins will\n // skip it. The compiler's transform returns this prepended code, which the\n // downstream esbuild/oxc strip leaves untouched (it's just an import).\n const prelude = `import ${JSON.stringify(virtualId)};\\n`\n return { code: prelude + compiledCode, virtualId }\n}\n\n// ─── v1.0.10a — compiler AST-export hook ─────────────────────────────────────\n//\n// Thin TS wrapper over the `aihu-compile --ast-json` flag. Returns the parsed\n// `.aihu` SFC AST in a stable, serializable shape consumed by the CSS engine's\n// AST scanner (`css-2-ast-scanner`). Mirrors the typed contract in\n// `docs/superpowers/specs/compiler-ast-export-hook.md` §4.\n\n/** Top-level AST export — one per .aihu SFC. */\nexport interface SfcAst {\n /** Resolved custom-element tag name (meta.name → route.name → file stem). */\n tag: string\n /** AST schema version — bumped on any breaking shape change (semver-tied). */\n astVersion: 1\n /** The @style block, if the SFC declared one. */\n style: SfcStyleBlock | null\n /** Parsed template tree. null when the SFC has no @template block. */\n template: SfcNode[] | null\n /** SFC-level metadata. */\n meta: SfcMeta\n}\n\nexport interface SfcStyleBlock {\n /** Verbatim CSS body of the @style block (braces stripped, $global token removed). */\n content: string\n /** 'scoped' (default) or 'global' (@style { $global ... }). */\n scope: 'scoped' | 'global'\n}\n\nexport interface SfcMeta {\n /** From @meta { name } / @route { name } / file stem — never null after resolution. */\n name: string\n}\n\n/** Discriminated union mirroring Rust `TemplateNode`. */\nexport type SfcNode =\n | { kind: 'element'; tag: string; attrs: SfcAttr[]; children: SfcNode[] }\n | { kind: 'macroElement'; name: string; attrs: SfcAttr[]; children: SfcNode[] }\n | { kind: 'text'; value: string }\n | { kind: 'interpolation'; expr: string }\n | { kind: 'ifBlock'; branches: Array<{ cond: string; body: SfcNode[] }> }\n | {\n kind: 'eachBlock'\n list: string\n item: string\n idx: string | null\n key: string | null\n body: SfcNode[]\n emptyBody: SfcNode[] | null\n }\n | { kind: 'htmlBlock'; expr: string }\n\n/** Discriminated union mirroring Rust `Attr` — the three class-forms key on `kind`. */\nexport type SfcAttr =\n | { kind: 'static'; name: string; value: string } // Form A\n | { kind: 'binding'; name: string; expr: string } // Form B\n | { kind: 'macro'; name: string; value: SfcMacroValue } // Form C (and on:/bind:/emit:/if/each/…)\n\nexport type SfcMacroValue =\n | { form: 'quoted'; value: string }\n | { form: 'curly'; expr: string }\n | { form: 'boolean' }\n\n/**\n * Parse a .aihu source string to its structured AST.\n *\n * Thin wrapper over the Rust binary (mirrors `transform()`): spawns\n * `aihu-compile --stdin --tag <stem> --ast-json`, feeds `source` on stdin, and\n * `JSON.parse`s stdout. `id` is optional and only used to derive the tag stem\n * and the `--path` arg (for `@route` C500 checks), identical to `transform()`.\n *\n * Throws on parse failure — the Rust binary exits non-zero and `execFileSync`\n * surfaces the diagnostic (same error path as `transform()`).\n */\nexport function compileToAst(source: string, id?: string): SfcAst {\n const stem = id ? basename(id, '.aihu') : 'Component'\n const args = ['--stdin', '--tag', stem, '--ast-json']\n if (id) {\n args.push('--path', id)\n }\n const json = execFileSync(resolveBinPath(), args, {\n input: source,\n encoding: 'utf8',\n })\n return JSON.parse(json) as SfcAst\n}\n\n/**\n * Inject `_setMount(mount)` + `_setSignal(signal)` auto-wiring into a compiled\n * `.aihu` module. Adds the necessary symbols to existing imports and inserts\n * the boot calls right after the last `import` statement.\n *\n * @internal\n */\nexport function _injectAutoWiring(code: string): string {\n // 1. Add `mount` to the @aihu/arbor import (or create it).\n let result: string\n if (code.includes(\"from '@aihu/arbor'\")) {\n result = code.replace(\n /import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/arbor'/,\n (_m: string, imports: string) => {\n const parts = imports\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (!parts.includes('mount')) parts.push('mount')\n return `import { ${parts.join(', ')} } from '@aihu/arbor'`\n },\n )\n } else {\n result = `import { mount } from '@aihu/arbor'\\n${code}`\n }\n\n // 2. Add `signal` to the non-type @aihu/signals import (or create it).\n // Note: `import\\s+\\{` does NOT match `import type {` (the regex needs `{` immediately\n // after whitespace, whereas `import type {` has `type` in between). No negation guard\n // is needed — the replace callback below already skips `import type` lines.\n if (/import\\s+\\{[^}]*\\}\\s+from\\s+'@aihu\\/signals'/.test(result)) {\n // There IS a value import from signals — add `signal` if missing.\n result = result.replace(\n /import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/signals'/,\n (_m: string, imports: string) => {\n // Skip type-only imports\n if (_m.startsWith('import type')) return _m\n const parts = imports\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (!parts.includes('signal')) parts.push('signal')\n return `import { ${parts.join(', ')} } from '@aihu/signals'`\n },\n )\n } else if (!/import.*from\\s*'@aihu\\/signals'/.test(result)) {\n // No signals import at all — insert after arbor import\n result = result.replace(\n /import\\s*\\{[^}]*\\}\\s*from\\s*'@aihu\\/arbor'/,\n (m: string) => `${m}\\nimport { signal } from '@aihu/signals'`,\n )\n }\n // If only `import type { Signal }` exists, insert value import after it\n else if (\n /import\\s+type\\s+\\{[^}]*\\}\\s+from\\s+'@aihu\\/signals'/.test(result) &&\n !result.match(/import\\s+\\{[^}]*\\}\\s+from\\s+'@aihu\\/signals'/)\n ) {\n result = result.replace(\n /(import\\s+type\\s+\\{[^}]*\\}\\s+from\\s+'@aihu\\/signals')/,\n (_m: string, typeImport: string) => `${typeImport}\\nimport { signal } from '@aihu/signals'`,\n )\n }\n\n // 3. Add `_setMount`, `_setSignal` to the @aihu/runtime import.\n result = result.replace(\n /import\\s*\\{([^}]*)\\}\\s*from\\s*'@aihu\\/runtime'/,\n (_m: string, imports: string) => {\n const parts = imports\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (!parts.includes('_setMount')) parts.push('_setMount')\n if (!parts.includes('_setSignal')) parts.push('_setSignal')\n return `import { ${parts.join(', ')} } from '@aihu/runtime'`\n },\n )\n\n // 4. Insert boot calls after the last `import` statement.\n const lines = result.split('\\n')\n let lastImportIdx = -1\n for (let i = lines.length - 1; i >= 0; i--) {\n const t = (lines[i] ?? '').trim()\n if (t.startsWith('import ') || t.startsWith('import{')) {\n lastImportIdx = i\n break\n }\n }\n if (lastImportIdx !== -1) {\n lines.splice(lastImportIdx + 1, 0, '_setMount(mount)', '_setSignal(signal)', '')\n result = lines.join('\\n')\n }\n\n return result\n}\n\n/**\n * Vite plugin that compiles .aihu files to TypeScript during build and dev.\n *\n * Use `enforce: 'pre'` so the hook fires before Vite/Rollup's built-in\n * parsers attempt to process the raw .aihu content as JavaScript.\n *\n * @example\n * // vite.config.ts\n * import { aihuCompilerPlugin } from '@aihu/compiler'\n * export default { plugins: [aihuCompilerPlugin()] }\n *\n * **Known Limitation — Bun + Rollup4 ESM incompatibility (v0):**\n *\n * `bun vite build` fails in the `fixtures/vite-counter` fixture with two\n * cascading errors:\n *\n * 1. **Missing devDependency:** `vite` is declared only as an optional\n * `peerDependency` in `packages/compiler/package.json`. Bun does not\n * install optional peers automatically, so `bun vite build` exits\n * immediately with `Cannot find package 'vite'`.\n *\n * 2. **Bun + Rollup4 bridge:** Even with Vite installed, Bun processes\n * `vite.config.ts` through its own internal bundler before handing off\n * to Rollup4. When `@aihu/compiler` is resolved from the workspace\n * symlink (`dist/index.js`), Bun's ESM loader evaluates the module at\n * config-load time. The subprocess call inside `transform()` depends on\n * the Rust binary being at `../bin/aihu-compile` relative to `dist/`\n * (written by the postinstall hook). In a dev workspace where postinstall\n * has not run, this path does not exist and `execFileSync` throws. Bun surfaces\n * the error as a config-load failure, not a per-file transform error,\n * causing the entire build to abort before any `.aihu` file is\n * processed.\n *\n * **Workaround (v0):** Use `bun run integrate.ts` directly from\n * `packages/compiler/fixtures/vite-counter/`. This script calls\n * `transform()` from `@aihu/compiler` without involving Vite or Rollup.\n * Preconditions: (1) `cargo build --release` in `packages/compiler/`,\n * (2) `bun install` at the repo root.\n *\n * **v1 resolution:** Add `vite` as a `devDependency` in\n * `packages/compiler/package.json`; add a WASM or pre-built binary\n * strategy so the Rust binary is bundled with the npm package and does not\n * require a separate `cargo build --release` step.\n */\n/**\n * Minimal structural type for the `@aihu/css-engine` module surface this\n * plugin uses. Declared locally so the compiler never type-imports the\n * css-engine package (which would create a compile-time edge against an\n * optional peer that may be absent).\n *\n * @internal\n */\ninterface CssEngineModule {\n compileSfc(source: string, id?: string): string\n}\n\n// Memoised resolution of the optional `@aihu/css-engine` peer. `undefined`\n// = not yet attempted; `null` = attempted and unavailable (no-op path);\n// a module object = available. The dynamic import is attempted once per\n// process — repeated absence does not re-pay the resolution cost.\nlet _cssEngine: CssEngineModule | null | undefined\n\n// Whether we've already surfaced a one-shot warning that css-engine resolved\n// but `compileSfc` threw (typically: native css-core binary unresolvable in\n// the consumer's install — e.g. lockfile pins the per-platform placeholder\n// version). The transform stays non-fatal, but going fully silent leaves users\n// chasing \"why did my utility classes never emit?\". One warn per process.\nlet _cssEngineWarned = false\n\n// The optional-peer module specifier, held in a VARIABLE so TypeScript never\n// statically resolves `@aihu/css-engine`'s declarations at typecheck time.\n// css-engine depends on @aihu/compiler for its AST, so the two form a\n// circular package relationship; under CI's frozen install + moon build\n// ordering, css-engine's `dist`/`.d.ts` are not guaranteed to exist when\n// `compiler:typecheck` runs. A literal `import('@aihu/css-engine')` makes the\n// compiler emit TS2307 in that window (the `as` cast affects the RESULT type\n// only, not whether TS attempts module resolution). Resolving through this\n// variable keeps the import fully dynamic — no compile-time edge on the peer.\nconst _CSS_ENGINE_SPECIFIER = '@aihu/css-engine'\n\n/**\n * Lazily resolve `@aihu/css-engine` and compile a `.aihu` source's utility\n * classes to scoped CSS. Returns `''` when css-engine is not installed\n * (the optional-peer no-op path) or when compilation fails for any reason —\n * a CSS-engine failure MUST NOT break an otherwise-valid `.aihu` build.\n *\n * Sets `process.env.SCRIBE_COMPILE_BIN` to this plugin's resolved compiler\n * binary before calling `compileSfc`: css-engine re-derives the SFC AST via\n * its own bundled copy of `compileToAst`, whose binary path is resolved\n * relative to the css-engine package — which does NOT ship the compiler\n * binary. Pointing it at our `binPath` guarantees the AST css-engine parses\n * is produced by the exact same compiler this build uses.\n *\n * @internal\n */\nasync function _maybeCompileUtilityCss(source: string, id: string): Promise<string> {\n if (_cssEngine === null) return ''\n // Ensure css-engine's bundled `compileToAst` spawns the SAME compiler\n // binary this plugin uses (it has no compiler binary of its own). Set\n // this BEFORE the dynamic import so that any module-load-time evaluation\n // of `process.env.SCRIBE_COMPILE_BIN` in css-engine's bundled dist (older\n // bundles capture this into a module-scope const at line 8 of\n // `packages/css-engine/dist/index.js`) sees the correct value. After Bug 6,\n // the source `compileToAst` resolves the bin lazily on each call, so once\n // css-engine is rebuilt this set-before-import is belt-and-braces.\n if (process.env.SCRIBE_COMPILE_BIN == null) {\n process.env.SCRIBE_COMPILE_BIN = resolveBinPath()\n }\n if (_cssEngine === undefined) {\n try {\n // Guarded, lazy, OPTIONAL — see the plugin transform for the rationale.\n // Importing via the `_CSS_ENGINE_SPECIFIER` variable (not a string\n // literal) keeps this fully dynamic: TS does NOT resolve the peer's\n // `.d.ts` at typecheck time, so `compiler:typecheck` passes even when\n // css-engine's `dist` has not been built (the CI build-order window).\n _cssEngine = (await import(_CSS_ENGINE_SPECIFIER)) as unknown as CssEngineModule\n } catch {\n _cssEngine = null\n return ''\n }\n }\n try {\n return _cssEngine.compileSfc(source, id)\n } catch (err) {\n // A css-engine compile failure is non-fatal: fall back to the no-op\n // path (utility classes don't emit) rather than aborting the build.\n // BUT — silently swallowing this means a user who clearly intends\n // css-engine to be active (the peer resolved) will never know their\n // utility classes are inert. Surface a one-shot warning with the\n // underlying error + an install/upgrade hint. Idempotent per process.\n if (!_cssEngineWarned) {\n _cssEngineWarned = true\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(\n `[@aihu/compiler] @aihu/css-engine is installed but compileSfc() failed; ` +\n `utility classes will not emit. Original error: ${msg}\\n` +\n `Hint: ensure the native css-core binary is installed ` +\n `(install/upgrade @aihu/css-engine + its per-platform optional dep, ` +\n `or run \\`cargo build --release -p aihu-css-core\\` in a dev clone).`,\n )\n }\n return ''\n }\n}\n\nexport function aihuCompilerPlugin(options?: AihuCompilerPluginOptions): VitePlugin {\n const islandsEnabled = options?.islands !== false\n const shadowMode = options?.shadowMode\n const target = options?.target\n\n // Bug 6 — per-instance store of virtual utility-CSS modules. Keyed by the\n // full virtual id (NUL-prefixed). Populated by the transform hook when\n // `shadowMode === 'none'` produces utility CSS; drained by the `load` hook\n // when Vite's CSS pipeline asks for the module body. Lives on the plugin\n // instance so multiple `aihuCompilerPlugin()` calls in the same build don't\n // alias each other's css.\n const utilityCssStore = new Map<string, string>()\n\n return {\n name: 'aihu-compiler',\n enforce: 'pre',\n resolveId(source) {\n // Own all `\\0virtual:aihu-utility/<hash>.css` ids so Vite's resolver\n // doesn't try to find them on disk. Returning the id verbatim is the\n // Rollup convention for \"I'll handle the load.\"\n if (source.startsWith(VIRTUAL_UTILITY_PREFIX)) return source\n return null\n },\n load(id) {\n if (!id.startsWith(VIRTUAL_UTILITY_PREFIX)) return null\n // Vite's CSS pipeline runs on the returned source because the id ends\n // in `.css` — it parses, minifies (in build), and hoists into a CSS\n // asset chunk that lands in `dist/assets/<name>-<hash>.css`.\n return utilityCssStore.get(id) ?? null\n },\n transform(code, id) {\n // Strip Vite query strings (e.g. `?import`, `?t=...`) before checking the extension.\n const rawId = id.split('?')[0]!\n if (!rawId.endsWith('.aihu')) return\n return (async () => {\n // B3b — write per-SFC `.aihu.ts` sidecar adjacent to source so\n // `tsc --noEmit` over `**/*.aihu.ts` type-checks template\n // expressions end-to-end (Architect spec §7 path (i)).\n const sidecarOut = `${rawId}.ts`\n const result = transform(code, rawId, target ? { sidecarOut, target } : { sidecarOut })\n let compiled = shadowMode != null ? _injectShadowMode(result.code, shadowMode) : result.code\n\n // ── css-engine hook (optional, lazy, no circular dep) ──────────────\n // @aihu/css-engine depends on @aihu/compiler (for its AST), so the\n // compiler MUST NOT hard-depend on it. It is declared an OPTIONAL\n // peerDependency and pulled in ONLY via this guarded dynamic import:\n // when present, we compile the SFC's utility classes to scoped CSS\n // and fold it into the component's shadow `<style>`; when absent the\n // import throws and we no-op (utility classes simply don't emit —\n // the pre-hook behaviour). This keeps css-engine an opt-in enhancement\n // with zero dependency cycle.\n const utilityCss = await _maybeCompileUtilityCss(code, rawId)\n if (utilityCss) {\n if (shadowMode === 'none') {\n // Bug 6 — no shadow root → `host.adoptedStyleSheets` is a no-op.\n // Route utility CSS through Vite's CSS pipeline via a virtual\n // `.css` import so it lands in `dist/assets/*.css` and reaches the\n // global cascade. The authored `@style` block (if any) still\n // emits via the Rust codegen's normal path and is unaffected.\n const folded = _foldCssEngineStylesGlobal(compiled, utilityCss, rawId)\n if (folded) {\n utilityCssStore.set(folded.virtualId, utilityCss)\n compiled = folded.code\n }\n } else {\n // `shadowMode: 'open' | 'closed'` (default): fold into the\n // per-component `CSSStyleSheet` adopted by the shadow root.\n compiled = _foldCssEngineStyles(compiled, utilityCss)\n }\n }\n\n const elementTag = _extractElementTag(compiled)\n\n let out: string\n\n // Plan 3.3 — static-island fast path. Bypasses HMR injection because\n // a component with no signals has no setup state to hot-replace.\n // Static islands strip @aihu/runtime entirely — do NOT inject auto-wiring\n // (it would reference _setMount/_setSignal as undefined identifiers).\n if (islandsEnabled && elementTag !== null && _classifyIsland(compiled) === 'static') {\n out = _buildStaticIsland(compiled, elementTag)\n } else if (elementTag !== null) {\n // Inject HMR instrumentation. The injected block is gated on\n // `typeof __DEV__ !== 'undefined' && __DEV__` so production\n // bundlers dead-code-eliminate it when they set __DEV__ = false.\n out = _buildHmrCode(compiled, elementTag)\n // Plan 3.3 — interactive islands also gain `defer` attribute\n // support so individual instances can opt into lazy hydration.\n out = _buildDeferredHydration(out, elementTag)\n // Inject auto-wiring so consumers don't need a manual main.ts bootstrap.\n out = _injectAutoWiring(out)\n } else {\n out = compiled\n // Inject auto-wiring so consumers don't need a manual main.ts bootstrap.\n out = _injectAutoWiring(out)\n }\n\n // The Rust compiler emits TypeScript (type casts, import type, etc.) and\n // the injected HMR / defer helpers also contain TS generics and casts.\n // Vite does NOT re-run its TS-strip step when a plugin returns code for a\n // non-.ts ID, so we must strip types ourselves before returning.\n //\n // Priority: always try transformWithEsbuild first — it strips types to\n // plain JS in both Vite 5 (via esbuild) and Vite 8 (deprecated wrapper).\n // Using moduleType:'ts' only as a last resort because `import('vite')`\n // resolves to the root node_modules vite (which may be v8 even when a\n // consumer project runs v5), causing v5's Rollup to receive raw TypeScript\n // and fail on import-type / as-casts.\n try {\n const vite = await import('vite')\n if ('transformWithEsbuild' in vite && typeof vite.transformWithEsbuild === 'function') {\n const stripped = await vite.transformWithEsbuild(out, 'component.ts', {\n target: 'esnext',\n sourcemap: false,\n })\n return { code: stripped.code, map: null }\n }\n // Fallback for future Vite versions where esbuild is fully removed:\n // return TS and let Rolldown strip types natively.\n // biome-ignore lint/suspicious/noExplicitAny: moduleType is rolldown API\n return { code: out, moduleType: 'ts', map: null } as any\n } catch {\n // If running outside Vite (e.g. tests, standalone transform), return as-is.\n return { code: out, map: null }\n }\n })()\n },\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aihu/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,19 +30,15 @@
|
|
|
30
30
|
"codemod:template-syntax": "bun js/codemods/template-syntax/run-migration.ts"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"vite": ">=5.0.0"
|
|
34
|
-
"@aihu/css-engine": ">=0.2.4"
|
|
33
|
+
"vite": ">=5.0.0"
|
|
35
34
|
},
|
|
36
35
|
"peerDependenciesMeta": {
|
|
37
36
|
"vite": {
|
|
38
37
|
"optional": true
|
|
39
|
-
},
|
|
40
|
-
"@aihu/css-engine": {
|
|
41
|
-
"optional": true
|
|
42
38
|
}
|
|
43
39
|
},
|
|
44
40
|
"devDependencies": {
|
|
45
|
-
"@aihu/css-engine": "0.
|
|
41
|
+
"@aihu/css-engine": "0.4.0"
|
|
46
42
|
},
|
|
47
43
|
"description": "Single File Component (.aihu) compiler — Rust binary + JS glue.",
|
|
48
44
|
"repository": {
|