@abide/abide 0.103.0 → 0.105.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/CHANGELOG.md +29 -0
- package/bin/abide.ts +80 -173
- package/docs/REGISTRY.md +27 -27
- package/docs/RULEBOOK.md +68 -24
- package/docs/abide.md +31 -46
- package/package.json +1 -1
- package/src/compiler/Lowering.ts +15 -5
- package/src/compiler/Refusals.ts +57 -0
- package/src/compiler/TypeSession.ts +46 -89
- package/src/compiler/appLayout.ts +447 -0
- package/src/compiler/appMap.ts +21 -25
- package/src/compiler/checkApp.ts +61 -97
- package/src/compiler/checkedShadow.ts +12 -10
- package/src/compiler/classifyRead.ts +3 -6
- package/src/compiler/compileApp.ts +183 -478
- package/src/compiler/componentGraph.ts +2 -71
- package/src/compiler/declaredExports.ts +79 -60
- package/src/compiler/emitClient.ts +11 -13
- package/src/compiler/emitComponent.ts +21 -6
- package/src/compiler/emitSource.ts +20 -20
- package/src/compiler/handlersUnder.ts +46 -36
- package/src/compiler/hookCalls.ts +152 -0
- package/src/compiler/mountsOf.ts +31 -54
- package/src/compiler/projectShadow.ts +1 -1
- package/src/compiler/readSources.ts +27 -42
- package/src/compiler/readsIn.ts +5 -16
- package/src/compiler/resolverIn.ts +77 -0
- package/src/server/abideModules.ts +152 -146
- package/src/server/answerRequest.ts +18 -4
- package/src/server/answerSocket.ts +136 -76
- package/src/server/answerValue.ts +28 -15
- package/src/server/appTree.ts +59 -73
- package/src/server/claudeApi.ts +2 -5
- package/src/server/clientBundle.ts +198 -0
- package/src/server/coerceArgs.ts +1 -1
- package/src/server/commands.ts +93 -57
- package/src/server/createApp.ts +90 -16
- package/src/server/deferredBuilds.ts +175 -0
- package/src/server/devWorker.ts +107 -319
- package/src/server/faceOf.ts +120 -0
- package/src/server/fenceServer.ts +10 -18
- package/src/server/generatedSurfaces.ts +12 -53
- package/src/server/index.ts +3 -0
- package/src/server/placement.ts +167 -0
- package/src/server/remote.ts +14 -42
- package/src/server/render.ts +25 -17
- package/src/server/renderComponent.ts +9 -5
- package/src/server/requestScope.ts +10 -4
- package/src/server/responseRelay.ts +389 -0
- package/src/server/runCompiled.ts +9 -12
- package/src/server/runDesktop.ts +6 -9
- package/src/server/serveBuild.ts +26 -73
- package/src/server/serveDev.ts +19 -176
- package/src/server/streamEvent.ts +29 -0
- package/src/server/versionOf.ts +1 -2
- package/src/server/withAbideSources.ts +2 -1
- package/src/shared/HandlerRoute.ts +68 -0
- package/src/shared/REFUSAL_KEY.ts +5 -0
- package/src/shared/RESERVED_DIRECTORIES.ts +6 -0
- package/src/shared/answerOf.ts +105 -78
- package/src/shared/filledPath.ts +1 -9
- package/src/shared/framing.ts +127 -128
- package/src/shared/queryText.ts +15 -7
- package/src/shared/reactive/Cursor.ts +69 -124
- package/src/shared/reactive/Reactive.ts +296 -162
- package/src/shared/reactive/callLoad.ts +4 -18
- package/src/shared/reactive/index.ts +11 -8
- package/src/shared/reactive/pairReactive.ts +1 -1
- package/src/shared/reactive/scoped.ts +4 -3
- package/src/shared/reactive/share.ts +11 -1
- package/src/shared/routePattern.ts +0 -110
- package/src/shared/rpc.ts +17 -24
- package/src/shared/rpcRequest.ts +18 -47
- package/src/shared/takeEarlyCall.ts +12 -37
- package/src/shared/url.ts +1 -1
- package/src/ui/Level.ts +276 -0
- package/src/ui/List.ts +17 -17
- package/src/ui/Router.ts +20 -87
- package/src/ui/applyHead.ts +92 -79
- package/src/ui/index.ts +4 -2
- package/src/ui/reconcile.ts +4 -110
- package/src/ui/rows.ts +123 -2
- package/src/ui/wire.ts +12 -27
- package/src/compiler/hooksCalled.ts +0 -35
- package/src/server/clientBuildOptions.ts +0 -54
- package/src/server/refuseAddressCollision.ts +0 -37
- package/src/shared/reactive/chunksOfStream.ts +0 -57
- package/src/ui/MOUNTING_LEVEL.ts +0 -18
|
@@ -21,6 +21,19 @@ import { basename, dirname, resolve } from 'node:path' // bun exposes no path he
|
|
|
21
21
|
import { SERVER_SPECIFIER } from '#shared/SPECIFIERS.ts'
|
|
22
22
|
import type { ServerTable } from './answerRequest.ts'
|
|
23
23
|
import type { MountRow } from './answerRpc.ts'
|
|
24
|
+
import {
|
|
25
|
+
type Asked,
|
|
26
|
+
BOUND_NAMESPACE,
|
|
27
|
+
CLIENT_NAMESPACE,
|
|
28
|
+
componentNamespaceOf,
|
|
29
|
+
type Face,
|
|
30
|
+
faceOf,
|
|
31
|
+
type HandlerFace,
|
|
32
|
+
type Lane,
|
|
33
|
+
realFileOf,
|
|
34
|
+
STYLE_NAMESPACE,
|
|
35
|
+
TEXT_NAMESPACE,
|
|
36
|
+
} from './faceOf.ts'
|
|
24
37
|
import { installSourceMaps, type SourceMaps } from './installSourceMaps.ts'
|
|
25
38
|
import {
|
|
26
39
|
CLIENT_QUERY,
|
|
@@ -68,24 +81,8 @@ export type Compilation = {
|
|
|
68
81
|
mounted: Record<string, string>
|
|
69
82
|
}
|
|
70
83
|
|
|
71
|
-
// A VIRTUAL FACE'S PATH: its file's, with a suffix that is not a query. Both registries key a module
|
|
72
|
-
// by its path whatever its namespace, and the runtime's drops a query from it — so a file's text, its
|
|
73
|
-
// stylesheet or its bound face under `?…` took the place of the module the file compiles to.
|
|
74
|
-
const BOUND = '::bound'
|
|
75
|
-
const CLIENT = '::client'
|
|
76
|
-
const TEXT = '::text'
|
|
77
|
-
const STYLE = '::style'
|
|
78
|
-
|
|
79
|
-
/** The namespace a bundle's component module is answered in, at its file's own path. */
|
|
80
|
-
export const COMPONENT_NAMESPACE = 'abide-component'
|
|
81
|
-
/** The namespaces a file's text and a component's stylesheet are loaded in. */
|
|
82
|
-
export const TEXT_NAMESPACE = 'abide-text'
|
|
83
|
-
export const STYLE_NAMESPACE = 'abide-style'
|
|
84
84
|
const RELATIVE_OR_SUBPATH = /^(?:\.\.?\/|#)/
|
|
85
85
|
|
|
86
|
-
/** One handler file, as `Compilation.handlers` holds it. */
|
|
87
|
-
type HandlerFace = { scope: string; client: string }
|
|
88
|
-
|
|
89
86
|
/** The plugin serving `compilation`'s files and tables, for one bundle. */
|
|
90
87
|
export function abideModules(
|
|
91
88
|
root: string,
|
|
@@ -123,13 +120,39 @@ let registered = false
|
|
|
123
120
|
*/
|
|
124
121
|
export function serveCompilation(root: string, compilation: Compilation): void {
|
|
125
122
|
const real = realOrAsGiven(root)
|
|
126
|
-
addCompilation(real, compilation, RUNTIME)
|
|
123
|
+
const handlerFiles = addCompilation(real, compilation, RUNTIME)
|
|
127
124
|
installSourceMaps(serverMapsOf(compilation, real), real)
|
|
125
|
+
if (handlerFiles.length !== 0) Bun.plugin(handlerFilesPlugin(handlerFiles))
|
|
128
126
|
if (registered) return
|
|
129
127
|
registered = true
|
|
130
128
|
Bun.plugin(modulesPlugin(RUNTIME))
|
|
131
129
|
}
|
|
132
130
|
|
|
131
|
+
// 16.2 — A HANDLER FILE LOADED AT ITS OWN PATH RE-EXPORTS ITS FACE, so every spelling of its import
|
|
132
|
+
// is the module the server mounted. The runtime asks `onResolve` only for a specifier with an
|
|
133
|
+
// extension, while `onLoad` is asked by the path the file resolved to, however it was spelled. One
|
|
134
|
+
// plugin per compilation because the filter is fixed at registration, and the runtime's `onLoad`
|
|
135
|
+
// cannot fall through, so the filter names the handler files and nothing else. The face it names
|
|
136
|
+
// is resolved by `modulesPlugin`, registered once.
|
|
137
|
+
function handlerFilesPlugin(paths: string[]): Bun.BunPlugin {
|
|
138
|
+
return {
|
|
139
|
+
name: 'abide-handler-files',
|
|
140
|
+
setup(build) {
|
|
141
|
+
build.onLoad(
|
|
142
|
+
{
|
|
143
|
+
filter: new RegExp(
|
|
144
|
+
`^(?:${paths.map(RegExp.escape).join('|')})$`,
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
(args) => ({
|
|
148
|
+
contents: `export { default } from ${JSON.stringify(`${args.path}${HANDLER_SUFFIX}`)}\n`,
|
|
149
|
+
loader: 'js',
|
|
150
|
+
}),
|
|
151
|
+
)
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
133
156
|
/** What the server's table exports: its routes and error pages, server blocks and handlers. */
|
|
134
157
|
export type BuiltTable = ServerTable & {
|
|
135
158
|
/** 37.1 — every module a `<script server>` runs in, imported before the app boots. */
|
|
@@ -162,18 +185,24 @@ export function serverMapsOf(
|
|
|
162
185
|
return maps
|
|
163
186
|
}
|
|
164
187
|
|
|
188
|
+
/** Adds `compilation` to `held`, and answers its handler files' absolute paths. */
|
|
165
189
|
function addCompilation(
|
|
166
190
|
root: string,
|
|
167
191
|
compilation: Compilation,
|
|
168
192
|
held: Held,
|
|
169
|
-
):
|
|
193
|
+
): string[] {
|
|
170
194
|
// RESOLVED, not joined: a dependency's file is keyed above the root (`../node_modules/…`).
|
|
171
195
|
for (const [relative, compiled] of Object.entries(compilation.files))
|
|
172
196
|
held.files.set(resolve(root, relative), compiled)
|
|
173
|
-
|
|
174
|
-
|
|
197
|
+
const handlerFiles: string[] = []
|
|
198
|
+
for (const [relative, face] of Object.entries(compilation.handlers)) {
|
|
199
|
+
const path = resolve(root, relative)
|
|
200
|
+
held.handlers.set(path, face)
|
|
201
|
+
handlerFiles.push(path)
|
|
202
|
+
}
|
|
175
203
|
for (const [name, text] of Object.entries(compilation.tables))
|
|
176
204
|
held.tables.set(`${root}/${VIRTUAL_DIRECTORY}/${name}`, text)
|
|
205
|
+
return handlerFiles
|
|
177
206
|
}
|
|
178
207
|
|
|
179
208
|
// `Bun.resolveSync` asks EVERY abide plugin's `onResolve` again — this one's, and the runtime's, which
|
|
@@ -203,79 +232,59 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
203
232
|
name: 'abide-modules',
|
|
204
233
|
setup(build) {
|
|
205
234
|
// THE LANE IS THE BUILD'S TARGET: a browser bundle gets each file's browser module, and
|
|
206
|
-
// the runtime — which has no config — and a server bundle get its server module.
|
|
207
|
-
// browser module the runtime was asked for (`CLIENT_QUERY`) is the one exception, and
|
|
208
|
-
// what it imports is in its lane.
|
|
235
|
+
// the runtime — which has no config — and a server bundle get its server module.
|
|
209
236
|
const bundling: boolean = 'config' in build
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
browser
|
|
213
|
-
|
|
214
|
-
// `file` path a plugin answers with `..` anywhere in it, which a `[...rest]` directory
|
|
215
|
-
// writes (23.5). The runtime has no such check, and its stack frames name the path.
|
|
216
|
-
const componentNamespace = bundling ? COMPONENT_NAMESPACE : 'file'
|
|
217
|
-
// The module `path` compiles to, in the lane its importer is in.
|
|
218
|
-
const componentFor = (path: string, importer: string) =>
|
|
219
|
-
inBrowser(importer) && !browser
|
|
220
|
-
? { path: `${path}${CLIENT}`, namespace: 'abide-client' }
|
|
221
|
-
: { path, namespace: componentNamespace }
|
|
237
|
+
const lane: Lane = {
|
|
238
|
+
bundling,
|
|
239
|
+
browser: bundling && build.config.target === 'browser',
|
|
240
|
+
}
|
|
222
241
|
const resolved = (specifier: string, importer: string): string =>
|
|
223
242
|
resolvedFile(specifier, dirname(pathOf(importer)))
|
|
243
|
+
// Each hook below only adapts bun's ask to `faceOf`'s: the file the specifier resolved to,
|
|
244
|
+
// its query taken off, and the answer handed back — a refusal thrown.
|
|
245
|
+
const answer = (
|
|
246
|
+
asked: Asked,
|
|
247
|
+
file: string,
|
|
248
|
+
importer: string,
|
|
249
|
+
): Face | undefined => {
|
|
250
|
+
const face = faceOf(
|
|
251
|
+
asked,
|
|
252
|
+
file,
|
|
253
|
+
pathOf(importer),
|
|
254
|
+
lane,
|
|
255
|
+
served.handlers,
|
|
256
|
+
)
|
|
257
|
+
if (face instanceof Error) throw face
|
|
258
|
+
return face
|
|
259
|
+
}
|
|
224
260
|
// A filter off the query's own constant, so renaming one cannot silently stop a match.
|
|
261
|
+
const asking = (asked: Asked, query: string, filter: RegExp) =>
|
|
262
|
+
build.onResolve({ filter }, (args) =>
|
|
263
|
+
resolving
|
|
264
|
+
? undefined
|
|
265
|
+
: answer(
|
|
266
|
+
asked,
|
|
267
|
+
resolved(
|
|
268
|
+
args.path.slice(
|
|
269
|
+
0,
|
|
270
|
+
args.path.length - query.length,
|
|
271
|
+
),
|
|
272
|
+
args.importer,
|
|
273
|
+
),
|
|
274
|
+
args.importer,
|
|
275
|
+
),
|
|
276
|
+
)
|
|
225
277
|
const abideWith = (query: string) =>
|
|
226
278
|
new RegExp(`\\.abide${RegExp.escape(query)}$`)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
(
|
|
231
|
-
if (resolving) return undefined
|
|
232
|
-
const specifier = args.path.slice(0, -TEXT_QUERY.length)
|
|
233
|
-
// A path of its own (`TEXT`): a file is often imported as code and as text at once
|
|
234
|
-
// — an example's Files tab.
|
|
235
|
-
return {
|
|
236
|
-
path: `${resolved(specifier, args.importer)}${TEXT}`,
|
|
237
|
-
namespace: TEXT_NAMESPACE,
|
|
238
|
-
}
|
|
239
|
-
},
|
|
279
|
+
asking(
|
|
280
|
+
'text',
|
|
281
|
+
TEXT_QUERY,
|
|
282
|
+
new RegExp(`${RegExp.escape(TEXT_QUERY)}$`),
|
|
240
283
|
)
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
path: `${resolved(specifier, args.importer)}${STYLE}`,
|
|
246
|
-
namespace: STYLE_NAMESPACE,
|
|
247
|
-
}
|
|
248
|
-
})
|
|
249
|
-
build.onResolve({ filter: abideWith(COMPONENT_QUERY) }, (args) => {
|
|
250
|
-
if (resolving) return undefined
|
|
251
|
-
const specifier = args.path.slice(0, -COMPONENT_QUERY.length)
|
|
252
|
-
return componentFor(
|
|
253
|
-
resolved(specifier, args.importer),
|
|
254
|
-
args.importer,
|
|
255
|
-
)
|
|
256
|
-
})
|
|
257
|
-
build.onResolve({ filter: abideWith(CLIENT_QUERY) }, (args) => {
|
|
258
|
-
if (resolving) return undefined
|
|
259
|
-
const path = resolved(
|
|
260
|
-
args.path.slice(0, -CLIENT_QUERY.length),
|
|
261
|
-
args.importer,
|
|
262
|
-
)
|
|
263
|
-
return browser
|
|
264
|
-
? { path, namespace: componentNamespace }
|
|
265
|
-
: { path: `${path}${CLIENT}`, namespace: 'abide-client' }
|
|
266
|
-
})
|
|
267
|
-
build.onResolve({ filter: /\.abide$/ }, (args) => {
|
|
268
|
-
if (resolving) return undefined
|
|
269
|
-
const path = resolved(args.path, args.importer)
|
|
270
|
-
// A bound face's own import of its component, and every browser import, is the
|
|
271
|
-
// component module.
|
|
272
|
-
if (
|
|
273
|
-
inBrowser(args.importer) ||
|
|
274
|
-
pathOf(args.importer) === `${path}${BOUND}`
|
|
275
|
-
)
|
|
276
|
-
return componentFor(path, args.importer)
|
|
277
|
-
return { path: `${path}${BOUND}`, namespace: 'abide-bound' }
|
|
278
|
-
})
|
|
284
|
+
asking('style', STYLE_QUERY, abideWith(STYLE_QUERY))
|
|
285
|
+
asking('component', COMPONENT_QUERY, abideWith(COMPONENT_QUERY))
|
|
286
|
+
asking('client', CLIENT_QUERY, abideWith(CLIENT_QUERY))
|
|
287
|
+
asking('abide', '', /\.abide$/)
|
|
279
288
|
build.onResolve(
|
|
280
289
|
{ filter: new RegExp(`/${VIRTUAL_DIRECTORY}/[^/]+\\.ts$`) },
|
|
281
290
|
(args) => {
|
|
@@ -290,48 +299,39 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
290
299
|
},
|
|
291
300
|
)
|
|
292
301
|
|
|
293
|
-
// A HANDLER FILE IS FOUND BY WHERE ITS IMPORT RESOLVES, however the import spells it
|
|
294
|
-
//
|
|
295
|
-
// each of a package's relative imports is a resolve per module of the bundle. IN A BUNDLE
|
|
302
|
+
// A HANDLER FILE IS FOUND BY WHERE ITS IMPORT RESOLVES, however the import spells it — the
|
|
303
|
+
// app's own, or a mounted package's own page importing a handler file of its (23.19).
|
|
296
304
|
// THE FILTER IS THE HANDLER FILES' NAMES: a hook matching every relative import costs the
|
|
297
305
|
// bundle abide's own `sideEffects`, and the barrel's rest — the validator, the csp — moved
|
|
298
|
-
// into the chunk every page loads (38.94)
|
|
299
|
-
//
|
|
306
|
+
// into the chunk every page loads (38.94); and since only an import ending in one of those
|
|
307
|
+
// names is resolved again, a package's other imports cost nothing, so a package's are not
|
|
308
|
+
// passed over by where they lie. ONLY IN A BUNDLE: the runtime finds a handler file by the
|
|
309
|
+
// path it loads at (`handlerFilesPlugin`), and asks no `onResolve` of an extensionless
|
|
310
|
+
// specifier at all.
|
|
300
311
|
// Each handler file's name without `.ts` — what an import of one ends in.
|
|
301
|
-
|
|
302
|
-
if (bundling)
|
|
303
|
-
const names = new Set<string>()
|
|
312
|
+
const stems = new Set<string>()
|
|
313
|
+
if (bundling)
|
|
304
314
|
for (const path of served.handlers.keys())
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
const face = served.handlers.get(path)
|
|
327
|
-
if (face === undefined) return undefined
|
|
328
|
-
// 20.4 — a handler the browser is handed no wrapper for is no import of its.
|
|
329
|
-
if (inBrowser(args.importer) && face.client === '')
|
|
330
|
-
throw new Error(
|
|
331
|
-
`abide: ${pathOf(args.importer)} imports ${args.path}, whose handler withholds itself from the browser (\`clients: { ui: false }\`). Call it from a <script server>, or let the browser have it. RULEBOOK 20.4.`,
|
|
332
|
-
)
|
|
333
|
-
return { path: `${path}${HANDLER_SUFFIX}` }
|
|
334
|
-
})
|
|
315
|
+
stems.add(basename(path, '.ts'))
|
|
316
|
+
if (stems.size !== 0)
|
|
317
|
+
build.onResolve(
|
|
318
|
+
{
|
|
319
|
+
filter: new RegExp(
|
|
320
|
+
`(?:^|/)(?:${[...stems].map(RegExp.escape).join('|')})(?:\\.ts)?$`,
|
|
321
|
+
),
|
|
322
|
+
},
|
|
323
|
+
(args) => {
|
|
324
|
+
if (resolving || !RELATIVE_OR_SUBPATH.test(args.path))
|
|
325
|
+
return undefined
|
|
326
|
+
let file: string
|
|
327
|
+
try {
|
|
328
|
+
file = resolved(args.path, args.importer)
|
|
329
|
+
} catch {
|
|
330
|
+
return undefined
|
|
331
|
+
}
|
|
332
|
+
return answer('handler', file, args.importer)
|
|
333
|
+
},
|
|
334
|
+
)
|
|
335
335
|
|
|
336
336
|
// A RELATIVE SPECIFIER A FACE IMPORTS AT RUN TIME is resolved from the file the face
|
|
337
337
|
// stands for: bun's runtime reads one from an importer in a namespace as no path at
|
|
@@ -340,19 +340,27 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
340
340
|
// and where a hook matching every relative import in a package costs the bundle that
|
|
341
341
|
// package's `sideEffects`: a two-page app grew by 12 KB, the validator one page never
|
|
342
342
|
// calls moved into the chunk both pages load.
|
|
343
|
-
if (!bundling)
|
|
343
|
+
if (!bundling) {
|
|
344
|
+
// A face `handlerFilesPlugin` re-exports, named by its absolute path, which no file
|
|
345
|
+
// is at.
|
|
346
|
+
build.onResolve({ filter: HANDLER_FACE }, (args) => ({
|
|
347
|
+
path: args.path,
|
|
348
|
+
}))
|
|
344
349
|
build.onResolve({ filter: /^\.\.?\// }, (args) =>
|
|
345
350
|
resolving || pathOf(args.importer) === args.importer
|
|
346
351
|
? undefined
|
|
347
352
|
: { path: resolved(args.path, args.importer) },
|
|
348
353
|
)
|
|
354
|
+
}
|
|
349
355
|
|
|
350
356
|
build.onLoad(
|
|
351
|
-
{ filter: /\.abide$/, namespace:
|
|
357
|
+
{ filter: /\.abide$/, namespace: componentNamespaceOf(lane) },
|
|
352
358
|
(args) => {
|
|
353
359
|
const compiled = compiledAt(served, args.path)
|
|
354
360
|
return {
|
|
355
|
-
contents: browser
|
|
361
|
+
contents: lane.browser
|
|
362
|
+
? compiled.client
|
|
363
|
+
: compiled.server,
|
|
356
364
|
loader: 'ts',
|
|
357
365
|
}
|
|
358
366
|
},
|
|
@@ -364,9 +372,9 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
364
372
|
// resolves from its directory as the file's own imports do. A namespace of abide's own
|
|
365
373
|
// had bun resolve a bare one from the working directory: a second abide, run from `/`.
|
|
366
374
|
build.onLoad({ filter: HANDLER_FACE }, async (args) => {
|
|
367
|
-
const path = args.path
|
|
375
|
+
const path = realFileOf(args.path)
|
|
368
376
|
const face = served.handlers.get(path) as HandlerFace
|
|
369
|
-
if (browser) return { contents: face.client, loader: 'js' }
|
|
377
|
+
if (lane.browser) return { contents: face.client, loader: 'js' }
|
|
370
378
|
const text = await Bun.file(path).text()
|
|
371
379
|
return {
|
|
372
380
|
contents: `import { fileRoute as __fileRoute } from '${SERVER_SPECIFIER}';__fileRoute(${JSON.stringify(face.scope)});${text}\n;__fileRoute(undefined)\n`,
|
|
@@ -374,22 +382,22 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
374
382
|
}
|
|
375
383
|
})
|
|
376
384
|
build.onLoad(
|
|
377
|
-
{ filter: /.*/, namespace:
|
|
385
|
+
{ filter: /.*/, namespace: CLIENT_NAMESPACE },
|
|
378
386
|
(args) => ({
|
|
379
|
-
contents: compiledAt(
|
|
380
|
-
served,
|
|
381
|
-
args.path.slice(0, -CLIENT.length),
|
|
382
|
-
).client,
|
|
387
|
+
contents: compiledAt(served, realFileOf(args.path)).client,
|
|
383
388
|
loader: 'ts',
|
|
384
389
|
}),
|
|
385
390
|
)
|
|
386
|
-
build.onLoad(
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
391
|
+
build.onLoad(
|
|
392
|
+
{ filter: /.*/, namespace: BOUND_NAMESPACE },
|
|
393
|
+
(args) => {
|
|
394
|
+
const path = realFileOf(args.path)
|
|
395
|
+
return {
|
|
396
|
+
contents: boundModule(path),
|
|
397
|
+
loader: 'js',
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
)
|
|
393
401
|
// A stylesheet is the bundle's; outside one there is no document to apply it to.
|
|
394
402
|
build.onLoad(
|
|
395
403
|
{ filter: /.*/, namespace: STYLE_NAMESPACE },
|
|
@@ -397,10 +405,8 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
397
405
|
bundling
|
|
398
406
|
? {
|
|
399
407
|
contents:
|
|
400
|
-
compiledAt(
|
|
401
|
-
|
|
402
|
-
args.path.slice(0, -STYLE.length),
|
|
403
|
-
).sheet ?? '',
|
|
408
|
+
compiledAt(served, realFileOf(args.path))
|
|
409
|
+
.sheet ?? '',
|
|
404
410
|
loader: 'css',
|
|
405
411
|
}
|
|
406
412
|
: { contents: '', loader: 'js' },
|
|
@@ -408,7 +414,7 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
408
414
|
build.onLoad(
|
|
409
415
|
{ filter: /.*/, namespace: TEXT_NAMESPACE },
|
|
410
416
|
async (args) => ({
|
|
411
|
-
contents: `export default ${JSON.stringify(await Bun.file(args.path
|
|
417
|
+
contents: `export default ${JSON.stringify(await Bun.file(realFileOf(args.path)).text())}\n`,
|
|
412
418
|
loader: 'js',
|
|
413
419
|
}),
|
|
414
420
|
)
|
|
@@ -20,6 +20,7 @@ import { DEBUG_META } from '#shared/DEBUG_META.ts'
|
|
|
20
20
|
import { decodedOrKept } from '#shared/decodedOrKept.ts'
|
|
21
21
|
import { escapeAttribute } from '#shared/escapeText.ts'
|
|
22
22
|
import { type Failed, isFailed } from '#shared/Failed.ts'
|
|
23
|
+
import { HandlerRoute } from '#shared/HandlerRoute.ts'
|
|
23
24
|
import { health } from '#shared/health.ts'
|
|
24
25
|
import { appName } from '#shared/log.ts'
|
|
25
26
|
import { compose } from '#shared/Middleware.ts'
|
|
@@ -50,6 +51,7 @@ import { CLIENT_ENTRY } from './CLIENT_BUILD.ts'
|
|
|
50
51
|
import { callerOrMint } from './callerOf.ts'
|
|
51
52
|
import { compressAnswer } from './compressAnswer.ts'
|
|
52
53
|
import { DOCUMENT_HEADERS } from './DOCUMENT_HEADERS.ts'
|
|
54
|
+
import { noteCallInRender } from './deferredBuilds.ts'
|
|
53
55
|
import { answerMcp, openApi } from './generatedSurfaces.ts'
|
|
54
56
|
import { type AppRung, failureOf, rungsOver } from './hooks.ts'
|
|
55
57
|
import { chunksOf, type Manifest } from './manifestOf.ts'
|
|
@@ -208,8 +210,8 @@ export async function answerRequest(
|
|
|
208
210
|
}
|
|
209
211
|
|
|
210
212
|
// 36.11 — A PUBLIC FILE IS ITS PATH BELOW THE DIRECTORY, so `/favicon.ico` is answered
|
|
211
|
-
// before any route is tried. The order is not load-bearing: `abide build` and
|
|
212
|
-
//
|
|
213
|
+
// before any route is tried. The order is not load-bearing: the compile `abide build` and
|
|
214
|
+
// `abide dev` share refuses a collision (36.13) over the same two trees before anything is served,
|
|
213
215
|
// so at run time there can never be a collision to resolve. 36.11 — keyed by its name on disk
|
|
214
216
|
// and asked for percent-encoded: `my photo.jpg` is `/my%20photo.jpg`.
|
|
215
217
|
const file = app.published.get(decodedOrKept(asked))
|
|
@@ -383,6 +385,10 @@ const SURFACES = new Map<
|
|
|
383
385
|
[BRIDGE_ADDRESS, (_app, request) => answerBridge(request)],
|
|
384
386
|
])
|
|
385
387
|
|
|
388
|
+
// Each handler's route, compiled once rather than per render that calls it — as its `Rpc` keeps its
|
|
389
|
+
// own; compiled again only where it was mounted at another address since.
|
|
390
|
+
const ROUTE_OF = new WeakMap<object, HandlerRoute>()
|
|
391
|
+
|
|
386
392
|
// What a call made in a page's render reports into: the seed buffer (41.13), and — where the handler
|
|
387
393
|
// is mounted — the request the browser will make for it, for the early-call script (41.20). Once per
|
|
388
394
|
// render, as the browser's `Rpc` makes it once, and the seed buffer is what says which is the once.
|
|
@@ -392,18 +398,26 @@ export function reachedInRender(
|
|
|
392
398
|
reached: unknown,
|
|
393
399
|
): void {
|
|
394
400
|
const page = currentRequestScope()?.page
|
|
395
|
-
if (page === undefined
|
|
401
|
+
if (page === undefined) return
|
|
402
|
+
noteCallInRender()
|
|
403
|
+
if (!page.seed(rpc, args, reached)) return
|
|
396
404
|
// 16.10 — the address the browser's own call is made at, stamped under the mount by
|
|
397
405
|
// `mountHandlers` — and `undefined` on a handler nothing mounted.
|
|
398
406
|
const { address, method } = rpc as Rpc<unknown, unknown>
|
|
399
407
|
if (address === undefined) return
|
|
408
|
+
let route = ROUTE_OF.get(rpc)
|
|
409
|
+
if (route?.address !== address) {
|
|
410
|
+
route = new HandlerRoute(address)
|
|
411
|
+
ROUTE_OF.set(rpc, route)
|
|
412
|
+
}
|
|
400
413
|
page.calls.push({
|
|
401
414
|
request: rpcRequest(
|
|
402
415
|
method,
|
|
403
|
-
|
|
416
|
+
route,
|
|
404
417
|
args as Record<string, unknown> | undefined,
|
|
405
418
|
),
|
|
406
419
|
reached,
|
|
420
|
+
painted: undefined,
|
|
407
421
|
})
|
|
408
422
|
}
|
|
409
423
|
|