@barefootjs/vite 0.31.3 → 0.31.5
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/vite",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.5",
|
|
4
4
|
"description": "Vite plugin for BarefootJS: Vite/Rollup owns bundling, hashing, chunking, tree-shaking and minification of client assets, BarefootJS keeps only the JSX to (template, client JS) compile",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -38,17 +38,17 @@
|
|
|
38
38
|
"directory": "packages/vite"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@barefootjs/shared": "0.31.
|
|
41
|
+
"@barefootjs/shared": "0.31.5"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@barefootjs/jsx": ">=0.2.0",
|
|
45
45
|
"vite": "^6.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@barefootjs/client": "0.31.
|
|
49
|
-
"@barefootjs/go-template": "0.31.
|
|
50
|
-
"@barefootjs/hono": "0.31.
|
|
51
|
-
"@barefootjs/jsx": "0.31.
|
|
48
|
+
"@barefootjs/client": "0.31.5",
|
|
49
|
+
"@barefootjs/go-template": "0.31.5",
|
|
50
|
+
"@barefootjs/hono": "0.31.5",
|
|
51
|
+
"@barefootjs/jsx": "0.31.5",
|
|
52
52
|
"typescript": "^5.0.0",
|
|
53
53
|
"vite": "^6.0.0"
|
|
54
54
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression (#2588): a relative specifier inside a DYNAMIC `import()` must
|
|
3
|
+
* be re-anchored to the emitted template's directory, exactly like the
|
|
4
|
+
* static `import` statements `rewriteImportsForTemplate` already handles.
|
|
5
|
+
*
|
|
6
|
+
* Dynamic imports never reach `ir.metadata.templateImports` — they ride
|
|
7
|
+
* along inside declaration source text that the adapter re-emits verbatim
|
|
8
|
+
* (`generateModuleScopeDeclarations`' consts/functions, and a component
|
|
9
|
+
* body's local handlers). Before the fix they were emitted untouched, so a
|
|
10
|
+
* specifier written relative to `components/` still said `../lib/heavy`
|
|
11
|
+
* once the template landed in `app/dist/components/` — a path that does
|
|
12
|
+
* not exist. The backend bundler then hard-fails (`Could not resolve
|
|
13
|
+
* "../lib/heavy"`), so this is a build break, not a type-only defect.
|
|
14
|
+
*
|
|
15
|
+
* The fixture mirrors the layout that hits this in the wild (piconic-ai/koma):
|
|
16
|
+
* a `components` dir and a plain `lib` dir side by side, with templates
|
|
17
|
+
* emitted to a DEEPER directory — root-relative and template-relative only
|
|
18
|
+
* diverge when the two depths differ, so a flat layout would not reproduce.
|
|
19
|
+
* Lives under `packages/vite/` (not a system tmpdir) so `@barefootjs/client`
|
|
20
|
+
* resolves through the monorepo's real workspace symlinks — same reason
|
|
21
|
+
* `e2e-fixture`/`e2e-fixture-dev`/`e2e-fixture-relimport` do.
|
|
22
|
+
*/
|
|
23
|
+
import { describe, test, expect, afterAll } from 'bun:test'
|
|
24
|
+
import { build } from 'vite'
|
|
25
|
+
import { mkdtemp, rm, readFile } from 'node:fs/promises'
|
|
26
|
+
import { tmpdir } from 'node:os'
|
|
27
|
+
import { join, resolve } from 'node:path'
|
|
28
|
+
import { HonoAdapter } from '@barefootjs/hono/adapter'
|
|
29
|
+
import { barefoot } from '../plugin.ts'
|
|
30
|
+
|
|
31
|
+
const FIXTURE_ROOT = resolve(import.meta.dirname, '../../e2e-fixture-dynimport')
|
|
32
|
+
const APP_ROOT = join(FIXTURE_ROOT, 'app')
|
|
33
|
+
const COMPONENTS_DIR = join(FIXTURE_ROOT, 'components')
|
|
34
|
+
|
|
35
|
+
// `app/dist/components/Lazy.tsx` → up three → the fixture root, where `lib/`
|
|
36
|
+
// sits. The source says `../lib/heavy` from `components/`; the emitted
|
|
37
|
+
// template has to say this instead.
|
|
38
|
+
const REANCHORED = '../../../lib/heavy'
|
|
39
|
+
|
|
40
|
+
describe('dynamic import() re-anchoring in emitted templates', () => {
|
|
41
|
+
let outDir: string
|
|
42
|
+
let templatesDir: string
|
|
43
|
+
|
|
44
|
+
afterAll(async () => {
|
|
45
|
+
await rm(outDir, { recursive: true, force: true })
|
|
46
|
+
await rm(join(APP_ROOT, 'dist'), { recursive: true, force: true })
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('re-anchors dynamic imports at module scope, in type position, and inside a component body', async () => {
|
|
50
|
+
outDir = await mkdtemp(join(tmpdir(), 'barefoot-vite-dynimport-dist-'))
|
|
51
|
+
templatesDir = join(APP_ROOT, 'dist/components')
|
|
52
|
+
|
|
53
|
+
await build({
|
|
54
|
+
configFile: false,
|
|
55
|
+
root: APP_ROOT,
|
|
56
|
+
base: '/static/',
|
|
57
|
+
logLevel: 'warn',
|
|
58
|
+
build: { outDir, emptyOutDir: true },
|
|
59
|
+
plugins: [
|
|
60
|
+
barefoot({
|
|
61
|
+
adapter: new HonoAdapter(),
|
|
62
|
+
components: [COMPONENTS_DIR],
|
|
63
|
+
templates: templatesDir,
|
|
64
|
+
}),
|
|
65
|
+
],
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const template = await readFile(join(templatesDir, 'Lazy.tsx'), 'utf8')
|
|
69
|
+
|
|
70
|
+
// Nothing anywhere may still carry the source-relative form. Asserted
|
|
71
|
+
// first and globally: a per-site check would pass while some fourth
|
|
72
|
+
// emission path silently leaked the old specifier.
|
|
73
|
+
expect(template).not.toContain("'../lib/heavy'")
|
|
74
|
+
|
|
75
|
+
// Module scope, TYPE position — `typeof import('…')` is an
|
|
76
|
+
// ImportTypeNode, a different AST node from the call expression below,
|
|
77
|
+
// and was missed independently.
|
|
78
|
+
expect(template).toContain(`typeof import('${REANCHORED}')`)
|
|
79
|
+
|
|
80
|
+
// Module scope, VALUE position — inside a re-emitted const's body.
|
|
81
|
+
expect(template).toContain(`modPromise = import('${REANCHORED}')`)
|
|
82
|
+
|
|
83
|
+
// Component scope — inside a local handler in the component body,
|
|
84
|
+
// which is emitted by a different code path than module declarations.
|
|
85
|
+
expect(template).toContain(`await import('${REANCHORED}')`)
|
|
86
|
+
|
|
87
|
+
// The re-anchored path must actually resolve on disk from the emitted
|
|
88
|
+
// template's own directory — the assertions above only pin the string.
|
|
89
|
+
const target = resolve(templatesDir, REANCHORED + '.ts')
|
|
90
|
+
expect(await readFile(target, 'utf8')).toContain('export function heavy')
|
|
91
|
+
}, 60_000)
|
|
92
|
+
})
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression (#2598): a mutable binding that survives into the emitted SSR
|
|
3
|
+
* template must keep the declarations that WRITE it.
|
|
4
|
+
*
|
|
5
|
+
* Reachability is seeded from the RENDERED JSX, after the client-only
|
|
6
|
+
* attributes are stripped — `ref={setRef}` leaves no `setRef` behind, and
|
|
7
|
+
* `onClick={handleClick}` renders as `onClick={() => {}}`. Pruning code
|
|
8
|
+
* reachable only from a handler is deliberate; it is client-only.
|
|
9
|
+
*
|
|
10
|
+
* The hole is a `let` that outlives its writer. It survives because some
|
|
11
|
+
* OTHER surviving declaration reads it, while its only assignment sat in a
|
|
12
|
+
* pruned handler — so the template declares it, reads it, and never assigns
|
|
13
|
+
* it. TypeScript then concludes it is permanently `null`, narrows every
|
|
14
|
+
* guarded use to `never`, and each member access fails with TS2339.
|
|
15
|
+
*
|
|
16
|
+
* The fixture reproduces the shape from the wild (piconic-ai/koma's
|
|
17
|
+
* FrameEditor), and the asymmetry that makes it reachable at all:
|
|
18
|
+
*
|
|
19
|
+
* <pre ref={handleHighlightRef} /> intrinsic — `ref` is STRIPPED, so
|
|
20
|
+
* the handler is pruned
|
|
21
|
+
* <Editable ref={handleTextareaRef}/> child component — props survive
|
|
22
|
+
* VERBATIM, so its handler is kept,
|
|
23
|
+
* which keeps `syncScroll`, which
|
|
24
|
+
* keeps `highlightEl` alive with no
|
|
25
|
+
* writer left
|
|
26
|
+
*
|
|
27
|
+
* A fixture with only intrinsic elements does NOT reproduce: the whole
|
|
28
|
+
* cluster is pruned together and nothing is left to narrow. The child
|
|
29
|
+
* component is load-bearing.
|
|
30
|
+
*/
|
|
31
|
+
import { describe, test, expect, afterAll } from 'bun:test'
|
|
32
|
+
import { build } from 'vite'
|
|
33
|
+
import { mkdtemp, rm, readFile } from 'node:fs/promises'
|
|
34
|
+
import { tmpdir } from 'node:os'
|
|
35
|
+
import { join, resolve } from 'node:path'
|
|
36
|
+
import ts from 'typescript'
|
|
37
|
+
import { HonoAdapter } from '@barefootjs/hono/adapter'
|
|
38
|
+
import { barefoot } from '../plugin.ts'
|
|
39
|
+
|
|
40
|
+
const FIXTURE_ROOT = resolve(import.meta.dirname, '../../e2e-fixture-refwriter')
|
|
41
|
+
const APP_ROOT = join(FIXTURE_ROOT, 'app')
|
|
42
|
+
const COMPONENTS_DIR = join(FIXTURE_ROOT, 'components')
|
|
43
|
+
|
|
44
|
+
describe('writers of surviving mutable bindings', () => {
|
|
45
|
+
let outDir: string | undefined
|
|
46
|
+
const templatesDir = join(APP_ROOT, 'dist/components')
|
|
47
|
+
|
|
48
|
+
afterAll(async () => {
|
|
49
|
+
// Guarded: if the test throws before `mkdtemp` returns, `outDir` is still
|
|
50
|
+
// undefined and an unguarded `rm` would throw here, replacing the real
|
|
51
|
+
// failure with a cleanup TypeError.
|
|
52
|
+
if (outDir) await rm(outDir, { recursive: true, force: true })
|
|
53
|
+
await rm(join(APP_ROOT, 'dist'), { recursive: true, force: true })
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('retains a ref handler whose binding survives, and the template type-checks', async () => {
|
|
57
|
+
outDir = await mkdtemp(join(tmpdir(), 'barefoot-vite-refwriter-dist-'))
|
|
58
|
+
|
|
59
|
+
await build({
|
|
60
|
+
configFile: false,
|
|
61
|
+
root: APP_ROOT,
|
|
62
|
+
base: '/static/',
|
|
63
|
+
logLevel: 'warn',
|
|
64
|
+
build: { outDir, emptyOutDir: true },
|
|
65
|
+
plugins: [
|
|
66
|
+
barefoot({
|
|
67
|
+
adapter: new HonoAdapter(),
|
|
68
|
+
components: [COMPONENTS_DIR],
|
|
69
|
+
templates: templatesDir,
|
|
70
|
+
}),
|
|
71
|
+
],
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const templatePath = join(templatesDir, 'ScrollSync.tsx')
|
|
75
|
+
const template = await readFile(templatePath, 'utf8')
|
|
76
|
+
|
|
77
|
+
// The binding survives (some surviving declaration reads it) …
|
|
78
|
+
expect(template).toContain('let highlightEl')
|
|
79
|
+
// … so its writer must survive with it. This is the line the bug dropped.
|
|
80
|
+
expect(template).toContain('highlightEl = el')
|
|
81
|
+
|
|
82
|
+
// `renderSeq` is written with `++` rather than `=`, from a declaration
|
|
83
|
+
// retained only by this same closure — pins that update expressions
|
|
84
|
+
// count as writes, not just assignment operators.
|
|
85
|
+
expect(template).toContain('let renderSeq')
|
|
86
|
+
expect(template).toContain('renderSeq++')
|
|
87
|
+
|
|
88
|
+
// The symptom itself, not a proxy for it: type-check the emitted
|
|
89
|
+
// template. Asserting on the retained source line alone would still pass
|
|
90
|
+
// if some later change made the binding un-narrowable for a different
|
|
91
|
+
// reason.
|
|
92
|
+
const program = ts.createProgram([templatePath], {
|
|
93
|
+
strict: true,
|
|
94
|
+
noEmit: true,
|
|
95
|
+
target: ts.ScriptTarget.ESNext,
|
|
96
|
+
module: ts.ModuleKind.ESNext,
|
|
97
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
98
|
+
jsx: ts.JsxEmit.ReactJSX,
|
|
99
|
+
jsxImportSource: '@barefootjs/hono/jsx',
|
|
100
|
+
lib: ['lib.esnext.d.ts', 'lib.dom.d.ts'],
|
|
101
|
+
allowImportingTsExtensions: true,
|
|
102
|
+
skipLibCheck: true,
|
|
103
|
+
})
|
|
104
|
+
// Asserted over the WHOLE error set, not a TS2339-only filter: this
|
|
105
|
+
// fixture's emitted template compiles completely clean today, so any
|
|
106
|
+
// error at all — a syntax break, an unresolved import, a wrong JSX
|
|
107
|
+
// setting — is a real regression, and a narrow filter would let those
|
|
108
|
+
// through while still claiming the template type-checks. TS2339 on
|
|
109
|
+
// `never` is simply the member of that set this PR is about.
|
|
110
|
+
const errors = ts
|
|
111
|
+
.getPreEmitDiagnostics(program)
|
|
112
|
+
.filter((d) => d.category === ts.DiagnosticCategory.Error)
|
|
113
|
+
.map((d) => `TS${d.code}: ${ts.flattenDiagnosticMessageText(d.messageText, ' ')}`)
|
|
114
|
+
expect(errors).toEqual([])
|
|
115
|
+
}, 60_000)
|
|
116
|
+
})
|