@barefootjs/hono 0.30.6 → 0.31.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/hono",
3
- "version": "0.30.6",
3
+ "version": "0.31.1",
4
4
  "description": "Hono integration for BarefootJS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -38,10 +38,6 @@
38
38
  "types": "./dist/preload.d.ts",
39
39
  "import": "./dist/preload.js"
40
40
  },
41
- "./dev": {
42
- "types": "./dist/dev.d.ts",
43
- "import": "./dist/dev.js"
44
- },
45
41
  "./dev-worker": {
46
42
  "types": "./dist/dev-worker.d.ts",
47
43
  "import": "./dist/dev-worker.js"
@@ -65,9 +61,9 @@
65
61
  "./test-render": {
66
62
  "bun": "./src/test-render.ts"
67
63
  },
68
- "./build": {
69
- "types": "./dist/build.d.ts",
70
- "import": "./dist/build.js"
64
+ "./vite": {
65
+ "types": "./dist/vite.d.ts",
66
+ "import": "./dist/vite.js"
71
67
  },
72
68
  "./app": {
73
69
  "types": "./dist/app.d.ts",
@@ -88,7 +84,7 @@
88
84
  ],
89
85
  "scripts": {
90
86
  "build": "bun run build:js && bun run build:types",
91
- "build:js": "bun build ./src/index.ts ./src/adapter/index.ts ./src/scripts.tsx ./src/portals.tsx ./src/portal-ssr.tsx ./src/dialog-context.tsx ./src/client-shim.ts ./src/preload.tsx ./src/dev.tsx ./src/dev-worker.ts ./src/jsx/jsx-runtime/index.ts ./src/jsx/jsx-dev-runtime/index.ts ./src/async.tsx ./src/utils.ts ./src/build.ts ./src/app.ts ./src/render.ts ./src/request-env.ts --root ./src --outdir ./dist --format esm --external hono --external @barefootjs/client --external @barefootjs/jsx --external @barefootjs/shared",
87
+ "build:js": "bun build ./src/index.ts ./src/adapter/index.ts ./src/scripts.tsx ./src/portals.tsx ./src/portal-ssr.tsx ./src/dialog-context.tsx ./src/client-shim.ts ./src/preload.tsx ./src/dev-worker.ts ./src/jsx/jsx-runtime/index.ts ./src/jsx/jsx-dev-runtime/index.ts ./src/async.tsx ./src/utils.ts ./src/app.ts ./src/render.ts ./src/request-env.ts --root ./src --outdir ./dist --format esm --external hono --external @barefootjs/client --external @barefootjs/jsx --external @barefootjs/shared --external @barefootjs/vite --external vite && bun build ./src/vite.ts --outfile ./dist/vite.js --format esm --target node --external @barefootjs/vite --external vite --external typescript",
92
88
  "build:types": "tsgo --emitDeclarationOnly --outDir ./dist",
93
89
  "test": "bun test",
94
90
  "clean": "rm -rf dist",
@@ -112,13 +108,25 @@
112
108
  "@barefootjs/client": ">=0.2.0",
113
109
  "@barefootjs/jsx": ">=0.2.0",
114
110
  "@barefootjs/shared": ">=0.2.0",
115
- "hono": "^4.0.0"
111
+ "@barefootjs/vite": ">=0.2.0",
112
+ "hono": "^4.0.0",
113
+ "vite": "^6.0.0"
114
+ },
115
+ "peerDependenciesMeta": {
116
+ "@barefootjs/vite": {
117
+ "optional": true
118
+ },
119
+ "vite": {
120
+ "optional": true
121
+ }
116
122
  },
117
123
  "devDependencies": {
118
124
  "@barefootjs/adapter-tests": "0.1.0",
125
+ "@barefootjs/vite": "0.31.1",
119
126
  "@types/jsdom": "^27.0.0",
120
127
  "hono": "^4.6.0",
121
128
  "jsdom": "^27.3.0",
122
- "typescript": "^5.0.0"
129
+ "typescript": "^5.0.0",
130
+ "vite": "^6.0.0"
123
131
  }
124
132
  }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Regression for piconic-ai/barefootjs#2559: type-check a CONSUMER program
3
+ * that imports a compiled template — the coverage gap that let
4
+ * `wrapWithInlineScripts`'s `unknown` return ship. Every generated
5
+ * component returns that call as its body, so an `unknown` return made
6
+ * every island fail TS2786 ("cannot be used as a JSX component") in any
7
+ * type-checking consumer (hit by sora's 0.26.2 → 0.31.0 migration), while
8
+ * nothing in-repo ever ran tsc over a program shaped like a consumer app.
9
+ *
10
+ * The test compiles a real `'use client'` component with a non-empty
11
+ * `scriptAssets` (so the emitted template wraps its return in
12
+ * `wrapWithInlineScripts`), writes it plus a scaffold-shaped `server.tsx`
13
+ * that renders the island, and type-checks the pair with the scaffold's
14
+ * own options (`strict`, `jsxImportSource: '@barefootjs/hono/jsx'`).
15
+ */
16
+ import { describe, expect, test } from 'bun:test'
17
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
18
+ import { join, resolve } from 'node:path'
19
+ import ts from 'typescript'
20
+ import { compileJSX } from '@barefootjs/jsx'
21
+ import { HonoAdapter } from '../adapter/index.ts'
22
+
23
+ const HERE = resolve(import.meta.dir)
24
+
25
+ const COMPONENT_SOURCE = `"use client"
26
+
27
+ import { createSignal } from '@barefootjs/client'
28
+
29
+ export function Counter() {
30
+ const [count, setCount] = createSignal(0)
31
+ return <button onClick={() => setCount(count() + 1)}>{count()}</button>
32
+ }
33
+ `
34
+
35
+ const SERVER_SOURCE = `import { Counter } from './components/Counter.tsx'
36
+
37
+ export function Page() {
38
+ return (
39
+ <div>
40
+ <Counter />
41
+ </div>
42
+ )
43
+ }
44
+ `
45
+
46
+ describe('consumer program type-check (#2559)', () => {
47
+ test('a compiled template used as a JSX component type-checks clean', () => {
48
+ const result = compileJSX(COMPONENT_SOURCE, '/virtual/Counter.tsx', {
49
+ adapter: new HonoAdapter(),
50
+ // Non-empty so the emitted template's component body returns
51
+ // wrapWithInlineScripts(...) — the #2559 shape.
52
+ scriptAssets: ['/static/components/assets/Counter.js'],
53
+ })
54
+ expect(result.errors.filter(e => e.severity === 'error')).toEqual([])
55
+ const template = result.files.find(f => f.type === 'markedTemplate')?.content
56
+ expect(template).toContain('wrapWithInlineScripts(')
57
+
58
+ // Inside the package so module resolution reaches the workspace's
59
+ // node_modules (hono, @barefootjs/*) exactly like a scaffolded app's.
60
+ const tmp = mkdtempSync(join(HERE, '.consumer-typecheck-'))
61
+ try {
62
+ mkdirSync(join(tmp, 'components'), { recursive: true })
63
+ writeFileSync(join(tmp, 'components', 'Counter.tsx'), template!)
64
+ writeFileSync(join(tmp, 'server.tsx'), SERVER_SOURCE)
65
+
66
+ const program = ts.createProgram(
67
+ [join(tmp, 'server.tsx')],
68
+ {
69
+ strict: true,
70
+ noEmit: true,
71
+ target: ts.ScriptTarget.ESNext,
72
+ module: ts.ModuleKind.ESNext,
73
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
74
+ jsx: ts.JsxEmit.ReactJSX,
75
+ jsxImportSource: '@barefootjs/hono/jsx',
76
+ lib: ['lib.esnext.d.ts', 'lib.dom.d.ts'],
77
+ allowImportingTsExtensions: true,
78
+ // Consumer apps skipLibCheck too; TS2786 fires in OUR files
79
+ // regardless, which is exactly what this test pins.
80
+ skipLibCheck: true,
81
+ },
82
+ )
83
+ const diagnostics = ts.getPreEmitDiagnostics(program).map(d => ({
84
+ code: d.code,
85
+ file: d.file?.fileName ?? '',
86
+ message: ts.flattenDiagnosticMessageText(d.messageText, ' '),
87
+ }))
88
+
89
+ // TS2786 = "'X' cannot be used as a JSX component." — the #2559
90
+ // failure. Assert none anywhere in the consumer program.
91
+ expect(diagnostics.filter(d => d.code === 2786)).toEqual([])
92
+ } finally {
93
+ rmSync(tmp, { recursive: true, force: true })
94
+ }
95
+ })
96
+ })
@@ -124,15 +124,22 @@ describe.skipIf(!INTEGRATION)(
124
124
  expect(wrangler.name).toBe('demo-app')
125
125
  })
126
126
 
127
- test('dev script wires bf build --watch + unocss + wrangler dev --live-reload', () => {
127
+ test('dev script wires vite dev + unocss + wrangler dev --live-reload', () => {
128
128
  const pkg = JSON.parse(
129
129
  readFileSync(path.join(projectDir, 'package.json'), 'utf-8'),
130
130
  ) as { scripts?: Record<string, string> }
131
- expect(pkg.scripts?.dev).toContain('bf build --watch')
131
+ expect(pkg.scripts?.dev).toContain('vite build')
132
+ expect(pkg.scripts?.dev).toContain('vite dev')
132
133
  expect(pkg.scripts?.dev).toContain('unocss --watch')
133
134
  expect(pkg.scripts?.dev).toContain('wrangler dev --live-reload')
134
135
  })
135
136
 
137
+ test('vite.config.ts composes @barefootjs/hono/vite', () => {
138
+ const cfg = readFileSync(path.join(projectDir, 'vite.config.ts'), 'utf-8')
139
+ expect(cfg).toContain("from '@barefootjs/hono/vite'")
140
+ expect(cfg).toContain("base: '/components/'")
141
+ })
142
+
136
143
  test('deploy script targets Cloudflare Workers', () => {
137
144
  expect(result.stdout).toContain('Deploy:')
138
145
  expect(result.stdout).toMatch(/npm run deploy\s+# deploy to Cloudflare Workers/)
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Coverage of `HonoAdapter.generate()`'s `AdapterGenerateOptions.scriptAssets`
3
+ * handling (see `scripts.tsx`'s `registerComponentScripts`/
4
+ * `wrapWithInlineScripts` docstrings and `hono-adapter.ts`'s `scriptAssets`
5
+ * field docstring for the full design).
6
+ *
7
+ * Mirrors `GoTemplateAdapter`'s own `scriptAssets` contract: `undefined` →
8
+ * no scriptAssets-driven output at all; `[]` → resolved, but nothing to
9
+ * register (server-only file, or a client file whose bundle isn't in the
10
+ * manifest yet) — no dead codegen; non-empty → bake exactly these URLs in
11
+ * via `registerComponentScripts`.
12
+ */
13
+ import { describe, test, expect } from 'bun:test'
14
+ import { compileJSX } from '@barefootjs/jsx'
15
+ import type { ComponentIR } from '@barefootjs/jsx'
16
+ import { HonoAdapter } from '../adapter'
17
+
18
+ function compileMarkedTemplate(source: string, scriptAssets: string[] | undefined, file = 'Demo.tsx'): string {
19
+ const adapter = new HonoAdapter()
20
+ const result = compileJSX(source, file, { adapter, scriptAssets })
21
+ const errors = result.errors.filter((e) => e.severity === 'error')
22
+ expect(errors).toEqual([])
23
+ const tmpl = result.files.find((f) => f.type === 'markedTemplate')
24
+ expect(tmpl).toBeDefined()
25
+ return tmpl!.content
26
+ }
27
+
28
+ const CLIENT_COMPONENT = `'use client'
29
+ import { createSignal } from '@barefootjs/client'
30
+
31
+ export function Counter(props: { initial: number }) {
32
+ const [count, setCount] = createSignal(props.initial)
33
+ return <button onClick={() => setCount(count() + 1)}>{count()}</button>
34
+ }
35
+ `
36
+
37
+ const CLIENT_COMPONENT_IF_ROOT = `'use client'
38
+ import { createSignal } from '@barefootjs/client'
39
+
40
+ export function Toggle(props: { asChild?: boolean }) {
41
+ const [open, setOpen] = createSignal(false)
42
+
43
+ if (props.asChild) {
44
+ return <span onClick={() => setOpen(!open())}>child</span>
45
+ }
46
+
47
+ return <button onClick={() => setOpen(!open())}>toggle</button>
48
+ }
49
+ `
50
+
51
+ describe('HonoAdapter scriptAssets codegen', () => {
52
+ test('undefined scriptAssets: no scriptAssets-driven codegen at all', () => {
53
+ const output = compileMarkedTemplate(CLIENT_COMPONENT, undefined)
54
+ expect(output).not.toContain('registerComponentScripts')
55
+ expect(output).not.toContain('wrapWithInlineScripts')
56
+ expect(output).not.toContain("@barefootjs/hono/scripts'")
57
+ })
58
+
59
+ test('empty scriptAssets: resolved-but-empty emits no registration codegen', () => {
60
+ const output = compileMarkedTemplate(CLIENT_COMPONENT, [])
61
+ expect(output).not.toContain('registerComponentScripts')
62
+ expect(output).not.toContain('wrapWithInlineScripts')
63
+ })
64
+
65
+ test('non-empty scriptAssets: bakes registerComponentScripts + wraps the return, no separate runtime script', () => {
66
+ const output = compileMarkedTemplate(CLIENT_COMPONENT, ['/static/build/assets/Counter-abc123.js'])
67
+
68
+ expect(output).toContain("import { registerComponentScripts, wrapWithInlineScripts } from '@barefootjs/hono/scripts'")
69
+ expect(output).toContain('const __bfInlineScripts = registerComponentScripts(["/static/build/assets/Counter-abc123.js"])')
70
+ expect(output).toContain('return wrapWithInlineScripts((')
71
+ expect(output).toContain('), __bfInlineScripts)')
72
+ // No hand-rolled `barefoot.js` registration — the runtime is a shared
73
+ // ESM chunk the resolved entry already imports.
74
+ expect(output).not.toContain('barefoot.js')
75
+ })
76
+
77
+ test('multiple scriptAssets URLs are all baked into one registerComponentScripts call', () => {
78
+ const output = compileMarkedTemplate(CLIENT_COMPONENT, [
79
+ 'http://localhost:5173/@vite/client',
80
+ 'http://localhost:5173/src/components/Counter.tsx',
81
+ ])
82
+ expect(output).toContain(
83
+ 'registerComponentScripts(["http://localhost:5173/@vite/client","http://localhost:5173/src/components/Counter.tsx"])',
84
+ )
85
+ })
86
+
87
+ test('if-statement root: both branches wrap their return with wrapWithInlineScripts', () => {
88
+ const output = compileMarkedTemplate(CLIENT_COMPONENT_IF_ROOT, ['/static/build/assets/Toggle-abc123.js'])
89
+
90
+ const wrapOpens = output.match(/return wrapWithInlineScripts\(\(/g) ?? []
91
+ const wrapCloses = output.match(/\), __bfInlineScripts\)/g) ?? []
92
+ // One wrapped return per branch (the `if` consequent and the trailing
93
+ // `return (...)` alternate) — an if-statement root never emits a bare,
94
+ // unwrapped `return (`.
95
+ expect(wrapOpens.length).toBe(2)
96
+ expect(wrapCloses.length).toBe(2)
97
+ // No branch falls back to a bare, unwrapped `return (`.
98
+ expect(output).not.toMatch(/return \(\n/)
99
+ })
100
+ })
101
+
102
+ /**
103
+ * Coverage of `HonoAdapter.generate()`'s `AdapterGenerateOptions.preloadAssets`
104
+ * handling — the sibling option to `scriptAssets` above. See
105
+ * `scripts.tsx`'s `registerComponentPreloads`/`wrapWithInlineScripts`
106
+ * docstrings and `hono-adapter.ts`'s `preloadAssets` field docstring for
107
+ * the full design.
108
+ *
109
+ * Same `undefined`/`[]`/non-empty contract as `scriptAssets`, with the
110
+ * added constraint that `preloadAssets` is only meaningful alongside a
111
+ * non-empty `scriptAssets` — see `AdapterGenerateOptions.preloadAssets`.
112
+ */
113
+ /** IR for a source, for the tests that drive `generate()` directly rather
114
+ * than through `compileJSX` — `AdapterGenerateOptions`-only fields (e.g.
115
+ * `skipScriptRegistration`) have no `CompileOptions` counterpart to travel
116
+ * through. Mirrors the same helper in every other adapter's suite. */
117
+ function compileToIR(source: string): ComponentIR {
118
+ const result = compileJSX(source.trimStart(), 'test.tsx', {
119
+ adapter: new HonoAdapter(),
120
+ outputIR: true,
121
+ })
122
+ const irFile = result.files.find(f => f.type === 'ir')
123
+ if (!irFile) throw new Error('No IR output')
124
+ return JSON.parse(irFile.content) as ComponentIR
125
+ }
126
+
127
+ function compileMarkedTemplateWithPreloads(
128
+ source: string,
129
+ scriptAssets: string[] | undefined,
130
+ preloadAssets: string[] | undefined,
131
+ file = 'Demo.tsx',
132
+ ): string {
133
+ const adapter = new HonoAdapter()
134
+ const result = compileJSX(source, file, { adapter, scriptAssets, preloadAssets })
135
+ const errors = result.errors.filter((e) => e.severity === 'error')
136
+ expect(errors).toEqual([])
137
+ const tmpl = result.files.find((f) => f.type === 'markedTemplate')
138
+ expect(tmpl).toBeDefined()
139
+ return tmpl!.content
140
+ }
141
+
142
+ describe('HonoAdapter preloadAssets codegen', () => {
143
+ test('preloadAssets bakes registerComponentPreloads and wraps the return with the preload list, before the script registration', () => {
144
+ const output = compileMarkedTemplateWithPreloads(
145
+ CLIENT_COMPONENT,
146
+ ['/static/build/assets/Counter-abc123.js'],
147
+ ['/static/build/assets/index-def456.js', '/static/build/assets/TodoItem-ghi789.js'],
148
+ )
149
+
150
+ expect(output).toContain("import { registerComponentScripts, registerComponentPreloads, wrapWithInlineScripts } from '@barefootjs/hono/scripts'")
151
+ expect(output).toContain(
152
+ 'const __bfInlinePreloads = registerComponentPreloads(["/static/build/assets/index-def456.js","/static/build/assets/TodoItem-ghi789.js"])',
153
+ )
154
+ const preloadIdx = output.indexOf('registerComponentPreloads(')
155
+ const scriptIdx = output.indexOf('registerComponentScripts(')
156
+ expect(preloadIdx).toBeGreaterThanOrEqual(0)
157
+ expect(scriptIdx).toBeGreaterThan(preloadIdx)
158
+ expect(output).toContain('return wrapWithInlineScripts((')
159
+ expect(output).toContain('), __bfInlineScripts, __bfInlinePreloads)')
160
+ })
161
+
162
+ test('preloadAssets: [] emits no registerComponentPreloads codegen', () => {
163
+ const output = compileMarkedTemplateWithPreloads(
164
+ CLIENT_COMPONENT,
165
+ ['/static/build/assets/Counter-abc123.js'],
166
+ [],
167
+ )
168
+ expect(output).not.toContain('registerComponentPreloads')
169
+ expect(output).not.toContain('__bfInlinePreloads')
170
+ // scriptAssets codegen is unaffected — same wrap shape as before.
171
+ expect(output).toContain('), __bfInlineScripts)')
172
+ })
173
+
174
+ test('preloadAssets: undefined emits no registerComponentPreloads codegen', () => {
175
+ const output = compileMarkedTemplateWithPreloads(
176
+ CLIENT_COMPONENT,
177
+ ['/static/build/assets/Counter-abc123.js'],
178
+ undefined,
179
+ )
180
+ expect(output).not.toContain('registerComponentPreloads')
181
+ expect(output).not.toContain('__bfInlinePreloads')
182
+ expect(output).toContain('), __bfInlineScripts)')
183
+ })
184
+
185
+ test('preloadAssets is ignored when scriptAssets is empty (only meaningful alongside a non-empty scriptAssets)', () => {
186
+ const output = compileMarkedTemplateWithPreloads(CLIENT_COMPONENT, [], ['/static/build/assets/index-def456.js'])
187
+ expect(output).not.toContain('registerComponentPreloads')
188
+ expect(output).not.toContain('registerComponentScripts')
189
+ })
190
+
191
+ // `skipScriptRegistration` lives on `AdapterGenerateOptions`, NOT on
192
+ // `CompileOptions` — a parent adapter sets it when emitting a child, so it
193
+ // never travels through `compileJSX`. Exercising it therefore means calling
194
+ // `generate()` directly, the same way every other adapter's suite does.
195
+ //
196
+ // Note what actually enforces this: `hasPreloadAssets()` is conjoined with
197
+ // `hasScriptAssets()`, so suppressing scripts suppresses preloads
198
+ // structurally — mutating the `skipScriptRegistration` branch alone cannot
199
+ // make this test fail. The independent guarantee is pinned by the
200
+ // "ignored when scriptAssets is empty" test above, which DOES go red when
201
+ // that conjunct is dropped. Both are kept: this one states the intent a
202
+ // reader looks for, that one holds the line.
203
+ test('skipScriptRegistration suppresses the preload registration as well as the script registration', () => {
204
+ const ir = compileToIR(CLIENT_COMPONENT)
205
+ const { template } = new HonoAdapter().generate(ir, {
206
+ skipScriptRegistration: true,
207
+ scriptAssets: ['/static/build/assets/Counter-abc123.js'],
208
+ preloadAssets: ['/static/build/assets/index-def456.js'],
209
+ })
210
+ expect(template).not.toContain('registerComponentPreloads')
211
+ expect(template).not.toContain('registerComponentScripts')
212
+ expect(template).not.toContain('wrapWithInlineScripts')
213
+ expect(template).not.toContain("@barefootjs/hono/scripts'")
214
+ })
215
+
216
+ test('if-statement root: both branches wrap with wrapWithInlineScripts passing __bfInlinePreloads', () => {
217
+ const output = compileMarkedTemplateWithPreloads(
218
+ CLIENT_COMPONENT_IF_ROOT,
219
+ ['/static/build/assets/Toggle-abc123.js'],
220
+ ['/static/build/assets/index-def456.js'],
221
+ )
222
+
223
+ const wrapOpens = output.match(/return wrapWithInlineScripts\(\(/g) ?? []
224
+ const wrapCloses = output.match(/\), __bfInlineScripts, __bfInlinePreloads\)/g) ?? []
225
+ expect(wrapOpens.length).toBe(2)
226
+ expect(wrapCloses.length).toBe(2)
227
+ })
228
+ })
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Coverage of `@barefootjs/hono/vite`'s `barefoot()`:
3
+ *
4
+ * - one real `vite build()` end to end (mirrors `@barefootjs/go-template/
5
+ * vite`'s own `vite.test.ts` rigor — a plugin that only passes mocked
6
+ * unit tests hasn't been shown to work) against a checked-in fixture
7
+ * (not a system tmpdir) so `@barefootjs/client` resolves through the
8
+ * monorepo's real node_modules symlinks;
9
+ * - the `assets` → generated `bf-assets.ts` behavior, exercised the same
10
+ * way (a real build, since it needs the real manifest Vite writes).
11
+ *
12
+ * Unlike Go, there is no `afterEmit`-driven combination step to test here
13
+ * (see `vite.ts`'s module docstring) — Hono's SSR template is
14
+ * self-contained per component, so `barefoot()` always returns a
15
+ * single-element array unless `assets` is set.
16
+ */
17
+ import { describe, test, expect } from 'bun:test'
18
+ import { build } from 'vite'
19
+ import { mkdtemp, rm, readFile } from 'node:fs/promises'
20
+ import { tmpdir } from 'node:os'
21
+ import { join, resolve } from 'node:path'
22
+ import { barefoot, barefoot as defaultBarefoot } from '../vite.ts'
23
+
24
+ const FIXTURE_ROOT = resolve(import.meta.dirname, '../../e2e-fixture')
25
+
26
+ describe('@barefootjs/hono/vite: real vite build', () => {
27
+ test('exports the same function as both named `barefoot` and default', () => {
28
+ expect(defaultBarefoot).toBe(barefoot)
29
+ })
30
+
31
+ test('returns a single-element plugin array when `assets` is omitted', () => {
32
+ const plugins = barefoot({ components: ['src/components'], templates: 'views' })
33
+ expect(plugins).toHaveLength(1)
34
+ })
35
+
36
+ // core's `barefoot()` attaches `api.options` (see `@barefootjs/vite`'s
37
+ // `BarefootPluginApi`) to the SAME plugin object this wrapper returns
38
+ // unchanged as `plugins[0]` — this pins that composition doesn't lose it,
39
+ // for either array shape (`assets` omitted vs. present, which adds a
40
+ // SECOND, unrelated companion plugin).
41
+ test('surfaces core\'s plugin.api.options unchanged on the returned plugin, with the HonoAdapter it constructed', () => {
42
+ const plugins = barefoot({ components: ['src/components', '../shared/blog'], templates: 'views' }) as any[]
43
+ const core = plugins[0]
44
+ expect(core.name).toBe('barefoot')
45
+ expect(core.api.options.components).toEqual(['src/components', '../shared/blog'])
46
+ expect(core.api.options.templates).toBe('views')
47
+ expect(core.api.options.adapter.constructor.name).toBe('HonoAdapter')
48
+ })
49
+
50
+ test('writes a self-contained SSR template with scriptAssets baked in, same as core alone would do', async () => {
51
+ const outDir = await mkdtemp(join(tmpdir(), 'barefoot-hono-vite-dist-'))
52
+ const templatesDir = await mkdtemp(join(tmpdir(), 'barefoot-hono-vite-views-'))
53
+
54
+ try {
55
+ await build({
56
+ configFile: false,
57
+ root: FIXTURE_ROOT,
58
+ base: '/static/build/',
59
+ logLevel: 'warn',
60
+ build: { outDir, emptyOutDir: true },
61
+ plugins: barefoot({
62
+ components: ['src/components'],
63
+ templates: templatesDir,
64
+ }),
65
+ })
66
+
67
+ const template = await readFile(join(templatesDir, 'Counter.tsx'), 'utf8')
68
+ // The scriptAssets-driven codegen path (see hono-adapter.ts).
69
+ expect(template).toContain('registerComponentScripts(')
70
+ expect(template).toContain("from '@barefootjs/hono/scripts'")
71
+ // No separate runtime registration — the shared `@barefootjs/client`
72
+ // chunk arrives as an ESM import the bundled entry already makes.
73
+ expect(template).not.toContain('barefoot.js')
74
+ } finally {
75
+ await rm(outDir, { recursive: true, force: true })
76
+ await rm(templatesDir, { recursive: true, force: true })
77
+ }
78
+ }, 60_000)
79
+
80
+ test('`assets` resolves a non-component entry\'s manifest-hashed URL into a generated `Assets` TS map', async () => {
81
+ const outDir = await mkdtemp(join(tmpdir(), 'barefoot-hono-vite-dist-assets-'))
82
+ const templatesDir = await mkdtemp(join(tmpdir(), 'barefoot-hono-vite-views-assets-'))
83
+ const assetsPath = join(FIXTURE_ROOT, 'dist/bf-assets.ts')
84
+
85
+ try {
86
+ await build({
87
+ configFile: false,
88
+ root: FIXTURE_ROOT,
89
+ base: '/static/build/',
90
+ logLevel: 'warn',
91
+ build: {
92
+ outDir,
93
+ emptyOutDir: true,
94
+ // Registering the non-component entry is the CALLER's job (stock
95
+ // Vite config) — `assets` below only resolves the URL Vite
96
+ // already bundled it to, it doesn't request the bundling.
97
+ rollupOptions: { input: { bootstrap: resolve(FIXTURE_ROOT, 'client/bootstrap.ts') } },
98
+ },
99
+ plugins: barefoot({
100
+ components: ['src/components'],
101
+ templates: templatesDir,
102
+ assets: { Bootstrap: 'client/bootstrap.ts' },
103
+ }),
104
+ })
105
+
106
+ const manifest = JSON.parse(await readFile(join(outDir, '.vite/manifest.json'), 'utf8'))
107
+ const expectedUrl = `/static/build/${manifest['client/bootstrap.ts'].file}`
108
+
109
+ const content = await readFile(assetsPath, 'utf8')
110
+ expect(content).toContain('export const Assets: Record<string, string>')
111
+ expect(content).toContain(`"Bootstrap": ${JSON.stringify(expectedUrl)}`)
112
+ } finally {
113
+ await rm(outDir, { recursive: true, force: true })
114
+ await rm(templatesDir, { recursive: true, force: true })
115
+ await rm(join(FIXTURE_ROOT, 'dist'), { recursive: true, force: true })
116
+ }
117
+ }, 60_000)
118
+
119
+ test('`assets` throws an actionable error when the entry was never registered as a Rollup input', async () => {
120
+ const outDir = await mkdtemp(join(tmpdir(), 'barefoot-hono-vite-dist-assets-missing-'))
121
+ const templatesDir = await mkdtemp(join(tmpdir(), 'barefoot-hono-vite-views-assets-missing-'))
122
+
123
+ try {
124
+ await expect(
125
+ build({
126
+ configFile: false,
127
+ root: FIXTURE_ROOT,
128
+ base: '/static/build/',
129
+ logLevel: 'silent',
130
+ build: { outDir, emptyOutDir: true },
131
+ plugins: barefoot({
132
+ components: ['src/components'],
133
+ templates: templatesDir,
134
+ assets: { Bootstrap: 'client/bootstrap.ts' },
135
+ }),
136
+ }),
137
+ ).rejects.toThrow(/was not found in the build manifest/)
138
+ } finally {
139
+ await rm(outDir, { recursive: true, force: true })
140
+ await rm(templatesDir, { recursive: true, force: true })
141
+ await rm(join(FIXTURE_ROOT, 'dist'), { recursive: true, force: true })
142
+ }
143
+ }, 60_000)
144
+ })