@barefootjs/jsx 0.27.0 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/compiler.d.ts.map +1 -1
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +955 -273
- package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts +6 -0
- package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts +2 -1
- package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts +7 -0
- package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts +107 -0
- package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts.map +1 -0
- package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts +9 -1
- package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts +203 -0
- package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts.map +1 -0
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +10 -0
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/branch-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts +98 -0
- package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts.map +1 -0
- package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts +5 -0
- package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow.d.ts.map +1 -1
- package/dist/ir-to-client-js/imports.d.ts +26 -2
- package/dist/ir-to-client-js/imports.d.ts.map +1 -1
- package/dist/value-references.d.ts +48 -0
- package/dist/value-references.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +162 -36
- package/src/__tests__/client-js-generation.test.ts +4 -1
- package/src/__tests__/composite-branch-loop.test.ts +12 -3
- package/src/__tests__/conditional-mapArray-key.test.ts +7 -1
- package/src/__tests__/create-selector.test.ts +21 -7
- package/src/__tests__/ir-to-client-js/imports.test.ts +98 -1
- package/src/__tests__/lazy-row-eligibility.test.ts +692 -0
- package/src/__tests__/loop-branch-bare-expression-reactive-text.test.ts +98 -0
- package/src/__tests__/loop-fallback-wrap.test.ts +31 -12
- package/src/__tests__/loop-hoisted-template.test.ts +10 -6
- package/src/__tests__/static-loop-csr-materialize.test.ts +6 -1
- package/src/__tests__/type-only-import-leak.test.ts +60 -0
- package/src/analyzer.ts +3 -0
- package/src/compiler.ts +6 -2
- package/src/errors.ts +11 -0
- package/src/index.ts +4 -0
- package/src/ir-to-client-js/collect-elements.ts +48 -13
- package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +6 -0
- package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +35 -12
- package/src/ir-to-client-js/control-flow/plan/build-insert.ts +9 -2
- package/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts +305 -0
- package/src/ir-to-client-js/control-flow/plan/build-loop.ts +44 -12
- package/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts +440 -0
- package/src/ir-to-client-js/control-flow/plan/loop.ts +10 -0
- package/src/ir-to-client-js/control-flow/stringify/branch-loop.ts +22 -0
- package/src/ir-to-client-js/control-flow/stringify/lazy-row.ts +478 -0
- package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +6 -1
- package/src/ir-to-client-js/control-flow/stringify/loop.ts +23 -0
- package/src/ir-to-client-js/control-flow.ts +7 -2
- package/src/ir-to-client-js/imports.ts +53 -4
- package/src/jsx-to-ir.ts +18 -1
- package/src/value-references.ts +108 -0
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Import detection and DOM import management.
|
|
3
3
|
*/
|
|
4
4
|
import type { ComponentIR } from '../types.ts';
|
|
5
|
-
export declare const RUNTIME_IMPORT_CANDIDATES: readonly ['createSignal', 'createMemo', 'createEffect', 'onCleanup', 'onMount', 'hydrate', 'insert', 'getLoopChildren', 'getLoopNodes', 'mapArray', 'mapArrayAnchored', 'patchLeaf', 'createDisposableEffect', 'createComponent', 'renderChild', 'registerComponent', 'registerTemplate', 'initChild', 'upsertChild', 'createPortal', 'provideContext', 'createContext', 'useContext', 'forwardProps', 'applyRestAttrs', 'splitProps', 'spreadAttrs', 'styleToCss', 'escapeAttr', 'escapeText', 'escapeTextOrNode', 'qsa', 'qsaItem', 'qsaChildScope', 'qsaChildScopes', 'upsertChildItem', '__slot', '__bfSlot', '__bfText', 'claimSlots', 'lazySlots', 'beginTurn', 'endTurn', 'date', 'formatDate'];
|
|
5
|
+
export declare const RUNTIME_IMPORT_CANDIDATES: readonly ['createSignal', 'createMemo', 'createEffect', 'onCleanup', 'onMount', 'hydrate', 'insert', 'getLoopChildren', 'getLoopNodes', 'mapArray', 'mapArrayAnchored', 'mapArrayLazy', 'patchLeaf', 'createDisposableEffect', 'createComponent', 'renderChild', 'registerComponent', 'registerTemplate', 'initChild', 'upsertChild', 'createPortal', 'provideContext', 'createContext', 'useContext', 'forwardProps', 'applyRestAttrs', 'splitProps', 'spreadAttrs', 'styleToCss', 'escapeAttr', 'escapeText', 'escapeTextOrNode', 'qsa', 'qsaItem', 'qsaChildScope', 'qsaChildScopes', 'upsertChildItem', '__slot', '__bfSlot', '__bfText', 'claimSlots', 'lazySlots', 'lazyClaimSlots', 'textOrNode', 'beginTurn', 'endTurn', 'date', 'formatDate'];
|
|
6
6
|
/** @deprecated Use RUNTIME_IMPORT_CANDIDATES */
|
|
7
|
-
export declare const DOM_IMPORT_CANDIDATES: readonly ["createSignal", "createMemo", "createEffect", "onCleanup", "onMount", "hydrate", "insert", "getLoopChildren", "getLoopNodes", "mapArray", "mapArrayAnchored", "patchLeaf", "createDisposableEffect", "createComponent", "renderChild", "registerComponent", "registerTemplate", "initChild", "upsertChild", "createPortal", "provideContext", "createContext", "useContext", "forwardProps", "applyRestAttrs", "splitProps", "spreadAttrs", "styleToCss", "escapeAttr", "escapeText", "escapeTextOrNode", "qsa", "qsaItem", "qsaChildScope", "qsaChildScopes", "upsertChildItem", "__slot", "__bfSlot", "__bfText", "claimSlots", "lazySlots", "beginTurn", "endTurn", "date", "formatDate"];
|
|
7
|
+
export declare const DOM_IMPORT_CANDIDATES: readonly ["createSignal", "createMemo", "createEffect", "onCleanup", "onMount", "hydrate", "insert", "getLoopChildren", "getLoopNodes", "mapArray", "mapArrayAnchored", "mapArrayLazy", "patchLeaf", "createDisposableEffect", "createComponent", "renderChild", "registerComponent", "registerTemplate", "initChild", "upsertChild", "createPortal", "provideContext", "createContext", "useContext", "forwardProps", "applyRestAttrs", "splitProps", "spreadAttrs", "styleToCss", "escapeAttr", "escapeText", "escapeTextOrNode", "qsa", "qsaItem", "qsaChildScope", "qsaChildScopes", "upsertChildItem", "__slot", "__bfSlot", "__bfText", "claimSlots", "lazySlots", "lazyClaimSlots", "textOrNode", "beginTurn", "endTurn", "date", "formatDate"];
|
|
8
8
|
export declare const RUNTIME_MODULE = "@barefootjs/client/runtime";
|
|
9
9
|
export declare const IMPORT_PLACEHOLDER = "/* __BAREFOOTJS_DOM_IMPORTS__ */";
|
|
10
10
|
export declare const MODULE_CONSTANTS_PLACEHOLDER = "/* __MODULE_LEVEL_CONSTANTS__ */";
|
|
@@ -17,6 +17,30 @@ export declare function detectUsedImports(code: string): Set<string>;
|
|
|
17
17
|
* compiler-emitted /runtime subpath).
|
|
18
18
|
*/
|
|
19
19
|
export declare function collectUserDomImports(ir: ComponentIR): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Build the "is this local name used as a value in the generated code?"
|
|
22
|
+
* test used to decide which imported specifiers survive into the client
|
|
23
|
+
* bundle. Prefers a real value-reference set over the historical
|
|
24
|
+
* `\bname\b` text scan (#2432: an object key or string literal that
|
|
25
|
+
* merely spells an imported name used to emit a phantom import). Falls
|
|
26
|
+
* back to a substring scan when the generated text cannot be parsed
|
|
27
|
+
* cleanly — a partial parse would under-report references and DROP a
|
|
28
|
+
* needed import. The reference set is computed at most once per call.
|
|
29
|
+
*
|
|
30
|
+
* The fallback is a plain `includes()`, not a `\bname\b` regex: `\b` is
|
|
31
|
+
* defined over `[A-Za-z0-9_]`, so a `$`-prefixed name (`$fetch`, as
|
|
32
|
+
* exported by `ofetch`) or a non-ASCII local both sit outside a word
|
|
33
|
+
* boundary and would never match — silently dropping the import, the one
|
|
34
|
+
* failure direction this helper must never take. Worse, splicing
|
|
35
|
+
* `localName` straight into `new RegExp(...)` treated `$` as the
|
|
36
|
+
* end-of-input anchor, so `\b$fetch\b` couldn't match `$fetch` at all.
|
|
37
|
+
* `includes()` is deliberately COARSER than a word-boundary scan (it
|
|
38
|
+
* matches `helper` inside `helperFoo` too) — that's fine here: the
|
|
39
|
+
* fallback's only job is "never under-report", and over-keeping an
|
|
40
|
+
* import whose binding already exists is harmless, while dropping one is
|
|
41
|
+
* fatal.
|
|
42
|
+
*/
|
|
43
|
+
export declare function makeValueUsageTest(generatedCode: string): (localName: string) => boolean;
|
|
20
44
|
/**
|
|
21
45
|
* Collect external (non-DOM, non-component) imports that are used in generated code.
|
|
22
46
|
* These are third-party libraries like @barefootjs/form, zod, etc. that need to be
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/imports.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAU,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/imports.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAU,MAAM,aAAa,CAAA;AAKtD,eAAO,MAAM,yBAAyB,YACpC,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EACpE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,cAAc,EAAE,WAAW,EAAE,wBAAwB,EAC7I,iBAAiB,EAAE,aAAa,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,WAAW,EAAE,aAAa,EACrG,cAAc,EACd,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAC/C,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAC3H,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAUxG,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAEzD,WAAW,EAAE,SAAS,EAItB,MAAM,EAIN,YAAY,CACJ,CAAA;AAEV,gDAAgD;AAChD,eAAO,MAAM,qBAAqB,wtBAA4B,CAAA;AAE9D,eAAO,MAAM,cAAc,+BAA+B,CAAA;AAE1D,eAAO,MAAM,kBAAkB,qCAAqC,CAAA;AACpE,eAAO,MAAM,4BAA4B,qCAAqC,CAAA;AAE9E;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAqB3D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,EAAE,CAoB/D;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAWxF;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAE,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAsCvH"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single door for "is this identifier a VALUE reference in emitted JS".
|
|
3
|
+
*
|
|
4
|
+
* Replaces `\bname\b` text scans at the import-emission sites (#2432): a
|
|
5
|
+
* regex scan can't tell a genuine value reference (`paperColor({ ... })`)
|
|
6
|
+
* from an object key or string literal that merely spells an imported
|
|
7
|
+
* name (`{ Theme: 'テーマ' }`). That false match used to make
|
|
8
|
+
* `collectExternalImports` re-emit a per-specifier type-only import
|
|
9
|
+
* (`import { paperColor, type Theme } from '../lib/theme'`) as a VALUE
|
|
10
|
+
* import, which the CLI's relative-import inliner then placed in the IIFE's
|
|
11
|
+
* `return { … }` with no binding — `ReferenceError: Theme is not defined`
|
|
12
|
+
* at load, killing the whole page's client JS.
|
|
13
|
+
*
|
|
14
|
+
* `packages/cli`'s `detectStrippedReferences` (in `resolve-imports.ts`)
|
|
15
|
+
* shares the same classifier for its own dangling-reference scan, so the
|
|
16
|
+
* two "is this a real use" checks in the pipeline can never drift apart.
|
|
17
|
+
*/
|
|
18
|
+
import ts from 'typescript';
|
|
19
|
+
/**
|
|
20
|
+
* Identifier-position classifier: returns `true` when `id` is being USED
|
|
21
|
+
* as a value, `false` when it's a declaration name, property key, member-
|
|
22
|
+
* access name, or other non-reference slot.
|
|
23
|
+
*
|
|
24
|
+
* A ShorthandPropertyAssignment (`{ Theme }`) intentionally counts as a
|
|
25
|
+
* reference — it reads the binding, it doesn't just spell its name.
|
|
26
|
+
*
|
|
27
|
+
* Caveat: this is a syntactic test, not a scope analysis. If a local
|
|
28
|
+
* function parameter happens to share a name with an imported binding,
|
|
29
|
+
* references inside that function's body will count as references to
|
|
30
|
+
* the import (false positive). Acceptable: over-counting a reference
|
|
31
|
+
* just means we keep an import we didn't strictly need, which is a
|
|
32
|
+
* strict improvement over the alternative failure direction (dropping a
|
|
33
|
+
* needed import and producing a `ReferenceError`).
|
|
34
|
+
*/
|
|
35
|
+
export declare function isValueReferenceIdentifier(id: ts.Identifier): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Parse `code` and collect the text of every identifier that is a VALUE
|
|
38
|
+
* reference per `isValueReferenceIdentifier`.
|
|
39
|
+
*
|
|
40
|
+
* Returns `null` when the text did not parse cleanly. `null` means
|
|
41
|
+
* "cannot answer" — callers MUST fall back to their previous (regex-scan)
|
|
42
|
+
* behaviour rather than treating it as an empty set. Narrowing on a
|
|
43
|
+
* partial parse would DROP a needed import, which is the failure
|
|
44
|
+
* direction we must never take (a phantom missing-import build failure
|
|
45
|
+
* is recoverable; a silently dead client bundle is not).
|
|
46
|
+
*/
|
|
47
|
+
export declare function collectValueReferencedNames(code: string): Set<string> | null;
|
|
48
|
+
//# sourceMappingURL=value-references.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"value-references.d.ts","sourceRoot":"","sources":["../src/value-references.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,EAAE,CAAC,UAAU,GAAG,OAAO,CAgCrE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CA0B5E"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/jsx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.1",
|
|
4
4
|
"description": "JSX compiler for BarefootJS - transforms JSX to server HTML + client JS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"directory": "packages/jsx"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@barefootjs/shared": "0.
|
|
56
|
+
"@barefootjs/shared": "0.28.1"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@barefootjs/client": ">=0.2.0",
|
|
@@ -4380,7 +4380,7 @@ export function Example(_p, __bfKey) { return createComponent('Example', _p, __b
|
|
|
4380
4380
|
`;
|
|
4381
4381
|
|
|
4382
4382
|
exports[`docs/core/adapters/hono-adapter.md doc-examples L190 — (no label) 1`] = `
|
|
4383
|
-
"import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots,
|
|
4383
|
+
"import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArrayLazy, textOrNode } from '@barefootjs/client/runtime'
|
|
4384
4384
|
|
|
4385
4385
|
export function initTodoItem(__scope, _p = {}) {
|
|
4386
4386
|
if (!__scope) return
|
|
@@ -4431,11 +4431,32 @@ export function initExample(__scope, _p = {}) {
|
|
|
4431
4431
|
|
|
4432
4432
|
const __tpl_l0 = document.createElement('template')
|
|
4433
4433
|
__tpl_l0.innerHTML = \`<li data-key=""><!--bf:s0--><!--/--></li>\`
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
return
|
|
4434
|
+
const __lzp_l0 = [[0]]
|
|
4435
|
+
const __lzs_l0 = [{ id: 's0', kind: 'text', path: [] }]
|
|
4436
|
+
const __lzsc_l0 = [{ id: 's0', kind: 'text', path: __lzp_l0[0] }]
|
|
4437
|
+
const __lzc_l0 = (__e) => {
|
|
4438
|
+
return [null]
|
|
4439
|
+
}
|
|
4440
|
+
mapArrayLazy(() => items(), _s1, (item) => String(item), {
|
|
4441
|
+
createRow: (__e, __idx) => {
|
|
4442
|
+
const item = () => __e.item
|
|
4443
|
+
const __el = __tpl_l0.content.firstElementChild.cloneNode(true)
|
|
4444
|
+
const __r = __e.refs = [lazySlots(__el, __lzsc_l0)]
|
|
4445
|
+
const __l = __e.last = []
|
|
4446
|
+
{ const __x = item()
|
|
4447
|
+
__r[0]('s0', textOrNode(__x))
|
|
4448
|
+
__l[0] = __x }
|
|
4449
|
+
return __el
|
|
4450
|
+
},
|
|
4451
|
+
applyItem: (__e) => {
|
|
4452
|
+
const item = () => __e.item
|
|
4453
|
+
const __r = __e.refs ?? (__e.refs = __lzc_l0(__e))
|
|
4454
|
+
const __l = __e.last ?? (__e.last = [])
|
|
4455
|
+
const __d = __r[0] ?? (__r[0] = lazySlots(__e.primaryEl, __lzs_l0))
|
|
4456
|
+
{ const __x = item()
|
|
4457
|
+
if (!(0 in __l) || !Object.is(__l[0], __x)) __d('s0', textOrNode(__x))
|
|
4458
|
+
__l[0] = __x }
|
|
4459
|
+
},
|
|
4439
4460
|
}, 'l0')
|
|
4440
4461
|
|
|
4441
4462
|
}
|
|
@@ -4494,7 +4515,7 @@ export function Counter(_p, __bfKey) { return createComponent('Counter', _p, __b
|
|
|
4494
4515
|
`;
|
|
4495
4516
|
|
|
4496
4517
|
exports[`docs/core/adapters/go-template-adapter.md doc-examples L168 — (no label) 1`] = `
|
|
4497
|
-
"import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots,
|
|
4518
|
+
"import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArrayLazy, textOrNode } from '@barefootjs/client/runtime'
|
|
4498
4519
|
|
|
4499
4520
|
export function initTodoItem(__scope, _p = {}) {
|
|
4500
4521
|
if (!__scope) return
|
|
@@ -4545,11 +4566,32 @@ export function initExample(__scope, _p = {}) {
|
|
|
4545
4566
|
|
|
4546
4567
|
const __tpl_l0 = document.createElement('template')
|
|
4547
4568
|
__tpl_l0.innerHTML = \`<li data-key=""><!--bf:s0--><!--/--></li>\`
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
return
|
|
4569
|
+
const __lzp_l0 = [[0]]
|
|
4570
|
+
const __lzs_l0 = [{ id: 's0', kind: 'text', path: [] }]
|
|
4571
|
+
const __lzsc_l0 = [{ id: 's0', kind: 'text', path: __lzp_l0[0] }]
|
|
4572
|
+
const __lzc_l0 = (__e) => {
|
|
4573
|
+
return [null]
|
|
4574
|
+
}
|
|
4575
|
+
mapArrayLazy(() => items(), _s1, (item) => String(item), {
|
|
4576
|
+
createRow: (__e, __idx) => {
|
|
4577
|
+
const item = () => __e.item
|
|
4578
|
+
const __el = __tpl_l0.content.firstElementChild.cloneNode(true)
|
|
4579
|
+
const __r = __e.refs = [lazySlots(__el, __lzsc_l0)]
|
|
4580
|
+
const __l = __e.last = []
|
|
4581
|
+
{ const __x = item()
|
|
4582
|
+
__r[0]('s0', textOrNode(__x))
|
|
4583
|
+
__l[0] = __x }
|
|
4584
|
+
return __el
|
|
4585
|
+
},
|
|
4586
|
+
applyItem: (__e) => {
|
|
4587
|
+
const item = () => __e.item
|
|
4588
|
+
const __r = __e.refs ?? (__e.refs = __lzc_l0(__e))
|
|
4589
|
+
const __l = __e.last ?? (__e.last = [])
|
|
4590
|
+
const __d = __r[0] ?? (__r[0] = lazySlots(__e.primaryEl, __lzs_l0))
|
|
4591
|
+
{ const __x = item()
|
|
4592
|
+
if (!(0 in __l) || !Object.is(__l[0], __x)) __d('s0', textOrNode(__x))
|
|
4593
|
+
__l[0] = __x }
|
|
4594
|
+
},
|
|
4553
4595
|
}, 'l0')
|
|
4554
4596
|
|
|
4555
4597
|
}
|
|
@@ -4559,7 +4601,7 @@ export function Example(_p, __bfKey) { return createComponent('Example', _p, __b
|
|
|
4559
4601
|
`;
|
|
4560
4602
|
|
|
4561
4603
|
exports[`docs/core/adapters/go-template-adapter.md doc-examples L180 — (no label) 1`] = `
|
|
4562
|
-
"import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots,
|
|
4604
|
+
"import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArrayLazy, textOrNode } from '@barefootjs/client/runtime'
|
|
4563
4605
|
|
|
4564
4606
|
export function initTodoItem(__scope, _p = {}) {
|
|
4565
4607
|
if (!__scope) return
|
|
@@ -4610,11 +4652,32 @@ export function initExample(__scope, _p = {}) {
|
|
|
4610
4652
|
|
|
4611
4653
|
const __tpl_l0 = document.createElement('template')
|
|
4612
4654
|
__tpl_l0.innerHTML = \`<li data-key=""><!--bf:s0--><!--/--></li>\`
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
return
|
|
4655
|
+
const __lzp_l0 = [[0]]
|
|
4656
|
+
const __lzs_l0 = [{ id: 's0', kind: 'text', path: [] }]
|
|
4657
|
+
const __lzsc_l0 = [{ id: 's0', kind: 'text', path: __lzp_l0[0] }]
|
|
4658
|
+
const __lzc_l0 = (__e) => {
|
|
4659
|
+
return [null]
|
|
4660
|
+
}
|
|
4661
|
+
mapArrayLazy(() => items().filter(item => item.active), _s1, (item) => String(item.id), {
|
|
4662
|
+
createRow: (__e, __idx) => {
|
|
4663
|
+
const item = () => __e.item
|
|
4664
|
+
const __el = __tpl_l0.content.firstElementChild.cloneNode(true)
|
|
4665
|
+
const __r = __e.refs = [lazySlots(__el, __lzsc_l0)]
|
|
4666
|
+
const __l = __e.last = []
|
|
4667
|
+
{ const __x = item().name
|
|
4668
|
+
__r[0]('s0', textOrNode(__x))
|
|
4669
|
+
__l[0] = __x }
|
|
4670
|
+
return __el
|
|
4671
|
+
},
|
|
4672
|
+
applyItem: (__e) => {
|
|
4673
|
+
const item = () => __e.item
|
|
4674
|
+
const __r = __e.refs ?? (__e.refs = __lzc_l0(__e))
|
|
4675
|
+
const __l = __e.last ?? (__e.last = [])
|
|
4676
|
+
const __d = __r[0] ?? (__r[0] = lazySlots(__e.primaryEl, __lzs_l0))
|
|
4677
|
+
{ const __x = item().name
|
|
4678
|
+
if (!(0 in __l) || !Object.is(__l[0], __x)) __d('s0', textOrNode(__x))
|
|
4679
|
+
__l[0] = __x }
|
|
4680
|
+
},
|
|
4618
4681
|
}, 'l0')
|
|
4619
4682
|
|
|
4620
4683
|
}
|
|
@@ -5032,7 +5095,7 @@ export function StatementExample(_p, __bfKey) { return createComponent('Statemen
|
|
|
5032
5095
|
`;
|
|
5033
5096
|
|
|
5034
5097
|
exports[`docs/core/advanced/compiler-internals.md doc-examples L126 — (no label) 1`] = `
|
|
5035
|
-
"import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots,
|
|
5098
|
+
"import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArrayLazy, textOrNode } from '@barefootjs/client/runtime'
|
|
5036
5099
|
|
|
5037
5100
|
export function initTodoItem(__scope, _p = {}) {
|
|
5038
5101
|
if (!__scope) return
|
|
@@ -5083,11 +5146,32 @@ export function initExample(__scope, _p = {}) {
|
|
|
5083
5146
|
|
|
5084
5147
|
const __tpl_l0 = document.createElement('template')
|
|
5085
5148
|
__tpl_l0.innerHTML = \`<li data-key=""><!--bf:s0--><!--/--></li>\`
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
return
|
|
5149
|
+
const __lzp_l0 = [[0]]
|
|
5150
|
+
const __lzs_l0 = [{ id: 's0', kind: 'text', path: [] }]
|
|
5151
|
+
const __lzsc_l0 = [{ id: 's0', kind: 'text', path: __lzp_l0[0] }]
|
|
5152
|
+
const __lzc_l0 = (__e) => {
|
|
5153
|
+
return [null]
|
|
5154
|
+
}
|
|
5155
|
+
mapArrayLazy(() => todos().filter(t => !t.done).toSorted((a, b) => a.date - b.date), _s1, (t) => String(t.id), {
|
|
5156
|
+
createRow: (__e, __idx) => {
|
|
5157
|
+
const t = () => __e.item
|
|
5158
|
+
const __el = __tpl_l0.content.firstElementChild.cloneNode(true)
|
|
5159
|
+
const __r = __e.refs = [lazySlots(__el, __lzsc_l0)]
|
|
5160
|
+
const __l = __e.last = []
|
|
5161
|
+
{ const __x = t().name
|
|
5162
|
+
__r[0]('s0', textOrNode(__x))
|
|
5163
|
+
__l[0] = __x }
|
|
5164
|
+
return __el
|
|
5165
|
+
},
|
|
5166
|
+
applyItem: (__e) => {
|
|
5167
|
+
const t = () => __e.item
|
|
5168
|
+
const __r = __e.refs ?? (__e.refs = __lzc_l0(__e))
|
|
5169
|
+
const __l = __e.last ?? (__e.last = [])
|
|
5170
|
+
const __d = __r[0] ?? (__r[0] = lazySlots(__e.primaryEl, __lzs_l0))
|
|
5171
|
+
{ const __x = t().name
|
|
5172
|
+
if (!(0 in __l) || !Object.is(__l[0], __x)) __d('s0', textOrNode(__x))
|
|
5173
|
+
__l[0] = __x }
|
|
5174
|
+
},
|
|
5091
5175
|
}, 'l0')
|
|
5092
5176
|
|
|
5093
5177
|
}
|
|
@@ -5159,7 +5243,7 @@ export function StatementExample(_p, __bfKey) { return createComponent('Statemen
|
|
|
5159
5243
|
`;
|
|
5160
5244
|
|
|
5161
5245
|
exports[`docs/core/advanced/performance.md doc-examples L67 — ✅ Stable key — DOM nodes reused when list changes 1`] = `
|
|
5162
|
-
"import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots,
|
|
5246
|
+
"import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArrayLazy, textOrNode } from '@barefootjs/client/runtime'
|
|
5163
5247
|
|
|
5164
5248
|
export function initTodoItem(__scope, _p = {}) {
|
|
5165
5249
|
if (!__scope) return
|
|
@@ -5210,11 +5294,32 @@ export function initExample(__scope, _p = {}) {
|
|
|
5210
5294
|
|
|
5211
5295
|
const __tpl_l0 = document.createElement('template')
|
|
5212
5296
|
__tpl_l0.innerHTML = \`<li data-key=""><!--bf:s0--><!--/--></li>\`
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
return
|
|
5297
|
+
const __lzp_l0 = [[0]]
|
|
5298
|
+
const __lzs_l0 = [{ id: 's0', kind: 'text', path: [] }]
|
|
5299
|
+
const __lzsc_l0 = [{ id: 's0', kind: 'text', path: __lzp_l0[0] }]
|
|
5300
|
+
const __lzc_l0 = (__e) => {
|
|
5301
|
+
return [null]
|
|
5302
|
+
}
|
|
5303
|
+
mapArrayLazy(() => items(), _s1, (item) => String(item.id), {
|
|
5304
|
+
createRow: (__e, __idx) => {
|
|
5305
|
+
const item = () => __e.item
|
|
5306
|
+
const __el = __tpl_l0.content.firstElementChild.cloneNode(true)
|
|
5307
|
+
const __r = __e.refs = [lazySlots(__el, __lzsc_l0)]
|
|
5308
|
+
const __l = __e.last = []
|
|
5309
|
+
{ const __x = item().name
|
|
5310
|
+
__r[0]('s0', textOrNode(__x))
|
|
5311
|
+
__l[0] = __x }
|
|
5312
|
+
return __el
|
|
5313
|
+
},
|
|
5314
|
+
applyItem: (__e) => {
|
|
5315
|
+
const item = () => __e.item
|
|
5316
|
+
const __r = __e.refs ?? (__e.refs = __lzc_l0(__e))
|
|
5317
|
+
const __l = __e.last ?? (__e.last = [])
|
|
5318
|
+
const __d = __r[0] ?? (__r[0] = lazySlots(__e.primaryEl, __lzs_l0))
|
|
5319
|
+
{ const __x = item().name
|
|
5320
|
+
if (!(0 in __l) || !Object.is(__l[0], __x)) __d('s0', textOrNode(__x))
|
|
5321
|
+
__l[0] = __x }
|
|
5322
|
+
},
|
|
5218
5323
|
}, 'l0')
|
|
5219
5324
|
|
|
5220
5325
|
}
|
|
@@ -5224,7 +5329,7 @@ export function Example(_p, __bfKey) { return createComponent('Example', _p, __b
|
|
|
5224
5329
|
`;
|
|
5225
5330
|
|
|
5226
5331
|
exports[`docs/core/advanced/performance.md doc-examples L70 — ❌ Index key — DOM nodes recreated on reorder 1`] = `
|
|
5227
|
-
"import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots,
|
|
5332
|
+
"import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArrayLazy, textOrNode } from '@barefootjs/client/runtime'
|
|
5228
5333
|
|
|
5229
5334
|
export function initTodoItem(__scope, _p = {}) {
|
|
5230
5335
|
if (!__scope) return
|
|
@@ -5275,11 +5380,32 @@ export function initExample(__scope, _p = {}) {
|
|
|
5275
5380
|
|
|
5276
5381
|
const __tpl_l0 = document.createElement('template')
|
|
5277
5382
|
__tpl_l0.innerHTML = \`<li data-key=""><!--bf:s0--><!--/--></li>\`
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
return
|
|
5383
|
+
const __lzp_l0 = [[0]]
|
|
5384
|
+
const __lzs_l0 = [{ id: 's0', kind: 'text', path: [] }]
|
|
5385
|
+
const __lzsc_l0 = [{ id: 's0', kind: 'text', path: __lzp_l0[0] }]
|
|
5386
|
+
const __lzc_l0 = (__e) => {
|
|
5387
|
+
return [null]
|
|
5388
|
+
}
|
|
5389
|
+
mapArrayLazy(() => items(), _s1, (item, i) => String(i), {
|
|
5390
|
+
createRow: (__e, i) => {
|
|
5391
|
+
const item = () => __e.item
|
|
5392
|
+
const __el = __tpl_l0.content.firstElementChild.cloneNode(true)
|
|
5393
|
+
const __r = __e.refs = [lazySlots(__el, __lzsc_l0)]
|
|
5394
|
+
const __l = __e.last = []
|
|
5395
|
+
{ const __x = item().name
|
|
5396
|
+
__r[0]('s0', textOrNode(__x))
|
|
5397
|
+
__l[0] = __x }
|
|
5398
|
+
return __el
|
|
5399
|
+
},
|
|
5400
|
+
applyItem: (__e) => {
|
|
5401
|
+
const item = () => __e.item
|
|
5402
|
+
const __r = __e.refs ?? (__e.refs = __lzc_l0(__e))
|
|
5403
|
+
const __l = __e.last ?? (__e.last = [])
|
|
5404
|
+
const __d = __r[0] ?? (__r[0] = lazySlots(__e.primaryEl, __lzs_l0))
|
|
5405
|
+
{ const __x = item().name
|
|
5406
|
+
if (!(0 in __l) || !Object.is(__l[0], __x)) __d('s0', textOrNode(__x))
|
|
5407
|
+
__l[0] = __x }
|
|
5408
|
+
},
|
|
5283
5409
|
}, 'l0')
|
|
5284
5410
|
|
|
5285
5411
|
}
|
|
@@ -2638,7 +2638,10 @@ describe('Client JS generation', () => {
|
|
|
2638
2638
|
const js = result.files.find(f => f.type === 'clientJs')!.content
|
|
2639
2639
|
// The branch arm's mapArray call must live inside a
|
|
2640
2640
|
// createDisposableEffect that is pushed into __disposers.
|
|
2641
|
-
|
|
2641
|
+
// `mapArray(` or `mapArrayLazy(` — a lazy-row-eligible branch loop
|
|
2642
|
+
// (spec/slot-unification.md §9.4) uses the latter and has exactly the
|
|
2643
|
+
// same disposal obligation.
|
|
2644
|
+
expect(js).toMatch(/__disposers\.push\(createDisposableEffect\(\(\)\s*=>\s*\{[\s\S]*?mapArray(Lazy)?\(/)
|
|
2642
2645
|
})
|
|
2643
2646
|
|
|
2644
2647
|
test('nested conditional inside a conditional branch is wrapped in createDisposableEffect', () => {
|
|
@@ -53,7 +53,10 @@ describe('composite loops inside conditional branches (#724)', () => {
|
|
|
53
53
|
const js = clientJs!.content
|
|
54
54
|
|
|
55
55
|
// Should use mapArray inside the branch's bindEvents
|
|
56
|
-
|
|
56
|
+
// `mapArray(` or `mapArrayLazy(` — a lazy-row-eligible branch loop
|
|
57
|
+
// (spec/slot-unification.md §9.4) uses the latter; either satisfies
|
|
58
|
+
// "keyed reconciliation, not a composite/component shape".
|
|
59
|
+
expect(js).toMatch(/\bmapArray(Lazy)?\(/)
|
|
57
60
|
|
|
58
61
|
// After O-1's mode-collapse PR, child component init goes through the
|
|
59
62
|
// runtime `upsertChild` helper instead of an emit-side
|
|
@@ -96,7 +99,10 @@ describe('composite loops inside conditional branches (#724)', () => {
|
|
|
96
99
|
const js = clientJs!.content
|
|
97
100
|
|
|
98
101
|
// Should use mapArray for per-item reactivity
|
|
99
|
-
|
|
102
|
+
// `mapArray(` or `mapArrayLazy(` — a lazy-row-eligible branch loop
|
|
103
|
+
// (spec/slot-unification.md §9.4) uses the latter; either satisfies
|
|
104
|
+
// "keyed reconciliation, not a composite/component shape".
|
|
105
|
+
expect(js).toMatch(/\bmapArray(Lazy)?\(/)
|
|
100
106
|
|
|
101
107
|
// Should NOT use createComponent inside the init body (no child
|
|
102
108
|
// components in this fixture). The CLI also emits a callable shim
|
|
@@ -147,7 +153,10 @@ describe('composite loops inside conditional branches (#724)', () => {
|
|
|
147
153
|
const js = clientJs!.content
|
|
148
154
|
|
|
149
155
|
// Should use mapArray for the loop
|
|
150
|
-
|
|
156
|
+
// `mapArray(` or `mapArrayLazy(` — a lazy-row-eligible branch loop
|
|
157
|
+
// (spec/slot-unification.md §9.4) uses the latter; either satisfies
|
|
158
|
+
// "keyed reconciliation, not a composite/component shape".
|
|
159
|
+
expect(js).toMatch(/\bmapArray(Lazy)?\(/)
|
|
151
160
|
|
|
152
161
|
// Should have event delegation (addEventListener + closest pattern)
|
|
153
162
|
expect(js).toContain(".addEventListener('click', (__bfEvt) => {")
|
|
@@ -29,11 +29,17 @@ function clientJs(source: string, file = 'CondKey.tsx'): string {
|
|
|
29
29
|
return cjs!.content
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Reconciler call sites, whichever family they use. A lazy-row-eligible loop
|
|
34
|
+
* (spec/slot-unification.md §9.4) emits `mapArrayLazy(`; everything else keeps
|
|
35
|
+
* `mapArray(`. The key expression this suite asserts on is the same third
|
|
36
|
+
* argument in both shapes, so match the family, not one member of it.
|
|
37
|
+
*/
|
|
32
38
|
function mapArrayCalls(content: string): string[] {
|
|
33
39
|
return content
|
|
34
40
|
.split('\n')
|
|
35
41
|
.map((ln) => ln.trim())
|
|
36
|
-
.filter((ln) => ln.startsWith('mapArray('))
|
|
42
|
+
.filter((ln) => ln.startsWith('mapArray(') || ln.startsWith('mapArrayLazy('))
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
describe('mapArray key extraction across conditional branches (#1098)', () => {
|
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
* `isSelected(row.id)` as reactive with no analyzer changes — the callee's
|
|
8
8
|
* type carries the brand, not the call's return type. These tests pin:
|
|
9
9
|
* - the classic js-framework-benchmark row compiles with `isSelected(row.id)`
|
|
10
|
-
* wired
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* wired through the lazy row graph — ZERO emitted per-row effects, the
|
|
11
|
+
* loop-level effect living in `mapArrayLazy` (spec §9). Until §9.3a
|
|
12
|
+
* was lifted this shape was gate-INELIGIBLE (an opaque local whose call
|
|
13
|
+
* is the reactive read could not be primed) and compiled to one per-row
|
|
14
|
+
* `createEffect`; the runtime re-subscribe seam replaced that, so the
|
|
15
|
+
* loop is emitted like any other. Either way it is never
|
|
16
|
+
* the legacy `selected() === row.id` shape, which subscribes every row
|
|
17
|
+
* to the raw signal.
|
|
13
18
|
* - the loop-param accessor wrap applies only to the call ARGUMENT
|
|
14
19
|
* (`row.id` -> `row().id`), leaving the `isSelected` callee untouched
|
|
15
20
|
* - the hoisted skeleton fast path (#2223) still fires for a selector-driven
|
|
@@ -36,7 +41,7 @@ function compile(source: string, filename: string): { clientJs: string; template
|
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
describe('createSelector (perf, #2143 gap 5)', () => {
|
|
39
|
-
test('isSelected(row.id) in a loop body compiles
|
|
44
|
+
test('isSelected(row.id) in a loop body compiles through the lazy row graph', () => {
|
|
40
45
|
const source = `
|
|
41
46
|
'use client'
|
|
42
47
|
import { createSignal, createSelector } from '@barefootjs/client'
|
|
@@ -66,12 +71,21 @@ describe('createSelector (perf, #2143 gap 5)', () => {
|
|
|
66
71
|
// The callee itself is never rewritten to an accessor call.
|
|
67
72
|
expect(clientJs).not.toContain('isSelected()(')
|
|
68
73
|
|
|
69
|
-
//
|
|
70
|
-
//
|
|
74
|
+
// The selector-driven class attribute costs NO emitted effect at all:
|
|
75
|
+
// the loop is lazy-eligible, so its one loop-level effect lives inside
|
|
76
|
+
// `mapArrayLazy` rather than in the emitted row body. It is also never
|
|
77
|
+
// the O(rows) `selected() === row.id` shape.
|
|
71
78
|
const effectCount = (clientJs.match(/createEffect\(/g) ?? []).length
|
|
72
|
-
expect(effectCount).toBe(
|
|
79
|
+
expect(effectCount).toBe(0)
|
|
80
|
+
expect(clientJs).toContain('mapArrayLazy(')
|
|
73
81
|
expect(clientJs).not.toContain('selected() ===')
|
|
74
82
|
|
|
83
|
+
// `isSelected` is an opaque local (its CALL is the reactive read). Nothing
|
|
84
|
+
// in the emission marks that: the runtime's re-subscribe seam applies to
|
|
85
|
+
// every loop-level outer effect, so the compiler has no judgement to
|
|
86
|
+
// encode here (§9.3a).
|
|
87
|
+
expect(clientJs).not.toContain('outerNeedsResubscribe')
|
|
88
|
+
|
|
75
89
|
// The hoisted skeleton fast path (#2223) still fires: a single-line
|
|
76
90
|
// renderItem clones from a once-per-loop template.
|
|
77
91
|
expect(clientJs).toMatch(/const __tpl_\w+ = document\.createElement\('template'\)/)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, test, expect } from 'bun:test'
|
|
2
|
-
import { collectExternalImports } from '../../ir-to-client-js/imports'
|
|
2
|
+
import { collectExternalImports, collectUserDomImports } from '../../ir-to-client-js/imports'
|
|
3
3
|
import type { ComponentIR, ImportInfo, SourceLocation } from '../../types'
|
|
4
4
|
|
|
5
5
|
const dummyLoc: SourceLocation = { file: 'test.tsx', start: { line: 1, column: 0 }, end: { line: 1, column: 0 } }
|
|
@@ -229,4 +229,101 @@ describe('collectExternalImports', () => {
|
|
|
229
229
|
"import { z } from 'zod'",
|
|
230
230
|
])
|
|
231
231
|
})
|
|
232
|
+
|
|
233
|
+
// #2432: `collectExternalImports` used a `\bname\b` text scan to decide
|
|
234
|
+
// which specifiers survive into the client bundle. That scan never
|
|
235
|
+
// consulted the per-specifier `isTypeOnly` flag, so a per-specifier
|
|
236
|
+
// `import { paperColor, type Theme } from '../lib/theme'` re-emitted
|
|
237
|
+
// `Theme` as a VALUE import whenever the word "Theme" merely appeared in
|
|
238
|
+
// the generated code (e.g. as an object key) — the CLI's relative-import
|
|
239
|
+
// inliner then put `Theme` in the IIFE's `return { … }` with no binding,
|
|
240
|
+
// producing `ReferenceError: Theme is not defined` at load.
|
|
241
|
+
test('per-specifier type-only specifier is not emitted, while its value sibling is (#2432)', () => {
|
|
242
|
+
const ir: ReturnType<typeof makeIR> = makeIR([{
|
|
243
|
+
source: '../lib/theme',
|
|
244
|
+
specifiers: [
|
|
245
|
+
{ name: 'paperColor', alias: null, isDefault: false, isNamespace: false },
|
|
246
|
+
{ name: 'Theme', alias: null, isDefault: false, isNamespace: false, isTypeOnly: true },
|
|
247
|
+
],
|
|
248
|
+
isTypeOnly: false,
|
|
249
|
+
loc: dummyLoc,
|
|
250
|
+
}])
|
|
251
|
+
const code = "const labels = { Theme: 'テーマ' };\nconst c = paperColor({ paper: '#fff' });"
|
|
252
|
+
const result = collectExternalImports(ir, code)
|
|
253
|
+
expect(result).toEqual(["import { paperColor } from '../lib/theme'"])
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
test('a value specifier that appears ONLY as an object key is not emitted (#2432)', () => {
|
|
257
|
+
const ir = makeIR([makeImport('./utils', ['helper'])])
|
|
258
|
+
const code = 'const o = { helper: 1 };'
|
|
259
|
+
const result = collectExternalImports(ir, code)
|
|
260
|
+
expect(result).toEqual([])
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
test('a value specifier that appears ONLY inside a string literal is not emitted (#2432)', () => {
|
|
264
|
+
const ir = makeIR([makeImport('./utils', ['helper'])])
|
|
265
|
+
const code = "const s = 'helper is a function';"
|
|
266
|
+
const result = collectExternalImports(ir, code)
|
|
267
|
+
expect(result).toEqual([])
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
test('a value specifier that appears ONLY as a property-access name is not emitted (#2432)', () => {
|
|
271
|
+
const ir = makeIR([makeImport('./utils', ['helper'])])
|
|
272
|
+
const code = 'obj.helper();'
|
|
273
|
+
const result = collectExternalImports(ir, code)
|
|
274
|
+
expect(result).toEqual([])
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
test('a value specifier used via shorthand property IS emitted (#2432)', () => {
|
|
278
|
+
const ir = makeIR([makeImport('./utils', ['helper'])])
|
|
279
|
+
const code = 'const o = { helper };'
|
|
280
|
+
const result = collectExternalImports(ir, code)
|
|
281
|
+
expect(result).toEqual(["import { helper } from './utils'"])
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
test('unparseable generated text falls back to the text scan (#2432)', () => {
|
|
285
|
+
const ir = makeIR([makeImport('some-lib', ['helper'])])
|
|
286
|
+
const code = 'MyComponent helper()'
|
|
287
|
+
const result = collectExternalImports(ir, code)
|
|
288
|
+
expect(result).toEqual(["import { helper } from 'some-lib'"])
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
// These two pin the fallback branch specifically (generated text that does
|
|
292
|
+
// NOT parse cleanly, so `makeValueUsageTest` falls back to a substring
|
|
293
|
+
// scan rather than the AST-based value-reference set). #2432: the
|
|
294
|
+
// fallback used to be a `new RegExp(`\\b${localName}\\b`)` scan, which
|
|
295
|
+
// silently DROPPED the import for either shape below — `\b` isn't defined
|
|
296
|
+
// for `$` or non-ASCII characters, and `$` is also a regex metacharacter
|
|
297
|
+
// (end-of-input anchor) when spliced unescaped into `new RegExp(...)`, so
|
|
298
|
+
// `\b$fetch\b` could never match `$fetch` at all. Dropping the import is
|
|
299
|
+
// the one failure direction this helper must never take.
|
|
300
|
+
test('fallback matches a $-prefixed specifier (#2432)', () => {
|
|
301
|
+
const ir = makeIR([makeImport('ofetch', ['$fetch'])])
|
|
302
|
+
const code = 'MyComponent $fetch()'
|
|
303
|
+
const result = collectExternalImports(ir, code)
|
|
304
|
+
expect(result).toEqual(["import { $fetch } from 'ofetch'"])
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
test('fallback matches a non-ASCII specifier (#2432)', () => {
|
|
308
|
+
const ir = makeIR([makeImport('some-lib', ['日本語'])])
|
|
309
|
+
const code = 'MyComponent 日本語()'
|
|
310
|
+
const result = collectExternalImports(ir, code)
|
|
311
|
+
expect(result).toEqual(["import { 日本語 } from 'some-lib'"])
|
|
312
|
+
})
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
describe('collectUserDomImports', () => {
|
|
316
|
+
test('per-specifier type-only specifier is not emitted from the runtime subpath (#2432)', () => {
|
|
317
|
+
const ir = makeIR([{
|
|
318
|
+
source: '@barefootjs/client',
|
|
319
|
+
specifiers: [
|
|
320
|
+
{ name: 'createSignal', alias: null, isDefault: false, isNamespace: false },
|
|
321
|
+
{ name: 'Signal', alias: null, isDefault: false, isNamespace: false, isTypeOnly: true },
|
|
322
|
+
],
|
|
323
|
+
isTypeOnly: false,
|
|
324
|
+
loc: dummyLoc,
|
|
325
|
+
}])
|
|
326
|
+
const result = collectUserDomImports(ir)
|
|
327
|
+
expect(result).toEqual(['createSignal'])
|
|
328
|
+
})
|
|
232
329
|
})
|