@bakery-framework/core 2.0.0-alpha.8 → 2.0.0-rc.1
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/README.md +8 -8
- package/package.json +2 -5
- package/src/cache/lru.ts +2 -2
- package/src/cache/shared-db.ts +81 -11
- package/src/cache/string.ts +4 -0
- package/src/cache/tiered.ts +11 -5
- package/src/client/globals.d.ts +7 -7
- package/src/client/livereload.ts +2 -2
- package/src/client/utils.ts +11 -6
- package/src/compiler/compiler.ts +48 -44
- package/src/compiler/dev-service.ts +29 -30
- package/src/compiler/prompt-tracker.ts +2 -2
- package/src/compiler/tsconfig-sync.ts +130 -59
- package/src/core/bakery.ts +6 -3
- package/src/core/cache-version.ts +68 -18
- package/src/core/config.ts +16 -16
- package/src/core/context.ts +8 -8
- package/src/core/define-route.ts +5 -6
- package/src/core/index.ts +19 -7
- package/src/core/init.ts +38 -35
- package/src/core/jsx.ts +19 -4
- package/src/core/paths.ts +3 -1
- package/src/core/plugins.ts +15 -4
- package/src/core/port.ts +6 -6
- package/src/global.d.ts +36 -18
- package/src/handlers/assets/google-font.ts +6 -6
- package/src/handlers/assets/image.ts +36 -3
- package/src/handlers/assets/nm.ts +7 -7
- package/src/handlers/assets/static.ts +55 -12
- package/src/handlers/assets/ts.ts +3 -3
- package/src/handlers/assets/tsx.ts +1 -1
- package/src/handlers/assets/virtual-asset.ts +1 -1
- package/src/handlers/core/$base.ts +24 -8
- package/src/handlers/core/$dynamic.ts +66 -23
- package/src/handlers/core/$error.ts +29 -17
- package/src/handlers/core/$middleware.ts +6 -6
- package/src/handlers/core/$mounts.ts +2 -2
- package/src/handlers/core/$registry.ts +16 -6
- package/src/handlers/core/$routing.ts +18 -19
- package/src/handlers/core/$static.ts +11 -12
- package/src/handlers/routes/api.ts +5 -5
- package/src/handlers/routes/html.ts +2 -2
- package/src/handlers/routes/proxy.ts +1 -1
- package/src/logger/clients.ts +2 -2
- package/src/logger/logger.ts +4 -4
- package/src/logger/serve-log.ts +32 -18
- package/src/plugins/index.ts +1 -1
- package/src/plugins/routes.ts +7 -7
- package/src/plugins/types.ts +13 -11
- package/src/router.ts +12 -12
- package/src/session.ts +41 -23
- package/src/shared.d.ts +5 -5
- package/src/startup.ts +3 -3
- package/src/types.d.ts +10 -10
- package/src/utils/common/case.ts +1 -1
- package/src/utils/common/match.ts +1 -1
- package/src/utils/common/misc.ts +1 -1
- package/src/utils/constants.ts +15 -15
- package/src/utils/fs.ts +97 -39
- package/src/utils/http/authorize.ts +24 -17
- package/src/utils/http/cors.ts +6 -6
- package/src/utils/http/credential.ts +4 -4
- package/src/utils/http/csrf.ts +6 -7
- package/src/utils/http/dom.ts +9 -9
- package/src/utils/http/etag.ts +54 -13
- package/src/utils/http/html.ts +29 -20
- package/src/utils/http/ip.ts +1 -1
- package/src/utils/http/sse.ts +11 -11
- package/src/utils/http/url.ts +4 -4
- package/src/utils/http/validate.ts +3 -4
- package/src/utils/isomorphic/escape.ts +1 -1
- package/src/utils/isomorphic/is.ts +2 -2
- package/src/utils/isomorphic/misc.ts +1 -1
- package/src/utils/isomorphic/stringify.ts +2 -3
- package/src/utils/isomorphic/try.ts +1 -1
- package/src/utils/shared-pool.ts +23 -16
- package/tsconfig.app.json +3 -3
- package/tsconfig.vue.json +3 -3
package/src/compiler/compiler.ts
CHANGED
|
@@ -20,13 +20,13 @@ const RX_IMPORT =
|
|
|
20
20
|
* evaluation.
|
|
21
21
|
*
|
|
22
22
|
* This used to be a top-level `await` reading `<cwd>/package.json`, which put a
|
|
23
|
-
* filesystem read on the boot path of every process that loads this module
|
|
23
|
+
* filesystem read on the boot path of every process that loads this module,
|
|
24
24
|
* and `handlers/assets/ts.ts` imports it, so that is every worker and every dev
|
|
25
25
|
* restart. The value it produces is needed only when something actually
|
|
26
26
|
* compiles; a production process serving out of a warm compile cache never
|
|
27
27
|
* needs it at all.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
29
|
+
* Memoized as a promise, not a value, so two concurrent first compiles share
|
|
30
30
|
* one read instead of racing to do it twice.
|
|
31
31
|
*/
|
|
32
32
|
let definesPromise: Promise<MapOf<string>> | null = null
|
|
@@ -37,8 +37,7 @@ async function buildDefines(): Promise<MapOf<string>> {
|
|
|
37
37
|
const file = await Bun.file(`${process.cwd()}/package.json`).json()
|
|
38
38
|
if (file?.version) bakeryVersion = file.version
|
|
39
39
|
} catch {
|
|
40
|
-
// No package.json in cwd, or it is unreadable. The version is cosmetic
|
|
41
|
-
// it lands in a banner and a build define — so the default above stands.
|
|
40
|
+
// No package.json in cwd, or it is unreadable. The version is cosmetic ( // it lands in a banner and a build define), so the default above stands.
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
return {
|
|
@@ -80,7 +79,13 @@ function nextVirtualId(): string {
|
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
function preprocessImports(source: string, filePath: fs.AbsolutePath): string {
|
|
83
|
-
|
|
82
|
+
// The file's **directory**, not the file. A relative import resolves against
|
|
83
|
+
// the directory containing the importer, and resolving against the path
|
|
84
|
+
// itself produced `…/src/entry.ts/a.css`, which exists nowhere, so every
|
|
85
|
+
// `import './x.css'` from a compiled module registered a virtual asset that
|
|
86
|
+
// could only 404. It has been wrong since before the workspace split, with
|
|
87
|
+
// no test and neither app using the feature.
|
|
88
|
+
const fileDir = fs.dirname(filePath)
|
|
84
89
|
|
|
85
90
|
const matches = [...source.matchAll(RX_IMPORT)]
|
|
86
91
|
|
|
@@ -91,7 +96,7 @@ function preprocessImports(source: string, filePath: fs.AbsolutePath): string {
|
|
|
91
96
|
|
|
92
97
|
// The key is the bare id, never the `/_virtual/` URL. Storing the full URL
|
|
93
98
|
// meant getKey() returned an already-prefixed value that got prefixed again
|
|
94
|
-
// on the next compile
|
|
99
|
+
// on the next compile, which both threw a StringCache collision and left
|
|
95
100
|
// VirtualAssetHandler (which looks up by bare id) unable to resolve it.
|
|
96
101
|
const id =
|
|
97
102
|
Strings.getKey(assetPath) || `${Date.now()}_${nextVirtualId()}.${ext}`
|
|
@@ -113,15 +118,15 @@ function preprocessImports(source: string, filePath: fs.AbsolutePath): string {
|
|
|
113
118
|
* Compile `source`, naming `path` so a failure can say where it happened.
|
|
114
119
|
*
|
|
115
120
|
* The two branches are not symmetric on purpose. Without a path there is no
|
|
116
|
-
* file to name and no caller that can render a 500
|
|
117
|
-
* fragments of an SFC it has already parsed
|
|
121
|
+
* file to name and no caller that can render a 500 (the Vue plugin compiles
|
|
122
|
+
* fragments of an SFC it has already parsed), so a failure logs and hands the
|
|
118
123
|
* source back. With a path the failure is a served request, and the answer is
|
|
119
124
|
* `null`: `TSHandler` turns that into the 500 it has always had a branch for.
|
|
120
125
|
*
|
|
121
126
|
* It used to be neither. The transpiler was wrapped only on the pathless
|
|
122
127
|
* branch, so a syntax error in a `.ts` asset threw straight past `compile()`,
|
|
123
|
-
* past `TSHandler`
|
|
124
|
-
* unreachable
|
|
128
|
+
* past `TSHandler` (leaving its `'Compilation Failed'` arm permanently
|
|
129
|
+
* unreachable), and into the worker's catch-all, which printed
|
|
125
130
|
* `Unhandled Server Error: <message>` with no file and no line while the client
|
|
126
131
|
* got `An unexpected error occurred.` A total blackout for a one-character typo.
|
|
127
132
|
*/
|
|
@@ -149,8 +154,7 @@ export async function compileText(source: string, path?: fs.AbsolutePath) {
|
|
|
149
154
|
|
|
150
155
|
if (failed) {
|
|
151
156
|
// `errorWithPosition`, not `errorMsg`: the thrown `BuildMessage` has no
|
|
152
|
-
// stack, so `errorMsg` degrades to the bare message and the line number
|
|
153
|
-
// the only thing that makes this actionable — is dropped. The file comes
|
|
157
|
+
// stack, so `errorMsg` degrades to the bare message and the line number ( // the only thing that makes this actionable) is dropped. The file comes
|
|
154
158
|
// from `{file}`; the diagnostic's own `position.file` is `input.ts`,
|
|
155
159
|
// because `transform()` was handed a string.
|
|
156
160
|
compLog.COMPILE_FAIL({ file: path, error: errorWithPosition(failed) })
|
|
@@ -165,7 +169,7 @@ export async function compileText(source: string, path?: fs.AbsolutePath) {
|
|
|
165
169
|
// relative directory imports went second, once `ts.test.ts` pinned that the
|
|
166
170
|
// handler already resolves `/lib`, `/lib.js` and their nested forms to the
|
|
167
171
|
// directory's index server-side. Resolution belongs to the import map and
|
|
168
|
-
// the handlers
|
|
172
|
+
// the handlers: the compiler only transpiles.
|
|
169
173
|
return await PluginHooks.onCompile(transformed!, path)
|
|
170
174
|
}
|
|
171
175
|
|
|
@@ -190,7 +194,7 @@ type CompileResult = {
|
|
|
190
194
|
* `export default …` and nothing else.
|
|
191
195
|
*
|
|
192
196
|
* Not all CJS: `exports.greet = …` is statically analysable and Bun emits a real
|
|
193
|
-
* named export for it. It is the whole-object form
|
|
197
|
+
* named export for it. It is the whole-object form: `module.exports = { … }`,
|
|
194
198
|
* whose members cannot be known without running the module, and that is the one
|
|
195
199
|
* that breaks.
|
|
196
200
|
*
|
|
@@ -208,7 +212,7 @@ type CompileResult = {
|
|
|
208
212
|
/**
|
|
209
213
|
* Bun's CJS wrapper, in a spelling minification cannot destroy.
|
|
210
214
|
*
|
|
211
|
-
* The obvious check
|
|
215
|
+
* The obvious check: `content.includes('__commonJS')`: held in dev and
|
|
212
216
|
* silently never matched in PROD, where minification renames the helper to a
|
|
213
217
|
* single letter. The consequence was the worst kind of split: named imports
|
|
214
218
|
* of CJS packages worked all through development and broke only in the
|
|
@@ -220,7 +224,7 @@ const RX_CJS_WRAPPER = /\{\s*exports\s*:\s*\{\s*\}\s*\}/
|
|
|
220
224
|
/**
|
|
221
225
|
* A `module.exports = { … }` assignment, whatever the module variable is
|
|
222
226
|
* called after minification (`n.exports={…}`). The helper's own
|
|
223
|
-
* `mod.exports);` never matches
|
|
227
|
+
* `mod.exports);` never matches: no `= {` follows it.
|
|
224
228
|
*/
|
|
225
229
|
const RX_MODULE_EXPORTS = /[A-Za-z_$][\w$]*\.exports\s*=\s*\{/g
|
|
226
230
|
|
|
@@ -241,7 +245,7 @@ export function isCjsDefaultOnly(content: string): boolean {
|
|
|
241
245
|
* A bundle consisting of nothing but a non-empty `export { … }` list.
|
|
242
246
|
*
|
|
243
247
|
* Such a file names bindings that were never declared, so every one of them is
|
|
244
|
-
* a `ReferenceError` the moment the browser evaluates it
|
|
248
|
+
* a `ReferenceError` the moment the browser evaluates it, and `Bun.build`
|
|
245
249
|
* reports it as **`success: true` with zero diagnostics**.
|
|
246
250
|
*
|
|
247
251
|
* The cause is `sideEffects`, and it is not specific to one package. When the
|
|
@@ -260,11 +264,11 @@ export function isCjsDefaultOnly(content: string): boolean {
|
|
|
260
264
|
* `["./dist/attach-styles.js", "./dist/assets/*.css.js"]`, so its barrel
|
|
261
265
|
* bundles to 3,549 bytes of pure export list and throws `AggregateError: 189
|
|
262
266
|
* errors` on import. Most modern libraries set `sideEffects: false`, so any
|
|
263
|
-
* re-export barrel among them is a candidate
|
|
267
|
+
* re-export barrel among them is a candidate. This is a wide class, not a
|
|
264
268
|
* single broken package.
|
|
265
269
|
*
|
|
266
|
-
* `bundleReExportShim` repairs it; this only
|
|
267
|
-
* own is a legal empty module and is not flagged
|
|
270
|
+
* `bundleReExportShim` repairs it; this only recognizes it. `export {}` on its
|
|
271
|
+
* own is a legal empty module and is not flagged: the list has to name
|
|
268
272
|
* something for the file to be self-contradictory.
|
|
269
273
|
*/
|
|
270
274
|
export function isEmptyExportList(content: string): boolean {
|
|
@@ -280,8 +284,8 @@ export function isEmptyExportList(content: string): boolean {
|
|
|
280
284
|
*
|
|
281
285
|
* The `sideEffects` drop described on {@link isEmptyExportList} keys on the
|
|
282
286
|
* *entry* being inside the offending package. Re-exporting the very same file
|
|
283
|
-
* from a module that is not
|
|
284
|
-
* inside `node_modules
|
|
287
|
+
* from a module that is not: the shim lands in `.cache/`, which is never
|
|
288
|
+
* inside `node_modules`: leaves the tree-shaker with an entry it has no
|
|
285
289
|
* manifest for, and the imports survive. Measured on `@vue-material/core`:
|
|
286
290
|
* 3,549 bytes of husk becomes a 428,470-byte bundle with all 189 exports.
|
|
287
291
|
*
|
|
@@ -290,16 +294,16 @@ export function isEmptyExportList(content: string): boolean {
|
|
|
290
294
|
* than a no-op. So: try with it, fall back to without.
|
|
291
295
|
*
|
|
292
296
|
* **Every installed package is `external`, and that is load-bearing rather than
|
|
293
|
-
* an
|
|
297
|
+
* an optimization.** Left to itself the shim inlines the whole reachable tree:
|
|
294
298
|
* `@vue-material/core` came out at 428KB with `vue` baked in, and `vue` is a
|
|
295
299
|
* *peer* dependency the app resolves for itself. Two Vue runtimes in one page
|
|
296
300
|
* is not a size problem, it is broken reactivity and a duplicated component
|
|
297
|
-
* registry.
|
|
301
|
+
* registry. Externalized, each dependency stays a bare specifier that the
|
|
298
302
|
* import map sends to its own `/_nm/<dep>`, so there is exactly one copy of
|
|
299
303
|
* each; the same bundle drops to 260KB.
|
|
300
304
|
*
|
|
301
|
-
*
|
|
302
|
-
* `packages: 'external'`, because that switch also
|
|
305
|
+
* Externalizing *installed packages* specifically, rather than Bun's
|
|
306
|
+
* `packages: 'external'`, because that switch also externalizes `node:*`,
|
|
303
307
|
* turning a builtin Bun would otherwise polyfill for the browser into a bare
|
|
304
308
|
* import nothing can resolve.
|
|
305
309
|
*/
|
|
@@ -324,7 +328,7 @@ async function bundleReExportShim(
|
|
|
324
328
|
if (writeErr) return null
|
|
325
329
|
|
|
326
330
|
// Wrapped, because naming a `default` the package does not export is a
|
|
327
|
-
// *throw* from `Bun.build`, not a `success: false
|
|
331
|
+
// *throw* from `Bun.build`, not a `success: false`, and that throw is the
|
|
328
332
|
// expected outcome of the first attempt for any package without one.
|
|
329
333
|
const [buildErr, build] = await Try.catch(() =>
|
|
330
334
|
Bun.build({
|
|
@@ -356,7 +360,7 @@ const RESERVED_EXPORT_NAMES = new Set(['default', '__esModule'])
|
|
|
356
360
|
* How long the export probe may take before it is killed.
|
|
357
361
|
*
|
|
358
362
|
* Generous, because it pays a process start and a module evaluation, and it runs
|
|
359
|
-
* once per package
|
|
363
|
+
* once per package: the result is cached with the bundle. Short enough that a
|
|
360
364
|
* package which hangs on import costs a pause rather than a wedged server.
|
|
361
365
|
*/
|
|
362
366
|
const CJS_PROBE_TIMEOUT_MS = 5_000
|
|
@@ -421,7 +425,7 @@ function objectLiteralKeys(src: string, open: number): string[] | null {
|
|
|
421
425
|
|
|
422
426
|
if (c === ':') {
|
|
423
427
|
take(i)
|
|
424
|
-
// `{ a: expr, b }
|
|
428
|
+
// `{ a: expr, b }`. The value is skipped wholesale so a comma inside it
|
|
425
429
|
// cannot be mistaken for the next key.
|
|
426
430
|
i = endOfValue(src, i + 1)
|
|
427
431
|
if (src[i] !== ',') return keys
|
|
@@ -435,25 +439,25 @@ function objectLiteralKeys(src: string, open: number): string[] | null {
|
|
|
435
439
|
/**
|
|
436
440
|
* Export names read out of the **bundled** output, without running anything.
|
|
437
441
|
*
|
|
438
|
-
* This is the job `cjs-module-lexer` does for Node and Vite:
|
|
442
|
+
* This is the job `cjs-module-lexer` does for Node and Vite: recognize a small
|
|
439
443
|
* set of known-safe `module.exports` shapes and answer nothing for the rest. It
|
|
440
|
-
* is deliberately one shape here
|
|
441
|
-
|
|
444
|
+
* is deliberately one shape here: `module.exports = { … }`, the object literal
|
|
445
|
+
*, because that is the only form that reaches this code path at all. Bun
|
|
442
446
|
* already emits real named exports for `exports.name = …`, so a package written
|
|
443
447
|
* that way never gets here.
|
|
444
448
|
*
|
|
445
449
|
* Reading the *bundle* rather than the source matters. Bun has already
|
|
446
|
-
*
|
|
450
|
+
* normalized the module into a `__commonJS((exports, module) => { … })` wrapper,
|
|
447
451
|
* so there is a single known shape to look inside. The string-literal hazard
|
|
448
452
|
* that made the old import rewriter corrupt user code is reduced, not
|
|
449
|
-
* eliminated
|
|
453
|
+
* eliminated: a string containing `module.exports = {` would still fool this,
|
|
450
454
|
* which is why a failed or empty reading falls through to the probe instead of
|
|
451
455
|
* being trusted as "no exports".
|
|
452
456
|
*/
|
|
453
457
|
export function staticCjsExportNames(bundled: string): string[] {
|
|
454
458
|
// `RX_MODULE_EXPORTS`, not the literal `module.exports = {`: minification
|
|
455
459
|
// renames the module variable and drops the spaces (`n.exports={`), and the
|
|
456
|
-
// literal spelling made this reader dev-only
|
|
460
|
+
// literal spelling made this reader dev-only, the probe silently took over
|
|
457
461
|
// every PROD bundle.
|
|
458
462
|
const matches = [...bundled.matchAll(RX_MODULE_EXPORTS)]
|
|
459
463
|
if (matches.length === 0) return []
|
|
@@ -483,12 +487,12 @@ export function staticCjsExportNames(bundled: string): string[] {
|
|
|
483
487
|
* The fallback for whatever `staticCjsExportNames` cannot see. Something has to
|
|
484
488
|
* run the module, and the question is only *where*: it used to be here, which
|
|
485
489
|
* meant an arbitrary `node_modules` package executing inside the server process
|
|
486
|
-
*
|
|
490
|
+
* (in production as well as dev) free to start a timer, open a socket, or
|
|
487
491
|
* mutate a global that then outlives the request that caused it.
|
|
488
492
|
*
|
|
489
493
|
* A child process does not make execution safe, and nothing can: a module-scope
|
|
490
494
|
* write to the filesystem still happens. What it buys is containment of
|
|
491
|
-
* everything *in-process*
|
|
495
|
+
* everything *in-process* (crashes, hangs, globals, listeners), and a package
|
|
492
496
|
* that hangs on import is killed by the timeout rather than wedging a bundle.
|
|
493
497
|
*/
|
|
494
498
|
async function cjsExportNames(path: string): Promise<string[]> {
|
|
@@ -511,7 +515,7 @@ async function cjsExportNames(path: string): Promise<string[]> {
|
|
|
511
515
|
proc.kill()
|
|
512
516
|
|
|
513
517
|
// **The exit code is deliberately not consulted.** A package that leaves a
|
|
514
|
-
// timer or a listener running
|
|
518
|
+
// timer or a listener running (which plenty do at module scope) has
|
|
515
519
|
// already printed its answer and then simply fails to exit, so the probe
|
|
516
520
|
// kills it and the exit code reports the kill. Requiring a clean exit threw
|
|
517
521
|
// away a correct result and fell back to default-only, which is the very
|
|
@@ -539,8 +543,8 @@ async function cjsExportNames(path: string): Promise<string[]> {
|
|
|
539
543
|
* export const greet = __cjs.greet
|
|
540
544
|
*
|
|
541
545
|
* That shim is what gets bundled, so the browser gets a module that really does
|
|
542
|
-
* provide `greet`. A package that throws on import
|
|
543
|
-
* module scope, say
|
|
546
|
+
* provide `greet`. A package that throws on import (one touching `window` at
|
|
547
|
+
* module scope, say) yields no names, and the caller keeps the plain bundle it
|
|
544
548
|
* already has.
|
|
545
549
|
*
|
|
546
550
|
* Non-identifier keys are skipped rather than mangled: `module.exports['a-b']`
|
|
@@ -605,14 +609,14 @@ export async function bundleModule(
|
|
|
605
609
|
//
|
|
606
610
|
// That is reachable from outside this repo. `PROD` is an accessor on
|
|
607
611
|
// `process.env`, and Bun's `process.env` proxy stringifies on write, so an
|
|
608
|
-
// embedder that assigns the flag at all
|
|
612
|
+
// embedder that assigns the flag at all, even `process.env.PROD = true`:
|
|
609
613
|
// leaves a string behind. The suite's own instance of this is fixed at the
|
|
610
614
|
// source (see `setModeFlag` in `src/tests/fixtures.ts`); this guards the
|
|
611
615
|
// writes we do not own.
|
|
612
616
|
minify: Boolean(import.meta.env.PROD),
|
|
613
617
|
define: await getDefines(),
|
|
614
618
|
// Installed packages stay bare imports and resolve through the import map
|
|
615
|
-
|
|
619
|
+
//, which covers every one of them by construction (`initImportMap`). The
|
|
616
620
|
// alternative was measured on `@vue-material/core`: each `/_nm/` bundle
|
|
617
621
|
// inlined its own copy of `vue`, and two Vue instances in one page is
|
|
618
622
|
// broken reactivity, not a size problem. A ref created by one Vue is
|
|
@@ -625,7 +629,7 @@ export async function bundleModule(
|
|
|
625
629
|
const content = await build.outputs[0].text()
|
|
626
630
|
|
|
627
631
|
// A bundle that cannot possibly work is not a success, whatever `build`
|
|
628
|
-
// says
|
|
632
|
+
// says. See `isEmptyExportList`. Repairable in the common case, so try
|
|
629
633
|
// that before refusing.
|
|
630
634
|
if (isEmptyExportList(content)) {
|
|
631
635
|
const repaired = await bundleReExportShim(path, await getDefines())
|
|
@@ -639,7 +643,7 @@ export async function bundleModule(
|
|
|
639
643
|
}
|
|
640
644
|
|
|
641
645
|
// Only for the shape that breaks, and only after the cheap build has proved
|
|
642
|
-
// it is that shape
|
|
646
|
+
// it is that shape, so nothing is imported speculatively.
|
|
643
647
|
if (isCjsDefaultOnly(content)) {
|
|
644
648
|
const interop = await bundleCjsWithNamedExports(
|
|
645
649
|
path,
|
|
@@ -29,7 +29,7 @@ export function notifySockets(server: any, filename: string) {
|
|
|
29
29
|
* `{type: 'error', title, body}`. client/livereload.ts renders it as an
|
|
30
30
|
* overlay and removes it on the next successful reload frame.
|
|
31
31
|
*
|
|
32
|
-
* Legacy reload frames stay plain filename strings
|
|
32
|
+
* Legacy reload frames stay plain filename strings: the client distinguishes
|
|
33
33
|
* the two by a leading `{`, so this must always be JSON and filenames must
|
|
34
34
|
* never be. Paths that cannot reach a live socket at all (worker dead, boot
|
|
35
35
|
* failure before `Bun.serve`) are covered client-side by the disconnect
|
|
@@ -38,7 +38,7 @@ export function notifySockets(server: any, filename: string) {
|
|
|
38
38
|
* Two producers reach here. The watcher's `catch` below sends internal watcher
|
|
39
39
|
* faults directly; every *request-time* failure arrives through `emitDevError`
|
|
40
40
|
* and the sink `startCompileService` installs. That second producer is the
|
|
41
|
-
* point of the whole apparatus
|
|
41
|
+
* point of the whole apparatus: for a long time only the first existed, so the
|
|
42
42
|
* overlay was reachable solely on an IO fault inside the watcher loop and never
|
|
43
43
|
* appeared for the failures developers actually hit.
|
|
44
44
|
*/
|
|
@@ -65,7 +65,7 @@ export const DEV_ERROR_PLUGIN = 'bakery:dev-error-overlay'
|
|
|
65
65
|
*
|
|
66
66
|
* 4xx is deliberately excluded. A 404 for a mistyped URL or an absent
|
|
67
67
|
* `favicon.ico` is the router working as designed, and covering the page for it
|
|
68
|
-
* would train developers to dismiss the overlay reflexively
|
|
68
|
+
* would train developers to dismiss the overlay reflexively, which is how the
|
|
69
69
|
* feature dies a second time. 5xx is the band that means "your code, or the
|
|
70
70
|
* framework, broke", and that is the band this overlay exists for.
|
|
71
71
|
*
|
|
@@ -113,7 +113,7 @@ export function formatDevErrorFrame(
|
|
|
113
113
|
const body = String(error?.errorBody ?? '')
|
|
114
114
|
|
|
115
115
|
return {
|
|
116
|
-
title: path ? `${code} ${text}
|
|
116
|
+
title: path ? `${code} ${text}: ${path}` : `${code} ${text}`,
|
|
117
117
|
body:
|
|
118
118
|
body.length > MAX_DEV_ERROR_BODY
|
|
119
119
|
? `${body.slice(0, MAX_DEV_ERROR_BODY)}\n… truncated`
|
|
@@ -132,7 +132,7 @@ export function formatDevErrorFrame(
|
|
|
132
132
|
* emits through a hook that is a no-op until it does.
|
|
133
133
|
*
|
|
134
134
|
* Null in PROD, in a cluster worker, and in every test that does not install
|
|
135
|
-
* one
|
|
135
|
+
* one: `emitDevError` is then a single optional call and nothing else runs.
|
|
136
136
|
*/
|
|
137
137
|
let devErrorSink: ((title: string, body: string) => void) | null = null
|
|
138
138
|
|
|
@@ -154,14 +154,14 @@ export function emitDevError(title: string, body: string): void {
|
|
|
154
154
|
* `ServerPlugin.onError` is the framework's own observation point for request
|
|
155
155
|
* errors: `router.ts handleRequestError` calls it first, before the plugin
|
|
156
156
|
* response check, before `config.onError`, and before the error-handler
|
|
157
|
-
* registry
|
|
157
|
+
* registry, so it sees every failure the error path sees (thrown handler,
|
|
158
158
|
* 5xx `Response`, and Bun's `error()` callback), with `errorBody` still holding
|
|
159
159
|
* the full stack. Using it is what lets the overlay be wired without router.ts
|
|
160
160
|
* or the error handlers ever hearing about the compiler.
|
|
161
161
|
*
|
|
162
162
|
* It returns `undefined` unconditionally. `PluginHooks.onError` treats that as
|
|
163
163
|
* "no opinion" and carries on down the list, so registering this cannot change
|
|
164
|
-
* which page a failing request answers with
|
|
164
|
+
* which page a failing request answers with: it only observes.
|
|
165
165
|
*/
|
|
166
166
|
export function createDevErrorPlugin(): ServerPlugin {
|
|
167
167
|
return {
|
|
@@ -186,7 +186,7 @@ export function createDevErrorPlugin(): ServerPlugin {
|
|
|
186
186
|
* running it first costs the others nothing.
|
|
187
187
|
*
|
|
188
188
|
* The array is mutated in place because `initConfig` hands back a frozen
|
|
189
|
-
* config
|
|
189
|
+
* config: `config.plugins = [...]` would throw, and the array itself is the
|
|
190
190
|
* only writable seam. Host configs are built with `{...base}` and never
|
|
191
191
|
* override `plugins`, so every host shares this exact array and one
|
|
192
192
|
* registration covers all of them. Idempotent by name, because a re-registration
|
|
@@ -339,7 +339,7 @@ export async function handleDevMaster(): Promise<never> {
|
|
|
339
339
|
...inspectArgs,
|
|
340
340
|
// Respawn whatever entry started this process. The entry now lives in
|
|
341
341
|
// @bakery-framework/cli, and core must not reach into a package that depends on
|
|
342
|
-
// it
|
|
342
|
+
// it: argv[1] is both correct and dependency-free.
|
|
343
343
|
process.argv[1],
|
|
344
344
|
'--dev',
|
|
345
345
|
'--dev-worker',
|
|
@@ -386,8 +386,8 @@ export async function handleDevMaster(): Promise<never> {
|
|
|
386
386
|
serveLog.RESTART_REQ()
|
|
387
387
|
// Clear only when the exiting worker had actually come up: then the
|
|
388
388
|
// scrollback is routine request logs and clearing keeps the console
|
|
389
|
-
// readable. A worker that exited before binding
|
|
390
|
-
// exits 42 mid-boot (orm/sync/engine.ts)
|
|
389
|
+
// readable. A worker that exited before binding: the sync engine also
|
|
390
|
+
// exits 42 mid-boot (orm/sync/engine.ts): printed the only record of
|
|
391
391
|
// why, and wiping it left the developer staring at a blank screen where
|
|
392
392
|
// the error had just been. Keeping scrollback is chosen over
|
|
393
393
|
// capture-and-reprint because worker stdio is inherited, not piped.
|
|
@@ -417,7 +417,7 @@ const fileTypeGlob = Glob.fromExt([
|
|
|
417
417
|
'jsx',
|
|
418
418
|
'vue',
|
|
419
419
|
])
|
|
420
|
-
// `.tsx`/`.jsx` are here
|
|
420
|
+
// `.tsx`/`.jsx` are here (the cheap path), and not in `prioFilesGlob`:
|
|
421
421
|
// TSXHandler re-imports the page module with `?v=<mtime>` (handlers/assets/
|
|
422
422
|
// tsx.ts, same mechanism as ApiHandler), so an edited page never serves stale
|
|
423
423
|
// and the route-cache flush + browser reload below is all a page edit needs.
|
|
@@ -430,7 +430,7 @@ const watchIgnores = Glob.strings(
|
|
|
430
430
|
// The framework's own cache directory (`Bakery.cacheDir`).
|
|
431
431
|
'**/.cache/**/*',
|
|
432
432
|
// `bakery/` (`Bakery.dataDir`) holds the database plus
|
|
433
|
-
// `backups/schema.<timestamp>.ts` files the DB backup writes at runtime
|
|
433
|
+
// `backups/schema.<timestamp>.ts` files the DB backup writes at runtime,
|
|
434
434
|
// without this, taking a backup while the dev server ran flushed the route
|
|
435
435
|
// cache and reloaded the browser. It is un-dotted on purpose (see
|
|
436
436
|
// `core/bakery.ts`), which is exactly why it has to be named here: a
|
|
@@ -443,20 +443,20 @@ const watchIgnores = Glob.strings(
|
|
|
443
443
|
)
|
|
444
444
|
|
|
445
445
|
// `'api/**/*'` used to be a third entry here, left over from when API routes
|
|
446
|
-
// lived at the repo root. They live under `<root>/api` now
|
|
447
|
-
// default
|
|
446
|
+
// lived at the repo root. They live under `<root>/api` now (`src/api` by
|
|
447
|
+
// default), so the pattern matched nothing and API routes never triggered the
|
|
448
448
|
// restart below. The route *file* still hot-reloaded through the `?v=<mtime>`
|
|
449
449
|
// cache-buster, which is why this looked fine: only its imports went stale.
|
|
450
450
|
// The replacement is `isBackendPriorityFile`, which asks the config where the
|
|
451
451
|
// api directory actually is instead of hard-coding it.
|
|
452
452
|
//
|
|
453
|
-
// `'**/*.tsx'` was the second entry here, and it made every page edit
|
|
454
|
-
// most common edit there is
|
|
453
|
+
// `'**/*.tsx'` was the second entry here, and it made every page edit (the
|
|
454
|
+
// most common edit there is) cost a full process restart (config, plugins,
|
|
455
455
|
// import map, tsconfig sync, schema-sync check). The only reason it needed a
|
|
456
456
|
// restart was Bun's module registry caching the imported page module, and
|
|
457
457
|
// TSXHandler now busts that per request in dev exactly as ApiHandler always
|
|
458
458
|
// has. Pages take the cheap path via `tsScriptGlob` above. The one thing the
|
|
459
|
-
// restart still bought
|
|
459
|
+
// restart still bought (flushing *components* a page imports) is a
|
|
460
460
|
// documented limitation (docs/getting-started/first-app.md): touch
|
|
461
461
|
// server.config.ts or restart when editing a shared Layout.tsx.
|
|
462
462
|
const prioFilesGlob = Glob.strings('server.config.ts')
|
|
@@ -465,8 +465,8 @@ const prioFilesGlob = Glob.strings('server.config.ts')
|
|
|
465
465
|
* Whether a change to `filePath` requires a full backend restart rather than a
|
|
466
466
|
* livereload notification.
|
|
467
467
|
*
|
|
468
|
-
* `filePath` is watcher-relative
|
|
469
|
-
* slashes
|
|
468
|
+
* `filePath` is watcher-relative, i.e. relative to the app's cwd, with forward
|
|
469
|
+
* slashes, so the configured api root is converted to the same shape.
|
|
470
470
|
*
|
|
471
471
|
* Host-specific roots are not consulted: a `hosts` entry can override `root`,
|
|
472
472
|
* but the watcher runs outside any request and so has no host context. The
|
|
@@ -490,8 +490,8 @@ export function isBackendPriorityFile(filePath: string): boolean {
|
|
|
490
490
|
* restart time. cli/dev.ts now hashes the schema sources, records the hash
|
|
491
491
|
* under `.cache/` after a *successful* sync only, and consults this
|
|
492
492
|
* before the next one. Pure so the fail-closed branches are testable: any
|
|
493
|
-
* indeterminate state (`currentHash: null`
|
|
494
|
-
* null`
|
|
493
|
+
* indeterminate state (`currentHash: null` (sources unreadable; `storedHash:
|
|
494
|
+
* null`) no successful sync on record) must sync, never skip.
|
|
495
495
|
*/
|
|
496
496
|
export function classifySchemaSync(opts: {
|
|
497
497
|
/** `--sync` / `-s` was passed: the developer asked, so sync regardless. */
|
|
@@ -534,7 +534,7 @@ const MODULE_ROUTE_EXT = /\.(tsx|jsx)$/
|
|
|
534
534
|
*
|
|
535
535
|
* Editing a `.tsx` page is instant: the page module carries a `?v=<mtime>`
|
|
536
536
|
* specifier, so Bun re-imports it and the change is served on the next
|
|
537
|
-
* request. Creating one is not
|
|
537
|
+
* request. Creating one is not: Bun caches the *directory listing* it
|
|
538
538
|
* resolved against, so a file that did not exist at boot fails to import at
|
|
539
539
|
* any specifier, including a freshly-stamped one. The page 500s with
|
|
540
540
|
* `Cannot find module '<path>?v=<mtime>'` until the process restarts. That is
|
|
@@ -543,13 +543,12 @@ const MODULE_ROUTE_EXT = /\.(tsx|jsx)$/
|
|
|
543
543
|
* change and restarts already.
|
|
544
544
|
*
|
|
545
545
|
* `rename` is the creation signal. Measured on Windows: an in-place write
|
|
546
|
-
* (`Bun.write`, `fs.writeFile`) emits only `change` and keeps the fast path
|
|
547
|
-
* 14-15ms edit-to-served — while creating a file emits `rename` then
|
|
546
|
+
* (`Bun.write`, `fs.writeFile`) emits only `change` and keeps the fast path ( * 14-15ms edit-to-served), while creating a file emits `rename` then
|
|
548
547
|
* `change`. Writers that replace rather than overwrite report `rename` for an
|
|
549
548
|
* *edit* too and pay one restart (~440ms): shell redirection (`> file`) does
|
|
550
549
|
* this, and an editor that saves atomically will as well. That is the safe
|
|
551
|
-
* direction to be wrong in
|
|
552
|
-
* at all
|
|
550
|
+
* direction to be wrong in (a slower save beats a page that does not serve
|
|
551
|
+
* at all), but it is why this is scoped as narrowly as possible.
|
|
553
552
|
* Deletes are `rename` with no file, and need no restart.
|
|
554
553
|
*
|
|
555
554
|
* Scoped to imported modules: a new `.html`, `.css` or client `.ts` is read or
|
|
@@ -577,7 +576,7 @@ async function processFileEvent(
|
|
|
577
576
|
}
|
|
578
577
|
|
|
579
578
|
// Checked before the cheap path below: a *new* page cannot be imported by
|
|
580
|
-
// this process at all
|
|
579
|
+
// this process at all. See `isCreatedRouteModule`.
|
|
581
580
|
if (
|
|
582
581
|
isCreatedRouteModule(
|
|
583
582
|
filePath,
|
|
@@ -636,7 +635,7 @@ export async function startCompileService(server: any): Promise<void> {
|
|
|
636
635
|
if (kind === 'ignored') continue
|
|
637
636
|
|
|
638
637
|
if (kind === 'package') {
|
|
639
|
-
// Log only, no restart
|
|
638
|
+
// Log only, no restart, deliberately. `bun install` rewrites the
|
|
640
639
|
// lockfile several times, and restarting on each would put the dev server
|
|
641
640
|
// in a loop for the duration of an install. The line tells you a restart
|
|
642
641
|
// is warranted; you decide when.
|
|
@@ -651,7 +650,7 @@ export async function startCompileService(server: any): Promise<void> {
|
|
|
651
650
|
// A single bad event must not kill the watcher: before this, a throw
|
|
652
651
|
// here unwound the for-await and file watching silently stopped until
|
|
653
652
|
// the next manual restart. The server is alive at this point, so the
|
|
654
|
-
// browser is told too
|
|
653
|
+
// browser is told too: this is the in-worker sender for the error
|
|
655
654
|
// overlay protocol.
|
|
656
655
|
serveLog.WATCHER_ERR({ error: String(error) })
|
|
657
656
|
notifyError(server, 'Dev watcher error', String(error))
|
|
@@ -3,7 +3,7 @@ import { cacheDir } from '../core/context'
|
|
|
3
3
|
import { Try } from '../utils/common/try'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* **`cacheDir` comes from `core/context`, not from `Bakery
|
|
6
|
+
* **`cacheDir` comes from `core/context`, not from `Bakery`. Do not change it
|
|
7
7
|
* back.** `logger.ts` imports this module, so importing `core/bakery` here
|
|
8
8
|
* completed a cycle:
|
|
9
9
|
*
|
|
@@ -14,7 +14,7 @@ import { Try } from '../utils/common/try'
|
|
|
14
14
|
* import arrived first found `Logger` still in its temporal dead zone. That
|
|
15
15
|
* shipped in 1.2.3 and made `import '@bakery-framework/core'` throw
|
|
16
16
|
* `ReferenceError: Cannot access 'Logger' before initialization` from a clean
|
|
17
|
-
* install
|
|
17
|
+
* install. See `tests/module-cycle.test.ts`.
|
|
18
18
|
*
|
|
19
19
|
* `core/context` holds the same single definition of the path and imports
|
|
20
20
|
* nothing that reaches the logger.
|