@abide/abide 0.101.0 → 0.103.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 +26 -0
- package/bin/abide.ts +226 -164
- package/docs/REGISTRY.md +730 -0
- package/docs/RULEBOOK.md +2768 -0
- package/docs/abide.md +13127 -0
- package/package.json +6 -3
- package/scaffold/AGENTS.md +38 -0
- package/scaffold/CLAUDE.md +1 -0
- package/scaffold/src/layout.abide +18 -0
- package/src/compiler/Lowering.ts +78 -9
- package/src/compiler/ShadowMap.ts +2 -2
- package/src/compiler/TypeSession.ts +341 -71
- package/src/compiler/appMap.ts +96 -0
- package/src/compiler/argsSchemaOf.ts +1 -1
- package/src/compiler/checkApp.ts +53 -8
- package/src/compiler/checkedShadow.ts +73 -6
- package/src/compiler/classifyRead.ts +6 -4
- package/src/compiler/classifyTemplate.ts +1 -1
- package/src/compiler/compileApp.ts +598 -200
- package/src/compiler/compiledEntry.ts +48 -40
- package/src/compiler/componentGraph.ts +57 -71
- package/src/compiler/declaredExports.ts +26 -9
- package/src/compiler/emitClient.ts +142 -72
- package/src/compiler/emitComponent.ts +41 -12
- package/src/compiler/emitSource.ts +102 -13
- package/src/compiler/handlersUnder.ts +164 -76
- package/src/compiler/hooksCalled.ts +35 -0
- package/src/compiler/index.ts +7 -0
- package/src/compiler/mountsOf.ts +150 -0
- package/src/compiler/nearest.ts +14 -0
- package/src/compiler/parseAbide.ts +157 -20
- package/src/compiler/projectShadow.ts +250 -19
- package/src/compiler/reactiveTypes.ts +20 -7
- package/src/compiler/readSources.ts +15 -2
- package/src/compiler/readsIn.ts +62 -8
- package/src/compiler/renderMarkup.ts +3 -3
- package/src/compiler/scaffold.ts +37 -15
- package/src/compiler/serveLanguage.ts +101 -34
- package/src/compiler/sharedModuleWrites.ts +2 -2
- package/src/compiler/sourceMapOf.ts +193 -0
- package/src/compiler/splitImports.ts +205 -83
- package/src/compiler/tokenize.ts +14 -0
- package/src/compiler/writeDefaultProject.ts +29 -2
- package/src/server/CLIENT_BUILD.ts +11 -13
- package/src/server/DEFAULT_PORT.ts +1 -1
- package/src/server/MODULE_QUERIES.ts +27 -0
- package/src/server/TABLES.ts +7 -0
- package/src/server/VIRTUAL_DIRECTORY.ts +4 -5
- package/src/server/abideModules.ts +457 -0
- package/src/server/answerFailure.ts +16 -6
- package/src/server/answerRequest.ts +241 -107
- package/src/server/answerRpc.ts +107 -33
- package/src/server/answerSocket.ts +65 -20
- package/src/server/answerValue.ts +22 -9
- package/src/server/appDirectories.ts +1 -1
- package/src/server/appPlugins.ts +1 -1
- package/src/server/appTree.ts +238 -0
- package/src/server/argsFor.ts +2 -2
- package/src/server/assistant.ts +2 -2
- package/src/server/bridgeRelay.ts +6 -8
- package/src/server/claudeCode.ts +0 -6
- package/src/server/clientBuildOptions.ts +8 -3
- package/src/server/coerceArgs.ts +1 -1
- package/src/server/commands.ts +38 -37
- package/src/server/componentModules.d.ts +2 -1
- package/src/server/compressAnswer.ts +4 -4
- package/src/server/config.ts +25 -29
- package/src/server/configFields.ts +31 -25
- package/src/server/createApp.ts +77 -79
- package/src/server/devWorker.ts +118 -181
- package/src/server/executableName.ts +4 -0
- package/src/server/fenceServer.ts +57 -32
- package/src/server/generatedSurfaces.ts +99 -66
- package/src/server/hooks.ts +137 -52
- package/src/server/iconAccent.ts +191 -0
- package/src/server/index.ts +12 -7
- package/src/server/installSourceMaps.ts +93 -0
- package/src/server/launcherOptions.ts +33 -31
- package/src/server/launcherPage.ts +107 -64
- package/src/server/manifestOf.ts +16 -13
- package/src/server/namesUnder.ts +2 -2
- package/src/server/nativeMenu.ts +6 -6
- package/src/server/negotiateEncoding.ts +1 -1
- package/src/server/preload.ts +13 -0
- package/src/server/principal.ts +10 -5
- package/src/server/realOrAsGiven.ts +10 -0
- package/src/server/refuseAddressCollision.ts +21 -14
- package/src/server/remote.ts +49 -52
- package/src/server/render.ts +65 -94
- package/src/server/renderComponent.ts +5 -0
- package/src/server/reportHealth.ts +2 -2
- package/src/server/requestScope.ts +12 -1
- package/src/server/runBuild.ts +24 -0
- package/src/server/runCompiled.ts +5 -5
- package/src/server/runDesktop.ts +48 -28
- package/src/server/serveBuild.ts +43 -52
- package/src/server/serveDev.ts +79 -37
- package/src/server/shell.ts +7 -22
- package/src/server/sourceMappings.ts +139 -0
- package/src/server/surfaceOf.ts +20 -3
- package/src/server/testBuild.ts +1 -19
- package/src/server/withAbideSources.ts +117 -0
- package/src/shared/ADDRESSES.ts +10 -18
- package/src/shared/APP_META.ts +7 -0
- package/src/shared/CopyOptions.ts +1 -1
- package/src/shared/DEBUG_META.ts +1 -1
- package/src/shared/LAUNCHER_FILES.ts +9 -0
- package/src/shared/RESERVED_DIRECTORIES.ts +24 -0
- package/src/shared/cookies.ts +2 -2
- package/src/shared/csp.ts +1 -1
- package/src/shared/escapeText.ts +1 -1
- package/src/shared/filledPath.ts +85 -0
- package/src/shared/framing.ts +79 -6
- package/src/shared/headKeyOf.ts +2 -0
- package/src/shared/index.ts +2 -1
- package/src/shared/log.ts +27 -13
- package/src/shared/matchRoute.ts +23 -4
- package/src/{ui → shared}/menuBridge.ts +2 -2
- package/src/shared/menuItem.ts +4 -4
- package/src/shared/onMenu.ts +58 -0
- package/src/shared/reactive/Keyed.ts +2 -2
- package/src/shared/reactive/NotLanded.ts +1 -1
- package/src/shared/reactive/Reactive.ts +43 -43
- package/src/shared/reactive/argsContract.ts +4 -4
- package/src/shared/reactive/canonicalKey.ts +4 -4
- package/src/shared/reactive/chunksOfStream.ts +6 -0
- package/src/shared/reactive/index.ts +1 -1
- package/src/shared/reactive/recordWrites.ts +103 -5
- package/src/shared/reactive/structural.ts +1 -1
- package/src/shared/reactive/tracking.ts +1 -1
- package/src/shared/reactive/watch.ts +3 -3
- package/src/shared/refuse.ts +1 -1
- package/src/{ui → shared}/reportMenu.ts +8 -10
- package/src/shared/routePattern.ts +122 -48
- package/src/shared/rpc.ts +37 -8
- package/src/shared/rpcRequest.ts +27 -3
- package/src/shared/url.ts +4 -44
- package/src/shared/writePath.ts +1 -1
- package/src/ui/LAYER_NAMES.ts +1 -1
- package/src/ui/List.ts +3 -3
- package/src/ui/MOUNTING_LEVEL.ts +18 -0
- package/src/ui/Mismatch.ts +1 -1
- package/src/ui/RawRun.ts +1 -1
- package/src/ui/Router.ts +37 -36
- package/src/ui/Scope.ts +1 -1
- package/src/ui/TryRegion.ts +2 -2
- package/src/ui/applyHead.ts +33 -0
- package/src/ui/index.ts +1 -1
- package/src/ui/moveNode.ts +1 -1
- package/src/ui/reconcile.ts +2 -2
- package/src/ui/routing.ts +2 -2
- package/src/ui/setAttribute.ts +1 -1
- package/src/ui/setText.ts +1 -1
- package/src/ui/socketWire.ts +9 -7
- package/src/ui/wire.ts +1 -1
- package/scaffold/src/server/app.ts +0 -6
- package/scaffold/src/shared/.gitkeep +0 -0
- package/scaffold/src/ui/app.html +0 -8
- package/src/server/componentImports.ts +0 -56
- /package/scaffold/src/{ui/pages/page.abide → index.abide} +0 -0
- /package/src/{compiler → shared}/SPECIFIERS.ts +0 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
// 34.14, 34.16, 32.17 — EVERY `.abide` IS SERVED AT ITS OWN PATH, compiled, by one plugin the runtime
|
|
2
|
+
// and the bundler share. Served there, a compiled module's relative, `#` and bare specifiers resolve
|
|
3
|
+
// from the file that wrote them through bun's own resolver — from the app, from a dependency under
|
|
4
|
+
// `node_modules`, or from a linked package outside the root alike — so nothing about a file's
|
|
5
|
+
// compile depends on where the build would have put it.
|
|
6
|
+
//
|
|
7
|
+
// WHICH FACE AN IMPORT GETS IS SPELLED ON ITS SPECIFIER (`MODULE_QUERIES`), because bun hands a plugin
|
|
8
|
+
// no import attributes: a compiled module asks for `?component`, a browser module for its own
|
|
9
|
+
// `?style`, a text import for `?text`. A `.abide` named bare is a module abide did not compile
|
|
10
|
+
// asking — on a server that is the bound face 34.16 states, and in a browser the component module
|
|
11
|
+
// itself.
|
|
12
|
+
//
|
|
13
|
+
// THE TABLES — the route, error and handler tables, and in a browser the router's table and the
|
|
14
|
+
// entry — are the build's own modules, served from `__abide` below the root, where no app writes.
|
|
15
|
+
//
|
|
16
|
+
// THE RUNTIME'S REGISTRATION IS THE PROCESS'S: a `.abide` filter is answered by the first plugin
|
|
17
|
+
// that registered one, so a second app booted in the same process — a test's — adds its compilation
|
|
18
|
+
// to this one rather than registering a plugin nobody asks.
|
|
19
|
+
|
|
20
|
+
import { basename, dirname, resolve } from 'node:path' // bun exposes no path helpers of its own
|
|
21
|
+
import { SERVER_SPECIFIER } from '#shared/SPECIFIERS.ts'
|
|
22
|
+
import type { ServerTable } from './answerRequest.ts'
|
|
23
|
+
import type { MountRow } from './answerRpc.ts'
|
|
24
|
+
import { installSourceMaps, type SourceMaps } from './installSourceMaps.ts'
|
|
25
|
+
import {
|
|
26
|
+
CLIENT_QUERY,
|
|
27
|
+
COMPONENT_QUERY,
|
|
28
|
+
HANDLER_FACE,
|
|
29
|
+
HANDLER_SUFFIX,
|
|
30
|
+
STYLE_QUERY,
|
|
31
|
+
TEXT_QUERY,
|
|
32
|
+
} from './MODULE_QUERIES.ts'
|
|
33
|
+
import { realOrAsGiven } from './realOrAsGiven.ts'
|
|
34
|
+
import { SERVER_TABLE } from './TABLES.ts'
|
|
35
|
+
import { VIRTUAL_DIRECTORY } from './VIRTUAL_DIRECTORY.ts'
|
|
36
|
+
|
|
37
|
+
/** One `.abide`, compiled: its server module, its browser module, its stylesheet and their maps. */
|
|
38
|
+
export type CompiledFile = {
|
|
39
|
+
server: string
|
|
40
|
+
/** `''` where the file has nothing to hydrate and nothing imports it by name. */
|
|
41
|
+
client: string
|
|
42
|
+
sheet: string | undefined
|
|
43
|
+
/** 38.97 — each module's `mappings` into the `.abide`, or `undefined` where it places nothing. */
|
|
44
|
+
serverMappings: string | undefined
|
|
45
|
+
clientMappings: string | undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* What a compile hands the plugin, and what `abide build` keeps and `abide dev` sends its worker.
|
|
50
|
+
*
|
|
51
|
+
* Every path RELATIVE TO THE ROOT, so a build moved to another directory or another machine is
|
|
52
|
+
* served from wherever its root is then.
|
|
53
|
+
*/
|
|
54
|
+
export type Compilation = {
|
|
55
|
+
/** Every `.abide` compiled, by its path below the root. */
|
|
56
|
+
files: Record<string, CompiledFile>
|
|
57
|
+
/**
|
|
58
|
+
* Every handler file, by its path below the root: the route its hooks are filed at (37.1), and
|
|
59
|
+
* the module a browser is handed in its place (16.74), `''` where the browser is handed none.
|
|
60
|
+
*/
|
|
61
|
+
handlers: Record<string, HandlerFace>
|
|
62
|
+
/** The build's own modules, by name below `__abide`. */
|
|
63
|
+
tables: Record<string, string>
|
|
64
|
+
/**
|
|
65
|
+
* 23.19 — every directory a file mounts, by its path below the root, to the position its files
|
|
66
|
+
* take: what the browser's fence reads a mounted file's place by (16.29).
|
|
67
|
+
*/
|
|
68
|
+
mounted: Record<string, string>
|
|
69
|
+
}
|
|
70
|
+
|
|
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
|
+
const RELATIVE_OR_SUBPATH = /^(?:\.\.?\/|#)/
|
|
85
|
+
|
|
86
|
+
/** One handler file, as `Compilation.handlers` holds it. */
|
|
87
|
+
type HandlerFace = { scope: string; client: string }
|
|
88
|
+
|
|
89
|
+
/** The plugin serving `compilation`'s files and tables, for one bundle. */
|
|
90
|
+
export function abideModules(
|
|
91
|
+
root: string,
|
|
92
|
+
compilation: Compilation,
|
|
93
|
+
): Bun.BunPlugin {
|
|
94
|
+
const held: Held = {
|
|
95
|
+
files: new Map(),
|
|
96
|
+
handlers: new Map(),
|
|
97
|
+
tables: new Map(),
|
|
98
|
+
}
|
|
99
|
+
addCompilation(realOrAsGiven(root), compilation, held)
|
|
100
|
+
return modulesPlugin(held)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* A compilation as the plugin keys it, and answers from: a compiled file, a handler file or a table
|
|
105
|
+
* by absolute path.
|
|
106
|
+
*/
|
|
107
|
+
type Held = {
|
|
108
|
+
files: Map<string, CompiledFile>
|
|
109
|
+
handlers: Map<string, HandlerFace>
|
|
110
|
+
tables: Map<string, string>
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const RUNTIME: Held = {
|
|
114
|
+
files: new Map(),
|
|
115
|
+
handlers: new Map(),
|
|
116
|
+
tables: new Map(),
|
|
117
|
+
}
|
|
118
|
+
let registered = false
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Serves `compilation` to this process's imports from now on, and places a stack frame in each of
|
|
122
|
+
* its files (38.97).
|
|
123
|
+
*/
|
|
124
|
+
export function serveCompilation(root: string, compilation: Compilation): void {
|
|
125
|
+
const real = realOrAsGiven(root)
|
|
126
|
+
addCompilation(real, compilation, RUNTIME)
|
|
127
|
+
installSourceMaps(serverMapsOf(compilation, real), real)
|
|
128
|
+
if (registered) return
|
|
129
|
+
registered = true
|
|
130
|
+
Bun.plugin(modulesPlugin(RUNTIME))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** What the server's table exports: its routes and error pages, server blocks and handlers. */
|
|
134
|
+
export type BuiltTable = ServerTable & {
|
|
135
|
+
/** 37.1 — every module a `<script server>` runs in, imported before the app boots. */
|
|
136
|
+
servers: (() => Promise<unknown>)[]
|
|
137
|
+
/** 16.2 — every handler, each row with its module's loader. */
|
|
138
|
+
handlers: MountRow[]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The server's table of the app at `root`, out of the compilation this process serves. */
|
|
142
|
+
export function servedTable(root: string): Promise<BuiltTable> {
|
|
143
|
+
return import(
|
|
144
|
+
Bun.pathToFileURL(tablePath(root, SERVER_TABLE)).href
|
|
145
|
+
) as Promise<BuiltTable>
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The path a table is imported at: `name` below `__abide` under `root`. */
|
|
149
|
+
export function tablePath(root: string, name: string): string {
|
|
150
|
+
return `${realOrAsGiven(root)}/${VIRTUAL_DIRECTORY}/${name}`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Each server module's map, by the `.abide` it is served at, below `root`. */
|
|
154
|
+
export function serverMapsOf(
|
|
155
|
+
compilation: Compilation,
|
|
156
|
+
root: string,
|
|
157
|
+
): SourceMaps {
|
|
158
|
+
const maps: SourceMaps = {}
|
|
159
|
+
for (const [relative, compiled] of Object.entries(compilation.files))
|
|
160
|
+
if (compiled.serverMappings !== undefined)
|
|
161
|
+
maps[relative] = [resolve(root, relative), compiled.serverMappings]
|
|
162
|
+
return maps
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function addCompilation(
|
|
166
|
+
root: string,
|
|
167
|
+
compilation: Compilation,
|
|
168
|
+
held: Held,
|
|
169
|
+
): void {
|
|
170
|
+
// RESOLVED, not joined: a dependency's file is keyed above the root (`../node_modules/…`).
|
|
171
|
+
for (const [relative, compiled] of Object.entries(compilation.files))
|
|
172
|
+
held.files.set(resolve(root, relative), compiled)
|
|
173
|
+
for (const [relative, face] of Object.entries(compilation.handlers))
|
|
174
|
+
held.handlers.set(resolve(root, relative), face)
|
|
175
|
+
for (const [name, text] of Object.entries(compilation.tables))
|
|
176
|
+
held.tables.set(`${root}/${VIRTUAL_DIRECTORY}/${name}`, text)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// `Bun.resolveSync` asks EVERY abide plugin's `onResolve` again — this one's, and the runtime's, which
|
|
180
|
+
// is registered for the whole process and so answers inside a bundle too. One flag for all of them
|
|
181
|
+
// is what lets the inner ask fall through to bun's resolver rather than be answered again from a
|
|
182
|
+
// directory read as a file.
|
|
183
|
+
let resolving = false
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Where `specifier` resolves from the directory `from`, as bun's own resolver answers — never as a
|
|
187
|
+
* face this plugin serves.
|
|
188
|
+
*
|
|
189
|
+
* The compiler asks where a `.abide` IS; in a process serving a compilation, the plugin would
|
|
190
|
+
* answer with the face an import of it gets instead.
|
|
191
|
+
*/
|
|
192
|
+
export function resolvedFile(specifier: string, from: string): string {
|
|
193
|
+
resolving = true
|
|
194
|
+
try {
|
|
195
|
+
return Bun.resolveSync(specifier, from)
|
|
196
|
+
} finally {
|
|
197
|
+
resolving = false
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
202
|
+
return {
|
|
203
|
+
name: 'abide-modules',
|
|
204
|
+
setup(build) {
|
|
205
|
+
// 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. A
|
|
207
|
+
// browser module the runtime was asked for (`CLIENT_QUERY`) is the one exception, and
|
|
208
|
+
// what it imports is in its lane.
|
|
209
|
+
const bundling: boolean = 'config' in build
|
|
210
|
+
const browser = bundling && build.config.target === 'browser'
|
|
211
|
+
const inBrowser = (importer: string): boolean =>
|
|
212
|
+
browser || pathOf(importer).endsWith(CLIENT)
|
|
213
|
+
// A BUNDLE'S COMPONENT MODULE IS IN A NAMESPACE OF ITS OWN: the bundler refuses a
|
|
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 }
|
|
222
|
+
const resolved = (specifier: string, importer: string): string =>
|
|
223
|
+
resolvedFile(specifier, dirname(pathOf(importer)))
|
|
224
|
+
// A filter off the query's own constant, so renaming one cannot silently stop a match.
|
|
225
|
+
const abideWith = (query: string) =>
|
|
226
|
+
new RegExp(`\\.abide${RegExp.escape(query)}$`)
|
|
227
|
+
|
|
228
|
+
build.onResolve(
|
|
229
|
+
{ filter: new RegExp(`${RegExp.escape(TEXT_QUERY)}$`) },
|
|
230
|
+
(args) => {
|
|
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
|
+
},
|
|
240
|
+
)
|
|
241
|
+
build.onResolve({ filter: abideWith(STYLE_QUERY) }, (args) => {
|
|
242
|
+
if (resolving) return undefined
|
|
243
|
+
const specifier = args.path.slice(0, -STYLE_QUERY.length)
|
|
244
|
+
return {
|
|
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
|
+
})
|
|
279
|
+
build.onResolve(
|
|
280
|
+
{ filter: new RegExp(`/${VIRTUAL_DIRECTORY}/[^/]+\\.ts$`) },
|
|
281
|
+
(args) => {
|
|
282
|
+
if (resolving) return undefined
|
|
283
|
+
const path = resolve(
|
|
284
|
+
dirname(pathOf(args.importer)),
|
|
285
|
+
args.path,
|
|
286
|
+
)
|
|
287
|
+
return served.tables.get(path) === undefined
|
|
288
|
+
? undefined
|
|
289
|
+
: { path, namespace: 'abide-table' }
|
|
290
|
+
},
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
// A HANDLER FILE IS FOUND BY WHERE ITS IMPORT RESOLVES, however the import spells it, and
|
|
294
|
+
// only from the app's own modules: a package's never names one, and asking bun again for
|
|
295
|
+
// each of a package's relative imports is a resolve per module of the bundle. IN A BUNDLE
|
|
296
|
+
// THE FILTER IS THE HANDLER FILES' NAMES: a hook matching every relative import costs the
|
|
297
|
+
// bundle abide's own `sideEffects`, and the barrel's rest — the validator, the csp — moved
|
|
298
|
+
// into the chunk every page loads (38.94). The runtime is told of each compilation after
|
|
299
|
+
// it registered, so it matches every relative or `#` import and asks.
|
|
300
|
+
// Each handler file's name without `.ts` — what an import of one ends in.
|
|
301
|
+
let stems: string[] | undefined
|
|
302
|
+
if (bundling) {
|
|
303
|
+
const names = new Set<string>()
|
|
304
|
+
for (const path of served.handlers.keys())
|
|
305
|
+
names.add(basename(path, '.ts'))
|
|
306
|
+
stems = [...names]
|
|
307
|
+
}
|
|
308
|
+
const handlerImport =
|
|
309
|
+
stems === undefined
|
|
310
|
+
? RELATIVE_OR_SUBPATH
|
|
311
|
+
: new RegExp(
|
|
312
|
+
`(?:^|/)(?:${stems.map(RegExp.escape).join('|')})(?:\\.ts)?$`,
|
|
313
|
+
)
|
|
314
|
+
if (stems?.length !== 0)
|
|
315
|
+
build.onResolve({ filter: handlerImport }, (args) => {
|
|
316
|
+
if (resolving || !RELATIVE_OR_SUBPATH.test(args.path))
|
|
317
|
+
return undefined
|
|
318
|
+
const importer = pathOf(args.importer)
|
|
319
|
+
if (importer.includes('/node_modules/')) return undefined
|
|
320
|
+
let path: string
|
|
321
|
+
try {
|
|
322
|
+
path = resolved(args.path, args.importer)
|
|
323
|
+
} catch {
|
|
324
|
+
return undefined
|
|
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
|
+
})
|
|
335
|
+
|
|
336
|
+
// A RELATIVE SPECIFIER A FACE IMPORTS AT RUN TIME is resolved from the file the face
|
|
337
|
+
// stands for: bun's runtime reads one from an importer in a namespace as no path at
|
|
338
|
+
// all. A bare one it resolves itself. Registered after every face, so a `.abide` is
|
|
339
|
+
// still answered as one — and NOT IN A BUNDLE, which needs it for no face it serves,
|
|
340
|
+
// and where a hook matching every relative import in a package costs the bundle that
|
|
341
|
+
// package's `sideEffects`: a two-page app grew by 12 KB, the validator one page never
|
|
342
|
+
// calls moved into the chunk both pages load.
|
|
343
|
+
if (!bundling)
|
|
344
|
+
build.onResolve({ filter: /^\.\.?\// }, (args) =>
|
|
345
|
+
resolving || pathOf(args.importer) === args.importer
|
|
346
|
+
? undefined
|
|
347
|
+
: { path: resolved(args.path, args.importer) },
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
build.onLoad(
|
|
351
|
+
{ filter: /\.abide$/, namespace: componentNamespace },
|
|
352
|
+
(args) => {
|
|
353
|
+
const compiled = compiledAt(served, args.path)
|
|
354
|
+
return {
|
|
355
|
+
contents: browser ? compiled.client : compiled.server,
|
|
356
|
+
loader: 'ts',
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
)
|
|
360
|
+
// 16.74, 37.1 — A HANDLER FILE: the browser's is its wrapper, and the server's is the file
|
|
361
|
+
// as written, between the two calls that file its hooks at its route — on its first
|
|
362
|
+
// line, so every line of the file stays where its frames name it. ITS FACE is its path
|
|
363
|
+
// with a suffix, in bun's own namespace — so what it imports, relative, `#` or bare,
|
|
364
|
+
// resolves from its directory as the file's own imports do. A namespace of abide's own
|
|
365
|
+
// had bun resolve a bare one from the working directory: a second abide, run from `/`.
|
|
366
|
+
build.onLoad({ filter: HANDLER_FACE }, async (args) => {
|
|
367
|
+
const path = args.path.slice(0, -HANDLER_SUFFIX.length)
|
|
368
|
+
const face = served.handlers.get(path) as HandlerFace
|
|
369
|
+
if (browser) return { contents: face.client, loader: 'js' }
|
|
370
|
+
const text = await Bun.file(path).text()
|
|
371
|
+
return {
|
|
372
|
+
contents: `import { fileRoute as __fileRoute } from '${SERVER_SPECIFIER}';__fileRoute(${JSON.stringify(face.scope)});${text}\n;__fileRoute(undefined)\n`,
|
|
373
|
+
loader: 'ts',
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
build.onLoad(
|
|
377
|
+
{ filter: /.*/, namespace: 'abide-client' },
|
|
378
|
+
(args) => ({
|
|
379
|
+
contents: compiledAt(
|
|
380
|
+
served,
|
|
381
|
+
args.path.slice(0, -CLIENT.length),
|
|
382
|
+
).client,
|
|
383
|
+
loader: 'ts',
|
|
384
|
+
}),
|
|
385
|
+
)
|
|
386
|
+
build.onLoad({ filter: /.*/, namespace: 'abide-bound' }, (args) => {
|
|
387
|
+
const path = args.path.slice(0, -BOUND.length)
|
|
388
|
+
return {
|
|
389
|
+
contents: boundModule(path),
|
|
390
|
+
loader: 'js',
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
// A stylesheet is the bundle's; outside one there is no document to apply it to.
|
|
394
|
+
build.onLoad(
|
|
395
|
+
{ filter: /.*/, namespace: STYLE_NAMESPACE },
|
|
396
|
+
(args) =>
|
|
397
|
+
bundling
|
|
398
|
+
? {
|
|
399
|
+
contents:
|
|
400
|
+
compiledAt(
|
|
401
|
+
served,
|
|
402
|
+
args.path.slice(0, -STYLE.length),
|
|
403
|
+
).sheet ?? '',
|
|
404
|
+
loader: 'css',
|
|
405
|
+
}
|
|
406
|
+
: { contents: '', loader: 'js' },
|
|
407
|
+
)
|
|
408
|
+
build.onLoad(
|
|
409
|
+
{ filter: /.*/, namespace: TEXT_NAMESPACE },
|
|
410
|
+
async (args) => ({
|
|
411
|
+
contents: `export default ${JSON.stringify(await Bun.file(args.path.slice(0, -TEXT.length)).text())}\n`,
|
|
412
|
+
loader: 'js',
|
|
413
|
+
}),
|
|
414
|
+
)
|
|
415
|
+
build.onLoad(
|
|
416
|
+
{ filter: /.*/, namespace: 'abide-table' },
|
|
417
|
+
(args) => ({
|
|
418
|
+
contents: served.tables.get(args.path) ?? '',
|
|
419
|
+
loader: 'ts',
|
|
420
|
+
}),
|
|
421
|
+
)
|
|
422
|
+
},
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// AN IMPORTER IN ONE OF THIS PLUGIN'S NAMESPACES CARRIES IT in front of its path at run time — with
|
|
427
|
+
// a `/` in front of that again, where the bundler hands the path alone.
|
|
428
|
+
function pathOf(importer: string): string {
|
|
429
|
+
return importer.replace(/^\/?abide-[a-z]+:/, '')
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// THE PATH BUN NAMES A FILE BY HAS ITS LINKS RESOLVED, and so has the root a compilation is served
|
|
433
|
+
// under (`serveCompilation`).
|
|
434
|
+
function compiledAt(served: Held, path: string): CompiledFile {
|
|
435
|
+
const compiled = served.files.get(path)
|
|
436
|
+
if (compiled === undefined)
|
|
437
|
+
throw new Error(
|
|
438
|
+
`abide: ${path} is imported, and no compile of this app reached it.`,
|
|
439
|
+
)
|
|
440
|
+
return compiled
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// 34.16 — BOUND, NOT RAW. The compiled module is a function of accessors answering its markup (33.4);
|
|
444
|
+
// what an app's own module calls is a function of plain props answering a `Component` (35.4), which
|
|
445
|
+
// `render` invokes with nothing. The component is the module's one export (34.17).
|
|
446
|
+
function boundModule(path: string): string {
|
|
447
|
+
return `import component from ${JSON.stringify(path)}
|
|
448
|
+
export default (props = {}) => {
|
|
449
|
+
const accessors = {}
|
|
450
|
+
for (const key in props) {
|
|
451
|
+
const value = props[key]
|
|
452
|
+
accessors[key] = () => value
|
|
453
|
+
}
|
|
454
|
+
return () => component(accessors)
|
|
455
|
+
}
|
|
456
|
+
`
|
|
457
|
+
}
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
import { type Failed, problemOf } from '#shared/Failed.ts'
|
|
7
7
|
import { RENDER_LOG } from '#shared/log.ts'
|
|
8
8
|
import { coversPrefix, partsOf } from '#shared/matchRoute.ts'
|
|
9
|
+
import { coversRoute, parseRoutePattern } from '#shared/routePattern.ts'
|
|
9
10
|
import type { AppSources } from './answerRequest.ts'
|
|
10
11
|
import { DOCUMENT_HEADERS } from './DOCUMENT_HEADERS.ts'
|
|
11
|
-
import { boundPage,
|
|
12
|
-
import { beginRender } from './requestScope.ts'
|
|
12
|
+
import { boundPage, renderDocument } from './render.ts'
|
|
13
|
+
import { beginRender, currentRequestScope } from './requestScope.ts'
|
|
13
14
|
|
|
14
15
|
/** What a page's failure is painted from: the error pages, and the document they go into. */
|
|
15
16
|
export type ErrorPages = Pick<AppSources, 'errors' | 'errorDocument'>
|
|
@@ -47,13 +48,22 @@ export function answered(failure: Failed<string, unknown>): Response {
|
|
|
47
48
|
/**
|
|
48
49
|
* The error pages for `pathname`, nearest first.
|
|
49
50
|
*
|
|
50
|
-
* 35.2 —
|
|
51
|
-
*
|
|
51
|
+
* 35.2 — AS A LAYOUT IS: over the route the request matched, every one whose route covers it, as
|
|
52
|
+
* a layout's and an `onError`'s does (35.1, 37.5). An address no route matched has only its path,
|
|
53
|
+
* and every one whose directory is a prefix of it answers, each segment meaning what it means in a
|
|
54
|
+
* route.
|
|
52
55
|
*/
|
|
53
56
|
function errorsFor(errors: RankedError[], pathname: string): RankedError[] {
|
|
57
|
+
const route = currentRequestScope()?.route
|
|
58
|
+
const found: RankedError[] = []
|
|
59
|
+
if (route !== undefined) {
|
|
60
|
+
const segments = parseRoutePattern(route)
|
|
61
|
+
for (const error of errors)
|
|
62
|
+
if (coversRoute(error.segments, segments)) found.push(error)
|
|
63
|
+
return found
|
|
64
|
+
}
|
|
54
65
|
// 15.16's address has no parts to be nearest to, so the root's page paints it.
|
|
55
66
|
const parts = partsOf(pathname) ?? []
|
|
56
|
-
const found: RankedError[] = []
|
|
57
67
|
for (const error of errors)
|
|
58
68
|
if (coversPrefix(error.segments, parts)) found.push(error)
|
|
59
69
|
return found
|
|
@@ -77,7 +87,7 @@ export async function answerFailure(
|
|
|
77
87
|
try {
|
|
78
88
|
const bound = boundPage(error, { failure: () => failure })
|
|
79
89
|
beginRender(undefined)
|
|
80
|
-
const document = await
|
|
90
|
+
const document = await renderDocument(bound, app.errorDocument)
|
|
81
91
|
return new Response(document, { status, headers })
|
|
82
92
|
} catch (thrown) {
|
|
83
93
|
RENDER_LOG.warning(
|