@barefootjs/hono 0.2.0 → 0.4.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/app.d.ts CHANGED
@@ -63,14 +63,48 @@ export interface BarefootBuildManifest {
63
63
  */
64
64
  export declare function manifestToScriptUrls(manifest: BarefootBuildManifest, base: string): string[];
65
65
  export declare function relPathFromComponentsBase(p: string): string;
66
+ /**
67
+ * Shape of `barefoot-externals.json`, written by `bf build` when
68
+ * `externals` / `bundleEntries` are configured. Only the fields
69
+ * `BfImportMap` consumes are typed here; the build also emits an
70
+ * `externals` array (the `--external` list) which the importmap
71
+ * doesn't need. Fields are optional so a partial/hand-written
72
+ * manifest still type-checks. See issue #1639.
73
+ */
74
+ export interface BarefootExternalsManifest {
75
+ /** Entries for the `<script type="importmap">`. */
76
+ importmap?: {
77
+ imports?: Record<string, string>;
78
+ };
79
+ /** URLs to emit as `<link rel="modulepreload">`. */
80
+ preloads?: string[];
81
+ }
66
82
  export interface BfImportMapProps {
67
83
  /** Base URL where the runtime + component bundles are served. */
68
84
  base: string;
85
+ /**
86
+ * Contents of `barefoot-externals.json` (import it and pass it
87
+ * through). Its `importmap.imports` are merged on top of the
88
+ * built-in `@barefootjs/client*` mappings so islands importing
89
+ * configured externals (e.g. `zod`, `@barefootjs/form`) resolve in
90
+ * the browser. When omitted, only the `@barefootjs/client*`
91
+ * mappings are emitted — the pre-#1639 behavior.
92
+ */
93
+ externals?: BarefootExternalsManifest;
94
+ /**
95
+ * Whether to also emit `<link rel="modulepreload">` for the
96
+ * manifest's `preloads`. Defaults to `true`; set `false` to emit
97
+ * the importmap only.
98
+ */
99
+ preload?: boolean;
69
100
  }
70
101
  /**
71
102
  * Emits the `<script type="importmap">` that maps the bare
72
103
  * `@barefootjs/client` / `@barefootjs/client/runtime` specifiers to
73
- * the runtime bundle. Place in `<head>`.
104
+ * the runtime bundle, plus any externals from `barefoot-externals.json`
105
+ * passed via the `externals` prop. Also emits `<link rel="modulepreload">`
106
+ * for the manifest's `preloads` unless `preload` is `false`. Place in
107
+ * `<head>`.
74
108
  */
75
109
  export declare function BfImportMap(props: BfImportMapProps): HtmlEscapedString | Promise<HtmlEscapedString>;
76
110
  export interface BfScriptsProps {
package/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAE7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAQxD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACpC,CAAC,aAAa,EAAE,MAAM,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,SAAS,CAAA;CAChF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,qBAAqB,EAC/B,IAAI,EAAE,MAAM,GACX,MAAM,EAAE,CAWV;AAED,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3D;AAID,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CASnG;AAED,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,QAAQ,EAAE,qBAAqB,CAAA;CAChC;AAQD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAa/F;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,GAAE,gBAAqB,GAAG,iBAAiB,GAAG,IAAI,CAelF;AAID,MAAM,WAAW,wBAAwB;IACvC,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;;OAMG;IACH,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,GAAG,iBAAiB,CAanF"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAE7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAQxD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACpC,CAAC,aAAa,EAAE,MAAM,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,SAAS,CAAA;CAChF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,qBAAqB,EAC/B,IAAI,EAAE,MAAM,GACX,MAAM,EAAE,CAWV;AAED,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3D;AAID;;;;;;;GAOG;AACH,MAAM,WAAW,yBAAyB;IACxC,mDAAmD;IACnD,SAAS,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAA;IAChD,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAA;IACrC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAuBnG;AAOD,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,QAAQ,EAAE,qBAAqB,CAAA;CAChC;AAQD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAa/F;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,GAAE,gBAAqB,GAAG,iBAAiB,GAAG,IAAI,CAelF;AAID,MAAM,WAAW,wBAAwB;IACvC,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;;OAMG;IACH,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,GAAG,iBAAiB,CAanF"}
package/dist/app.js CHANGED
@@ -84,13 +84,18 @@ function relPathFromComponentsBase(p) {
84
84
  }
85
85
  function BfImportMap(props) {
86
86
  const base = props.base.replace(/\/$/, "");
87
- const json = JSON.stringify({
88
- imports: {
89
- "@barefootjs/client": `${base}/barefoot.js`,
90
- "@barefootjs/client/runtime": `${base}/barefoot.js`
91
- }
92
- });
93
- return html`<script type="importmap">${raw(json)}</script>`;
87
+ const imports = {
88
+ "@barefootjs/client": `${base}/barefoot.js`,
89
+ "@barefootjs/client/runtime": `${base}/barefoot.js`,
90
+ ...props.externals?.importmap?.imports ?? {}
91
+ };
92
+ const json = JSON.stringify({ imports });
93
+ const preloads = props.preload === false ? [] : props.externals?.preloads ?? [];
94
+ const links = preloads.map((href) => `<link rel="modulepreload" href="${escapeAttr(href)}" crossorigin>`).join("");
95
+ return html`<script type="importmap">${raw(json)}</script>${raw(links)}`;
96
+ }
97
+ function escapeAttr(value) {
98
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
94
99
  }
95
100
  var __bfEmptyManifestWarned = false;
96
101
  function BfScripts(props) {
package/dist/scripts.js CHANGED
@@ -84,13 +84,18 @@ function relPathFromComponentsBase(p) {
84
84
  }
85
85
  function BfImportMap(props) {
86
86
  const base = props.base.replace(/\/$/, "");
87
- const json = JSON.stringify({
88
- imports: {
89
- "@barefootjs/client": `${base}/barefoot.js`,
90
- "@barefootjs/client/runtime": `${base}/barefoot.js`
91
- }
92
- });
93
- return html`<script type="importmap">${raw(json)}</script>`;
87
+ const imports = {
88
+ "@barefootjs/client": `${base}/barefoot.js`,
89
+ "@barefootjs/client/runtime": `${base}/barefoot.js`,
90
+ ...props.externals?.importmap?.imports ?? {}
91
+ };
92
+ const json = JSON.stringify({ imports });
93
+ const preloads = props.preload === false ? [] : props.externals?.preloads ?? [];
94
+ const links = preloads.map((href) => `<link rel="modulepreload" href="${escapeAttr(href)}" crossorigin>`).join("");
95
+ return html`<script type="importmap">${raw(json)}</script>${raw(links)}`;
96
+ }
97
+ function escapeAttr(value) {
98
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
94
99
  }
95
100
  var __bfEmptyManifestWarned = false;
96
101
  function BfScripts(props) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/hono",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Hono integration for BarefootJS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -101,9 +101,9 @@
101
101
  "directory": "packages/adapter-hono"
102
102
  },
103
103
  "peerDependencies": {
104
- "@barefootjs/client": "0.2.0",
105
- "@barefootjs/jsx": "0.2.0",
106
- "@barefootjs/shared": "0.2.0",
104
+ "@barefootjs/client": ">=0.2.0",
105
+ "@barefootjs/jsx": ">=0.2.0",
106
+ "@barefootjs/shared": ">=0.2.0",
107
107
  "hono": "^4.0.0"
108
108
  },
109
109
  "devDependencies": {
@@ -0,0 +1,97 @@
1
+ /**
2
+ * BfImportMap tests
3
+ *
4
+ * Verifies the importmap merges configured externals from
5
+ * `barefoot-externals.json` (issue #1639) and emits modulepreload
6
+ * links, while preserving the pre-#1639 `@barefootjs/client*` defaults
7
+ * when no externals are passed.
8
+ */
9
+ import { describe, test, expect } from 'bun:test'
10
+ import { BfImportMap, type BarefootExternalsManifest } from '../app'
11
+
12
+ function parseImportMap(html: string): Record<string, string> {
13
+ const match = html.match(/<script type="importmap">(.*?)<\/script>/s)
14
+ if (!match) throw new Error(`no importmap in: ${html}`)
15
+ return JSON.parse(match[1]).imports
16
+ }
17
+
18
+ describe('BfImportMap', () => {
19
+ test('emits @barefootjs/client defaults when no externals passed', () => {
20
+ const html = String(BfImportMap({ base: '/components' }))
21
+ expect(parseImportMap(html)).toEqual({
22
+ '@barefootjs/client': '/components/barefoot.js',
23
+ '@barefootjs/client/runtime': '/components/barefoot.js',
24
+ })
25
+ expect(html).not.toContain('modulepreload')
26
+ })
27
+
28
+ test('strips trailing slash from base', () => {
29
+ const html = String(BfImportMap({ base: '/components/' }))
30
+ expect(parseImportMap(html)['@barefootjs/client']).toBe('/components/barefoot.js')
31
+ })
32
+
33
+ test('merges externals importmap on top of the client defaults', () => {
34
+ const externals: BarefootExternalsManifest = {
35
+ importmap: {
36
+ imports: {
37
+ zod: 'https://esm.sh/zod@4.4.3',
38
+ '@barefootjs/form': '/components/form.js',
39
+ },
40
+ },
41
+ preloads: [],
42
+ }
43
+ const imports = parseImportMap(String(BfImportMap({ base: '/components', externals })))
44
+ expect(imports).toEqual({
45
+ '@barefootjs/client': '/components/barefoot.js',
46
+ '@barefootjs/client/runtime': '/components/barefoot.js',
47
+ zod: 'https://esm.sh/zod@4.4.3',
48
+ '@barefootjs/form': '/components/form.js',
49
+ })
50
+ })
51
+
52
+ test('manifest @barefootjs/client mapping wins over the prop-derived one', () => {
53
+ const externals: BarefootExternalsManifest = {
54
+ importmap: { imports: { '@barefootjs/client': '/vendor/barefoot.js' } },
55
+ }
56
+ const imports = parseImportMap(String(BfImportMap({ base: '/components', externals })))
57
+ expect(imports['@barefootjs/client']).toBe('/vendor/barefoot.js')
58
+ })
59
+
60
+ test('emits modulepreload links for manifest preloads', () => {
61
+ const externals: BarefootExternalsManifest = {
62
+ importmap: { imports: {} },
63
+ preloads: ['/components/form.js', 'https://esm.sh/zod@4.4.3'],
64
+ }
65
+ const html = String(BfImportMap({ base: '/components', externals }))
66
+ expect(html).toContain('<link rel="modulepreload" href="/components/form.js" crossorigin>')
67
+ expect(html).toContain('<link rel="modulepreload" href="https://esm.sh/zod@4.4.3" crossorigin>')
68
+ })
69
+
70
+ test('emits crossorigin on modulepreload so cross-origin CDN preloads are reused', () => {
71
+ const externals: BarefootExternalsManifest = {
72
+ preloads: ['https://esm.sh/zod@4.4.3'],
73
+ }
74
+ const html = String(BfImportMap({ base: '/components', externals }))
75
+ const match = html.match(/<link rel="modulepreload"[^>]*>/)
76
+ expect(match?.[0]).toContain('crossorigin')
77
+ })
78
+
79
+ test('preload=false suppresses modulepreload links', () => {
80
+ const externals: BarefootExternalsManifest = {
81
+ preloads: ['/components/form.js'],
82
+ }
83
+ const html = String(BfImportMap({ base: '/components', externals, preload: false }))
84
+ expect(html).not.toContain('modulepreload')
85
+ // importmap still emitted
86
+ expect(parseImportMap(html)['@barefootjs/client']).toBe('/components/barefoot.js')
87
+ })
88
+
89
+ test('escapes double quotes in preload hrefs', () => {
90
+ const externals: BarefootExternalsManifest = {
91
+ preloads: ['/components/"onerror=alert(1).js'],
92
+ }
93
+ const html = String(BfImportMap({ base: '/components', externals }))
94
+ expect(html).not.toContain('"onerror=alert(1)')
95
+ expect(html).toContain('&quot;onerror=alert(1)')
96
+ })
97
+ })
package/src/app.ts CHANGED
@@ -88,25 +88,77 @@ export function relPathFromComponentsBase(p: string): string {
88
88
 
89
89
  // ── JSX components ─────────────────────────────────────────────────────────
90
90
 
91
+ /**
92
+ * Shape of `barefoot-externals.json`, written by `bf build` when
93
+ * `externals` / `bundleEntries` are configured. Only the fields
94
+ * `BfImportMap` consumes are typed here; the build also emits an
95
+ * `externals` array (the `--external` list) which the importmap
96
+ * doesn't need. Fields are optional so a partial/hand-written
97
+ * manifest still type-checks. See issue #1639.
98
+ */
99
+ export interface BarefootExternalsManifest {
100
+ /** Entries for the `<script type="importmap">`. */
101
+ importmap?: { imports?: Record<string, string> }
102
+ /** URLs to emit as `<link rel="modulepreload">`. */
103
+ preloads?: string[]
104
+ }
105
+
91
106
  export interface BfImportMapProps {
92
107
  /** Base URL where the runtime + component bundles are served. */
93
108
  base: string
109
+ /**
110
+ * Contents of `barefoot-externals.json` (import it and pass it
111
+ * through). Its `importmap.imports` are merged on top of the
112
+ * built-in `@barefootjs/client*` mappings so islands importing
113
+ * configured externals (e.g. `zod`, `@barefootjs/form`) resolve in
114
+ * the browser. When omitted, only the `@barefootjs/client*`
115
+ * mappings are emitted — the pre-#1639 behavior.
116
+ */
117
+ externals?: BarefootExternalsManifest
118
+ /**
119
+ * Whether to also emit `<link rel="modulepreload">` for the
120
+ * manifest's `preloads`. Defaults to `true`; set `false` to emit
121
+ * the importmap only.
122
+ */
123
+ preload?: boolean
94
124
  }
95
125
 
96
126
  /**
97
127
  * Emits the `<script type="importmap">` that maps the bare
98
128
  * `@barefootjs/client` / `@barefootjs/client/runtime` specifiers to
99
- * the runtime bundle. Place in `<head>`.
129
+ * the runtime bundle, plus any externals from `barefoot-externals.json`
130
+ * passed via the `externals` prop. Also emits `<link rel="modulepreload">`
131
+ * for the manifest's `preloads` unless `preload` is `false`. Place in
132
+ * `<head>`.
100
133
  */
101
134
  export function BfImportMap(props: BfImportMapProps): HtmlEscapedString | Promise<HtmlEscapedString> {
102
135
  const base = props.base.replace(/\/$/, '')
103
- const json = JSON.stringify({
104
- imports: {
105
- '@barefootjs/client': `${base}/barefoot.js`,
106
- '@barefootjs/client/runtime': `${base}/barefoot.js`,
107
- },
108
- })
109
- return html`<script type="importmap">${raw(json)}</script>`
136
+ // Built-in defaults first, then manifest imports so a configured
137
+ // `@barefootjs/client` mapping (emitted by `bf build` against the
138
+ // build's `externalsBasePath`) wins over the prop-derived one.
139
+ const imports: Record<string, string> = {
140
+ '@barefootjs/client': `${base}/barefoot.js`,
141
+ '@barefootjs/client/runtime': `${base}/barefoot.js`,
142
+ ...(props.externals?.importmap?.imports ?? {}),
143
+ }
144
+ const json = JSON.stringify({ imports })
145
+
146
+ const preloads = props.preload === false ? [] : props.externals?.preloads ?? []
147
+ // `crossorigin` is required so a cross-origin (CDN) preload's request
148
+ // matches the actual module `import` (always a CORS fetch); without it
149
+ // the browser discards the preload and re-fetches. It's harmless for
150
+ // same-origin module preloads, which use the same credentials mode
151
+ // whether or not the attribute is present. See issue #1648.
152
+ const links = preloads
153
+ .map((href) => `<link rel="modulepreload" href="${escapeAttr(href)}" crossorigin>`)
154
+ .join('')
155
+
156
+ return html`<script type="importmap">${raw(json)}</script>${raw(links)}`
157
+ }
158
+
159
+ /** Minimal double-quoted-attribute escaping for config-derived URLs. */
160
+ function escapeAttr(value: string): string {
161
+ return value.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;')
110
162
  }
111
163
 
112
164
  export interface BfScriptsProps {