@barefootjs/mojolicious 0.30.6 → 0.31.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/dist/adapter/expr/emitters.d.ts +1 -1
- package/dist/adapter/expr/emitters.d.ts.map +1 -1
- package/dist/adapter/index.js +11 -3
- package/dist/adapter/mojo-adapter.d.ts +0 -1
- package/dist/adapter/mojo-adapter.d.ts.map +1 -1
- package/dist/index.js +11 -3
- package/dist/vite.d.ts +38 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +6663 -0
- package/lib/BarefootJS/Backend/Mojo.pm +1 -1
- package/lib/Mojolicious/Plugin/BarefootJS/DevReload.pm +1 -1
- package/lib/Mojolicious/Plugin/BarefootJS.pm +1 -1
- package/package.json +21 -8
- package/src/__tests__/mojo-adapter.test.ts +143 -0
- package/src/__tests__/multi-component-registry.test.ts +3 -3
- package/src/__tests__/scaffold.test.ts +7 -7
- package/src/__tests__/stock-route.test.ts +6 -5
- package/src/__tests__/vite.test.ts +126 -0
- package/src/adapter/expr/emitters.ts +37 -13
- package/src/adapter/mojo-adapter.ts +27 -5
- package/src/vite.ts +216 -0
- package/dist/build.d.ts +0 -28
- package/dist/build.d.ts.map +0 -1
- package/dist/build.js +0 -1933
- package/src/build.ts +0 -37
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/mojolicious",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Mojolicious EP template adapter for BarefootJS - generates .html.ep files from IR",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
"./test-render": {
|
|
18
18
|
"bun": "./src/test-render.ts"
|
|
19
19
|
},
|
|
20
|
-
"./
|
|
21
|
-
"types": "./dist/
|
|
22
|
-
"import": "./dist/
|
|
20
|
+
"./vite": {
|
|
21
|
+
"types": "./dist/vite.d.ts",
|
|
22
|
+
"import": "./dist/vite.js"
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
],
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "bun run build:js && bun run build:types",
|
|
32
|
-
"build:js": "bun build ./src/index.ts ./src/adapter/index.ts
|
|
32
|
+
"build:js": "bun build ./src/index.ts ./src/adapter/index.ts --root ./src --outdir ./dist --format esm --external @barefootjs/jsx --external @barefootjs/shared --external typescript --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",
|
|
33
33
|
"build:types": "tsgo --emitDeclarationOnly --outDir ./dist",
|
|
34
34
|
"test": "bun test",
|
|
35
35
|
"clean": "rm -rf dist",
|
|
@@ -52,14 +52,27 @@
|
|
|
52
52
|
"directory": "packages/adapter-mojolicious"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@barefootjs/shared": "0.
|
|
55
|
+
"@barefootjs/shared": "0.31.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@barefootjs/jsx": ">=0.2.0",
|
|
59
|
-
"typescript": "^5.0.0"
|
|
59
|
+
"typescript": "^5.0.0",
|
|
60
|
+
"@barefootjs/vite": ">=0.2.0",
|
|
61
|
+
"vite": "^6.0.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependenciesMeta": {
|
|
64
|
+
"@barefootjs/vite": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"vite": {
|
|
68
|
+
"optional": true
|
|
69
|
+
}
|
|
60
70
|
},
|
|
61
71
|
"devDependencies": {
|
|
62
72
|
"@barefootjs/adapter-tests": "0.1.0",
|
|
63
|
-
"@barefootjs/jsx": "0.
|
|
73
|
+
"@barefootjs/jsx": "0.31.0",
|
|
74
|
+
"@barefootjs/vite": "0.31.0",
|
|
75
|
+
"@barefootjs/client": "0.31.0",
|
|
76
|
+
"vite": "^6.0.0"
|
|
64
77
|
}
|
|
65
78
|
}
|
|
@@ -2133,3 +2133,146 @@ export function Parent() {
|
|
|
2133
2133
|
expect(template).not.toContain('$bf_prop_data')
|
|
2134
2134
|
})
|
|
2135
2135
|
})
|
|
2136
|
+
|
|
2137
|
+
describe('MojoAdapter - scriptAssets (Vite late-binding, PR1)', () => {
|
|
2138
|
+
const CLIENT_COMPONENT = `
|
|
2139
|
+
'use client'
|
|
2140
|
+
import { createSignal } from '@barefootjs/client'
|
|
2141
|
+
export function Counter() {
|
|
2142
|
+
const [count, setCount] = createSignal(0)
|
|
2143
|
+
return <button onClick={() => setCount(count() + 1)}>{count()}</button>
|
|
2144
|
+
}
|
|
2145
|
+
`
|
|
2146
|
+
|
|
2147
|
+
test('emits one register_script per URL, in order, when scriptAssets is set', () => {
|
|
2148
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
2149
|
+
const { template } = new MojoAdapter().generate(ir, {
|
|
2150
|
+
scriptAssets: ['/assets/runtime-abc123.js', '/assets/counter-def456.js'],
|
|
2151
|
+
})
|
|
2152
|
+
const runtimeIdx = template.indexOf("bf->register_script('/assets/runtime-abc123.js')")
|
|
2153
|
+
const compIdx = template.indexOf("bf->register_script('/assets/counter-def456.js')")
|
|
2154
|
+
expect(runtimeIdx).toBeGreaterThanOrEqual(0)
|
|
2155
|
+
expect(compIdx).toBeGreaterThanOrEqual(0)
|
|
2156
|
+
expect(runtimeIdx).toBeLessThan(compIdx)
|
|
2157
|
+
expect(template).not.toContain('/static/components/barefoot.js')
|
|
2158
|
+
expect(template).not.toContain('Counter.client.js')
|
|
2159
|
+
})
|
|
2160
|
+
|
|
2161
|
+
test('emits a single registration for a single-element scriptAssets array', () => {
|
|
2162
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
2163
|
+
const { template } = new MojoAdapter().generate(ir, {
|
|
2164
|
+
scriptAssets: ['/assets/only-one.js'],
|
|
2165
|
+
})
|
|
2166
|
+
expect(template).toContain("bf->register_script('/assets/only-one.js')")
|
|
2167
|
+
expect(template.match(/register_script/g)?.length).toBe(1)
|
|
2168
|
+
})
|
|
2169
|
+
|
|
2170
|
+
test('an empty scriptAssets array emits no script registrations', () => {
|
|
2171
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
2172
|
+
const { template } = new MojoAdapter().generate(ir, { scriptAssets: [] })
|
|
2173
|
+
expect(template).not.toContain('register_script')
|
|
2174
|
+
})
|
|
2175
|
+
|
|
2176
|
+
test('skipScriptRegistration still wins when scriptAssets is also set', () => {
|
|
2177
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
2178
|
+
const { template } = new MojoAdapter().generate(ir, {
|
|
2179
|
+
skipScriptRegistration: true,
|
|
2180
|
+
scriptAssets: ['/assets/should-not-appear.js'],
|
|
2181
|
+
})
|
|
2182
|
+
expect(template).not.toContain('register_script')
|
|
2183
|
+
})
|
|
2184
|
+
|
|
2185
|
+
test('absent scriptAssets falls back to adapter-computed script paths', () => {
|
|
2186
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
2187
|
+
const computed = new MojoAdapter().generate(ir).template
|
|
2188
|
+
const explicitUndefined = new MojoAdapter().generate(ir, { scriptAssets: undefined }).template
|
|
2189
|
+
expect(computed).toContain("bf->register_script('/static/components/barefoot.js')")
|
|
2190
|
+
expect(computed).toContain("bf->register_script('/static/components/Counter.client.js')")
|
|
2191
|
+
expect(explicitUndefined).toBe(computed)
|
|
2192
|
+
})
|
|
2193
|
+
})
|
|
2194
|
+
|
|
2195
|
+
describe('MojoAdapter - preloadAssets', () => {
|
|
2196
|
+
const CLIENT_COMPONENT = `
|
|
2197
|
+
'use client'
|
|
2198
|
+
import { createSignal } from '@barefootjs/client'
|
|
2199
|
+
export function Counter() {
|
|
2200
|
+
const [count, setCount] = createSignal(0)
|
|
2201
|
+
return <button onClick={() => setCount(count() + 1)}>{count()}</button>
|
|
2202
|
+
}
|
|
2203
|
+
`
|
|
2204
|
+
|
|
2205
|
+
test('non-empty preloadAssets + non-empty scriptAssets: preload registrations emitted, in order, before script registrations', () => {
|
|
2206
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
2207
|
+
const { template } = new MojoAdapter().generate(ir, {
|
|
2208
|
+
scriptAssets: ['/assets/runtime-abc123.js', '/assets/counter-def456.js'],
|
|
2209
|
+
preloadAssets: ['/assets/index-pre1.js', '/assets/shared-pre2.js'],
|
|
2210
|
+
})
|
|
2211
|
+
const pre1Idx = template.indexOf("% bf->register_preload('/assets/index-pre1.js');")
|
|
2212
|
+
const pre2Idx = template.indexOf("% bf->register_preload('/assets/shared-pre2.js');")
|
|
2213
|
+
const script1Idx = template.indexOf("% bf->register_script('/assets/runtime-abc123.js');")
|
|
2214
|
+
const script2Idx = template.indexOf("% bf->register_script('/assets/counter-def456.js');")
|
|
2215
|
+
expect(pre1Idx).toBeGreaterThanOrEqual(0)
|
|
2216
|
+
expect(pre2Idx).toBeGreaterThan(pre1Idx)
|
|
2217
|
+
expect(script1Idx).toBeGreaterThan(pre2Idx)
|
|
2218
|
+
expect(script2Idx).toBeGreaterThan(script1Idx)
|
|
2219
|
+
})
|
|
2220
|
+
|
|
2221
|
+
test('preloadAssets: [] emits no preload registration', () => {
|
|
2222
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
2223
|
+
const { template } = new MojoAdapter().generate(ir, {
|
|
2224
|
+
scriptAssets: ['/assets/runtime-abc123.js'],
|
|
2225
|
+
preloadAssets: [],
|
|
2226
|
+
})
|
|
2227
|
+
expect(template).not.toContain('register_preload')
|
|
2228
|
+
expect(template).toContain("bf->register_script('/assets/runtime-abc123.js')")
|
|
2229
|
+
})
|
|
2230
|
+
|
|
2231
|
+
test('preloadAssets: undefined emits no preload registration', () => {
|
|
2232
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
2233
|
+
const { template } = new MojoAdapter().generate(ir, {
|
|
2234
|
+
scriptAssets: ['/assets/runtime-abc123.js'],
|
|
2235
|
+
preloadAssets: undefined,
|
|
2236
|
+
})
|
|
2237
|
+
expect(template).not.toContain('register_preload')
|
|
2238
|
+
expect(template).toContain("bf->register_script('/assets/runtime-abc123.js')")
|
|
2239
|
+
})
|
|
2240
|
+
|
|
2241
|
+
test('preloadAssets non-empty but scriptAssets: [] emits no preload registration (preloads are only meaningful alongside a real script)', () => {
|
|
2242
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
2243
|
+
const { template } = new MojoAdapter().generate(ir, {
|
|
2244
|
+
scriptAssets: [],
|
|
2245
|
+
preloadAssets: ['/assets/index-pre1.js'],
|
|
2246
|
+
})
|
|
2247
|
+
expect(template).not.toContain('register_preload')
|
|
2248
|
+
expect(template).not.toContain('register_script')
|
|
2249
|
+
})
|
|
2250
|
+
|
|
2251
|
+
test('skipScriptRegistration: true suppresses both preloads and scripts', () => {
|
|
2252
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
2253
|
+
const { template } = new MojoAdapter().generate(ir, {
|
|
2254
|
+
skipScriptRegistration: true,
|
|
2255
|
+
scriptAssets: ['/assets/runtime-abc123.js'],
|
|
2256
|
+
preloadAssets: ['/assets/index-pre1.js'],
|
|
2257
|
+
})
|
|
2258
|
+
expect(template).not.toContain('register_preload')
|
|
2259
|
+
expect(template).not.toContain('register_script')
|
|
2260
|
+
})
|
|
2261
|
+
|
|
2262
|
+
// Regression guard: a previous attempt emitted a literal
|
|
2263
|
+
// `<link rel="modulepreload">` tag directly into the component template,
|
|
2264
|
+
// which injected a rendered DOM node before the component's root and
|
|
2265
|
+
// broke hydration across all eight integrations (blade, erb,
|
|
2266
|
+
// go-template, jinja, mojolicious, rust, twig, xslate). Preload hints
|
|
2267
|
+
// must ONLY ever be emitted as no-output register statements (here,
|
|
2268
|
+
// `% bf->register_preload('...');`) that the adapter's runtime later
|
|
2269
|
+
// renders itself — never as literal markup baked into the template.
|
|
2270
|
+
test('never emits a literal <link tag into the template', () => {
|
|
2271
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
2272
|
+
const { template } = new MojoAdapter().generate(ir, {
|
|
2273
|
+
scriptAssets: ['/assets/runtime-abc123.js'],
|
|
2274
|
+
preloadAssets: ['/assets/index-pre1.js'],
|
|
2275
|
+
})
|
|
2276
|
+
expect(template).not.toContain('<link')
|
|
2277
|
+
})
|
|
2278
|
+
})
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
// This is the in-repo equivalent of the issue's repro: `bf add toast` →
|
|
13
13
|
// render a <Toast>-using component on mojo → expect 200 with toast markup.
|
|
14
14
|
// It boots a real Mojolicious app with the production plugin against
|
|
15
|
-
// compiler-produced templates plus a manifest in the shape `
|
|
15
|
+
// compiler-produced templates plus a manifest in the shape `@barefootjs/vite`
|
|
16
16
|
// emits (per-component rows under `components` — pinned on the emitter side
|
|
17
|
-
// by packages/
|
|
17
|
+
// by packages/vite/src/__tests__/component-manifest.test.ts).
|
|
18
18
|
//
|
|
19
19
|
// Runs only when `perl` with Mojolicious is installed (same skip policy as
|
|
20
20
|
// stock-route.test.ts, which this file mirrors).
|
|
@@ -176,7 +176,7 @@ describe.skipIf(!PERL_AVAILABLE)('Mojo multi-component registry modules (#2132)'
|
|
|
176
176
|
if (!probeTemplate) throw new Error('probe compile produced no template')
|
|
177
177
|
writeFileSync(path.join(appDir, 'dist/templates/ToastProbe.html.ep'), probeTemplate.content)
|
|
178
178
|
|
|
179
|
-
// Same entry shape `
|
|
179
|
+
// Same entry shape `@barefootjs/vite` writes to dist/templates/manifest.json
|
|
180
180
|
// (see build-manifest-components.test.ts for the emitter pin).
|
|
181
181
|
writeFileSync(
|
|
182
182
|
path.join(appDir, 'dist/templates/manifest.json'),
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// cross-adapter contract defined in `create-barefootjs` and the
|
|
5
5
|
// Mojo-specific wiring:
|
|
6
6
|
//
|
|
7
|
-
// - `
|
|
8
|
-
// uses `
|
|
7
|
+
// - `vite.config.ts` targets `@barefootjs/mojolicious/vite` and
|
|
8
|
+
// uses `base: '/static/components/'`.
|
|
9
9
|
// - `app.pl` forwards `/static/*` URLs to the on-disk static paths
|
|
10
10
|
// (Mojolicious's built-in dispatcher does not honour URL prefixes,
|
|
11
11
|
// so the explicit routes are load-bearing — without them every
|
|
@@ -116,7 +116,7 @@ describe.skipIf(!INTEGRATION)(
|
|
|
116
116
|
|
|
117
117
|
describe('app.pl serves static assets', () => {
|
|
118
118
|
// Mojolicious's built-in static dispatcher does not honour URL
|
|
119
|
-
// prefixes. The scaffold's `
|
|
119
|
+
// prefixes. The scaffold's `vite.config.ts` and layout `<link>`s
|
|
120
120
|
// all reference `/static/*` URLs, so `app.pl` needs explicit
|
|
121
121
|
// forwarding routes — without them every stylesheet and client bundle
|
|
122
122
|
// 404s in the browser even though the SSR HTML rendered correctly.
|
|
@@ -176,10 +176,10 @@ describe.skipIf(!INTEGRATION)(
|
|
|
176
176
|
expect(app).not.toContain("app->home->child('dist/templates/manifest.json')")
|
|
177
177
|
})
|
|
178
178
|
|
|
179
|
-
test('
|
|
180
|
-
const cfg = readFileSync(path.join(projectDir, '
|
|
181
|
-
expect(cfg).toContain("from '@barefootjs/mojolicious/
|
|
182
|
-
expect(cfg).toContain("
|
|
179
|
+
test('vite.config.ts targets the mojolicious adapter', () => {
|
|
180
|
+
const cfg = readFileSync(path.join(projectDir, 'vite.config.ts'), 'utf-8')
|
|
181
|
+
expect(cfg).toContain("from '@barefootjs/mojolicious/vite'")
|
|
182
|
+
expect(cfg).toContain("base: '/static/components/'")
|
|
183
183
|
})
|
|
184
184
|
|
|
185
185
|
test('layout stylesheets point at /static/*.css', () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Stock-route smoke test for the Mojo scaffold contract (#2126).
|
|
2
2
|
//
|
|
3
|
-
// The scaffold's happy path is: `npm run dev` (which starts `
|
|
4
|
-
//
|
|
3
|
+
// The scaffold's happy path is: `npm run dev` (which starts `vite dev`
|
|
4
|
+
// and morbo *concurrently*), then open `/`. That page renders a
|
|
5
5
|
// component whose EP template reads props/signals as bare scalars
|
|
6
6
|
// (`% my $count = ($initial // 0);`), and Mojo templates compile under
|
|
7
7
|
// `use strict` — so every one of those scalars must be declared by the
|
|
@@ -114,8 +114,8 @@ __DATA__
|
|
|
114
114
|
// manifest-at-boot — the manifest exists before the app loads
|
|
115
115
|
// (server restarted after a completed build).
|
|
116
116
|
// manifest-after-boot — the app loads first, the manifest appears
|
|
117
|
-
// afterwards (the concurrent `
|
|
118
|
-
//
|
|
117
|
+
// afterwards (the concurrent `vite dev` +
|
|
118
|
+
// morbo dev race on a fresh scaffold).
|
|
119
119
|
const SMOKE_PL = `use Mojo::Base -strict;
|
|
120
120
|
use Test::More;
|
|
121
121
|
use Test::Mojo;
|
|
@@ -161,7 +161,8 @@ describe.skipIf(!PERL_AVAILABLE)('Mojo scaffold stock route (#2126)', () => {
|
|
|
161
161
|
if (!template || !ssrDefaults) throw new Error('compileJSX produced no template/ssrDefaults')
|
|
162
162
|
|
|
163
163
|
writeFileSync(path.join(appDir, 'dist/templates/Counter.html.ep'), template.content)
|
|
164
|
-
// Same entry shape `
|
|
164
|
+
// Same entry shape `@barefootjs/vite` writes to
|
|
165
|
+
// dist/templates/manifest.json.
|
|
165
166
|
writeFileSync(
|
|
166
167
|
path.join(appDir, 'manifest.staged.json'),
|
|
167
168
|
JSON.stringify({
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage of `@barefootjs/mojolicious/vite`'s `barefoot()`:
|
|
3
|
+
*
|
|
4
|
+
* - one real `vite build()` end to end (mirrors `@barefootjs/go-template/
|
|
5
|
+
* vite`, `@barefootjs/hono/vite`, `@barefootjs/blade/vite`'s,
|
|
6
|
+
* `@barefootjs/jinja/vite`'s, and `@barefootjs/erb/vite`'s own
|
|
7
|
+
* `vite.test.ts` rigor — a plugin that only passes mocked unit tests
|
|
8
|
+
* hasn't been shown to work) against a checked-in fixture (not a system
|
|
9
|
+
* tmpdir) so `@barefootjs/client` resolves through the monorepo's real
|
|
10
|
+
* node_modules symlinks;
|
|
11
|
+
* - the `assets` → generated `bf-assets.json` behavior, exercised the same
|
|
12
|
+
* way (a real build, since it needs the real manifest Vite writes).
|
|
13
|
+
*
|
|
14
|
+
* Unlike Go, there is no `afterEmit`-driven type-combination step to test
|
|
15
|
+
* here (see `vite.ts`'s module docstring) — `MojoAdapter.generate()` never
|
|
16
|
+
* produces a `types` section at all, so `barefoot()` always returns a
|
|
17
|
+
* single-element array unless `assets` is set.
|
|
18
|
+
*/
|
|
19
|
+
import { describe, test, expect } from 'bun:test'
|
|
20
|
+
import { build } from 'vite'
|
|
21
|
+
import { mkdtemp, rm, readFile } from 'node:fs/promises'
|
|
22
|
+
import { tmpdir } from 'node:os'
|
|
23
|
+
import { join, resolve } from 'node:path'
|
|
24
|
+
import { barefoot, barefoot as defaultBarefoot } from '../vite.ts'
|
|
25
|
+
|
|
26
|
+
const FIXTURE_ROOT = resolve(import.meta.dirname, '../../e2e-fixture')
|
|
27
|
+
|
|
28
|
+
describe('@barefootjs/mojolicious/vite: real vite build', () => {
|
|
29
|
+
test('exports the same function as both named `barefoot` and default', () => {
|
|
30
|
+
expect(defaultBarefoot).toBe(barefoot)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('returns a single-element plugin array when `assets` is omitted', () => {
|
|
34
|
+
const plugins = barefoot({ components: ['src/components'], templates: 'views' })
|
|
35
|
+
expect(plugins).toHaveLength(1)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('writes a self-contained .html.ep template with scriptAssets baked in, same as core alone would do', async () => {
|
|
39
|
+
const outDir = await mkdtemp(join(tmpdir(), 'barefoot-mojolicious-vite-dist-'))
|
|
40
|
+
const templatesDir = await mkdtemp(join(tmpdir(), 'barefoot-mojolicious-vite-views-'))
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
await build({
|
|
44
|
+
configFile: false,
|
|
45
|
+
root: FIXTURE_ROOT,
|
|
46
|
+
base: '/static/build/',
|
|
47
|
+
logLevel: 'warn',
|
|
48
|
+
build: { outDir, emptyOutDir: true },
|
|
49
|
+
plugins: barefoot({
|
|
50
|
+
components: ['src/components'],
|
|
51
|
+
templates: templatesDir,
|
|
52
|
+
}),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const template = await readFile(join(templatesDir, 'Counter.html.ep'), 'utf8')
|
|
56
|
+
expect(template).toContain("bf->register_script(")
|
|
57
|
+
} finally {
|
|
58
|
+
await rm(outDir, { recursive: true, force: true })
|
|
59
|
+
await rm(templatesDir, { recursive: true, force: true })
|
|
60
|
+
}
|
|
61
|
+
}, 60_000)
|
|
62
|
+
|
|
63
|
+
test('`assets` resolves a non-component entry\'s manifest-hashed URL into a generated JSON asset map', async () => {
|
|
64
|
+
const outDir = await mkdtemp(join(tmpdir(), 'barefoot-mojolicious-vite-dist-assets-'))
|
|
65
|
+
const templatesDir = await mkdtemp(join(tmpdir(), 'barefoot-mojolicious-vite-views-assets-'))
|
|
66
|
+
const assetsPath = join(FIXTURE_ROOT, 'dist/bf-assets.json')
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await build({
|
|
70
|
+
configFile: false,
|
|
71
|
+
root: FIXTURE_ROOT,
|
|
72
|
+
base: '/static/build/',
|
|
73
|
+
logLevel: 'warn',
|
|
74
|
+
build: {
|
|
75
|
+
outDir,
|
|
76
|
+
emptyOutDir: true,
|
|
77
|
+
// Registering the non-component entry is the CALLER's job (stock
|
|
78
|
+
// Vite config) — `assets` below only resolves the URL Vite
|
|
79
|
+
// already bundled it to, it doesn't request the bundling.
|
|
80
|
+
rollupOptions: { input: { bootstrap: resolve(FIXTURE_ROOT, 'client/bootstrap.ts') } },
|
|
81
|
+
},
|
|
82
|
+
plugins: barefoot({
|
|
83
|
+
components: ['src/components'],
|
|
84
|
+
templates: templatesDir,
|
|
85
|
+
assets: { Bootstrap: 'client/bootstrap.ts' },
|
|
86
|
+
}),
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const manifest = JSON.parse(await readFile(join(outDir, '.vite/manifest.json'), 'utf8'))
|
|
90
|
+
const expectedUrl = `/static/build/${manifest['client/bootstrap.ts'].file}`
|
|
91
|
+
|
|
92
|
+
const content = JSON.parse(await readFile(assetsPath, 'utf8'))
|
|
93
|
+
expect(content).toEqual({ Bootstrap: expectedUrl })
|
|
94
|
+
} finally {
|
|
95
|
+
await rm(outDir, { recursive: true, force: true })
|
|
96
|
+
await rm(templatesDir, { recursive: true, force: true })
|
|
97
|
+
await rm(join(FIXTURE_ROOT, 'dist'), { recursive: true, force: true })
|
|
98
|
+
}
|
|
99
|
+
}, 60_000)
|
|
100
|
+
|
|
101
|
+
test('`assets` throws an actionable error when the entry was never registered as a Rollup input', async () => {
|
|
102
|
+
const outDir = await mkdtemp(join(tmpdir(), 'barefoot-mojolicious-vite-dist-assets-missing-'))
|
|
103
|
+
const templatesDir = await mkdtemp(join(tmpdir(), 'barefoot-mojolicious-vite-views-assets-missing-'))
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await expect(
|
|
107
|
+
build({
|
|
108
|
+
configFile: false,
|
|
109
|
+
root: FIXTURE_ROOT,
|
|
110
|
+
base: '/static/build/',
|
|
111
|
+
logLevel: 'silent',
|
|
112
|
+
build: { outDir, emptyOutDir: true },
|
|
113
|
+
plugins: barefoot({
|
|
114
|
+
components: ['src/components'],
|
|
115
|
+
templates: templatesDir,
|
|
116
|
+
assets: { Bootstrap: 'client/bootstrap.ts' },
|
|
117
|
+
}),
|
|
118
|
+
}),
|
|
119
|
+
).rejects.toThrow(/was not found in the build manifest/)
|
|
120
|
+
} finally {
|
|
121
|
+
await rm(outDir, { recursive: true, force: true })
|
|
122
|
+
await rm(templatesDir, { recursive: true, force: true })
|
|
123
|
+
await rm(join(FIXTURE_ROOT, 'dist'), { recursive: true, force: true })
|
|
124
|
+
}
|
|
125
|
+
}, 60_000)
|
|
126
|
+
})
|
|
@@ -77,19 +77,37 @@ const PREDICATE_METHODS: ReadonlySet<string> = new Set([
|
|
|
77
77
|
])
|
|
78
78
|
|
|
79
79
|
export class MojoFilterEmitter implements ParsedExprEmitter {
|
|
80
|
+
// Plain field declarations + assignment, NOT TS constructor-parameter-
|
|
81
|
+
// property shorthand: Vite's `bundleConfigFile` externalizes any bare
|
|
82
|
+
// (non-relative) import when loading `vite.config.ts` (see
|
|
83
|
+
// `@barefootjs/mojolicious/vite`'s docstring), so this file can be loaded
|
|
84
|
+
// directly by Node's OWN native TypeScript type-stripping (enabled by
|
|
85
|
+
// default since Node 22.18/23.6) rather than esbuild — and Node's
|
|
86
|
+
// strip-only mode does not support parameter properties (`SyntaxError
|
|
87
|
+
// [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]`), only plain type annotations.
|
|
88
|
+
private readonly param: string
|
|
89
|
+
private readonly localVarMap: Map<string, string>
|
|
90
|
+
// Reports whether a getter/prop name is string-typed, so `===`/`!==`
|
|
91
|
+
// against it lowers to `eq`/`ne` (#1672). Defaults to "never" for callers
|
|
92
|
+
// that don't thread it through.
|
|
93
|
+
private readonly isStringName: (n: string) => boolean
|
|
94
|
+
// Records a BF101 for nested callback shapes this emitter can only
|
|
95
|
+
// degrade — `find*` and the non-predicate methods (#2038). Optional so
|
|
96
|
+
// emitter construction stays possible without an adapter; a missing hook
|
|
97
|
+
// keeps the old silent-degrade emit.
|
|
98
|
+
private readonly onUnsupported?: (message: string, reason?: string) => void
|
|
99
|
+
|
|
80
100
|
constructor(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
private readonly onUnsupported?: (message: string, reason?: string) => void,
|
|
92
|
-
) {}
|
|
101
|
+
param: string,
|
|
102
|
+
localVarMap: Map<string, string>,
|
|
103
|
+
isStringName: (n: string) => boolean = () => false,
|
|
104
|
+
onUnsupported?: (message: string, reason?: string) => void,
|
|
105
|
+
) {
|
|
106
|
+
this.param = param
|
|
107
|
+
this.localVarMap = localVarMap
|
|
108
|
+
this.isStringName = isStringName
|
|
109
|
+
this.onUnsupported = onUnsupported
|
|
110
|
+
}
|
|
93
111
|
|
|
94
112
|
identifier(name: string): string {
|
|
95
113
|
if (name === this.param) return `$${this.param}`
|
|
@@ -296,7 +314,13 @@ export class MojoFilterEmitter implements ParsedExprEmitter {
|
|
|
296
314
|
* shapes the AST can't classify still emit something coherent.
|
|
297
315
|
*/
|
|
298
316
|
export class MojoTopLevelEmitter implements ParsedExprEmitter {
|
|
299
|
-
|
|
317
|
+
// Plain field + assignment, not a parameter property — see
|
|
318
|
+
// `MojoFilterEmitter`'s constructor comment above for why.
|
|
319
|
+
private readonly ctx: MojoEmitContext
|
|
320
|
+
|
|
321
|
+
constructor(ctx: MojoEmitContext) {
|
|
322
|
+
this.ctx = ctx
|
|
323
|
+
}
|
|
300
324
|
|
|
301
325
|
identifier(name: string): string {
|
|
302
326
|
// `undefined` / `null` nested inside a larger expression tree
|
|
@@ -144,9 +144,6 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
144
144
|
name = 'mojolicious'
|
|
145
145
|
extension = '.html.ep'
|
|
146
146
|
templatesPerComponent = true
|
|
147
|
-
// Template-string target with no component layer: `bf build` emits a static
|
|
148
|
-
// `barefoot-importmap.html` to `%= include` into the page <head> (#1644).
|
|
149
|
-
importMapInjection = 'html-snippet' as const
|
|
150
147
|
|
|
151
148
|
/**
|
|
152
149
|
* Identifier-path callees the Mojo runtime can render in template
|
|
@@ -323,7 +320,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
323
320
|
// Generate script registration
|
|
324
321
|
const scriptReg = options?.skipScriptRegistration
|
|
325
322
|
? ''
|
|
326
|
-
: this.generateScriptRegistrations(ir, options?.scriptBaseName)
|
|
323
|
+
: this.generateScriptRegistrations(ir, options?.scriptBaseName, options?.scriptAssets, options?.preloadAssets)
|
|
327
324
|
|
|
328
325
|
// SSR context consumers (`const x = useContext(Ctx)`): seed each local
|
|
329
326
|
// from the active provider value (or the `createContext` default) so the
|
|
@@ -467,7 +464,32 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
467
464
|
// Script Registration
|
|
468
465
|
// ===========================================================================
|
|
469
466
|
|
|
470
|
-
private generateScriptRegistrations(
|
|
467
|
+
private generateScriptRegistrations(
|
|
468
|
+
ir: ComponentIR,
|
|
469
|
+
scriptBaseName?: string,
|
|
470
|
+
scriptAssets?: string[],
|
|
471
|
+
preloadAssets?: string[],
|
|
472
|
+
): string {
|
|
473
|
+
// `scriptAssets`, when present (including `[]`), fully supersedes the
|
|
474
|
+
// adapter-computed `barefootJsPath` / `clientJsBasePath` pair — see
|
|
475
|
+
// `AdapterGenerateOptions.scriptAssets`. The caller (e.g. the Vite
|
|
476
|
+
// plugin) has already decided the exact ordered URL list, including
|
|
477
|
+
// whether any script is needed at all.
|
|
478
|
+
if (scriptAssets) {
|
|
479
|
+
if (scriptAssets.length === 0) return ''
|
|
480
|
+
// `preloadAssets` is only meaningful alongside a non-empty
|
|
481
|
+
// `scriptAssets` (see `AdapterGenerateOptions.preloadAssets`), and
|
|
482
|
+
// every preload registration is emitted BEFORE every script
|
|
483
|
+
// registration — a hint that arrives after the script it describes
|
|
484
|
+
// is useless. `% ...;` is a no-output EP statement line, same as
|
|
485
|
+
// `register_script` below — `register_preload`'s return value is
|
|
486
|
+
// never printed.
|
|
487
|
+
const lines = (preloadAssets ?? []).map((url) => `% bf->register_preload('${url}');`)
|
|
488
|
+
lines.push(...scriptAssets.map((url) => `% bf->register_script('${url}');`))
|
|
489
|
+
lines.push('')
|
|
490
|
+
return lines.join('\n')
|
|
491
|
+
}
|
|
492
|
+
|
|
471
493
|
const hasInteractivity = hasClientInteractivity(ir)
|
|
472
494
|
if (!hasInteractivity) return ''
|
|
473
495
|
|