@barefootjs/hono 0.4.0 → 0.5.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/dist/adapter/hono-adapter.d.ts +10 -2
- package/dist/adapter/hono-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +22 -6
- package/dist/app.d.ts +15 -17
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +2 -6
- package/dist/build.js +22 -6
- package/dist/index.js +22 -6
- package/dist/scripts.js +2 -6
- package/package.json +1 -1
- package/src/__tests__/import-map.test.ts +8 -7
- package/src/adapter/hono-adapter.ts +55 -6
- package/src/app.ts +18 -32
|
@@ -44,6 +44,7 @@ export declare class HonoAdapter extends JsxAdapter implements IRNodeEmitter<Hon
|
|
|
44
44
|
name: string;
|
|
45
45
|
extension: string;
|
|
46
46
|
clientShimSource: string;
|
|
47
|
+
importMapInjection: 'component';
|
|
47
48
|
acceptsTemplateCall: () => boolean;
|
|
48
49
|
protected jsxConfig: JsxAdapterConfig;
|
|
49
50
|
private options;
|
|
@@ -77,7 +78,7 @@ export declare class HonoAdapter extends JsxAdapter implements IRNodeEmitter<Hon
|
|
|
77
78
|
emitElement(node: IRElement, ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string;
|
|
78
79
|
emitText(node: IRText): string;
|
|
79
80
|
emitExpression(node: IRExpression): string;
|
|
80
|
-
emitConditional(node: IRConditional,
|
|
81
|
+
emitConditional(node: IRConditional, ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string;
|
|
81
82
|
emitLoop(node: IRLoop, _ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string;
|
|
82
83
|
emitComponent(node: IRComponent, ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string;
|
|
83
84
|
emitFragment(node: IRFragment, _ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string;
|
|
@@ -90,7 +91,14 @@ export declare class HonoAdapter extends JsxAdapter implements IRNodeEmitter<Hon
|
|
|
90
91
|
}): string;
|
|
91
92
|
private renderText;
|
|
92
93
|
renderExpression(expr: IRExpression): string;
|
|
93
|
-
renderConditional(cond: IRConditional): string;
|
|
94
|
+
renderConditional(cond: IRConditional, ctx?: HonoRenderCtx): string;
|
|
95
|
+
/**
|
|
96
|
+
* Like the base `renderNodeRaw`, but threads a render ctx through to
|
|
97
|
+
* `renderNode` so a conditional branch can mark its element as a loop item
|
|
98
|
+
* root (#1665). The `null` / `undefined` expression branch carries no
|
|
99
|
+
* element, so it short-circuits exactly as the base helper does.
|
|
100
|
+
*/
|
|
101
|
+
private renderNodeRawCtx;
|
|
94
102
|
private wrapWithCondMarker;
|
|
95
103
|
renderLoop(loop: IRLoop): string;
|
|
96
104
|
private renderChildrenInLoop;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/hono-adapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,MAAM,EAIX,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAElB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,UAAU,EAEf,UAAU,EAMX,MAAM,iBAAiB,CAAA;AAExB;;;;;GAKG;AACH,KAAK,aAAa,GAAG;IACnB,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB,CAAA;AAGD,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAEzB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAEzB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAuBD,qBAAa,WAAY,SAAQ,UAAW,YAAW,aAAa,CAAC,aAAa,CAAC;IACjF,IAAI,SAAS;IACb,SAAS,SAAS;IAClB,gBAAgB,SAAiC;
|
|
1
|
+
{"version":3,"file":"hono-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/hono-adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/adapter/index.js
CHANGED
|
@@ -21,6 +21,7 @@ class HonoAdapter extends JsxAdapter {
|
|
|
21
21
|
name = "hono";
|
|
22
22
|
extension = ".tsx";
|
|
23
23
|
clientShimSource = "@barefootjs/hono/client-shim";
|
|
24
|
+
importMapInjection = "component";
|
|
24
25
|
acceptsTemplateCall = () => true;
|
|
25
26
|
jsxConfig = { preserveTypes: true };
|
|
26
27
|
options;
|
|
@@ -259,9 +260,13 @@ export default ${this.componentName}` : "";
|
|
|
259
260
|
}
|
|
260
261
|
fullPropsDestructure = `{ ${parts.join(", ")} }`;
|
|
261
262
|
}
|
|
263
|
+
const hasRequiredProps = ir.metadata.propsParams.some((p) => !p.optional && p.defaultValue === undefined && !p.isRest);
|
|
264
|
+
const wantsNoArgDefault = propsObjectName ? !propsTypeName : !hasRequiredProps;
|
|
265
|
+
const propsTypeExpr = typeAnnotation.replace(/^:\s*/, "");
|
|
266
|
+
const noArgDefault = wantsNoArgDefault ? ` = {} as ${propsTypeExpr}` : "";
|
|
262
267
|
const lines = [];
|
|
263
268
|
const exportPrefix = ir.metadata.isExported === false ? "" : "export ";
|
|
264
|
-
lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}) {`);
|
|
269
|
+
lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}${noArgDefault}) {`);
|
|
265
270
|
if (propsExtraction) {
|
|
266
271
|
lines.push(propsExtraction);
|
|
267
272
|
}
|
|
@@ -312,8 +317,8 @@ export default ${this.componentName}` : "";
|
|
|
312
317
|
emitExpression(node) {
|
|
313
318
|
return this.renderExpression(node);
|
|
314
319
|
}
|
|
315
|
-
emitConditional(node,
|
|
316
|
-
return this.renderConditional(node);
|
|
320
|
+
emitConditional(node, ctx, _emit) {
|
|
321
|
+
return this.renderConditional(node, ctx);
|
|
317
322
|
}
|
|
318
323
|
emitLoop(node, _ctx, _emit) {
|
|
319
324
|
return this.renderLoop(node);
|
|
@@ -400,12 +405,13 @@ export default ${this.componentName}` : "";
|
|
|
400
405
|
}
|
|
401
406
|
return `{${expr.expr}}`;
|
|
402
407
|
}
|
|
403
|
-
renderConditional(cond) {
|
|
408
|
+
renderConditional(cond, ctx) {
|
|
404
409
|
if (cond.clientOnly && cond.slotId) {
|
|
405
410
|
return `{bfComment("cond-start:${cond.slotId}")}{bfComment("cond-end:${cond.slotId}")}`;
|
|
406
411
|
}
|
|
407
|
-
const
|
|
408
|
-
|
|
412
|
+
const branchCtx = ctx?.isLoopItemRoot ? { isLoopItemRoot: true } : undefined;
|
|
413
|
+
const whenTrue = this.renderNodeRawCtx(cond.whenTrue, branchCtx);
|
|
414
|
+
let whenFalse = this.renderNodeRawCtx(cond.whenFalse, branchCtx);
|
|
409
415
|
if (!whenFalse || whenFalse === "" || whenFalse === "null") {
|
|
410
416
|
whenFalse = "null";
|
|
411
417
|
}
|
|
@@ -416,6 +422,14 @@ export default ${this.componentName}` : "";
|
|
|
416
422
|
}
|
|
417
423
|
return `{${cond.condition} ? ${whenTrue} : ${whenFalse}}`;
|
|
418
424
|
}
|
|
425
|
+
renderNodeRawCtx(node, ctx) {
|
|
426
|
+
if (node.type === "expression") {
|
|
427
|
+
if (node.expr === "null" || node.expr === "undefined")
|
|
428
|
+
return "null";
|
|
429
|
+
return node.expr;
|
|
430
|
+
}
|
|
431
|
+
return this.renderNode(node, ctx);
|
|
432
|
+
}
|
|
419
433
|
wrapWithCondMarker(node, content, condId) {
|
|
420
434
|
if (node.type === "component") {
|
|
421
435
|
return `<>{bfComment("cond-start:${condId}")}${content}{bfComment("cond-end:${condId}")}</>`;
|
|
@@ -446,6 +460,8 @@ export default ${this.componentName}` : "";
|
|
|
446
460
|
let safeChildren = children.startsWith("{") ? `<>${children}</>` : children;
|
|
447
461
|
if (loop.bodyIsMultiRoot) {
|
|
448
462
|
safeChildren = `<>{bfComment('bf-loop-i')}${children}</>`;
|
|
463
|
+
} else if (loop.bodyIsItemConditional && loop.key) {
|
|
464
|
+
safeChildren = `<>{bfComment('loop-i:' + String(${loop.key}))}${children}</>`;
|
|
449
465
|
}
|
|
450
466
|
let chainedArray = applyHonoLoopChain(loop);
|
|
451
467
|
const iterMethod = loop.method ?? "map";
|
package/dist/app.d.ts
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import type { MiddlewareHandler } from 'hono';
|
|
29
29
|
import type { HtmlEscapedString } from 'hono/utils/html';
|
|
30
|
+
import { type ImportMapManifest } from '@barefootjs/jsx/import-map';
|
|
30
31
|
/**
|
|
31
32
|
* Build manifest shape produced by `bf build`. Each compiled
|
|
32
33
|
* component is keyed by its manifest name; `__barefoot__` is the
|
|
@@ -63,22 +64,6 @@ export interface BarefootBuildManifest {
|
|
|
63
64
|
*/
|
|
64
65
|
export declare function manifestToScriptUrls(manifest: BarefootBuildManifest, base: string): string[];
|
|
65
66
|
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
|
-
}
|
|
82
67
|
export interface BfImportMapProps {
|
|
83
68
|
/** Base URL where the runtime + component bundles are served. */
|
|
84
69
|
base: string;
|
|
@@ -89,8 +74,12 @@ export interface BfImportMapProps {
|
|
|
89
74
|
* configured externals (e.g. `zod`, `@barefootjs/form`) resolve in
|
|
90
75
|
* the browser. When omitted, only the `@barefootjs/client*`
|
|
91
76
|
* mappings are emitted — the pre-#1639 behavior.
|
|
77
|
+
*
|
|
78
|
+
* Typed with the shared {@link ImportMapManifest} from `@barefootjs/jsx`,
|
|
79
|
+
* so the component and the `bf build` snippet path describe the manifest
|
|
80
|
+
* with one type.
|
|
92
81
|
*/
|
|
93
|
-
externals?:
|
|
82
|
+
externals?: ImportMapManifest;
|
|
94
83
|
/**
|
|
95
84
|
* Whether to also emit `<link rel="modulepreload">` for the
|
|
96
85
|
* manifest's `preloads`. Defaults to `true`; set `false` to emit
|
|
@@ -105,6 +94,15 @@ export interface BfImportMapProps {
|
|
|
105
94
|
* passed via the `externals` prop. Also emits `<link rel="modulepreload">`
|
|
106
95
|
* for the manifest's `preloads` unless `preload` is `false`. Place in
|
|
107
96
|
* `<head>`.
|
|
97
|
+
*
|
|
98
|
+
* The merge of the `@barefootjs/client*` defaults (synthesized from `base`
|
|
99
|
+
* for prop-less / hand-written manifests) is Hono-specific; the actual HTML
|
|
100
|
+
* rendering — importmap JSON escaping, `<link rel="modulepreload">`
|
|
101
|
+
* emission with `crossorigin` (#1648) — is delegated to the shared
|
|
102
|
+
* `renderImportMapHtml` so this path can never drift from the static
|
|
103
|
+
* `barefoot-importmap.html` snippet `bf build` emits for template-string
|
|
104
|
+
* adapters (#1644). Imported from the `@barefootjs/jsx/import-map` subpath,
|
|
105
|
+
* a zero-dependency module, to keep this runtime file free of the compiler.
|
|
108
106
|
*/
|
|
109
107
|
export declare function BfImportMap(props: BfImportMapProps): HtmlEscapedString | Promise<HtmlEscapedString>;
|
|
110
108
|
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;
|
|
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;AAIxD,OAAO,EAAuB,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAOxF;;;;;;;;;;;;;;;;;;;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;IACZ;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAanG;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"}
|
package/dist/app.js
CHANGED
|
@@ -64,6 +64,7 @@ data: ${BOOT_ID}
|
|
|
64
64
|
// src/app.ts
|
|
65
65
|
import { html, raw } from "hono/html";
|
|
66
66
|
import { useRequestContext } from "hono/jsx-renderer";
|
|
67
|
+
import { renderImportMapHtml } from "@barefootjs/jsx/import-map";
|
|
67
68
|
var DEV_RELOAD_ENDPOINT_KEY = "bfDevReloadEndpoint";
|
|
68
69
|
function manifestToScriptUrls(manifest, base) {
|
|
69
70
|
const out = [];
|
|
@@ -89,13 +90,8 @@ function BfImportMap(props) {
|
|
|
89
90
|
"@barefootjs/client/runtime": `${base}/barefoot.js`,
|
|
90
91
|
...props.externals?.importmap?.imports ?? {}
|
|
91
92
|
};
|
|
92
|
-
const json = JSON.stringify({ imports });
|
|
93
93
|
const preloads = props.preload === false ? [] : props.externals?.preloads ?? [];
|
|
94
|
-
|
|
95
|
-
return html`<script type="importmap">${raw(json)}</script>${raw(links)}`;
|
|
96
|
-
}
|
|
97
|
-
function escapeAttr(value) {
|
|
98
|
-
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
94
|
+
return html`${raw(renderImportMapHtml({ importmap: { imports }, preloads }))}`;
|
|
99
95
|
}
|
|
100
96
|
var __bfEmptyManifestWarned = false;
|
|
101
97
|
function BfScripts(props) {
|
package/dist/build.js
CHANGED
|
@@ -21,6 +21,7 @@ class HonoAdapter extends JsxAdapter {
|
|
|
21
21
|
name = "hono";
|
|
22
22
|
extension = ".tsx";
|
|
23
23
|
clientShimSource = "@barefootjs/hono/client-shim";
|
|
24
|
+
importMapInjection = "component";
|
|
24
25
|
acceptsTemplateCall = () => true;
|
|
25
26
|
jsxConfig = { preserveTypes: true };
|
|
26
27
|
options;
|
|
@@ -259,9 +260,13 @@ export default ${this.componentName}` : "";
|
|
|
259
260
|
}
|
|
260
261
|
fullPropsDestructure = `{ ${parts.join(", ")} }`;
|
|
261
262
|
}
|
|
263
|
+
const hasRequiredProps = ir.metadata.propsParams.some((p) => !p.optional && p.defaultValue === undefined && !p.isRest);
|
|
264
|
+
const wantsNoArgDefault = propsObjectName ? !propsTypeName : !hasRequiredProps;
|
|
265
|
+
const propsTypeExpr = typeAnnotation.replace(/^:\s*/, "");
|
|
266
|
+
const noArgDefault = wantsNoArgDefault ? ` = {} as ${propsTypeExpr}` : "";
|
|
262
267
|
const lines = [];
|
|
263
268
|
const exportPrefix = ir.metadata.isExported === false ? "" : "export ";
|
|
264
|
-
lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}) {`);
|
|
269
|
+
lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}${noArgDefault}) {`);
|
|
265
270
|
if (propsExtraction) {
|
|
266
271
|
lines.push(propsExtraction);
|
|
267
272
|
}
|
|
@@ -312,8 +317,8 @@ export default ${this.componentName}` : "";
|
|
|
312
317
|
emitExpression(node) {
|
|
313
318
|
return this.renderExpression(node);
|
|
314
319
|
}
|
|
315
|
-
emitConditional(node,
|
|
316
|
-
return this.renderConditional(node);
|
|
320
|
+
emitConditional(node, ctx, _emit) {
|
|
321
|
+
return this.renderConditional(node, ctx);
|
|
317
322
|
}
|
|
318
323
|
emitLoop(node, _ctx, _emit) {
|
|
319
324
|
return this.renderLoop(node);
|
|
@@ -400,12 +405,13 @@ export default ${this.componentName}` : "";
|
|
|
400
405
|
}
|
|
401
406
|
return `{${expr.expr}}`;
|
|
402
407
|
}
|
|
403
|
-
renderConditional(cond) {
|
|
408
|
+
renderConditional(cond, ctx) {
|
|
404
409
|
if (cond.clientOnly && cond.slotId) {
|
|
405
410
|
return `{bfComment("cond-start:${cond.slotId}")}{bfComment("cond-end:${cond.slotId}")}`;
|
|
406
411
|
}
|
|
407
|
-
const
|
|
408
|
-
|
|
412
|
+
const branchCtx = ctx?.isLoopItemRoot ? { isLoopItemRoot: true } : undefined;
|
|
413
|
+
const whenTrue = this.renderNodeRawCtx(cond.whenTrue, branchCtx);
|
|
414
|
+
let whenFalse = this.renderNodeRawCtx(cond.whenFalse, branchCtx);
|
|
409
415
|
if (!whenFalse || whenFalse === "" || whenFalse === "null") {
|
|
410
416
|
whenFalse = "null";
|
|
411
417
|
}
|
|
@@ -416,6 +422,14 @@ export default ${this.componentName}` : "";
|
|
|
416
422
|
}
|
|
417
423
|
return `{${cond.condition} ? ${whenTrue} : ${whenFalse}}`;
|
|
418
424
|
}
|
|
425
|
+
renderNodeRawCtx(node, ctx) {
|
|
426
|
+
if (node.type === "expression") {
|
|
427
|
+
if (node.expr === "null" || node.expr === "undefined")
|
|
428
|
+
return "null";
|
|
429
|
+
return node.expr;
|
|
430
|
+
}
|
|
431
|
+
return this.renderNode(node, ctx);
|
|
432
|
+
}
|
|
419
433
|
wrapWithCondMarker(node, content, condId) {
|
|
420
434
|
if (node.type === "component") {
|
|
421
435
|
return `<>{bfComment("cond-start:${condId}")}${content}{bfComment("cond-end:${condId}")}</>`;
|
|
@@ -446,6 +460,8 @@ export default ${this.componentName}` : "";
|
|
|
446
460
|
let safeChildren = children.startsWith("{") ? `<>${children}</>` : children;
|
|
447
461
|
if (loop.bodyIsMultiRoot) {
|
|
448
462
|
safeChildren = `<>{bfComment('bf-loop-i')}${children}</>`;
|
|
463
|
+
} else if (loop.bodyIsItemConditional && loop.key) {
|
|
464
|
+
safeChildren = `<>{bfComment('loop-i:' + String(${loop.key}))}${children}</>`;
|
|
449
465
|
}
|
|
450
466
|
let chainedArray = applyHonoLoopChain(loop);
|
|
451
467
|
const iterMethod = loop.method ?? "map";
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ class HonoAdapter extends JsxAdapter {
|
|
|
21
21
|
name = "hono";
|
|
22
22
|
extension = ".tsx";
|
|
23
23
|
clientShimSource = "@barefootjs/hono/client-shim";
|
|
24
|
+
importMapInjection = "component";
|
|
24
25
|
acceptsTemplateCall = () => true;
|
|
25
26
|
jsxConfig = { preserveTypes: true };
|
|
26
27
|
options;
|
|
@@ -259,9 +260,13 @@ export default ${this.componentName}` : "";
|
|
|
259
260
|
}
|
|
260
261
|
fullPropsDestructure = `{ ${parts.join(", ")} }`;
|
|
261
262
|
}
|
|
263
|
+
const hasRequiredProps = ir.metadata.propsParams.some((p) => !p.optional && p.defaultValue === undefined && !p.isRest);
|
|
264
|
+
const wantsNoArgDefault = propsObjectName ? !propsTypeName : !hasRequiredProps;
|
|
265
|
+
const propsTypeExpr = typeAnnotation.replace(/^:\s*/, "");
|
|
266
|
+
const noArgDefault = wantsNoArgDefault ? ` = {} as ${propsTypeExpr}` : "";
|
|
262
267
|
const lines = [];
|
|
263
268
|
const exportPrefix = ir.metadata.isExported === false ? "" : "export ";
|
|
264
|
-
lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}) {`);
|
|
269
|
+
lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}${noArgDefault}) {`);
|
|
265
270
|
if (propsExtraction) {
|
|
266
271
|
lines.push(propsExtraction);
|
|
267
272
|
}
|
|
@@ -312,8 +317,8 @@ export default ${this.componentName}` : "";
|
|
|
312
317
|
emitExpression(node) {
|
|
313
318
|
return this.renderExpression(node);
|
|
314
319
|
}
|
|
315
|
-
emitConditional(node,
|
|
316
|
-
return this.renderConditional(node);
|
|
320
|
+
emitConditional(node, ctx, _emit) {
|
|
321
|
+
return this.renderConditional(node, ctx);
|
|
317
322
|
}
|
|
318
323
|
emitLoop(node, _ctx, _emit) {
|
|
319
324
|
return this.renderLoop(node);
|
|
@@ -400,12 +405,13 @@ export default ${this.componentName}` : "";
|
|
|
400
405
|
}
|
|
401
406
|
return `{${expr.expr}}`;
|
|
402
407
|
}
|
|
403
|
-
renderConditional(cond) {
|
|
408
|
+
renderConditional(cond, ctx) {
|
|
404
409
|
if (cond.clientOnly && cond.slotId) {
|
|
405
410
|
return `{bfComment("cond-start:${cond.slotId}")}{bfComment("cond-end:${cond.slotId}")}`;
|
|
406
411
|
}
|
|
407
|
-
const
|
|
408
|
-
|
|
412
|
+
const branchCtx = ctx?.isLoopItemRoot ? { isLoopItemRoot: true } : undefined;
|
|
413
|
+
const whenTrue = this.renderNodeRawCtx(cond.whenTrue, branchCtx);
|
|
414
|
+
let whenFalse = this.renderNodeRawCtx(cond.whenFalse, branchCtx);
|
|
409
415
|
if (!whenFalse || whenFalse === "" || whenFalse === "null") {
|
|
410
416
|
whenFalse = "null";
|
|
411
417
|
}
|
|
@@ -416,6 +422,14 @@ export default ${this.componentName}` : "";
|
|
|
416
422
|
}
|
|
417
423
|
return `{${cond.condition} ? ${whenTrue} : ${whenFalse}}`;
|
|
418
424
|
}
|
|
425
|
+
renderNodeRawCtx(node, ctx) {
|
|
426
|
+
if (node.type === "expression") {
|
|
427
|
+
if (node.expr === "null" || node.expr === "undefined")
|
|
428
|
+
return "null";
|
|
429
|
+
return node.expr;
|
|
430
|
+
}
|
|
431
|
+
return this.renderNode(node, ctx);
|
|
432
|
+
}
|
|
419
433
|
wrapWithCondMarker(node, content, condId) {
|
|
420
434
|
if (node.type === "component") {
|
|
421
435
|
return `<>{bfComment("cond-start:${condId}")}${content}{bfComment("cond-end:${condId}")}</>`;
|
|
@@ -446,6 +460,8 @@ export default ${this.componentName}` : "";
|
|
|
446
460
|
let safeChildren = children.startsWith("{") ? `<>${children}</>` : children;
|
|
447
461
|
if (loop.bodyIsMultiRoot) {
|
|
448
462
|
safeChildren = `<>{bfComment('bf-loop-i')}${children}</>`;
|
|
463
|
+
} else if (loop.bodyIsItemConditional && loop.key) {
|
|
464
|
+
safeChildren = `<>{bfComment('loop-i:' + String(${loop.key}))}${children}</>`;
|
|
449
465
|
}
|
|
450
466
|
let chainedArray = applyHonoLoopChain(loop);
|
|
451
467
|
const iterMethod = loop.method ?? "map";
|
package/dist/scripts.js
CHANGED
|
@@ -64,6 +64,7 @@ data: ${BOOT_ID}
|
|
|
64
64
|
// src/app.ts
|
|
65
65
|
import { html, raw } from "hono/html";
|
|
66
66
|
import { useRequestContext } from "hono/jsx-renderer";
|
|
67
|
+
import { renderImportMapHtml } from "@barefootjs/jsx/import-map";
|
|
67
68
|
var DEV_RELOAD_ENDPOINT_KEY = "bfDevReloadEndpoint";
|
|
68
69
|
function manifestToScriptUrls(manifest, base) {
|
|
69
70
|
const out = [];
|
|
@@ -89,13 +90,8 @@ function BfImportMap(props) {
|
|
|
89
90
|
"@barefootjs/client/runtime": `${base}/barefoot.js`,
|
|
90
91
|
...props.externals?.importmap?.imports ?? {}
|
|
91
92
|
};
|
|
92
|
-
const json = JSON.stringify({ imports });
|
|
93
93
|
const preloads = props.preload === false ? [] : props.externals?.preloads ?? [];
|
|
94
|
-
|
|
95
|
-
return html`<script type="importmap">${raw(json)}</script>${raw(links)}`;
|
|
96
|
-
}
|
|
97
|
-
function escapeAttr(value) {
|
|
98
|
-
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
94
|
+
return html`${raw(renderImportMapHtml({ importmap: { imports }, preloads }))}`;
|
|
99
95
|
}
|
|
100
96
|
var __bfEmptyManifestWarned = false;
|
|
101
97
|
function BfScripts(props) {
|
package/package.json
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* when no externals are passed.
|
|
8
8
|
*/
|
|
9
9
|
import { describe, test, expect } from 'bun:test'
|
|
10
|
-
import { BfImportMap
|
|
10
|
+
import { BfImportMap } from '../app'
|
|
11
|
+
import type { ImportMapManifest } from '@barefootjs/jsx'
|
|
11
12
|
|
|
12
13
|
function parseImportMap(html: string): Record<string, string> {
|
|
13
14
|
const match = html.match(/<script type="importmap">(.*?)<\/script>/s)
|
|
@@ -31,7 +32,7 @@ describe('BfImportMap', () => {
|
|
|
31
32
|
})
|
|
32
33
|
|
|
33
34
|
test('merges externals importmap on top of the client defaults', () => {
|
|
34
|
-
const externals:
|
|
35
|
+
const externals: ImportMapManifest = {
|
|
35
36
|
importmap: {
|
|
36
37
|
imports: {
|
|
37
38
|
zod: 'https://esm.sh/zod@4.4.3',
|
|
@@ -50,7 +51,7 @@ describe('BfImportMap', () => {
|
|
|
50
51
|
})
|
|
51
52
|
|
|
52
53
|
test('manifest @barefootjs/client mapping wins over the prop-derived one', () => {
|
|
53
|
-
const externals:
|
|
54
|
+
const externals: ImportMapManifest = {
|
|
54
55
|
importmap: { imports: { '@barefootjs/client': '/vendor/barefoot.js' } },
|
|
55
56
|
}
|
|
56
57
|
const imports = parseImportMap(String(BfImportMap({ base: '/components', externals })))
|
|
@@ -58,7 +59,7 @@ describe('BfImportMap', () => {
|
|
|
58
59
|
})
|
|
59
60
|
|
|
60
61
|
test('emits modulepreload links for manifest preloads', () => {
|
|
61
|
-
const externals:
|
|
62
|
+
const externals: ImportMapManifest = {
|
|
62
63
|
importmap: { imports: {} },
|
|
63
64
|
preloads: ['/components/form.js', 'https://esm.sh/zod@4.4.3'],
|
|
64
65
|
}
|
|
@@ -68,7 +69,7 @@ describe('BfImportMap', () => {
|
|
|
68
69
|
})
|
|
69
70
|
|
|
70
71
|
test('emits crossorigin on modulepreload so cross-origin CDN preloads are reused', () => {
|
|
71
|
-
const externals:
|
|
72
|
+
const externals: ImportMapManifest = {
|
|
72
73
|
preloads: ['https://esm.sh/zod@4.4.3'],
|
|
73
74
|
}
|
|
74
75
|
const html = String(BfImportMap({ base: '/components', externals }))
|
|
@@ -77,7 +78,7 @@ describe('BfImportMap', () => {
|
|
|
77
78
|
})
|
|
78
79
|
|
|
79
80
|
test('preload=false suppresses modulepreload links', () => {
|
|
80
|
-
const externals:
|
|
81
|
+
const externals: ImportMapManifest = {
|
|
81
82
|
preloads: ['/components/form.js'],
|
|
82
83
|
}
|
|
83
84
|
const html = String(BfImportMap({ base: '/components', externals, preload: false }))
|
|
@@ -87,7 +88,7 @@ describe('BfImportMap', () => {
|
|
|
87
88
|
})
|
|
88
89
|
|
|
89
90
|
test('escapes double quotes in preload hrefs', () => {
|
|
90
|
-
const externals:
|
|
91
|
+
const externals: ImportMapManifest = {
|
|
91
92
|
preloads: ['/components/"onerror=alert(1).js'],
|
|
92
93
|
}
|
|
93
94
|
const html = String(BfImportMap({ base: '/components', externals }))
|
|
@@ -103,6 +103,9 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
|
|
|
103
103
|
name = 'hono'
|
|
104
104
|
extension = '.tsx'
|
|
105
105
|
clientShimSource = '@barefootjs/hono/client-shim'
|
|
106
|
+
// Importmap is injected at render time by the `BfImportMap` component
|
|
107
|
+
// (reads `barefoot-externals.json`), so `bf build` emits no static snippet.
|
|
108
|
+
importMapInjection = 'component' as const
|
|
106
109
|
|
|
107
110
|
// The Hono SSR runtime is JavaScript (Node / Bun / CF Workers), so any
|
|
108
111
|
// synchronous JS call the user writes can be rendered as-is at template
|
|
@@ -507,11 +510,28 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
|
|
|
507
510
|
fullPropsDestructure = `{ ${parts.join(', ')} }`
|
|
508
511
|
}
|
|
509
512
|
|
|
513
|
+
// Default the props param to `{}` when the component has no required
|
|
514
|
+
// props, so a bare no-arg call (`Foo()`) doesn't crash on destructuring
|
|
515
|
+
// `undefined`. This makes a JSX-returning arrow hoisted from an
|
|
516
|
+
// object-literal value (e.g. `THEME_LOGOS[id]()`) renderable at SSR
|
|
517
|
+
// (#1663). `hasRequiredProps` ignores props that carry a destructuring
|
|
518
|
+
// default, but the declared props type may still mark that field
|
|
519
|
+
// required — so a bare `= {}` would fail `tsc`. Assert the default to the
|
|
520
|
+
// param's own annotated type (`{} as T`); the destructuring defaults
|
|
521
|
+
// supply the values at runtime. The SolidJS-style (`propsObjectName`)
|
|
522
|
+
// branch opts in whenever the annotation is satisfiable by `{} as T`.
|
|
523
|
+
const hasRequiredProps = ir.metadata.propsParams.some(
|
|
524
|
+
(p: ParamInfo) => !p.optional && p.defaultValue === undefined && !p.isRest,
|
|
525
|
+
)
|
|
526
|
+
const wantsNoArgDefault = propsObjectName ? !propsTypeName : !hasRequiredProps
|
|
527
|
+
const propsTypeExpr = typeAnnotation.replace(/^:\s*/, '')
|
|
528
|
+
const noArgDefault = wantsNoArgDefault ? ` = {} as ${propsTypeExpr}` : ''
|
|
529
|
+
|
|
510
530
|
const lines: string[] = []
|
|
511
531
|
// Module-export keyword belongs to the adapter: it knows the target language
|
|
512
532
|
// and whether the source declared the component as exported.
|
|
513
533
|
const exportPrefix = ir.metadata.isExported === false ? '' : 'export '
|
|
514
|
-
lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}) {`)
|
|
534
|
+
lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}${noArgDefault}) {`)
|
|
515
535
|
|
|
516
536
|
// Add props extraction for SolidJS-style pattern
|
|
517
537
|
if (propsExtraction) {
|
|
@@ -597,8 +617,8 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
|
|
|
597
617
|
return this.renderExpression(node)
|
|
598
618
|
}
|
|
599
619
|
|
|
600
|
-
emitConditional(node: IRConditional,
|
|
601
|
-
return this.renderConditional(node)
|
|
620
|
+
emitConditional(node: IRConditional, ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string {
|
|
621
|
+
return this.renderConditional(node, ctx)
|
|
602
622
|
}
|
|
603
623
|
|
|
604
624
|
emitLoop(node: IRLoop, _ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string {
|
|
@@ -718,14 +738,21 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
|
|
|
718
738
|
return `{${expr.expr}}`
|
|
719
739
|
}
|
|
720
740
|
|
|
721
|
-
renderConditional(cond: IRConditional): string {
|
|
741
|
+
renderConditional(cond: IRConditional, ctx?: HonoRenderCtx): string {
|
|
722
742
|
// Handle @client directive - render comment markers for client-side evaluation
|
|
723
743
|
if (cond.clientOnly && cond.slotId) {
|
|
724
744
|
return `{bfComment("cond-start:${cond.slotId}")}{bfComment("cond-end:${cond.slotId}")}`
|
|
725
745
|
}
|
|
726
746
|
|
|
727
|
-
|
|
728
|
-
|
|
747
|
+
// A conditional that is itself a loop item root (#1665 whole-item
|
|
748
|
+
// conditional: `arr.map(t => cond && <li/>)`) makes its branch element the
|
|
749
|
+
// loop item's root, so the `data-key` that reconciliation/hydration expect
|
|
750
|
+
// belongs on that element — exactly like a non-conditional loop root. Pass
|
|
751
|
+
// the flag through so `renderElement` emits `data-key`, matching the Go /
|
|
752
|
+
// CSR adapters' generic `key`→`data-key` rewrite.
|
|
753
|
+
const branchCtx: HonoRenderCtx | undefined = ctx?.isLoopItemRoot ? { isLoopItemRoot: true } : undefined
|
|
754
|
+
const whenTrue = this.renderNodeRawCtx(cond.whenTrue, branchCtx)
|
|
755
|
+
let whenFalse = this.renderNodeRawCtx(cond.whenFalse, branchCtx)
|
|
729
756
|
|
|
730
757
|
// Handle empty/null whenFalse
|
|
731
758
|
if (!whenFalse || whenFalse === '' || whenFalse === 'null') {
|
|
@@ -746,6 +773,20 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
|
|
|
746
773
|
return `{${cond.condition} ? ${whenTrue} : ${whenFalse}}`
|
|
747
774
|
}
|
|
748
775
|
|
|
776
|
+
/**
|
|
777
|
+
* Like the base `renderNodeRaw`, but threads a render ctx through to
|
|
778
|
+
* `renderNode` so a conditional branch can mark its element as a loop item
|
|
779
|
+
* root (#1665). The `null` / `undefined` expression branch carries no
|
|
780
|
+
* element, so it short-circuits exactly as the base helper does.
|
|
781
|
+
*/
|
|
782
|
+
private renderNodeRawCtx(node: IRNode, ctx?: HonoRenderCtx): string {
|
|
783
|
+
if (node.type === 'expression') {
|
|
784
|
+
if (node.expr === 'null' || node.expr === 'undefined') return 'null'
|
|
785
|
+
return node.expr
|
|
786
|
+
}
|
|
787
|
+
return this.renderNode(node, ctx)
|
|
788
|
+
}
|
|
789
|
+
|
|
749
790
|
private wrapWithCondMarker(node: IRNode, content: string, condId: string): string {
|
|
750
791
|
// Components don't reliably forward bf-c to their root element.
|
|
751
792
|
// Use comment markers so insert() can find them via TreeWalker.
|
|
@@ -813,6 +854,14 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
|
|
|
813
854
|
// literal here to match the adapter's existing convention of
|
|
814
855
|
// emitting comment-marker strings directly.
|
|
815
856
|
safeChildren = `<>{bfComment('bf-loop-i')}${children}</>`
|
|
857
|
+
} else if (loop.bodyIsItemConditional && loop.key) {
|
|
858
|
+
// Whole-item conditional (#1665): a per-item `<!--bf-loop-i:KEY-->`
|
|
859
|
+
// anchor that is ALWAYS present (even when the item's conditional
|
|
860
|
+
// renders nothing), carrying the key so the client's
|
|
861
|
+
// `mapArrayAnchored` can hydrate every SSR-rendered item by its anchor.
|
|
862
|
+
// `bfComment(k)` emits `<!--bf-${k}-->`, so the `loop-i:` argument
|
|
863
|
+
// yields `<!--bf-loop-i:KEY-->`.
|
|
864
|
+
safeChildren = `<>{bfComment('loop-i:' + String(${loop.key}))}${children}</>`
|
|
816
865
|
}
|
|
817
866
|
// Apply chained `.sort()` / `.filter()` extracted to
|
|
818
867
|
// `loop.sortComparator` / `loop.filterPredicate` (#1448 Tier B).
|
package/src/app.ts
CHANGED
|
@@ -30,6 +30,9 @@ import type { MiddlewareHandler } from 'hono'
|
|
|
30
30
|
import { html, raw } from 'hono/html'
|
|
31
31
|
import type { HtmlEscapedString } from 'hono/utils/html'
|
|
32
32
|
import { useRequestContext } from 'hono/jsx-renderer'
|
|
33
|
+
// Zero-dependency subpath — keeps the compiler (and its `typescript` dep) out
|
|
34
|
+
// of this runtime/Workers-bundled module while sharing one importmap renderer.
|
|
35
|
+
import { renderImportMapHtml, type ImportMapManifest } from '@barefootjs/jsx/import-map'
|
|
33
36
|
import { createDevReloader } from './dev-worker'
|
|
34
37
|
|
|
35
38
|
const DEV_RELOAD_ENDPOINT_KEY = 'bfDevReloadEndpoint'
|
|
@@ -88,21 +91,6 @@ export function relPathFromComponentsBase(p: string): string {
|
|
|
88
91
|
|
|
89
92
|
// ── JSX components ─────────────────────────────────────────────────────────
|
|
90
93
|
|
|
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
|
-
|
|
106
94
|
export interface BfImportMapProps {
|
|
107
95
|
/** Base URL where the runtime + component bundles are served. */
|
|
108
96
|
base: string
|
|
@@ -113,8 +101,12 @@ export interface BfImportMapProps {
|
|
|
113
101
|
* configured externals (e.g. `zod`, `@barefootjs/form`) resolve in
|
|
114
102
|
* the browser. When omitted, only the `@barefootjs/client*`
|
|
115
103
|
* mappings are emitted — the pre-#1639 behavior.
|
|
104
|
+
*
|
|
105
|
+
* Typed with the shared {@link ImportMapManifest} from `@barefootjs/jsx`,
|
|
106
|
+
* so the component and the `bf build` snippet path describe the manifest
|
|
107
|
+
* with one type.
|
|
116
108
|
*/
|
|
117
|
-
externals?:
|
|
109
|
+
externals?: ImportMapManifest
|
|
118
110
|
/**
|
|
119
111
|
* Whether to also emit `<link rel="modulepreload">` for the
|
|
120
112
|
* manifest's `preloads`. Defaults to `true`; set `false` to emit
|
|
@@ -130,6 +122,15 @@ export interface BfImportMapProps {
|
|
|
130
122
|
* passed via the `externals` prop. Also emits `<link rel="modulepreload">`
|
|
131
123
|
* for the manifest's `preloads` unless `preload` is `false`. Place in
|
|
132
124
|
* `<head>`.
|
|
125
|
+
*
|
|
126
|
+
* The merge of the `@barefootjs/client*` defaults (synthesized from `base`
|
|
127
|
+
* for prop-less / hand-written manifests) is Hono-specific; the actual HTML
|
|
128
|
+
* rendering — importmap JSON escaping, `<link rel="modulepreload">`
|
|
129
|
+
* emission with `crossorigin` (#1648) — is delegated to the shared
|
|
130
|
+
* `renderImportMapHtml` so this path can never drift from the static
|
|
131
|
+
* `barefoot-importmap.html` snippet `bf build` emits for template-string
|
|
132
|
+
* adapters (#1644). Imported from the `@barefootjs/jsx/import-map` subpath,
|
|
133
|
+
* a zero-dependency module, to keep this runtime file free of the compiler.
|
|
133
134
|
*/
|
|
134
135
|
export function BfImportMap(props: BfImportMapProps): HtmlEscapedString | Promise<HtmlEscapedString> {
|
|
135
136
|
const base = props.base.replace(/\/$/, '')
|
|
@@ -141,24 +142,9 @@ export function BfImportMap(props: BfImportMapProps): HtmlEscapedString | Promis
|
|
|
141
142
|
'@barefootjs/client/runtime': `${base}/barefoot.js`,
|
|
142
143
|
...(props.externals?.importmap?.imports ?? {}),
|
|
143
144
|
}
|
|
144
|
-
const json = JSON.stringify({ imports })
|
|
145
|
-
|
|
146
145
|
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
146
|
|
|
159
|
-
|
|
160
|
-
function escapeAttr(value: string): string {
|
|
161
|
-
return value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<')
|
|
147
|
+
return html`${raw(renderImportMapHtml({ importmap: { imports }, preloads }))}`
|
|
162
148
|
}
|
|
163
149
|
|
|
164
150
|
export interface BfScriptsProps {
|