@barefootjs/hono 0.5.1 → 0.5.3
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/test-render.d.ts +10 -0
- package/dist/test-render.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/test-render.ts +116 -16
package/dist/test-render.d.ts
CHANGED
|
@@ -14,6 +14,16 @@ export interface RenderOptions {
|
|
|
14
14
|
props?: Record<string, unknown>;
|
|
15
15
|
/** Additional component files (filename → source) */
|
|
16
16
|
components?: Record<string, string>;
|
|
17
|
+
/**
|
|
18
|
+
* Pre-compiled child component modules (import specifier → absolute
|
|
19
|
+
* module path) — #1467 Phase 2a. When the parent imports one of these
|
|
20
|
+
* specifiers, the import is *re-anchored* to the given module path
|
|
21
|
+
* (kept as a real ESM import) instead of having the child inlined via
|
|
22
|
+
* `components`. The module is a committed, export-intact marked
|
|
23
|
+
* template, so SSR loads it through the module system — no export
|
|
24
|
+
* stripping. Takes precedence over `components` for the same key.
|
|
25
|
+
*/
|
|
26
|
+
componentModules?: Record<string, string>;
|
|
17
27
|
/**
|
|
18
28
|
* Explicit component to render when the source declares multiple
|
|
19
29
|
* exports. When omitted, the first function-valued export in
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-render.d.ts","sourceRoot":"","sources":["../src/test-render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"test-render.d.ts","sourceRoot":"","sources":["../src/test-render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAStD,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,OAAO,EAAE,eAAe,CAAA;IACxB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzC;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AA2BD,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA0KjF"}
|
package/package.json
CHANGED
package/src/test-render.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { compileJSX } from '@barefootjs/jsx'
|
|
9
9
|
import type { TemplateAdapter } from '@barefootjs/jsx'
|
|
10
10
|
import { Hono } from 'hono'
|
|
11
|
+
import { readFileSync } from 'node:fs'
|
|
11
12
|
import { mkdir, rm } from 'node:fs/promises'
|
|
12
13
|
import { resolve } from 'node:path'
|
|
13
14
|
|
|
@@ -23,6 +24,16 @@ export interface RenderOptions {
|
|
|
23
24
|
props?: Record<string, unknown>
|
|
24
25
|
/** Additional component files (filename → source) */
|
|
25
26
|
components?: Record<string, string>
|
|
27
|
+
/**
|
|
28
|
+
* Pre-compiled child component modules (import specifier → absolute
|
|
29
|
+
* module path) — #1467 Phase 2a. When the parent imports one of these
|
|
30
|
+
* specifiers, the import is *re-anchored* to the given module path
|
|
31
|
+
* (kept as a real ESM import) instead of having the child inlined via
|
|
32
|
+
* `components`. The module is a committed, export-intact marked
|
|
33
|
+
* template, so SSR loads it through the module system — no export
|
|
34
|
+
* stripping. Takes precedence over `components` for the same key.
|
|
35
|
+
*/
|
|
36
|
+
componentModules?: Record<string, string>
|
|
26
37
|
/**
|
|
27
38
|
* Explicit component to render when the source declares multiple
|
|
28
39
|
* exports. When omitted, the first function-valued export in
|
|
@@ -34,14 +45,46 @@ export interface RenderOptions {
|
|
|
34
45
|
componentName?: string
|
|
35
46
|
}
|
|
36
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Drop module-level exports from a compiled marked template so it can be
|
|
50
|
+
* inlined as plain declarations alongside other components. Specifier
|
|
51
|
+
* blocks (`export { … }`, `export type { … }`, with or without a
|
|
52
|
+
* trailing `from '…'` re-export source) are removed whole; declaration
|
|
53
|
+
* forms (`export function/const/let/type/interface`, `export default`)
|
|
54
|
+
* keep their body with only the leading keyword stripped.
|
|
55
|
+
*
|
|
56
|
+
* The set of forms is bounded by `generateModuleExports` in
|
|
57
|
+
* @barefootjs/jsx — see the caller for the enumeration. This stays a
|
|
58
|
+
* line-oriented text pass (rather than a real parse) because the input
|
|
59
|
+
* is compiler-generated with a stable, single-line-per-export shape.
|
|
60
|
+
*/
|
|
61
|
+
function stripModuleExports(code: string): string {
|
|
62
|
+
return code
|
|
63
|
+
// `export [type] { … } [from '…']` specifier / re-export blocks.
|
|
64
|
+
.replace(
|
|
65
|
+
/^[ \t]*export\s+(?:type\s+)?\{[^}]*\}(?:[ \t]*from[ \t]*['"][^'"]*['"])?[ \t]*;?[ \t]*$/gm,
|
|
66
|
+
'',
|
|
67
|
+
)
|
|
68
|
+
// Leading keyword on declaration forms (`export function`,
|
|
69
|
+
// `export const X = …`, `export default …`, etc.).
|
|
70
|
+
.replace(/\bexport\s+(default\s+)?/g, '')
|
|
71
|
+
}
|
|
72
|
+
|
|
37
73
|
export async function renderHonoComponent(options: RenderOptions): Promise<string> {
|
|
38
|
-
const { source, adapter, props, components, componentName: requestedName } = options
|
|
74
|
+
const { source, adapter, props, components, componentModules, componentName: requestedName } = options
|
|
39
75
|
|
|
40
|
-
//
|
|
76
|
+
// Child imports re-anchored to a pre-compiled module (#1467 Phase 2a):
|
|
77
|
+
// import specifier → absolute path. These are NOT inlined; the parent's
|
|
78
|
+
// matching import is rewritten to the path and loaded as a real module.
|
|
79
|
+
const moduleMap = new Map<string, string>(Object.entries(componentModules ?? {}))
|
|
80
|
+
|
|
81
|
+
// Compile child components first (inline path). Keys also present in
|
|
82
|
+
// `moduleMap` are skipped here — they load as real modules instead.
|
|
41
83
|
const childCodes: string[] = []
|
|
42
84
|
const componentKeys = new Set<string>()
|
|
43
85
|
if (components) {
|
|
44
86
|
for (const [filename, childSource] of Object.entries(components)) {
|
|
87
|
+
if (moduleMap.has(filename)) continue
|
|
45
88
|
componentKeys.add(filename)
|
|
46
89
|
const childResult = compileJSX(childSource, filename, { adapter })
|
|
47
90
|
const childErrors = childResult.errors.filter(e => e.severity === 'error')
|
|
@@ -50,8 +93,23 @@ export async function renderHonoComponent(options: RenderOptions): Promise<strin
|
|
|
50
93
|
}
|
|
51
94
|
const childTemplate = childResult.files.find(f => f.type === 'markedTemplate')
|
|
52
95
|
if (!childTemplate) throw new Error(`No marked template for ${filename}`)
|
|
53
|
-
// Strip
|
|
54
|
-
|
|
96
|
+
// Strip exports so only the parent component is exported, inlining
|
|
97
|
+
// the child as plain top-level declarations. The marked template's
|
|
98
|
+
// export forms are fixed by `generateModuleExports` (+ the
|
|
99
|
+
// component's own `export function`) in @barefootjs/jsx, each on
|
|
100
|
+
// its own line:
|
|
101
|
+
//
|
|
102
|
+
// export const/let X = … export function / async function …
|
|
103
|
+
// export type X = … export interface X { … }
|
|
104
|
+
// export { A, B } [from '…'] export type { A } [from '…']
|
|
105
|
+
//
|
|
106
|
+
// The `export { … }` / `export type { … }` *specifier* blocks
|
|
107
|
+
// (with or without a trailing `from '…'`) must be dropped whole —
|
|
108
|
+
// their bindings are already declared inline, and naively removing
|
|
109
|
+
// just the `export ` keyword leaves a bare `{ A }` / `type { A }`
|
|
110
|
+
// (the latter a syntax error). Declaration forms keep their body;
|
|
111
|
+
// only the leading `export `/`export default ` is removed.
|
|
112
|
+
const localCode = stripModuleExports(childTemplate.content)
|
|
55
113
|
childCodes.push(localCode)
|
|
56
114
|
}
|
|
57
115
|
}
|
|
@@ -67,22 +125,56 @@ export async function renderHonoComponent(options: RenderOptions): Promise<strin
|
|
|
67
125
|
const templateFile = result.files.find(f => f.type === 'markedTemplate')
|
|
68
126
|
if (!templateFile) throw new Error('No marked template in compile output')
|
|
69
127
|
|
|
128
|
+
// Pre-compiled child modules are committed under the adapter-tests
|
|
129
|
+
// fixtures tree, where `hono/jsx` is NOT resolvable (hono lives in
|
|
130
|
+
// this package's node_modules — the very reason render temp files go
|
|
131
|
+
// here). Copy each committed module verbatim into the render temp dir
|
|
132
|
+
// and re-anchor the parent import there. The committed file stays the
|
|
133
|
+
// reviewable source of truth; this is a byte copy, not export surgery.
|
|
134
|
+
const childModuleWrites: Array<{ path: string; content: string }> = []
|
|
135
|
+
const moduleTempPaths = new Map<string, string>()
|
|
136
|
+
for (const [key, modPath] of moduleMap) {
|
|
137
|
+
const safe = key.replace(/[^a-zA-Z0-9]+/g, '_')
|
|
138
|
+
const tempPath = resolve(
|
|
139
|
+
RENDER_TEMP_DIR,
|
|
140
|
+
`child-${safe}-${Date.now()}-${Math.random().toString(36).slice(2)}.tsx`,
|
|
141
|
+
)
|
|
142
|
+
moduleTempPaths.set(key, tempPath)
|
|
143
|
+
childModuleWrites.push({ path: tempPath, content: readFileSync(modPath, 'utf8') })
|
|
144
|
+
}
|
|
145
|
+
|
|
70
146
|
let parentCode = templateFile.content
|
|
71
|
-
//
|
|
72
|
-
|
|
147
|
+
// Resolve each child import: re-anchor to a pre-compiled module's temp
|
|
148
|
+
// copy (`moduleTempPaths`), strip it (inlined via `components`), or
|
|
149
|
+
// leave it. Both maps key on the import specifier; match the parent's
|
|
150
|
+
// import path with or without a `.tsx` extension (`./badge` ↔
|
|
151
|
+
// `./badge.tsx`).
|
|
152
|
+
//
|
|
153
|
+
// Assumes one import statement per line — the marked-template adapter
|
|
154
|
+
// emits single-line imports (`import { Slot } from '../slot'`), so the
|
|
155
|
+
// per-line scan is sufficient. A multi-line import would not match
|
|
156
|
+
// here; the unrewritten `../slot` then fails loudly at module
|
|
157
|
+
// resolution rather than rendering wrong output.
|
|
158
|
+
if (componentKeys.size > 0 || moduleTempPaths.size > 0) {
|
|
159
|
+
const matchKey = (importPath: string, keys: Iterable<string>): string | undefined => {
|
|
160
|
+
for (const key of keys) {
|
|
161
|
+
const keyWithoutExt = key.replace(/\.tsx?$/, '')
|
|
162
|
+
if (importPath === keyWithoutExt || importPath === key) return key
|
|
163
|
+
}
|
|
164
|
+
return undefined
|
|
165
|
+
}
|
|
73
166
|
parentCode = parentCode
|
|
74
167
|
.split('\n')
|
|
75
|
-
.
|
|
76
|
-
const importMatch = line.match(
|
|
77
|
-
if (!importMatch) return
|
|
78
|
-
const importPath = importMatch
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
return true
|
|
168
|
+
.map(line => {
|
|
169
|
+
const importMatch = line.match(/^(\s*import\s+.*from\s+['"])(.+?)(['"].*)$/)
|
|
170
|
+
if (!importMatch) return line
|
|
171
|
+
const [, prefix, importPath, suffix] = importMatch
|
|
172
|
+
const moduleKey = matchKey(importPath, moduleTempPaths.keys())
|
|
173
|
+
if (moduleKey) return `${prefix}${moduleTempPaths.get(moduleKey)}${suffix}`
|
|
174
|
+
if (matchKey(importPath, componentKeys)) return null
|
|
175
|
+
return line
|
|
85
176
|
})
|
|
177
|
+
.filter((line): line is string => line !== null)
|
|
86
178
|
.join('\n')
|
|
87
179
|
}
|
|
88
180
|
|
|
@@ -95,6 +187,11 @@ export async function renderHonoComponent(options: RenderOptions): Promise<strin
|
|
|
95
187
|
const code = codeParts.join('\n')
|
|
96
188
|
|
|
97
189
|
await mkdir(RENDER_TEMP_DIR, { recursive: true })
|
|
190
|
+
// Materialise the verbatim child-module copies next to the parent so
|
|
191
|
+
// their `hono/jsx` pragma resolves.
|
|
192
|
+
for (const { path, content } of childModuleWrites) {
|
|
193
|
+
await Bun.write(path, content)
|
|
194
|
+
}
|
|
98
195
|
// Unique filename per render to avoid Bun's process-level module cache
|
|
99
196
|
// (bun#12371: re-importing the same path returns stale module)
|
|
100
197
|
const tempFile = resolve(
|
|
@@ -139,5 +236,8 @@ export async function renderHonoComponent(options: RenderOptions): Promise<strin
|
|
|
139
236
|
return await res.text()
|
|
140
237
|
} finally {
|
|
141
238
|
await rm(tempFile, { force: true }).catch(() => {})
|
|
239
|
+
for (const { path } of childModuleWrites) {
|
|
240
|
+
await rm(path, { force: true }).catch(() => {})
|
|
241
|
+
}
|
|
142
242
|
}
|
|
143
243
|
}
|