@barefootjs/jinja 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/README.md +10 -6
- package/dist/adapter/expr/emitters.d.ts +1 -1
- package/dist/adapter/expr/emitters.d.ts.map +1 -1
- package/dist/adapter/index.js +18 -3
- package/dist/adapter/jinja-adapter.d.ts +0 -1
- package/dist/adapter/jinja-adapter.d.ts.map +1 -1
- package/dist/index.js +18 -3
- package/dist/vite.d.ts +38 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +6742 -0
- package/package.json +22 -9
- package/python/barefootjs/runtime.py +33 -3
- package/src/__tests__/jinja-adapter-unit.test.ts +146 -0
- package/src/__tests__/vite.test.ts +125 -0
- package/src/adapter/expr/emitters.ts +33 -9
- package/src/adapter/jinja-adapter.ts +27 -5
- package/src/vite.ts +215 -0
- package/dist/build.d.ts +0 -28
- package/dist/build.d.ts.map +0 -1
- package/dist/build.js +0 -189257
- package/src/build.ts +0 -37
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/jinja",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Jinja2 adapter for BarefootJS — compiles IR to .jinja templates and ships the Python BarefootJS rendering runtime; runs under any Python web framework (Flask, etc.)",
|
|
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": [
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
],
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "bun run build:js && bun run build:types",
|
|
33
|
-
"build:js": "bun build ./src/index.ts ./src/adapter/index.ts
|
|
33
|
+
"build:js": "bun build ./src/index.ts ./src/adapter/index.ts --root ./src --outdir ./dist --format esm --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",
|
|
34
34
|
"build:types": "tsgo --emitDeclarationOnly --outDir ./dist",
|
|
35
35
|
"test": "bun test",
|
|
36
36
|
"clean": "rm -rf dist",
|
|
@@ -53,14 +53,27 @@
|
|
|
53
53
|
"directory": "packages/adapter-jinja"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@barefootjs/shared": "0.
|
|
56
|
+
"@barefootjs/shared": "0.31.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@barefootjs/jsx": ">=0.2.0"
|
|
59
|
+
"@barefootjs/jsx": ">=0.2.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.
|
|
64
|
-
"
|
|
73
|
+
"@barefootjs/jsx": "0.31.0",
|
|
74
|
+
"@barefootjs/vite": "0.31.0",
|
|
75
|
+
"@barefootjs/client": "0.31.0",
|
|
76
|
+
"typescript": "^5.0.0",
|
|
77
|
+
"vite": "^6.0.0"
|
|
65
78
|
}
|
|
66
79
|
}
|
|
@@ -531,6 +531,8 @@ class BarefootJS:
|
|
|
531
531
|
|
|
532
532
|
_scripts = _dual_accessor("_scripts", lambda self: [])
|
|
533
533
|
_script_seen = _dual_accessor("_script_seen", lambda self: {})
|
|
534
|
+
_preloads = _dual_accessor("_preloads", lambda self: [])
|
|
535
|
+
_preload_seen = _dual_accessor("_preload_seen", lambda self: {})
|
|
534
536
|
_child_renderers = _dual_accessor("_child_renderers", lambda self: {})
|
|
535
537
|
_is_child = _dual_accessor("_is_child", False)
|
|
536
538
|
_scope_id = _dual_accessor("_scope_id")
|
|
@@ -675,6 +677,24 @@ class BarefootJS:
|
|
|
675
677
|
seen[path] = True
|
|
676
678
|
self._scripts().append(path)
|
|
677
679
|
|
|
680
|
+
def register_preload(self, path: str) -> None:
|
|
681
|
+
"""Register a `<link rel="modulepreload">` hint (a component's
|
|
682
|
+
resolved TRANSITIVE-chunk preload URL, per
|
|
683
|
+
`AdapterGenerateOptions.preloadAssets`) -- mirrors `register_script`
|
|
684
|
+
above exactly. Kept as a SEPARATE `_preloads`/`_preload_seen` pair
|
|
685
|
+
-- a preload URL and a script URL are never the same concern, so
|
|
686
|
+
there is no cross-dedup to do between the two. Python lists/dicts
|
|
687
|
+
are mutable REFERENCE types (unlike PHP arrays), so
|
|
688
|
+
`child_bf._preloads(parent._preloads())` shares the SAME underlying
|
|
689
|
+
list -- no `ArrayObject`-style wrapper is needed for cross-instance
|
|
690
|
+
propagation the way the PHP port requires (see that port's
|
|
691
|
+
`register_preload` docblock)."""
|
|
692
|
+
seen = self._preload_seen()
|
|
693
|
+
if seen.get(path):
|
|
694
|
+
return
|
|
695
|
+
seen[path] = True
|
|
696
|
+
self._preloads().append(path)
|
|
697
|
+
|
|
678
698
|
# -----------------------------------------------------------------
|
|
679
699
|
# Child Component Rendering
|
|
680
700
|
# -----------------------------------------------------------------
|
|
@@ -721,8 +741,9 @@ class BarefootJS:
|
|
|
721
741
|
def register_components_from_manifest(
|
|
722
742
|
self, manifest: dict, signal_init: Optional[dict] = None
|
|
723
743
|
) -> None:
|
|
724
|
-
"""`
|
|
725
|
-
|
|
744
|
+
"""`vite build` (via `@barefootjs/jinja/vite`'s `barefoot()` plugin)
|
|
745
|
+
emits a manifest describing every component the page might invoke.
|
|
746
|
+
This walks that manifest and registers one child
|
|
726
747
|
renderer per UI registry entry -- the path shape `ui/<name>/index`
|
|
727
748
|
maps to the `<name>` slot key the generated template invokes via
|
|
728
749
|
`bf.render_child('<name>', ...)`.
|
|
@@ -787,6 +808,8 @@ class BarefootJS:
|
|
|
787
808
|
child_bf._child_renderers(parent._child_renderers())
|
|
788
809
|
child_bf._scripts(parent._scripts())
|
|
789
810
|
child_bf._script_seen(parent._script_seen())
|
|
811
|
+
child_bf._preloads(parent._preloads())
|
|
812
|
+
child_bf._preload_seen(parent._preload_seen())
|
|
790
813
|
|
|
791
814
|
extra: dict = {}
|
|
792
815
|
if signal_init_fn:
|
|
@@ -810,7 +833,14 @@ class BarefootJS:
|
|
|
810
833
|
# -----------------------------------------------------------------
|
|
811
834
|
|
|
812
835
|
def scripts(self) -> str:
|
|
813
|
-
|
|
836
|
+
"""Renders every collected `<link rel="modulepreload">` hint, THEN
|
|
837
|
+
every collected `<script type="module">` tag -- preloads always
|
|
838
|
+
precede the scripts they describe, or the hint is useless. Preloads
|
|
839
|
+
carry no execution-order constraint the way scripts do (they are
|
|
840
|
+
just hints, not code that runs), so registration order is emitted
|
|
841
|
+
as-is."""
|
|
842
|
+
tags = [f'<link rel="modulepreload" crossorigin href="{path}">' for path in self._preloads()]
|
|
843
|
+
tags += [f'<script type="module" src="{path}"></script>' for path in self._scripts()]
|
|
814
844
|
return "\n".join(tags)
|
|
815
845
|
|
|
816
846
|
# -----------------------------------------------------------------
|
|
@@ -567,3 +567,149 @@ export function Child() {
|
|
|
567
567
|
expect(template).not.toContain('scope_comment')
|
|
568
568
|
})
|
|
569
569
|
})
|
|
570
|
+
|
|
571
|
+
describe('JinjaAdapter - scriptAssets (Vite late-binding, PR1)', () => {
|
|
572
|
+
const CLIENT_COMPONENT = `
|
|
573
|
+
'use client'
|
|
574
|
+
import { createSignal } from '@barefootjs/client'
|
|
575
|
+
export function Counter() {
|
|
576
|
+
const [count, setCount] = createSignal(0)
|
|
577
|
+
return <button onClick={() => setCount(count() + 1)}>{count()}</button>
|
|
578
|
+
}
|
|
579
|
+
`
|
|
580
|
+
|
|
581
|
+
test('emits one register_script per URL, in order, when scriptAssets is set', () => {
|
|
582
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
583
|
+
const { template } = new JinjaAdapter().generate(ir, {
|
|
584
|
+
scriptAssets: ['/assets/runtime-abc123.js', '/assets/counter-def456.js'],
|
|
585
|
+
})
|
|
586
|
+
const runtimeIdx = template.indexOf("bf.register_script('/assets/runtime-abc123.js')")
|
|
587
|
+
const compIdx = template.indexOf("bf.register_script('/assets/counter-def456.js')")
|
|
588
|
+
expect(runtimeIdx).toBeGreaterThanOrEqual(0)
|
|
589
|
+
expect(compIdx).toBeGreaterThanOrEqual(0)
|
|
590
|
+
expect(runtimeIdx).toBeLessThan(compIdx)
|
|
591
|
+
expect(template).toContain('_bf_reg0')
|
|
592
|
+
expect(template).toContain('_bf_reg1')
|
|
593
|
+
expect(template).not.toContain('/static/components/barefoot.js')
|
|
594
|
+
expect(template).not.toContain('Counter.client.js')
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
test('emits a single registration for a single-element scriptAssets array', () => {
|
|
598
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
599
|
+
const { template } = new JinjaAdapter().generate(ir, {
|
|
600
|
+
scriptAssets: ['/assets/only-one.js'],
|
|
601
|
+
})
|
|
602
|
+
expect(template).toContain("bf.register_script('/assets/only-one.js')")
|
|
603
|
+
expect(template.match(/register_script/g)?.length).toBe(1)
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
test('an empty scriptAssets array emits no script registrations', () => {
|
|
607
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
608
|
+
const { template } = new JinjaAdapter().generate(ir, { scriptAssets: [] })
|
|
609
|
+
expect(template).not.toContain('register_script')
|
|
610
|
+
})
|
|
611
|
+
|
|
612
|
+
test('skipScriptRegistration still wins when scriptAssets is also set', () => {
|
|
613
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
614
|
+
const { template } = new JinjaAdapter().generate(ir, {
|
|
615
|
+
skipScriptRegistration: true,
|
|
616
|
+
scriptAssets: ['/assets/should-not-appear.js'],
|
|
617
|
+
})
|
|
618
|
+
expect(template).not.toContain('register_script')
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
test('absent scriptAssets falls back to adapter-computed script paths', () => {
|
|
622
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
623
|
+
const computed = new JinjaAdapter().generate(ir).template
|
|
624
|
+
const explicitUndefined = new JinjaAdapter().generate(ir, { scriptAssets: undefined }).template
|
|
625
|
+
expect(computed).toContain("bf.register_script('/static/components/barefoot.js')")
|
|
626
|
+
expect(computed).toContain("bf.register_script('/static/components/Counter.client.js')")
|
|
627
|
+
expect(explicitUndefined).toBe(computed)
|
|
628
|
+
})
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
describe('JinjaAdapter - preloadAssets', () => {
|
|
632
|
+
const CLIENT_COMPONENT = `
|
|
633
|
+
'use client'
|
|
634
|
+
import { createSignal } from '@barefootjs/client'
|
|
635
|
+
export function Counter() {
|
|
636
|
+
const [count, setCount] = createSignal(0)
|
|
637
|
+
return <button onClick={() => setCount(count() + 1)}>{count()}</button>
|
|
638
|
+
}
|
|
639
|
+
`
|
|
640
|
+
|
|
641
|
+
test('non-empty preloadAssets + non-empty scriptAssets: preload registrations emitted, in order, before script registrations', () => {
|
|
642
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
643
|
+
const { template } = new JinjaAdapter().generate(ir, {
|
|
644
|
+
scriptAssets: ['/assets/runtime-abc123.js', '/assets/counter-def456.js'],
|
|
645
|
+
preloadAssets: ['/assets/index-pre1.js', '/assets/shared-pre2.js'],
|
|
646
|
+
})
|
|
647
|
+
const pre1Idx = template.indexOf("{% set _bf_pre0 = bf.register_preload('/assets/index-pre1.js') %}")
|
|
648
|
+
const pre2Idx = template.indexOf("{% set _bf_pre1 = bf.register_preload('/assets/shared-pre2.js') %}")
|
|
649
|
+
const script1Idx = template.indexOf("{% set _bf_reg0 = bf.register_script('/assets/runtime-abc123.js') %}")
|
|
650
|
+
const script2Idx = template.indexOf("{% set _bf_reg1 = bf.register_script('/assets/counter-def456.js') %}")
|
|
651
|
+
expect(pre1Idx).toBeGreaterThanOrEqual(0)
|
|
652
|
+
expect(pre2Idx).toBeGreaterThan(pre1Idx)
|
|
653
|
+
expect(script1Idx).toBeGreaterThan(pre2Idx)
|
|
654
|
+
expect(script2Idx).toBeGreaterThan(script1Idx)
|
|
655
|
+
})
|
|
656
|
+
|
|
657
|
+
test('preloadAssets: [] emits no preload registration', () => {
|
|
658
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
659
|
+
const { template } = new JinjaAdapter().generate(ir, {
|
|
660
|
+
scriptAssets: ['/assets/runtime-abc123.js'],
|
|
661
|
+
preloadAssets: [],
|
|
662
|
+
})
|
|
663
|
+
expect(template).not.toContain('register_preload')
|
|
664
|
+
expect(template).toContain("bf.register_script('/assets/runtime-abc123.js')")
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
test('preloadAssets: undefined emits no preload registration', () => {
|
|
668
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
669
|
+
const { template } = new JinjaAdapter().generate(ir, {
|
|
670
|
+
scriptAssets: ['/assets/runtime-abc123.js'],
|
|
671
|
+
preloadAssets: undefined,
|
|
672
|
+
})
|
|
673
|
+
expect(template).not.toContain('register_preload')
|
|
674
|
+
expect(template).toContain("bf.register_script('/assets/runtime-abc123.js')")
|
|
675
|
+
})
|
|
676
|
+
|
|
677
|
+
test('preloadAssets non-empty but scriptAssets: [] emits no preload registration (preloads are only meaningful alongside a real script)', () => {
|
|
678
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
679
|
+
const { template } = new JinjaAdapter().generate(ir, {
|
|
680
|
+
scriptAssets: [],
|
|
681
|
+
preloadAssets: ['/assets/index-pre1.js'],
|
|
682
|
+
})
|
|
683
|
+
expect(template).not.toContain('register_preload')
|
|
684
|
+
expect(template).not.toContain('register_script')
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
test('skipScriptRegistration: true suppresses both preloads and scripts', () => {
|
|
688
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
689
|
+
const { template } = new JinjaAdapter().generate(ir, {
|
|
690
|
+
skipScriptRegistration: true,
|
|
691
|
+
scriptAssets: ['/assets/runtime-abc123.js'],
|
|
692
|
+
preloadAssets: ['/assets/index-pre1.js'],
|
|
693
|
+
})
|
|
694
|
+
expect(template).not.toContain('register_preload')
|
|
695
|
+
expect(template).not.toContain('register_script')
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
// Regression guard: a previous attempt emitted a literal
|
|
699
|
+
// `<link rel="modulepreload">` tag directly into the component template,
|
|
700
|
+
// which injected a rendered DOM node before the component's root and
|
|
701
|
+
// broke hydration across all eight integrations (blade, erb,
|
|
702
|
+
// go-template, jinja, mojolicious, rust, twig, xslate). Preload hints
|
|
703
|
+
// must ONLY ever be emitted as no-output register statements (here,
|
|
704
|
+
// `{% set _bf_preN = bf.register_preload(...) %}`) that the adapter's
|
|
705
|
+
// runtime later renders itself — never as literal markup baked into the
|
|
706
|
+
// template.
|
|
707
|
+
test('never emits a literal <link tag into the template', () => {
|
|
708
|
+
const ir = compileToIR(CLIENT_COMPONENT)
|
|
709
|
+
const { template } = new JinjaAdapter().generate(ir, {
|
|
710
|
+
scriptAssets: ['/assets/runtime-abc123.js'],
|
|
711
|
+
preloadAssets: ['/assets/index-pre1.js'],
|
|
712
|
+
})
|
|
713
|
+
expect(template).not.toContain('<link')
|
|
714
|
+
})
|
|
715
|
+
})
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage of `@barefootjs/jinja/vite`'s `barefoot()`:
|
|
3
|
+
*
|
|
4
|
+
* - one real `vite build()` end to end (mirrors `@barefootjs/go-template/
|
|
5
|
+
* vite`, `@barefootjs/hono/vite`, and `@barefootjs/blade/vite`'s own
|
|
6
|
+
* `vite.test.ts` rigor — a plugin that only passes mocked unit tests
|
|
7
|
+
* hasn't been shown to work) against a checked-in fixture (not a system
|
|
8
|
+
* tmpdir) so `@barefootjs/client` resolves through the monorepo's real
|
|
9
|
+
* node_modules symlinks;
|
|
10
|
+
* - the `assets` → generated `bf-assets.json` behavior, exercised the same
|
|
11
|
+
* way (a real build, since it needs the real manifest Vite writes).
|
|
12
|
+
*
|
|
13
|
+
* Unlike Go, there is no `afterEmit`-driven type-combination step to test
|
|
14
|
+
* here (see `vite.ts`'s module docstring) — `JinjaAdapter.generate()` never
|
|
15
|
+
* produces a `types` section at all, so `barefoot()` always returns a
|
|
16
|
+
* single-element array unless `assets` is set.
|
|
17
|
+
*/
|
|
18
|
+
import { describe, test, expect } from 'bun:test'
|
|
19
|
+
import { build } from 'vite'
|
|
20
|
+
import { mkdtemp, rm, readFile } from 'node:fs/promises'
|
|
21
|
+
import { tmpdir } from 'node:os'
|
|
22
|
+
import { join, resolve } from 'node:path'
|
|
23
|
+
import { barefoot, barefoot as defaultBarefoot } from '../vite.ts'
|
|
24
|
+
|
|
25
|
+
const FIXTURE_ROOT = resolve(import.meta.dirname, '../../e2e-fixture')
|
|
26
|
+
|
|
27
|
+
describe('@barefootjs/jinja/vite: real vite build', () => {
|
|
28
|
+
test('exports the same function as both named `barefoot` and default', () => {
|
|
29
|
+
expect(defaultBarefoot).toBe(barefoot)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('returns a single-element plugin array when `assets` is omitted', () => {
|
|
33
|
+
const plugins = barefoot({ components: ['src/components'], templates: 'views' })
|
|
34
|
+
expect(plugins).toHaveLength(1)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('writes a self-contained .jinja template with scriptAssets baked in, same as core alone would do', async () => {
|
|
38
|
+
const outDir = await mkdtemp(join(tmpdir(), 'barefoot-jinja-vite-dist-'))
|
|
39
|
+
const templatesDir = await mkdtemp(join(tmpdir(), 'barefoot-jinja-vite-views-'))
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
await build({
|
|
43
|
+
configFile: false,
|
|
44
|
+
root: FIXTURE_ROOT,
|
|
45
|
+
base: '/static/build/',
|
|
46
|
+
logLevel: 'warn',
|
|
47
|
+
build: { outDir, emptyOutDir: true },
|
|
48
|
+
plugins: barefoot({
|
|
49
|
+
components: ['src/components'],
|
|
50
|
+
templates: templatesDir,
|
|
51
|
+
}),
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const template = await readFile(join(templatesDir, 'Counter.jinja'), 'utf8')
|
|
55
|
+
expect(template).toContain("bf.register_script(")
|
|
56
|
+
} finally {
|
|
57
|
+
await rm(outDir, { recursive: true, force: true })
|
|
58
|
+
await rm(templatesDir, { recursive: true, force: true })
|
|
59
|
+
}
|
|
60
|
+
}, 60_000)
|
|
61
|
+
|
|
62
|
+
test('`assets` resolves a non-component entry\'s manifest-hashed URL into a generated JSON asset map', async () => {
|
|
63
|
+
const outDir = await mkdtemp(join(tmpdir(), 'barefoot-jinja-vite-dist-assets-'))
|
|
64
|
+
const templatesDir = await mkdtemp(join(tmpdir(), 'barefoot-jinja-vite-views-assets-'))
|
|
65
|
+
const assetsPath = join(FIXTURE_ROOT, 'dist/bf-assets.json')
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
await build({
|
|
69
|
+
configFile: false,
|
|
70
|
+
root: FIXTURE_ROOT,
|
|
71
|
+
base: '/static/build/',
|
|
72
|
+
logLevel: 'warn',
|
|
73
|
+
build: {
|
|
74
|
+
outDir,
|
|
75
|
+
emptyOutDir: true,
|
|
76
|
+
// Registering the non-component entry is the CALLER's job (stock
|
|
77
|
+
// Vite config) — `assets` below only resolves the URL Vite
|
|
78
|
+
// already bundled it to, it doesn't request the bundling.
|
|
79
|
+
rollupOptions: { input: { bootstrap: resolve(FIXTURE_ROOT, 'client/bootstrap.ts') } },
|
|
80
|
+
},
|
|
81
|
+
plugins: barefoot({
|
|
82
|
+
components: ['src/components'],
|
|
83
|
+
templates: templatesDir,
|
|
84
|
+
assets: { Bootstrap: 'client/bootstrap.ts' },
|
|
85
|
+
}),
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const manifest = JSON.parse(await readFile(join(outDir, '.vite/manifest.json'), 'utf8'))
|
|
89
|
+
const expectedUrl = `/static/build/${manifest['client/bootstrap.ts'].file}`
|
|
90
|
+
|
|
91
|
+
const content = JSON.parse(await readFile(assetsPath, 'utf8'))
|
|
92
|
+
expect(content).toEqual({ Bootstrap: expectedUrl })
|
|
93
|
+
} finally {
|
|
94
|
+
await rm(outDir, { recursive: true, force: true })
|
|
95
|
+
await rm(templatesDir, { recursive: true, force: true })
|
|
96
|
+
await rm(join(FIXTURE_ROOT, 'dist'), { recursive: true, force: true })
|
|
97
|
+
}
|
|
98
|
+
}, 60_000)
|
|
99
|
+
|
|
100
|
+
test('`assets` throws an actionable error when the entry was never registered as a Rollup input', async () => {
|
|
101
|
+
const outDir = await mkdtemp(join(tmpdir(), 'barefoot-jinja-vite-dist-assets-missing-'))
|
|
102
|
+
const templatesDir = await mkdtemp(join(tmpdir(), 'barefoot-jinja-vite-views-assets-missing-'))
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
await expect(
|
|
106
|
+
build({
|
|
107
|
+
configFile: false,
|
|
108
|
+
root: FIXTURE_ROOT,
|
|
109
|
+
base: '/static/build/',
|
|
110
|
+
logLevel: 'silent',
|
|
111
|
+
build: { outDir, emptyOutDir: true },
|
|
112
|
+
plugins: barefoot({
|
|
113
|
+
components: ['src/components'],
|
|
114
|
+
templates: templatesDir,
|
|
115
|
+
assets: { Bootstrap: 'client/bootstrap.ts' },
|
|
116
|
+
}),
|
|
117
|
+
}),
|
|
118
|
+
).rejects.toThrow(/was not found in the build manifest/)
|
|
119
|
+
} finally {
|
|
120
|
+
await rm(outDir, { recursive: true, force: true })
|
|
121
|
+
await rm(templatesDir, { recursive: true, force: true })
|
|
122
|
+
await rm(join(FIXTURE_ROOT, 'dist'), { recursive: true, force: true })
|
|
123
|
+
}
|
|
124
|
+
}, 60_000)
|
|
125
|
+
})
|
|
@@ -117,15 +117,33 @@ export function truthyTest(node: ParsedExpr, rendered: string): string {
|
|
|
117
117
|
* callback's receiver.
|
|
118
118
|
*/
|
|
119
119
|
export class JinjaFilterEmitter implements ParsedExprEmitter {
|
|
120
|
+
// Plain field declarations + assignment, NOT TS constructor-parameter-
|
|
121
|
+
// property shorthand: Vite's `bundleConfigFile` externalizes any bare
|
|
122
|
+
// (non-relative) import when loading `vite.config.ts` (see
|
|
123
|
+
// `@barefootjs/jinja/vite`'s docstring), so this file can be loaded
|
|
124
|
+
// directly by Node's OWN native TypeScript type-stripping (enabled by
|
|
125
|
+
// default since Node 22.18/23.6) rather than esbuild — and Node's
|
|
126
|
+
// strip-only mode does not support parameter properties (`SyntaxError
|
|
127
|
+
// [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]`), only plain type annotations.
|
|
128
|
+
private readonly param: string
|
|
129
|
+
private readonly localVarMap: Map<string, string>
|
|
130
|
+
private readonly isStringName: (n: string) => boolean
|
|
131
|
+
// Records a BF101 for predicate shapes this emitter can only degrade
|
|
132
|
+
// (#2038). Optional so emitter construction stays possible without an
|
|
133
|
+
// adapter; a missing hook keeps the old silent-degrade emit.
|
|
134
|
+
private readonly onUnsupported?: (message: string, reason?: string) => void
|
|
135
|
+
|
|
120
136
|
constructor(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
param: string,
|
|
138
|
+
localVarMap: Map<string, string>,
|
|
139
|
+
isStringName: (n: string) => boolean = () => false,
|
|
140
|
+
onUnsupported?: (message: string, reason?: string) => void,
|
|
141
|
+
) {
|
|
142
|
+
this.param = param
|
|
143
|
+
this.localVarMap = localVarMap
|
|
144
|
+
this.isStringName = isStringName
|
|
145
|
+
this.onUnsupported = onUnsupported
|
|
146
|
+
}
|
|
129
147
|
|
|
130
148
|
identifier(name: string): string {
|
|
131
149
|
if (name === this.param) return jinjaIdent(this.param)
|
|
@@ -296,7 +314,13 @@ export class JinjaFilterEmitter implements ParsedExprEmitter {
|
|
|
296
314
|
* - no lambda fallback exists (see the file header, divergence 2).
|
|
297
315
|
*/
|
|
298
316
|
export class JinjaTopLevelEmitter implements ParsedExprEmitter {
|
|
299
|
-
|
|
317
|
+
// Plain field + assignment, not a parameter property — see
|
|
318
|
+
// `JinjaFilterEmitter`'s constructor comment above for why.
|
|
319
|
+
private readonly ctx: JinjaEmitContext
|
|
320
|
+
|
|
321
|
+
constructor(ctx: JinjaEmitContext) {
|
|
322
|
+
this.ctx = ctx
|
|
323
|
+
}
|
|
300
324
|
|
|
301
325
|
identifier(name: string): string {
|
|
302
326
|
// `undefined` / `null` nested inside a larger expression tree — Jinja
|
|
@@ -193,9 +193,6 @@ export class JinjaAdapter extends BaseAdapter implements IRNodeEmitter<JinjaRend
|
|
|
193
193
|
name = 'jinja'
|
|
194
194
|
extension = '.jinja'
|
|
195
195
|
templatesPerComponent = true
|
|
196
|
-
// Template-string target with no component layer: `bf build` emits a static
|
|
197
|
-
// import-map HTML snippet to include into the page <head>.
|
|
198
|
-
importMapInjection = 'html-snippet' as const
|
|
199
196
|
|
|
200
197
|
/**
|
|
201
198
|
* Identifier-path callees the Jinja runtime can render in template scope.
|
|
@@ -360,7 +357,7 @@ export class JinjaAdapter extends BaseAdapter implements IRNodeEmitter<JinjaRend
|
|
|
360
357
|
// Generate script registration
|
|
361
358
|
const scriptReg = options?.skipScriptRegistration
|
|
362
359
|
? ''
|
|
363
|
-
: this.generateScriptRegistrations(ir, options?.scriptBaseName)
|
|
360
|
+
: this.generateScriptRegistrations(ir, options?.scriptBaseName, options?.scriptAssets, options?.preloadAssets)
|
|
364
361
|
|
|
365
362
|
// SSR context consumers (`const x = useContext(Ctx)`): seed each local
|
|
366
363
|
// from the active provider value (or the `createContext` default). The
|
|
@@ -402,7 +399,32 @@ export class JinjaAdapter extends BaseAdapter implements IRNodeEmitter<JinjaRend
|
|
|
402
399
|
// Script Registration
|
|
403
400
|
// ===========================================================================
|
|
404
401
|
|
|
405
|
-
private generateScriptRegistrations(ir: ComponentIR, scriptBaseName?: string): string {
|
|
402
|
+
private generateScriptRegistrations(ir: ComponentIR, scriptBaseName?: string, scriptAssets?: string[], preloadAssets?: string[]): string {
|
|
403
|
+
// `scriptAssets`, when present (including `[]`), fully supersedes the
|
|
404
|
+
// adapter-computed `barefootJsPath` / `clientJsBasePath` pair — see
|
|
405
|
+
// `AdapterGenerateOptions.scriptAssets`. The caller (e.g. the Vite
|
|
406
|
+
// plugin) has already decided the exact ordered URL list, including
|
|
407
|
+
// whether any script is needed at all.
|
|
408
|
+
if (scriptAssets) {
|
|
409
|
+
if (scriptAssets.length === 0) return ''
|
|
410
|
+
const lines: string[] = []
|
|
411
|
+
// `preloadAssets` is only meaningful alongside a non-empty
|
|
412
|
+
// `scriptAssets` (see `AdapterGenerateOptions.preloadAssets`) — this
|
|
413
|
+
// branch is only reached when that already holds. Emitted BEFORE the
|
|
414
|
+
// script registrations: every preload hint must precede the script
|
|
415
|
+
// tags it describes, or the hint is useless.
|
|
416
|
+
if (preloadAssets && preloadAssets.length > 0) {
|
|
417
|
+
preloadAssets.forEach((url, i) => {
|
|
418
|
+
lines.push(`{% set _bf_pre${i} = bf.register_preload('${url}') %}`)
|
|
419
|
+
})
|
|
420
|
+
}
|
|
421
|
+
scriptAssets.forEach((url, i) => {
|
|
422
|
+
lines.push(`{% set _bf_reg${i} = bf.register_script('${url}') %}`)
|
|
423
|
+
})
|
|
424
|
+
lines.push('')
|
|
425
|
+
return lines.join('\n')
|
|
426
|
+
}
|
|
427
|
+
|
|
406
428
|
const hasInteractivity = hasClientInteractivity(ir)
|
|
407
429
|
if (!hasInteractivity) return ''
|
|
408
430
|
|