@barefootjs/hono 0.1.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/adapter/hono-adapter.d.ts +141 -0
- package/dist/adapter/hono-adapter.d.ts.map +1 -0
- package/dist/adapter/index.d.ts +6 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +632 -0
- package/dist/app.d.ts +131 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +139 -0
- package/dist/async.d.ts +15 -0
- package/dist/async.d.ts.map +1 -0
- package/dist/async.js +12 -0
- package/dist/build.d.ts +65 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +785 -0
- package/dist/client-shim.d.ts +59 -0
- package/dist/client-shim.d.ts.map +1 -0
- package/dist/client-shim.js +90 -0
- package/dist/dev-worker.d.ts +25 -0
- package/dist/dev-worker.d.ts.map +1 -0
- package/dist/dev-worker.js +65 -0
- package/dist/dev.d.ts +36 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +418 -0
- package/dist/dialog-context.d.ts +13 -0
- package/dist/dialog-context.d.ts.map +1 -0
- package/dist/dialog-context.js +10 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +632 -0
- package/dist/jsx/jsx-dev-runtime/index.d.ts +9 -0
- package/dist/jsx/jsx-dev-runtime/index.d.ts.map +1 -0
- package/dist/jsx/jsx-dev-runtime/index.js +6 -0
- package/dist/jsx/jsx-runtime/index.d.ts +32 -0
- package/dist/jsx/jsx-runtime/index.d.ts.map +1 -0
- package/dist/jsx/jsx-runtime/index.js +10 -0
- package/dist/portal-ssr.d.ts +22 -0
- package/dist/portal-ssr.d.ts.map +1 -0
- package/dist/portal-ssr.js +73 -0
- package/dist/portals.d.ts +26 -0
- package/dist/portals.d.ts.map +1 -0
- package/dist/portals.js +41 -0
- package/dist/preload.d.ts +56 -0
- package/dist/preload.d.ts.map +1 -0
- package/dist/preload.js +51 -0
- package/dist/scripts.d.ts +80 -0
- package/dist/scripts.d.ts.map +1 -0
- package/dist/scripts.js +198 -0
- package/dist/test-render.d.ts +28 -0
- package/dist/test-render.d.ts.map +1 -0
- package/dist/utils.d.ts +16 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +16 -0
- package/package.json +116 -0
- package/src/__tests__/async.test.tsx +106 -0
- package/src/__tests__/bfscripts-entry-roots.test.tsx +135 -0
- package/src/__tests__/build.test.ts +299 -0
- package/src/__tests__/dev.test.tsx +123 -0
- package/src/__tests__/hydration-props-type.test.ts +141 -0
- package/src/__tests__/manifest-scripts.test.ts +87 -0
- package/src/__tests__/scaffold.test.ts +209 -0
- package/src/__tests__/ssr-context-bridge.test.ts +110 -0
- package/src/__tests__/string-literal-css-var-prop.test.ts +84 -0
- package/src/__tests__/stub-deps-scripts.test.ts +183 -0
- package/src/adapter/hono-adapter.ts +1114 -0
- package/src/adapter/index.ts +6 -0
- package/src/app.ts +220 -0
- package/src/async.tsx +55 -0
- package/src/build.ts +230 -0
- package/src/client-shim.ts +164 -0
- package/src/dev-worker.ts +93 -0
- package/src/dev.tsx +146 -0
- package/src/dialog-context.tsx +44 -0
- package/src/index.ts +26 -0
- package/src/jsx/jsx-dev-runtime/index.ts +9 -0
- package/src/jsx/jsx-runtime/index.ts +40 -0
- package/src/portal-ssr.tsx +92 -0
- package/src/portals.tsx +98 -0
- package/src/preload.tsx +166 -0
- package/src/scripts.tsx +220 -0
- package/src/test-render.ts +143 -0
- package/src/utils.ts +26 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono adapter: string-literal props containing JS-shaped values (#135, #1255).
|
|
3
|
+
*
|
|
4
|
+
* A JSX string attribute like `fill="var(--area-fill)"` is an SVG
|
|
5
|
+
* presentation attribute whose value happens to look like a JS function
|
|
6
|
+
* call. An older adapter implementation used a regex (`isJsExpression`)
|
|
7
|
+
* to disambiguate string literals from arrow / call expressions, and
|
|
8
|
+
* tripped on values shaped like `var(...)`, `url(...)`, or `calc(...)`,
|
|
9
|
+
* emitting them as `fill={var(--area-fill)}` — `var` was then parsed as
|
|
10
|
+
* a JS identifier and the build failed with "Unexpected var".
|
|
11
|
+
*
|
|
12
|
+
* The IR's `isLiteral` flag is the authoritative source of truth here.
|
|
13
|
+
* #1255 deleted the regex fallback: every prop emitted by Phase 1
|
|
14
|
+
* already carries `dynamic | isLiteral | boolean-shorthand`, so the
|
|
15
|
+
* decision is made from IR alone, not by re-classifying the value
|
|
16
|
+
* string at emit time.
|
|
17
|
+
*
|
|
18
|
+
* Surfaced by the area chart palette demo (#135 Concrete Additions).
|
|
19
|
+
*/
|
|
20
|
+
import { describe, test, expect } from 'bun:test'
|
|
21
|
+
import { compileJSX } from '@barefootjs/jsx'
|
|
22
|
+
import { HonoAdapter } from '../adapter'
|
|
23
|
+
|
|
24
|
+
const adapter = new HonoAdapter()
|
|
25
|
+
|
|
26
|
+
describe('string-literal component prop containing CSS function-shape (#135)', () => {
|
|
27
|
+
test('fill="var(--x)" survives as a string literal in the marked template', () => {
|
|
28
|
+
const source = `
|
|
29
|
+
function Area(props: { fill: string }) {
|
|
30
|
+
return <path d="M0 0" fill={props.fill} />
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function Demo() {
|
|
34
|
+
return <Area fill="var(--area-fill)" />
|
|
35
|
+
}
|
|
36
|
+
`
|
|
37
|
+
const result = compileJSX(source, 'Demo.tsx', { adapter })
|
|
38
|
+
expect(result.errors).toHaveLength(0)
|
|
39
|
+
const template = result.files.find((f) => f.type === 'markedTemplate')
|
|
40
|
+
expect(template).toBeDefined()
|
|
41
|
+
const content = template!.content
|
|
42
|
+
|
|
43
|
+
// Must emit the string literal verbatim — NOT as a JS expression
|
|
44
|
+
expect(content).toContain('fill="var(--area-fill)"')
|
|
45
|
+
expect(content).not.toContain('fill={var(--area-fill)}')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('stroke="url(#grad)" survives as a string literal', () => {
|
|
49
|
+
const source = `
|
|
50
|
+
function S(props: { stroke: string }) {
|
|
51
|
+
return <line x1="0" y1="0" x2="10" y2="10" stroke={props.stroke} />
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function Demo() {
|
|
55
|
+
return <S stroke="url(#grad)" />
|
|
56
|
+
}
|
|
57
|
+
`
|
|
58
|
+
const result = compileJSX(source, 'Demo.tsx', { adapter })
|
|
59
|
+
expect(result.errors).toHaveLength(0)
|
|
60
|
+
const content = result.files.find((f) => f.type === 'markedTemplate')!.content
|
|
61
|
+
|
|
62
|
+
expect(content).toContain('stroke="url(#grad)"')
|
|
63
|
+
expect(content).not.toContain('stroke={url(#grad)}')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('arrow-function expression prop is still emitted as a JS expression', () => {
|
|
67
|
+
// Regression guard — the JS-expression branch must remain reachable
|
|
68
|
+
// for non-literal values (the case `isJsExpression` was added for).
|
|
69
|
+
const source = `
|
|
70
|
+
function B(props: { onClick: (e: Event) => void }) {
|
|
71
|
+
return <button onClick={props.onClick} />
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function Demo() {
|
|
75
|
+
return <B onClick={() => 1} />
|
|
76
|
+
}
|
|
77
|
+
`
|
|
78
|
+
const result = compileJSX(source, 'Demo.tsx', { adapter })
|
|
79
|
+
expect(result.errors).toHaveLength(0)
|
|
80
|
+
const content = result.files.find((f) => f.type === 'markedTemplate')!.content
|
|
81
|
+
|
|
82
|
+
expect(content).toContain('onClick={() => 1}')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test'
|
|
2
|
+
import { collectStubDepScripts } from '../scripts'
|
|
3
|
+
|
|
4
|
+
// Issue #1243: the per-page script collector emits a <script src> for
|
|
5
|
+
// every rendered component, but until #1243 a component reached ONLY
|
|
6
|
+
// through an imperative `createComponent('Foo', ...)` stub call (no
|
|
7
|
+
// JSX render) never had its `.client.js` shipped. `collectStubDepScripts`
|
|
8
|
+
// closes the gap: given the manifest and the set of names whose SSR
|
|
9
|
+
// function did execute, it returns the script URLs for every transitively
|
|
10
|
+
// reachable stubDep.
|
|
11
|
+
|
|
12
|
+
describe('collectStubDepScripts (#1243)', () => {
|
|
13
|
+
test('emits a script for a single-hop stubDep', () => {
|
|
14
|
+
const out = collectStubDepScripts(
|
|
15
|
+
{
|
|
16
|
+
IssueCardNode: {
|
|
17
|
+
clientJs: 'components/IssueCardNode.client.js',
|
|
18
|
+
stubDeps: ['DraftTitleEditor'],
|
|
19
|
+
},
|
|
20
|
+
DraftTitleEditor: { clientJs: 'components/DraftTitleEditor.client.js' },
|
|
21
|
+
},
|
|
22
|
+
'/static/components/',
|
|
23
|
+
new Set(['IssueCardNode']),
|
|
24
|
+
new Set(['IssueCardNode']),
|
|
25
|
+
)
|
|
26
|
+
expect([...out.values()]).toEqual([{ src: '/static/components/DraftTitleEditor.client.js' }])
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('walks transitively (A → B → C)', () => {
|
|
30
|
+
const out = collectStubDepScripts(
|
|
31
|
+
{
|
|
32
|
+
A: { clientJs: 'components/A.client.js', stubDeps: ['B'] },
|
|
33
|
+
B: { clientJs: 'components/B.client.js', stubDeps: ['C'] },
|
|
34
|
+
C: { clientJs: 'components/C.client.js' },
|
|
35
|
+
},
|
|
36
|
+
'/static/components/',
|
|
37
|
+
new Set(['A']),
|
|
38
|
+
new Set(['A']),
|
|
39
|
+
)
|
|
40
|
+
expect(new Set([...out.keys()])).toEqual(new Set(['B', 'C']))
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Issue #1243 — Copilot review:
|
|
44
|
+
// `<script type="module">` tags execute in document order with
|
|
45
|
+
// microtask checkpoints between them. A's `hydrate()` schedules a
|
|
46
|
+
// walk that fires before later scripts evaluate, so any stub call
|
|
47
|
+
// from A.init must resolve against a registry the LATER scripts
|
|
48
|
+
// populated. Emitting B before C in document order would let B's
|
|
49
|
+
// `createComponent('C', ...)` fail. The walker must produce
|
|
50
|
+
// post-order (deepest first → A→B→C ships C, then B).
|
|
51
|
+
test('emits a transitive chain in post-order (C before B for A → B → C)', () => {
|
|
52
|
+
const out = collectStubDepScripts(
|
|
53
|
+
{
|
|
54
|
+
A: { clientJs: 'components/A.client.js', stubDeps: ['B'] },
|
|
55
|
+
B: { clientJs: 'components/B.client.js', stubDeps: ['C'] },
|
|
56
|
+
C: { clientJs: 'components/C.client.js' },
|
|
57
|
+
},
|
|
58
|
+
'/static/components/',
|
|
59
|
+
new Set(['A']),
|
|
60
|
+
new Set(['A']),
|
|
61
|
+
)
|
|
62
|
+
// Map preserves insertion order; iteration = emission order in DOM.
|
|
63
|
+
expect([...out.keys()]).toEqual(['C', 'B'])
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// Post-order isn't just leaves-first; for a DAG where a sibling
|
|
67
|
+
// depends on another sibling (A → B, A → C, C → B), `B` must
|
|
68
|
+
// precede `C` in the output even though both are direct children
|
|
69
|
+
// of A. BFS+reverse can't express this — DFS post-order does.
|
|
70
|
+
test('emits DAG edges in topological order (B before C for A → B, A → C, C → B)', () => {
|
|
71
|
+
const out = collectStubDepScripts(
|
|
72
|
+
{
|
|
73
|
+
A: { clientJs: 'components/A.client.js', stubDeps: ['B', 'C'] },
|
|
74
|
+
B: { clientJs: 'components/B.client.js' },
|
|
75
|
+
C: { clientJs: 'components/C.client.js', stubDeps: ['B'] },
|
|
76
|
+
},
|
|
77
|
+
'/static/components/',
|
|
78
|
+
new Set(['A']),
|
|
79
|
+
new Set(['A']),
|
|
80
|
+
)
|
|
81
|
+
// A's first dep is B → visited (no further deps) → emit B.
|
|
82
|
+
// A's second dep is C → recurse into C's deps → B already visited
|
|
83
|
+
// → emit C. So order is B then C.
|
|
84
|
+
expect([...out.keys()]).toEqual(['B', 'C'])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('short-circuits on a cycle (A → B → A)', () => {
|
|
88
|
+
const out = collectStubDepScripts(
|
|
89
|
+
{
|
|
90
|
+
A: { clientJs: 'components/A.client.js', stubDeps: ['B'] },
|
|
91
|
+
B: { clientJs: 'components/B.client.js', stubDeps: ['A'] },
|
|
92
|
+
},
|
|
93
|
+
'/static/components/',
|
|
94
|
+
new Set(['A']),
|
|
95
|
+
new Set(['A']),
|
|
96
|
+
)
|
|
97
|
+
// A is in `excluded` so it's not re-emitted; B is reached once.
|
|
98
|
+
expect([...out.keys()]).toEqual(['B'])
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('skips deps already in excluded set', () => {
|
|
102
|
+
const out = collectStubDepScripts(
|
|
103
|
+
{
|
|
104
|
+
Parent: { clientJs: 'components/Parent.client.js', stubDeps: ['AlreadyEmitted'] },
|
|
105
|
+
AlreadyEmitted: { clientJs: 'components/AlreadyEmitted.client.js' },
|
|
106
|
+
},
|
|
107
|
+
'/static/components/',
|
|
108
|
+
new Set(['Parent']),
|
|
109
|
+
// Caller already emitted AlreadyEmitted's script some other way.
|
|
110
|
+
new Set(['Parent', 'AlreadyEmitted']),
|
|
111
|
+
)
|
|
112
|
+
expect(out.size).toBe(0)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('mutates excluded so caller can pass it to a later pass', () => {
|
|
116
|
+
const excluded = new Set(['Root'])
|
|
117
|
+
collectStubDepScripts(
|
|
118
|
+
{
|
|
119
|
+
Root: { clientJs: 'components/Root.client.js', stubDeps: ['Leaf'] },
|
|
120
|
+
Leaf: { clientJs: 'components/Leaf.client.js' },
|
|
121
|
+
},
|
|
122
|
+
'/static/components/',
|
|
123
|
+
new Set(['Root']),
|
|
124
|
+
excluded,
|
|
125
|
+
)
|
|
126
|
+
expect(excluded.has('Leaf')).toBe(true)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('honors a base path with no trailing slash', () => {
|
|
130
|
+
const out = collectStubDepScripts(
|
|
131
|
+
{
|
|
132
|
+
Parent: { clientJs: 'components/Parent.client.js', stubDeps: ['Child'] },
|
|
133
|
+
Child: { clientJs: 'components/Child.client.js' },
|
|
134
|
+
},
|
|
135
|
+
'/assets/bf',
|
|
136
|
+
new Set(['Parent']),
|
|
137
|
+
new Set(['Parent']),
|
|
138
|
+
)
|
|
139
|
+
expect(out.get('Child')?.src).toBe('/assets/bf/Child.client.js')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('returns an empty map when no stubDeps are present', () => {
|
|
143
|
+
const out = collectStubDepScripts(
|
|
144
|
+
{
|
|
145
|
+
Solo: { clientJs: 'components/Solo.client.js' },
|
|
146
|
+
},
|
|
147
|
+
'/static/components/',
|
|
148
|
+
new Set(['Solo']),
|
|
149
|
+
new Set(['Solo']),
|
|
150
|
+
)
|
|
151
|
+
expect(out.size).toBe(0)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('skips a dep whose manifest entry has no clientJs', () => {
|
|
155
|
+
// A misconfigured manifest could list a stubDep that resolves to
|
|
156
|
+
// an SSR-only entry. The walker still records it as visited (so
|
|
157
|
+
// it doesn't loop) but emits no script for it.
|
|
158
|
+
const out = collectStubDepScripts(
|
|
159
|
+
{
|
|
160
|
+
Parent: { clientJs: 'components/Parent.client.js', stubDeps: ['Phantom'] },
|
|
161
|
+
Phantom: { clientJs: undefined },
|
|
162
|
+
},
|
|
163
|
+
'/static/components/',
|
|
164
|
+
new Set(['Parent']),
|
|
165
|
+
new Set(['Parent']),
|
|
166
|
+
)
|
|
167
|
+
expect(out.size).toBe(0)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test('ignores __barefoot__ as a root (it has no stubDeps and would noise the walk)', () => {
|
|
171
|
+
const out = collectStubDepScripts(
|
|
172
|
+
{
|
|
173
|
+
__barefoot__: { clientJs: 'components/barefoot.js' },
|
|
174
|
+
Parent: { clientJs: 'components/Parent.client.js', stubDeps: ['Child'] },
|
|
175
|
+
Child: { clientJs: 'components/Child.client.js' },
|
|
176
|
+
},
|
|
177
|
+
'/static/components/',
|
|
178
|
+
new Set(['__barefoot__', 'Parent']),
|
|
179
|
+
new Set(['__barefoot__', 'Parent']),
|
|
180
|
+
)
|
|
181
|
+
expect([...out.keys()]).toEqual(['Child'])
|
|
182
|
+
})
|
|
183
|
+
})
|