@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/src/vite.ts ADDED
@@ -0,0 +1,215 @@
1
+ /**
2
+ * `@barefootjs/jinja/vite` — a Jinja2-specific COMPOSITION of core
3
+ * `@barefootjs/vite`'s `barefoot()`, mirroring `@barefootjs/go-template/
4
+ * vite`'s, `@barefootjs/hono/vite`'s, and `@barefootjs/blade/vite`'s shape
5
+ * and naming (`barefoot`, named AND default export; a user never passes
6
+ * `adapter`, this constructs `JinjaAdapter` itself).
7
+ *
8
+ * import { barefoot } from '@barefootjs/jinja/vite'
9
+ *
10
+ * export default defineConfig({
11
+ * base: '/integrations/django/client/',
12
+ * build: { outDir: 'dist/client' },
13
+ * plugins: barefoot({
14
+ * components: ['../shared/components', '../shared/blog'],
15
+ * templates: 'dist/templates',
16
+ * }),
17
+ * })
18
+ *
19
+ * ## Why this needs no `afterEmit`-driven type-combination step (unlike Go)
20
+ *
21
+ * `@barefootjs/go-template/vite` exists mainly to combine every discovered
22
+ * file's `types` fragment into ONE compilable `components.go` — Go's
23
+ * per-file fragments assume a shared `randomID` helper and a single package
24
+ * header, so they are not independently valid Go source, and an unused
25
+ * import fails the build outright (see that module's docstring).
26
+ * `JinjaAdapter.generate()` never produces a `types` section at all (Jinja2
27
+ * templates have no JS-style imports/types/exports to combine — see
28
+ * `jinja-adapter.ts`'s `generate()`, whose `sections.types` is always
29
+ * `''`), so there is nothing across files to stitch together, and no
30
+ * unused-import failure mode to guard against either. So this composition's
31
+ * core job is just what core's `barefoot()` already does: construct
32
+ * `JinjaAdapter` and hand it to core.
33
+ *
34
+ * ## No `adapterOptions` either — the other thing Go/Hono still need
35
+ *
36
+ * `JinjaAdapterOptions` has exactly two fields, `clientJsBasePath` and
37
+ * `barefootJsPath` — and `JinjaAdapter.generateScriptRegistrations` only
38
+ * falls back to them when `scriptAssets` is `undefined` (i.e. the adapter
39
+ * is invoked without this Vite plugin). Core's `barefoot()` plugin ALWAYS
40
+ * passes a resolved `scriptAssets` array (build: manifest-hashed; dev:
41
+ * origin-based — see `plugin.ts`), so that fallback is dead code on every
42
+ * Vite-driven build.
43
+ * Unlike Go (`packageName`, still real) or Hono (`clientJsFilename`, still
44
+ * real), Jinja has no adapter option left with any effect once Vite drives
45
+ * the build — so this options interface simply omits the field rather than
46
+ * plumbing through two options that would always be ignored.
47
+ *
48
+ * ## `assets` — the one thing this DOES need, mirroring go-template/hono/blade
49
+ *
50
+ * A hand-written, non-component client bootstrap (e.g. an integration's
51
+ * `client/router-entry.ts`, which boots `@barefootjs/router` for the blog)
52
+ * isn't a `.tsx` component, so core's own discovery/`scriptAssets` machinery
53
+ * never sees it — but the compiled blog shell still needs a `<script src>`
54
+ * for it, and that URL is only knowable after Vite bundles it (dev:
55
+ * origin-based; build: manifest-hashed). `assets` resolves exactly that,
56
+ * into a generated JSON file the Python app reads at request time (the same
57
+ * `dist/templates/manifest.json` — read-a-JSON-file-at-runtime — idiom
58
+ * app.py already uses for `ssrDefaults`; unlike Go/Hono there is no compile
59
+ * step on the Python side, so plain JSON is enough — no generated Go/TS
60
+ * source needed). See `@barefootjs/go-template/vite`'s docstring for the
61
+ * full "why a companion config-capture plugin" rationale (`afterEmit`'s
62
+ * `AfterEmitContext` is deliberately narrow — no `ResolvedConfig`, no dev
63
+ * origin, no manifest — so a tiny second plugin captures those via its own
64
+ * `configResolved`/`configureServer` hooks for `afterEmit`, in the same
65
+ * closure, to read).
66
+ */
67
+ import { mkdir, readFile, writeFile } from 'node:fs/promises'
68
+ import { dirname, resolve } from 'node:path'
69
+ import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite'
70
+ import { barefoot as coreBarefoot } from '@barefootjs/vite'
71
+ import type { AfterEmitContext } from '@barefootjs/vite'
72
+ import { devModuleUrl, loadManifest, resolveDevOrigin, resolveScriptAssets, toPosixRelative } from '@barefootjs/vite'
73
+ import { JinjaAdapter } from './adapter/index.ts'
74
+
75
+ export interface JinjaViteOptions {
76
+ /** Source directories to scan for `.tsx` components, relative to the
77
+ * Vite project root (or absolute). */
78
+ components: string[]
79
+ /** Where compiled `.jinja` templates and `ssrDefaults` land — relative to
80
+ * the Vite project root (or absolute). This is a backend source
81
+ * directory Python reads, NOT `build.outDir` (Vite's client-asset
82
+ * output). */
83
+ templates: string
84
+ /**
85
+ * Extra, non-component script entries whose Vite-resolved URL (dev:
86
+ * origin-based; production: content-hashed manifest path) should be
87
+ * exposed to the Python app as a generated JSON asset map — e.g. a
88
+ * hand-written client bootstrap script that isn't a `.tsx` component, so
89
+ * it never goes through core's discovery/`scriptAssets` machinery, but
90
+ * still needs a `<script src="...">` URL only knowable after bundling.
91
+ *
92
+ * Keyed by the identifier the resolved URL should appear under in the
93
+ * generated map; values are entry paths relative to the Vite project
94
+ * root. You must ALSO register the same path as a Rollup entry yourself
95
+ * via stock `build.rollupOptions.input` — this plugin never adds
96
+ * bundling configuration on your behalf; this option only resolves the
97
+ * URL Vite already bundled it to, it doesn't request the bundling.
98
+ */
99
+ assets?: Record<string, string>
100
+ /** Output path for the generated JSON asset map, relative to the Vite
101
+ * project root. Default: 'dist/bf-assets.json'. Ignored when `assets`
102
+ * is empty. Placed under `dist/` (already gitignored) rather than
103
+ * committed like Go's `bf_assets.go`: Python reads this file at REQUEST
104
+ * time, so — unlike Go, which must compile a static map into the
105
+ * binary — there is nothing to commit; a fresh copy is generated on
106
+ * every build (dev AND production) and never checked in. */
107
+ assetsOutputFile?: string
108
+ }
109
+
110
+ /** write-if-changed: writes `content` to `absPath` only if it differs from
111
+ * what's already there, logging `label` when it actually wrote. Avoids
112
+ * touching mtime (and so falsely tripping a file watcher, e.g. Python's own
113
+ * dev server) on a pass that produced byte-identical output. */
114
+ async function writeIfChanged(absPath: string, content: string, label: string): Promise<void> {
115
+ const prev = await readFile(absPath, 'utf-8').catch(() => null)
116
+ if (prev === content) return
117
+ // Default `assetsOutputFile` lives under `dist/` — a directory `vite
118
+ // build` may not have created yet on a clean checkout — so ensure it
119
+ // exists before writing.
120
+ await mkdir(dirname(absPath), { recursive: true })
121
+ await writeFile(absPath, content)
122
+ console.log(`Generated: ${label}`)
123
+ }
124
+
125
+ /** Resolves ONE asset entry's URL for the current pass: manifest-hashed for
126
+ * `mode: 'build'`, dev-origin-based for `mode: 'dev'`. Throws with an
127
+ * actionable message when the entry isn't in the build manifest — almost
128
+ * always means the caller forgot to also add it to
129
+ * `build.rollupOptions.input`. */
130
+ function resolveAssetUrl(
131
+ ctx: AfterEmitContext,
132
+ config: ResolvedConfig,
133
+ devServer: ViteDevServer | undefined,
134
+ entryRelPath: string,
135
+ manifest: Record<string, { file: string }> | undefined,
136
+ ): string {
137
+ const absPath = resolve(ctx.projectDir, entryRelPath)
138
+ if (ctx.mode === 'dev') {
139
+ if (!devServer) throw new Error(`[jinja/vite] asset "${entryRelPath}": dev server not ready`)
140
+ return devModuleUrl(config, resolveDevOrigin(devServer), absPath)
141
+ }
142
+
143
+ const manifestKey = toPosixRelative(config.root, absPath)
144
+ const [url] = resolveScriptAssets(manifest ?? {}, manifestKey, config.base)
145
+ if (!url) {
146
+ throw new Error(
147
+ `[jinja/vite] asset "${entryRelPath}" was not found in the build manifest. ` +
148
+ `Did you also add it to build.rollupOptions.input?`,
149
+ )
150
+ }
151
+ return url
152
+ }
153
+
154
+ /** Builds and write-if-changed's the generated JSON asset map. No-op when
155
+ * `assets` is empty. */
156
+ async function writeAssetMap(
157
+ ctx: AfterEmitContext,
158
+ config: ResolvedConfig,
159
+ devServer: ViteDevServer | undefined,
160
+ assets: Record<string, string>,
161
+ assetsOutputFile: string,
162
+ ): Promise<void> {
163
+ const keys = Object.keys(assets)
164
+ if (keys.length === 0) return
165
+
166
+ const manifest = ctx.mode === 'build' ? await loadManifest(ctx.outDir, config.build.manifest) : undefined
167
+
168
+ const resolved: Record<string, string> = {}
169
+ for (const name of keys) {
170
+ resolved[name] = resolveAssetUrl(ctx, config, devServer, assets[name]!, manifest)
171
+ }
172
+
173
+ const content = `${JSON.stringify(resolved, null, 2)}\n`
174
+ await writeIfChanged(resolve(ctx.projectDir, assetsOutputFile), content, assetsOutputFile)
175
+ }
176
+
177
+ export function barefoot(options: JinjaViteOptions): Plugin[] {
178
+ const assets = options.assets ?? {}
179
+ const assetsOutputFile = options.assetsOutputFile ?? 'dist/bf-assets.json'
180
+
181
+ // Populated by `jinjaAssetsConfigCapture` below (only added to the
182
+ // returned array when `assets` is non-empty). Read by `afterEmit` — see
183
+ // `@barefootjs/go-template/vite`'s identical mechanism for the ordering
184
+ // guarantee (`configResolved`/`configureServer` always run before the
185
+ // eager pass that triggers `afterEmit`, for both build and dev).
186
+ let resolvedConfig: ResolvedConfig | undefined
187
+ let devServer: ViteDevServer | undefined
188
+
189
+ const core = coreBarefoot({
190
+ adapter: new JinjaAdapter(),
191
+ components: options.components,
192
+ templates: options.templates,
193
+ async afterEmit(ctx) {
194
+ if (Object.keys(assets).length > 0 && resolvedConfig) {
195
+ await writeAssetMap(ctx, resolvedConfig, devServer, assets, assetsOutputFile)
196
+ }
197
+ },
198
+ })
199
+
200
+ if (Object.keys(assets).length === 0) return [core]
201
+
202
+ const jinjaAssetsConfigCapture: Plugin = {
203
+ name: 'barefoot-jinja-assets-config-capture',
204
+ configResolved(config) {
205
+ resolvedConfig = config
206
+ },
207
+ configureServer(server) {
208
+ devServer = server
209
+ },
210
+ }
211
+
212
+ return [core, jinjaAssetsConfigCapture]
213
+ }
214
+
215
+ export { barefoot as default }
package/dist/build.d.ts DELETED
@@ -1,28 +0,0 @@
1
- import type { BuildOptions } from '@barefootjs/jsx';
2
- import { JinjaAdapter } from './adapter/index.ts';
3
- import type { JinjaAdapterOptions } from './adapter/index.ts';
4
- export interface JinjaBuildOptions extends BuildOptions {
5
- /** Adapter-specific options passed to JinjaAdapter */
6
- adapterOptions?: JinjaAdapterOptions;
7
- }
8
- /**
9
- * Create a BarefootBuildConfig for Jinja2 template projects.
10
- *
11
- * Uses structural typing — does not import BarefootBuildConfig to avoid a
12
- * circular dependency between @barefootjs/jinja and @barefootjs/cli.
13
- */
14
- export declare function createConfig(options?: JinjaBuildOptions): {
15
- adapter: JinjaAdapter;
16
- paths: import("@barefootjs/jsx").BarefootPaths | undefined;
17
- components: string[] | undefined;
18
- outDir: string | undefined;
19
- minify: boolean | undefined;
20
- contentHash: boolean | undefined;
21
- externals: Record<string, import("@barefootjs/jsx").ExternalSpec> | undefined;
22
- externalsBasePath: string | undefined;
23
- bundleEntries: import("@barefootjs/jsx").BundleEntry[] | undefined;
24
- localImportPrefixes: string[] | undefined;
25
- outputLayout: import("@barefootjs/jsx").OutputLayout;
26
- postBuild: ((ctx: import("@barefootjs/jsx").PostBuildContext) => Promise<void> | void) | undefined;
27
- };
28
- //# sourceMappingURL=build.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAE7D,MAAM,WAAW,iBAAkB,SAAQ,YAAY;IACrD,sDAAsD;IACtD,cAAc,CAAC,EAAE,mBAAmB,CAAA;CACrC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,iBAAsB;IAExD,OAAO;IACP,KAAK;IACL,UAAU;IACV,MAAM;IACN,MAAM;IACN,WAAW;IACX,SAAS;IACT,iBAAiB;IACjB,aAAa;IACb,mBAAmB;IACnB,YAAY;IAKZ,SAAS;EAEZ"}