@abide/abide 0.101.0 → 0.102.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 +16 -0
- package/bin/abide.ts +223 -164
- package/docs/REGISTRY.md +726 -0
- package/docs/RULEBOOK.md +2753 -0
- package/docs/abide.md +12878 -0
- package/package.json +5 -3
- package/scaffold/AGENTS.md +31 -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 +262 -67
- package/src/compiler/argsSchemaOf.ts +1 -1
- package/src/compiler/checkApp.ts +43 -15
- package/src/compiler/checkedShadow.ts +39 -5
- package/src/compiler/classifyRead.ts +6 -4
- package/src/compiler/classifyTemplate.ts +1 -1
- package/src/compiler/compileApp.ts +433 -193
- package/src/compiler/compiledEntry.ts +43 -39
- package/src/compiler/componentGraph.ts +58 -71
- package/src/compiler/declaredExports.ts +24 -7
- package/src/compiler/emitClient.ts +141 -71
- package/src/compiler/emitComponent.ts +37 -8
- package/src/compiler/emitSource.ts +106 -13
- package/src/compiler/handlersUnder.ts +106 -59
- package/src/compiler/index.ts +7 -0
- package/src/compiler/mountsOf.ts +138 -0
- package/src/compiler/nearest.ts +14 -0
- package/src/compiler/parseAbide.ts +157 -20
- package/src/compiler/projectShadow.ts +213 -7
- package/src/compiler/reactiveTypes.ts +20 -7
- package/src/compiler/readSources.ts +15 -2
- package/src/compiler/readsIn.ts +61 -7
- package/src/compiler/renderMarkup.ts +3 -3
- package/src/compiler/scaffold.ts +30 -11
- package/src/compiler/serveLanguage.ts +125 -34
- package/src/compiler/sourceMapOf.ts +193 -0
- package/src/compiler/splitImports.ts +205 -83
- package/src/compiler/syncTypes.ts +32 -0
- package/src/compiler/tokenize.ts +14 -0
- package/src/compiler/writeDefaultProject.ts +1 -1
- package/src/compiler/writeTypes.ts +82 -0
- package/src/server/CLIENT_BUILD.ts +11 -13
- package/src/server/DEFAULT_PORT.ts +1 -1
- package/src/server/LAUNCHER_FILES.ts +9 -0
- package/src/server/MODULE_QUERIES.ts +18 -0
- package/src/server/TABLES.ts +7 -0
- package/src/server/VIRTUAL_DIRECTORY.ts +4 -5
- package/src/server/abideModules.ts +391 -0
- package/src/server/answerFailure.ts +16 -6
- package/src/server/answerRequest.ts +235 -106
- package/src/server/answerRpc.ts +102 -30
- 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 +179 -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 +37 -37
- package/src/server/componentModules.d.ts +3 -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 +59 -73
- package/src/server/devWorker.ts +114 -181
- package/src/server/executableName.ts +4 -0
- package/src/server/fenceServer.ts +37 -35
- package/src/server/generatedSurfaces.ts +99 -66
- package/src/server/hooks.ts +124 -52
- package/src/server/iconAccent.ts +191 -0
- package/src/server/index.ts +11 -7
- package/src/server/installSourceMaps.ts +88 -0
- package/src/server/launcherOptions.ts +34 -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 +5 -5
- package/src/server/negotiateEncoding.ts +1 -1
- 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 +52 -27
- package/src/server/serveBuild.ts +43 -52
- package/src/server/serveDev.ts +73 -37
- package/src/server/shell.ts +7 -22
- package/src/server/sourceMappings.ts +139 -0
- package/src/server/surfaceOf.ts +18 -3
- package/src/server/testBuild.ts +1 -19
- package/src/server/withAbideSources.ts +117 -0
- package/src/shared/ADDRESSES.ts +7 -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/RESERVED_DIRECTORIES.ts +15 -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 +1 -1
- package/src/shared/log.ts +27 -13
- package/src/shared/matchRoute.ts +23 -4
- package/src/shared/menuItem.ts +3 -3
- 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/shared/routePattern.ts +82 -46
- 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 +38 -31
- 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/menuBridge.ts +1 -1
- package/src/ui/moveNode.ts +1 -1
- package/src/ui/reconcile.ts +2 -2
- package/src/ui/reportMenu.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 → page.abide} +0 -0
package/src/server/devWorker.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// 38.
|
|
1
|
+
// 38.14 — THE APP, IN A CHILD PROCESS. Everything an app's own code touches is in here: the modules
|
|
2
2
|
// it compiled to, the client bundle, and the render. The listener is the parent (`serveDev`), so
|
|
3
3
|
// replacing this process is how a reload happens and nothing an app left behind — a module in the
|
|
4
4
|
// registry, a timer, a `share`, a file bun once failed to find — survives one.
|
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
// THE COMPILER IS NOT THE APP, and it stays outside: `serveDev` compiles and sends the modules
|
|
7
7
|
// in, so the compiler is loaded once for every reload — `serveDev` says what that saves.
|
|
8
8
|
//
|
|
9
|
-
// NOTHING IS WRITTEN TO DISK (38.
|
|
10
|
-
// plugin
|
|
11
|
-
// bundle a browser fetches
|
|
12
|
-
//
|
|
9
|
+
// NOTHING IS WRITTEN TO DISK (38.16). The compilation arrives as text and is served through the one
|
|
10
|
+
// plugin `abide start` serves a build's through (`abideModules`): at runtime for the server modules
|
|
11
|
+
// this worker imports, and in the bundle a browser fetches — each `.abide` at its own path, so a
|
|
12
|
+
// module's imports resolve from the source that wrote them in either.
|
|
13
13
|
//
|
|
14
|
-
// A REQUEST ARRIVES AS A MESSAGE, and its answer leaves as a head and then a chunk per pull (38.
|
|
15
|
-
// 38.
|
|
14
|
+
// A REQUEST ARRIVES AS A MESSAGE, and its answer leaves as a head and then a chunk per pull (38.35,
|
|
15
|
+
// 38.36): the listener asks for the next chunk when its connection has taken the last, so a stream
|
|
16
16
|
// is read no faster than the browser reads it. A request BODY is still collected, which is what
|
|
17
|
-
// lets the listener re-ask a request of the replacement (38.
|
|
17
|
+
// lets the listener re-ask a request of the replacement (38.24).
|
|
18
18
|
//
|
|
19
19
|
// 18.1, 18.3 — AND SO DOES A SOCKET. The listener holds the browser's web socket, which has to
|
|
20
20
|
// outlive this worker; what crosses is each frame, tagged with the connection it came over, and
|
|
@@ -23,13 +23,16 @@
|
|
|
23
23
|
import { CLIENT_ADDRESS, DEV_ADDRESS } from '#shared/ADDRESSES.ts'
|
|
24
24
|
import { escapeText } from '#shared/escapeText.ts'
|
|
25
25
|
import {
|
|
26
|
-
|
|
27
|
-
type
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
abideModules,
|
|
27
|
+
type Compilation,
|
|
28
|
+
serveCompilation,
|
|
29
|
+
servedTable,
|
|
30
|
+
} from './abideModules.ts'
|
|
31
|
+
import { type AppSources, appSources } from './answerRequest.ts'
|
|
30
32
|
import type { Mux, MuxConnection } from './answerSocket.ts'
|
|
31
33
|
import { appPlugins } from './appPlugins.ts'
|
|
32
|
-
import {
|
|
34
|
+
import { publishedUnder } from './appTree.ts'
|
|
35
|
+
import { CLIENT_ENTRY } from './CLIENT_BUILD.ts'
|
|
33
36
|
import {
|
|
34
37
|
clientBuildOptions,
|
|
35
38
|
stylesheetUnderMount,
|
|
@@ -40,26 +43,28 @@ import { fenceServer } from './fenceServer.ts'
|
|
|
40
43
|
import { type Manifest, manifestOf, NO_MANIFEST } from './manifestOf.ts'
|
|
41
44
|
import { admitsMux } from './muxUpgrade.ts'
|
|
42
45
|
import { refuseAddressCollision } from './refuseAddressCollision.ts'
|
|
43
|
-
import { shellUnder } from './render.ts'
|
|
44
46
|
import { LISTENING } from './requestScope.ts'
|
|
45
|
-
import {
|
|
46
|
-
import { VIRTUAL_DIRECTORY } from './VIRTUAL_DIRECTORY.ts'
|
|
47
|
+
import { withAbideSources } from './withAbideSources.ts'
|
|
47
48
|
|
|
48
49
|
// The reload client, which the worker serves and every document it answers loads.
|
|
49
50
|
const RELOAD_CLIENT_ADDRESS = `${DEV_ADDRESS}/client.js`
|
|
50
51
|
|
|
51
52
|
// The bundle this worker answers `/__abide/client/…` out of, the type each name is served as, and
|
|
52
|
-
// the chunks each route module needs (38.
|
|
53
|
+
// the chunks each route module needs (38.28).
|
|
53
54
|
type Bundle = {
|
|
54
|
-
// A stylesheet is held as its text, rewritten (`stylesheetUnderMount`);
|
|
55
|
-
|
|
55
|
+
// A stylesheet is held as its text, rewritten (`stylesheetUnderMount`); a source map as the
|
|
56
|
+
// rewrite of it, taken on the first ask (`withAbideSources`); the rest as built.
|
|
57
|
+
files: Map<
|
|
58
|
+
string,
|
|
59
|
+
{ bytes: ArrayBuffer | string | (() => Promise<string>); type: string }
|
|
60
|
+
>
|
|
56
61
|
manifest: Manifest
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
/**
|
|
60
65
|
* A message from the listener to this worker.
|
|
61
66
|
*
|
|
62
|
-
* 38.
|
|
67
|
+
* 38.14 — WHAT CROSSES BETWEEN THE LISTENER AND THIS WORKER, one union per direction. In: the app
|
|
63
68
|
* compiled (and what stopped it compiling, where something did), a request, a pull or cancel of
|
|
64
69
|
* its body, and each browser web socket's frames for the mux.
|
|
65
70
|
*/
|
|
@@ -67,7 +72,9 @@ export type ToWorker =
|
|
|
67
72
|
| {
|
|
68
73
|
kind: 'start'
|
|
69
74
|
root: string
|
|
70
|
-
|
|
75
|
+
compilation: Compilation
|
|
76
|
+
/** Every public file, to its address, as the compile's walk found them. */
|
|
77
|
+
published: Map<string, string>
|
|
71
78
|
failure: string
|
|
72
79
|
}
|
|
73
80
|
| ({ kind: 'request' } & Ask)
|
|
@@ -120,12 +127,12 @@ export type Ask = {
|
|
|
120
127
|
/**
|
|
121
128
|
* Sends a message to the listener.
|
|
122
129
|
*
|
|
123
|
-
* 38.
|
|
130
|
+
* 38.14 — over bun's IPC: this is its child process (`serveDev` says why a process).
|
|
124
131
|
*/
|
|
125
132
|
function toListener(message: FromWorker): void {
|
|
126
133
|
process.send?.(message)
|
|
127
134
|
}
|
|
128
|
-
// 38.
|
|
135
|
+
// 38.96 — EACH REQUEST'S SIGNAL, by request id, until its answer ends: the listener holds the
|
|
129
136
|
// connection, so a hang-up reaches this worker as a `cancel` rather than through bun.
|
|
130
137
|
const HANG_UPS = new Map<number, AbortController>()
|
|
131
138
|
|
|
@@ -157,9 +164,9 @@ let app: BootedApp | undefined
|
|
|
157
164
|
// 18.1 — the mux over this worker's handlers, and each connection the listener holds.
|
|
158
165
|
let mux: Mux | undefined
|
|
159
166
|
// What the mux carries, which `admitsMux` reads: an app with no handler has no mux to open.
|
|
160
|
-
let carried:
|
|
167
|
+
let carried: AppSources | undefined
|
|
161
168
|
const connections = new Map<number, MuxConnection>()
|
|
162
|
-
// What stopped the app compiling, kept rather than thrown: 38.4 keeps the server up and 38.
|
|
169
|
+
// What stopped the app compiling, kept rather than thrown: 38.4 keeps the server up and 38.17
|
|
163
170
|
// keeps it up through a bundle that failed, so the failure is something to SERVE.
|
|
164
171
|
let failure = ''
|
|
165
172
|
|
|
@@ -196,7 +203,7 @@ async function receive(message: ToWorker): Promise<void> {
|
|
|
196
203
|
})
|
|
197
204
|
},
|
|
198
205
|
// NOTHING TO CLOSE: the listener holds the browser's socket, and it outlives this
|
|
199
|
-
// worker (38.
|
|
206
|
+
// worker (38.14). Nothing here stops the mux, either.
|
|
200
207
|
close() {},
|
|
201
208
|
},
|
|
202
209
|
)
|
|
@@ -343,79 +350,74 @@ async function open(
|
|
|
343
350
|
failure = start.failure
|
|
344
351
|
if (failure !== '') return
|
|
345
352
|
const root = start.root
|
|
353
|
+
const compilation = start.compilation
|
|
346
354
|
try {
|
|
347
|
-
const base = `${root}/${VIRTUAL_DIRECTORY}`
|
|
348
|
-
const modules = new Map<string, string>()
|
|
349
|
-
for (const [name, text] of start.modules)
|
|
350
|
-
modules.set(`${base}/${name}`, text)
|
|
351
355
|
// A page among what compiled — an app of handlers alone has the tables and no client to
|
|
352
356
|
// bundle.
|
|
353
|
-
const paged =
|
|
357
|
+
const paged = compilation.tables[`${CLIENT_ENTRY}.ts`] !== undefined
|
|
354
358
|
|
|
355
359
|
// ONE PLUGIN, TWO CALLERS: the runtime resolves this worker's `import()` through it and
|
|
356
360
|
// the bundler resolves the browser's graph through it, so the two lanes cannot disagree
|
|
357
361
|
// about what a specifier means. Registering it is process-wide — which is a worker rather
|
|
358
362
|
// than the command, so a reload is a new registry rather than a cache to bust.
|
|
359
|
-
|
|
360
|
-
Bun.plugin(plugin)
|
|
363
|
+
serveCompilation(root, compilation)
|
|
361
364
|
|
|
362
|
-
// ASKED FOR TOGETHER. The bundle, the
|
|
365
|
+
// ASKED FOR TOGETHER. The bundle, the public directory and the server's own
|
|
363
366
|
// tables depend on none of each other — the plugin is registered above, before any of
|
|
364
367
|
// them asks it anything — and on a reload every one of them is latency the author waits on,
|
|
365
368
|
// so the bundle is handed over still building (`SourceParts.client`).
|
|
366
369
|
//
|
|
367
|
-
// ONE LISTING of the public
|
|
368
|
-
// 38.
|
|
369
|
-
// output, so the address answers where the author's file actually is.
|
|
370
|
-
const
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
)
|
|
375
|
-
// 38.44 — the app's own plugins, imported beside the reads below rather than ahead of them.
|
|
370
|
+
// ONE LISTING of the public directories — the compile's — for the bundle's externals and for
|
|
371
|
+
// what is served. 38.19 — OUT OF THE SOURCE TREE. `abide build` copies these into its output
|
|
372
|
+
// and dev has no output, so the address answers where the author's file actually is.
|
|
373
|
+
const publicSources = start.published
|
|
374
|
+
const published = publishedUnder(root, publicSources)
|
|
375
|
+
const publicAddresses = [...publicSources.values()]
|
|
376
|
+
// 38.43 — the app's own plugins, imported beside the reads below rather than ahead of them.
|
|
376
377
|
const bundling = appPlugins(root).then((own) =>
|
|
377
378
|
bundleInMemory(
|
|
378
379
|
root,
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
[plugin, fenceServer(root, start.modules), ...own],
|
|
380
|
+
// 16.28, 16.29 — the browser's view of the server, as `abide build` gives it.
|
|
381
|
+
[fenceServer(root), abideModules(root, compilation), ...own],
|
|
382
382
|
paged,
|
|
383
383
|
publicAddresses,
|
|
384
|
+
compilation,
|
|
384
385
|
),
|
|
385
386
|
)
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
import(
|
|
389
|
-
Bun.pathToFileURL(`${base}/server.ts`).href
|
|
390
|
-
) as Promise<ServerTable>,
|
|
391
|
-
handlersIn(base),
|
|
392
|
-
])
|
|
393
|
-
// 38.18 — THE RELOAD CLIENT IS THIS WORKER'S, not the bundle's, and it goes in whether or
|
|
394
|
-
// not there is an entry beside it: a bundle that failed is exactly when a reader needs the
|
|
395
|
-
// next save to reach them. `clientTags` writes nothing for a manifest with no entry.
|
|
396
|
-
const sources = await appSources({
|
|
397
|
-
table,
|
|
398
|
-
shell,
|
|
399
|
-
handlers,
|
|
400
|
-
handlerModule: sourceModuleLoader(root, base),
|
|
401
|
-
published,
|
|
402
|
-
client: bundling.then((bundle) => ({
|
|
403
|
-
manifest: bundle.manifest,
|
|
404
|
-
head: `<script type="module" src="${RELOAD_CLIENT_ADDRESS}"></script>`,
|
|
405
|
-
framework: (pathname) => served(bundle, pathname),
|
|
406
|
-
})),
|
|
407
|
-
explained: (thrown) => failedDocument(describe(thrown)),
|
|
408
|
-
})
|
|
409
|
-
|
|
387
|
+
const table = await servedTable(root)
|
|
388
|
+
const handlers = table.handlers
|
|
410
389
|
// 36.13, IN THIS LANE TOO. A throw here lands in `failure`, which is what 38.4 asks
|
|
411
390
|
// for: the command keeps answering and what it answers names both halves. Without it
|
|
412
391
|
// `answerRequest` below would silently prefer the file — its public-before-routes
|
|
413
392
|
// order rests on this collision being impossible.
|
|
414
|
-
refuseAddressCollision(
|
|
393
|
+
refuseAddressCollision(
|
|
394
|
+
table.routes,
|
|
395
|
+
handlers,
|
|
396
|
+
publicSources,
|
|
397
|
+
'abide dev',
|
|
398
|
+
)
|
|
415
399
|
|
|
416
|
-
|
|
417
|
-
//
|
|
418
|
-
|
|
400
|
+
// THE SAME `App` `abide start` boots, so an app's hooks and middleware run in dev too — and its
|
|
401
|
+
// sources are read INSIDE the boot, as `abide start` reads them: a document's head carries
|
|
402
|
+
// what the resolved configuration says (28.4's app name), which is not resolved before it.
|
|
403
|
+
app = await bootApp(originUnder(root), async () => {
|
|
404
|
+
// 38.17 — THE RELOAD CLIENT IS THIS WORKER'S, not the bundle's, and it goes in whether or
|
|
405
|
+
// not there is an entry beside it: a bundle that failed is exactly when a reader needs the
|
|
406
|
+
// next save to reach them. `clientTags` writes nothing for a manifest with no entry.
|
|
407
|
+
const sources = await appSources({
|
|
408
|
+
table,
|
|
409
|
+
handlers,
|
|
410
|
+
published,
|
|
411
|
+
client: bundling.then((bundle) => ({
|
|
412
|
+
manifest: bundle.manifest,
|
|
413
|
+
head: `<script type="module" src="${RELOAD_CLIENT_ADDRESS}"></script>`,
|
|
414
|
+
framework: (pathname) => served(bundle, pathname),
|
|
415
|
+
})),
|
|
416
|
+
explained: (thrown) => failedDocument(describe(thrown)),
|
|
417
|
+
})
|
|
418
|
+
carried = sources
|
|
419
|
+
return sources
|
|
420
|
+
})
|
|
419
421
|
// 18.1 — the mux `App.listen` would open, over the app's own record: a frame's `rpc.raw` and
|
|
420
422
|
// MCP surface are this app's.
|
|
421
423
|
mux = app.openMux()
|
|
@@ -435,9 +437,21 @@ function served(bundle: Bundle, pathname: string): Response | undefined {
|
|
|
435
437
|
if (!pathname.startsWith(`${CLIENT_ADDRESS}/`)) return undefined
|
|
436
438
|
const found = bundle.files.get(pathname.slice(CLIENT_ADDRESS.length + 1))
|
|
437
439
|
if (found === undefined) return undefined
|
|
440
|
+
const { bytes } = found
|
|
441
|
+
const body =
|
|
442
|
+
typeof bytes === 'function'
|
|
443
|
+
? new ReadableStream({
|
|
444
|
+
async start(controller) {
|
|
445
|
+
controller.enqueue(
|
|
446
|
+
new TextEncoder().encode(await bytes()),
|
|
447
|
+
)
|
|
448
|
+
controller.close()
|
|
449
|
+
},
|
|
450
|
+
})
|
|
451
|
+
: bytes
|
|
438
452
|
// NO CACHING AT ALL, which is where dev parts company with 21.5: the names are stable across
|
|
439
453
|
// rebuilds, so a browser told to keep `entry.js` for a year keeps the app it had at boot.
|
|
440
|
-
return new Response(
|
|
454
|
+
return new Response(body, {
|
|
441
455
|
headers: { 'content-type': found.type, 'cache-control': 'no-store' },
|
|
442
456
|
})
|
|
443
457
|
}
|
|
@@ -445,30 +459,28 @@ function served(bundle: Bundle, pathname: string): Response | undefined {
|
|
|
445
459
|
/**
|
|
446
460
|
* The client bundle, built into memory.
|
|
447
461
|
*
|
|
448
|
-
* 38.
|
|
462
|
+
* 38.16 — Unminified, and the entry named by its source rather than content-hashed, so what a
|
|
449
463
|
* browser loads and what a stack trace names are the module the author wrote. 38.5's hashes and
|
|
450
464
|
* manifest are the BUILD's, and the difference is deliberate: a dev chunk that changed has to be
|
|
451
465
|
* fetched again, and a name that changes with it is a cache the reload cannot clear.
|
|
452
466
|
*
|
|
453
|
-
* A FAILURE IS AN EMPTY BUNDLE AND NOT A THROW (38.
|
|
467
|
+
* A FAILURE IS AN EMPTY BUNDLE AND NOT A THROW (38.17). The pages still render, the document
|
|
454
468
|
* still loads the reload client, and the next save is what fixes it.
|
|
455
469
|
*/
|
|
456
470
|
async function bundleInMemory(
|
|
457
471
|
root: string,
|
|
458
|
-
base: string,
|
|
459
472
|
plugins: Bun.BunPlugin[],
|
|
460
473
|
paged: boolean,
|
|
461
474
|
publicAddresses: string[],
|
|
475
|
+
compilation: Compilation,
|
|
462
476
|
): Promise<Bundle> {
|
|
463
477
|
const bundle: Bundle = { files: new Map(), manifest: NO_MANIFEST }
|
|
464
478
|
if (!paged) return bundle
|
|
465
479
|
const built = await Bun.build({
|
|
466
|
-
// 36.15's addresses off the source tree this lane answers them out of (38.
|
|
467
|
-
...clientBuildOptions(
|
|
468
|
-
//
|
|
469
|
-
|
|
470
|
-
// below makes them flat anyway.
|
|
471
|
-
root,
|
|
480
|
+
// 36.15's addresses off the source tree this lane answers them out of (38.19).
|
|
481
|
+
...clientBuildOptions(root, plugins, publicAddresses),
|
|
482
|
+
// 38.97 — a debugger in the browser reads the `.abide` (`withAbideSources`).
|
|
483
|
+
sourcemap: 'linked',
|
|
472
484
|
naming: {
|
|
473
485
|
entry: '[name].[ext]',
|
|
474
486
|
chunk: '[name]-[hash].[ext]',
|
|
@@ -476,7 +488,7 @@ async function bundleInMemory(
|
|
|
476
488
|
},
|
|
477
489
|
throw: false,
|
|
478
490
|
})
|
|
479
|
-
// THE TERMINAL RATHER THAN THE DOCUMENT. 38.
|
|
491
|
+
// THE TERMINAL RATHER THAN THE DOCUMENT. 38.17 keeps the server up, and a page that still
|
|
480
492
|
// paints is what it keeps up — so the pages stay served, unhydrated, and the author reads the
|
|
481
493
|
// build error where they started the command.
|
|
482
494
|
if (!built.success) {
|
|
@@ -493,103 +505,24 @@ async function bundleInMemory(
|
|
|
493
505
|
const output = built.outputs[index] as Bun.BuildArtifact
|
|
494
506
|
const name = output.path.slice(output.path.lastIndexOf('/') + 1)
|
|
495
507
|
const bytes = read[index] as ArrayBuffer
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* The plugin serving the compiled modules out of `modules`, for the runtime and the bundler alike.
|
|
511
|
-
*
|
|
512
|
-
* A module in the map wins; everything else is resolved from where that module PRETENDS to be,
|
|
513
|
-
* which is inside the app's package — so `abide`, `#ui/…` and a stylesheet all answer exactly as
|
|
514
|
-
* they do for a build on disk.
|
|
515
|
-
*/
|
|
516
|
-
function virtualModules(modules: Map<string, string>): Bun.BunPlugin {
|
|
517
|
-
return {
|
|
518
|
-
name: 'abide-dev',
|
|
519
|
-
setup(build) {
|
|
520
|
-
build.onResolve({ filter: /.*/ }, (args) => {
|
|
521
|
-
if (modules.has(args.path))
|
|
522
|
-
return { path: args.path, namespace: 'abide-dev' }
|
|
523
|
-
// The importer carries its namespace in front of the path when it is one of
|
|
524
|
-
// ours — and the runtime puts a `/` in front of that again, where the bundler
|
|
525
|
-
// does not. Both spellings are stripped here because a `file://` URL refuses to
|
|
526
|
-
// take either as a base, and the one that was missed cost the whole route table:
|
|
527
|
-
// every dynamic import in it read as a module nobody could find.
|
|
528
|
-
const importer = args.importer.replace(/^\/?abide-dev:/, '')
|
|
529
|
-
if (!modules.has(importer)) return undefined
|
|
530
|
-
const from = importer.slice(0, importer.lastIndexOf('/'))
|
|
531
|
-
if (args.path.startsWith('.')) {
|
|
532
|
-
const beside = Bun.fileURLToPath(
|
|
533
|
-
new URL(args.path, Bun.pathToFileURL(`${from}/`)),
|
|
534
|
-
)
|
|
535
|
-
if (modules.has(beside))
|
|
536
|
-
return { path: beside, namespace: 'abide-dev' }
|
|
537
|
-
}
|
|
538
|
-
// OUT OF THE MAP AND INTO THE PACKAGE. Resolved from the virtual directory rather
|
|
539
|
-
// than left to the default, which has an `abide-dev:` prefix to walk up from and
|
|
540
|
-
// finds nothing.
|
|
541
|
-
try {
|
|
542
|
-
return {
|
|
543
|
-
path: Bun.resolveSync(args.path, from),
|
|
544
|
-
namespace: 'file',
|
|
545
|
-
}
|
|
546
|
-
} catch {
|
|
547
|
-
return undefined
|
|
548
|
-
}
|
|
549
|
-
})
|
|
550
|
-
// ONLY THE RUNTIME'S LOADS ARE REWRITTEN: the bundler asks `onResolve` above for a `#`
|
|
551
|
-
// specifier itself, and a reload's loads are nearly all the bundler's — 181 of 183 on
|
|
552
|
-
// the docs app, where the rewrite's parse was ~25 ms of each reload.
|
|
553
|
-
const bundling = 'config' in build
|
|
554
|
-
build.onLoad({ filter: /.*/, namespace: 'abide-dev' }, (args) => ({
|
|
555
|
-
contents: bundling
|
|
556
|
-
? (modules.get(args.path) ?? '')
|
|
557
|
-
: withSeamsResolved(
|
|
558
|
-
modules.get(args.path) ?? '',
|
|
559
|
-
args.path,
|
|
560
|
-
),
|
|
561
|
-
// 34.4 — a component's scoped styles are a stylesheet the bundle collects.
|
|
562
|
-
loader: args.path.endsWith('.css') ? 'css' : 'ts',
|
|
563
|
-
}))
|
|
564
|
-
},
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// A `#` SPECIFIER IN A MODULE FROM THE MAP, resolved before the module is handed over. The runtime
|
|
569
|
-
// takes a `#` specifier to the package's `imports` itself rather than through this plugin, and from
|
|
570
|
-
// a module in the map it takes the target as an exact file — so the extensionless
|
|
571
|
-
// `#server/rpc/contacts` a component writes found nothing, where the bundler, which does ask the
|
|
572
|
-
// plugin, probed its way to the `.ts` through the same `Bun.resolveSync` — so the rewrite is the
|
|
573
|
-
// runtime's alone, and both lanes land on the same path.
|
|
574
|
-
const TRANSPILER = new Bun.Transpiler({ loader: 'ts' })
|
|
575
|
-
|
|
576
|
-
function withSeamsResolved(source: string, path: string): string {
|
|
577
|
-
if (!path.endsWith('.ts')) return source
|
|
578
|
-
const from = path.slice(0, path.lastIndexOf('/'))
|
|
579
|
-
let resolved = source
|
|
580
|
-
for (const { path: specifier } of TRANSPILER.scanImports(source)) {
|
|
581
|
-
if (!specifier.startsWith('#')) continue
|
|
582
|
-
let target: string
|
|
583
|
-
try {
|
|
584
|
-
target = Bun.resolveSync(specifier, from)
|
|
585
|
-
} catch {
|
|
586
|
-
continue
|
|
508
|
+
let served: ArrayBuffer | string | (() => Promise<string>) = bytes
|
|
509
|
+
if (name.endsWith('.css'))
|
|
510
|
+
served = stylesheetUnderMount(new TextDecoder().decode(bytes))
|
|
511
|
+
// 38.97 — PLACED ON THE FIRST ASK: only a debugger reads a map, and placing every chunk's
|
|
512
|
+
// up front was ~45 ms of each reload on the docs app, whether or not one was open.
|
|
513
|
+
else if (output.kind === 'sourcemap') {
|
|
514
|
+
let placed: Promise<string> | undefined
|
|
515
|
+
served = () =>
|
|
516
|
+
(placed ??= withAbideSources(
|
|
517
|
+
new TextDecoder().decode(bytes),
|
|
518
|
+
compilation,
|
|
519
|
+
root,
|
|
520
|
+
))
|
|
587
521
|
}
|
|
588
|
-
|
|
589
|
-
.replaceAll(`'${specifier}'`, `'${target}'`)
|
|
590
|
-
.replaceAll(`"${specifier}"`, `"${target}"`)
|
|
522
|
+
bundle.files.set(name, { bytes: served, type: output.type })
|
|
591
523
|
}
|
|
592
|
-
|
|
524
|
+
bundle.manifest = manifestOf(built.metafile as Bun.BuildMetafile, root)
|
|
525
|
+
return bundle
|
|
593
526
|
}
|
|
594
527
|
|
|
595
528
|
/**
|
|
@@ -620,7 +553,7 @@ function failureMarkup(why: string): string {
|
|
|
620
553
|
return `<main><h1>abide dev</h1><pre>${escapeText(why)}</pre></main>`
|
|
621
554
|
}
|
|
622
555
|
|
|
623
|
-
// 38.
|
|
556
|
+
// 38.15's other end. The listener force-closes every live socket when it replaces this worker, so
|
|
624
557
|
// a CLOSE is the rebuild and the reconnect that follows is the new app — there is no message to
|
|
625
558
|
// send and nothing to version. A reader who never opened the socket is told nothing, which is the
|
|
626
559
|
// same as before the save.
|
|
@@ -1,48 +1,50 @@
|
|
|
1
|
-
// 16.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// (
|
|
5
|
-
// the server barrel cannot ride into the browser by being imported from the wrong file.
|
|
1
|
+
// 16.29 — WHAT THE BROWSER'S BUNDLE SEES OF THE SERVER: nothing under a `server` directory, at any
|
|
2
|
+
// depth, so a secret or a database client cannot ride into the browser by being imported from the
|
|
3
|
+
// wrong file. A handler crosses as the wrapper its file's browser module carries in place of the
|
|
4
|
+
// server block (34.23), which the compiler wrote; this is the fence around everything else.
|
|
6
5
|
//
|
|
7
6
|
// A PLUGIN ON THE BUNDLE, NOT A REWRITE OF THE PAGES. A rewrite reaches the specifiers the compiler
|
|
8
|
-
// emitted; the bundle reaches every edge — a
|
|
7
|
+
// emitted; the bundle reaches every edge — a helper the page imports, importing a server module,
|
|
8
|
+
// included.
|
|
9
|
+
//
|
|
10
|
+
// REGISTERED AHEAD OF `abideModules`, AND IN ITS NAMESPACES: a load is answered by the first plugin
|
|
11
|
+
// that answers it, and a component or a file's text is loaded there rather than as a `file`.
|
|
12
|
+
|
|
13
|
+
import { SERVER_DIRECTORY } from '#shared/RESERVED_DIRECTORIES.ts'
|
|
14
|
+
import { placeOf } from '#shared/routePattern.ts'
|
|
15
|
+
import {
|
|
16
|
+
COMPONENT_NAMESPACE,
|
|
17
|
+
STYLE_NAMESPACE,
|
|
18
|
+
TEXT_NAMESPACE,
|
|
19
|
+
} from './abideModules.ts'
|
|
9
20
|
|
|
10
|
-
|
|
11
|
-
|
|
21
|
+
// Every namespace a browser bundle loads a file of the app's in: its modules, its components, a
|
|
22
|
+
// file's text and a component's stylesheet.
|
|
23
|
+
const FENCED = ['file', COMPONENT_NAMESPACE, TEXT_NAMESPACE, STYLE_NAMESPACE]
|
|
12
24
|
|
|
13
25
|
/**
|
|
14
|
-
* The bundler plugin
|
|
26
|
+
* The bundler plugin refusing everything under a `server` directory to the browser's bundle.
|
|
15
27
|
*
|
|
16
|
-
* `
|
|
17
|
-
*
|
|
28
|
+
* `root` is its real path (`appRoot`), which is how the bundler names a file. Listed before
|
|
29
|
+
* `abideModules`.
|
|
18
30
|
*/
|
|
19
|
-
export function fenceServer(
|
|
20
|
-
root
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const roots = new Set([root, realpathSync(root)])
|
|
31
|
+
export function fenceServer(root: string): Bun.BunPlugin {
|
|
32
|
+
// Anything with a `server` directory in its path below the root, and `placeOf` decides — a load
|
|
33
|
+
// it passes on falls through to the next plugin's, or the bundler's own.
|
|
34
|
+
const filter = new RegExp(
|
|
35
|
+
`^${RegExp.escape(root)}/(?:.*/)?${SERVER_DIRECTORY}/`,
|
|
36
|
+
)
|
|
26
37
|
return {
|
|
27
38
|
name: 'abide-fence',
|
|
28
39
|
setup(build) {
|
|
29
|
-
for (const
|
|
30
|
-
build.onLoad(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
`${WIRE_DIRECTORY}/${source}`,
|
|
38
|
-
)
|
|
39
|
-
if (wrapper === undefined)
|
|
40
|
-
throw new Error(
|
|
41
|
-
`abide: ${source} is server code, and the browser's bundle imports it. A browser may import a handler from a module under server/rpc and nothing else under server/. RULEBOOK 16.29.`,
|
|
42
|
-
)
|
|
43
|
-
return { contents: wrapper, loader: 'ts' }
|
|
44
|
-
},
|
|
45
|
-
)
|
|
40
|
+
for (const namespace of FENCED)
|
|
41
|
+
build.onLoad({ filter, namespace }, (args) => {
|
|
42
|
+
const below = args.path.slice(root.length + 1)
|
|
43
|
+
if (placeOf(below) !== 'server') return undefined
|
|
44
|
+
throw new Error(
|
|
45
|
+
`abide: ${below.replace(/::\w+$/, '')} is under a server directory, and the browser's bundle imports it. RULEBOOK 16.29.`,
|
|
46
|
+
)
|
|
47
|
+
})
|
|
46
48
|
},
|
|
47
49
|
}
|
|
48
50
|
}
|