@akanjs/devkit 3.0.0-beta.24 → 3.0.0-beta.26
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/DEV_RUNTIME_KNOBS.md +44 -26
- package/akanApp/akanApp.host.ts +113 -23
- package/akanApp/devBootLatch.test.ts +39 -0
- package/akanApp/devBootLatch.ts +39 -0
- package/akanApp/devHostPolicy.test.ts +71 -1
- package/akanApp/devHostPolicy.ts +29 -4
- package/cloud/globalConfig.ts +3 -17
- package/csrE2e/csrE2eHarness.fixture.ts +66 -11
- package/csrE2e/csrPatchFirst.e2e.test.ts +87 -0
- package/csrE2e/devHmrLatency.e2e.test.ts +391 -0
- package/csrE2e/ssrRegistry.e2e.test.ts +644 -0
- package/fileSys.ts +16 -1
- package/frontendBuild/buildRouteClient.test.ts +61 -0
- package/frontendBuild/bundleDefine.test.ts +12 -0
- package/frontendBuild/bundleDefine.ts +5 -1
- package/frontendBuild/clientEntryDiscovery.test.ts +112 -0
- package/frontendBuild/clientEntryDiscovery.ts +117 -70
- package/frontendBuild/csrArtifactBuilder.ts +3 -6
- package/frontendBuild/csrDevArtifactWriter.test.ts +143 -0
- package/frontendBuild/csrDevArtifactWriter.ts +158 -24
- package/frontendBuild/csrDevBundler.ts +44 -205
- package/frontendBuild/csrDevModuleCompiler.test.ts +89 -0
- package/frontendBuild/csrDevModuleCompiler.ts +56 -10
- package/frontendBuild/csrDevPatcher.test.ts +572 -0
- package/frontendBuild/csrDevPatcher.ts +548 -0
- package/frontendBuild/csrDevPaths.ts +81 -1
- package/frontendBuild/csrDevResolver.test.ts +239 -0
- package/frontendBuild/csrDevResolver.ts +119 -18
- package/frontendBuild/csrDevTypes.ts +16 -1
- package/frontendBuild/csrEntryFiles.ts +24 -0
- package/frontendBuild/cssCompiler.ts +2 -4
- package/frontendBuild/devRegistryBundler.ts +192 -0
- package/frontendBuild/hmrWatcher.test.ts +51 -0
- package/frontendBuild/hmrWatcher.ts +17 -3
- package/frontendBuild/index.ts +3 -0
- package/frontendBuild/pagesBundleBuilder.ts +24 -1
- package/frontendBuild/pagesEntrySourceGenerator.ts +9 -2
- package/frontendBuild/registryMetadataFingerprint.test.ts +63 -0
- package/frontendBuild/registryMetadataFingerprint.ts +28 -0
- package/frontendBuild/routeClientBuilder.ts +79 -18
- package/frontendBuild/serverGraphFile.test.ts +77 -0
- package/frontendBuild/serverGraphFile.ts +93 -0
- package/frontendBuild/ssrDevBundler.ts +85 -0
- package/incrementalBuilder/buildBatch.proc.ts +94 -16
- package/incrementalBuilder/buildBatchProtocol.ts +6 -3
- package/incrementalBuilder/buildBatchRunner.test.ts +31 -0
- package/incrementalBuilder/buildBatchRunner.ts +17 -8
- package/incrementalBuilder/builderWorkQueue.test.ts +94 -0
- package/incrementalBuilder/builderWorkQueue.ts +126 -0
- package/incrementalBuilder/incrementalBuilder.host.test.ts +35 -1
- package/incrementalBuilder/incrementalBuilder.host.ts +50 -6
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +190 -2
- package/incrementalBuilder/incrementalBuilder.proc.ts +560 -60
- package/integration/devStability.integration.test.ts +25 -7
- package/package.json +2 -2
- package/transforms/rscUseClientTransform.ts +7 -0
- package/transforms/transforms.test.ts +13 -0
- package/transforms/useClientBundlePlugin.ts +4 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { CSR_DEV_ROUTE_PREFIX } from "akanjs/server/hmr/csrDevManifest";
|
|
3
2
|
import type { BunPlugin } from "bun";
|
|
4
3
|
import type { App } from "../commandDecorators";
|
|
5
4
|
import { CsrDevPaths } from "./csrDevPaths";
|
|
@@ -23,6 +22,21 @@ export interface CsrDevModuleCompilerOptions {
|
|
|
23
22
|
resolver: CsrDevResolver;
|
|
24
23
|
context: CsrDevContext;
|
|
25
24
|
outDir: string;
|
|
25
|
+
/** Where the dev server serves `outDir`: an imported asset's URL starts with it. */
|
|
26
|
+
routePrefix: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CsrDevCompileOptions {
|
|
30
|
+
/** Stop before building an npm module the graph does not know yet, and name it instead. */
|
|
31
|
+
refuseNewVendor?: boolean;
|
|
32
|
+
/** Stop at an import no recorded resolution answers, instead of running the resolution build (a whole-app build). */
|
|
33
|
+
refusePrepass?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CsrDevCompileResult {
|
|
37
|
+
modules: CsrDevCompiledModule[];
|
|
38
|
+
refusedVendors: string[];
|
|
39
|
+
unresolved: string[];
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
//* Every module becomes its own CJS factory: one Bun.build with each import external, so a save re-runs only the
|
|
@@ -40,21 +54,37 @@ export class CsrDevModuleCompiler {
|
|
|
40
54
|
readonly #resolver: CsrDevResolver;
|
|
41
55
|
readonly #context: CsrDevContext;
|
|
42
56
|
readonly #outDir: string;
|
|
57
|
+
readonly #routePrefix: string;
|
|
58
|
+
readonly #assets = new Set<string>();
|
|
43
59
|
|
|
44
|
-
constructor({ app, paths, resolver, context, outDir }: CsrDevModuleCompilerOptions) {
|
|
60
|
+
constructor({ app, paths, resolver, context, outDir, routePrefix }: CsrDevModuleCompilerOptions) {
|
|
45
61
|
this.#app = app;
|
|
46
62
|
this.#paths = paths;
|
|
47
63
|
this.#resolver = resolver;
|
|
48
64
|
this.#context = context;
|
|
49
65
|
this.#outDir = outDir;
|
|
66
|
+
this.#routePrefix = routePrefix;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Every asset file this compiler wrote into the registry directory. */
|
|
70
|
+
get assets(): string[] {
|
|
71
|
+
return [...this.#assets];
|
|
50
72
|
}
|
|
51
73
|
|
|
52
|
-
async compile(
|
|
74
|
+
async compile(
|
|
75
|
+
startFiles: string[],
|
|
76
|
+
known: Set<string>,
|
|
77
|
+
{ refuseNewVendor = false, refusePrepass = false }: CsrDevCompileOptions = {},
|
|
78
|
+
): Promise<CsrDevCompileResult> {
|
|
53
79
|
const compiled = new Map<string, CsrDevCompiledModule>();
|
|
54
80
|
const seen = new Set([...known, ...startFiles]);
|
|
55
81
|
let frontier = [...new Set(startFiles)];
|
|
56
82
|
while (frontier.length > 0) {
|
|
83
|
+
const refusedVendors = refuseNewVendor ? frontier.filter((file) => CsrDevPaths.isVendorFile(file)) : [];
|
|
84
|
+
if (refusedVendors.length > 0) return { modules: [...compiled.values()], refusedVendors, unresolved: [] };
|
|
57
85
|
const round = await this.#compileRound(frontier);
|
|
86
|
+
if (round.misses.length > 0 && !this.#resolver.prepassDone && refusePrepass)
|
|
87
|
+
return { modules: [...compiled.values()], refusedVendors: [], unresolved: round.misses };
|
|
58
88
|
if (round.misses.length > 0 && !this.#resolver.prepassDone) {
|
|
59
89
|
this.#app.verbose(`[csr-dev] ${round.misses.length} unresolved import(s); rerunning the resolution build`);
|
|
60
90
|
await this.#resolver.prepass();
|
|
@@ -76,7 +106,7 @@ export class CsrDevModuleCompiler {
|
|
|
76
106
|
this.#app.verbose(
|
|
77
107
|
`[csr-dev] ${fallbacks.size} import(s) outside the browser build's graph resolved with Bun's runtime resolver: ${[...fallbacks].slice(0, 5).join(", ")}${fallbacks.size > 5 ? ", ..." : ""}`,
|
|
78
108
|
);
|
|
79
|
-
return [...compiled.values()];
|
|
109
|
+
return { modules: [...compiled.values()], refusedVendors: [], unresolved: [] };
|
|
80
110
|
}
|
|
81
111
|
|
|
82
112
|
async #compileRound(files: string[]): Promise<Omit<BuildRound, "tangled">> {
|
|
@@ -100,9 +130,12 @@ export class CsrDevModuleCompiler {
|
|
|
100
130
|
}
|
|
101
131
|
|
|
102
132
|
async #bunBuild(files: string[], vendor: boolean): Promise<BuildRound> {
|
|
103
|
-
|
|
133
|
+
//? Keyed as the output is: by the disk's spelling, which a case-only rename leaves apart from the requested path.
|
|
134
|
+
const mtimes = new Map(files.map((file) => [CsrDevPaths.realpath(file), CsrDevPaths.mtimeOf(file)]));
|
|
104
135
|
const hashes = new Map(
|
|
105
|
-
await Promise.all(
|
|
136
|
+
await Promise.all(
|
|
137
|
+
files.map(async (file) => [CsrDevPaths.realpath(file), vendor ? "" : await CsrDevPaths.hashOf(file)] as const),
|
|
138
|
+
),
|
|
106
139
|
);
|
|
107
140
|
const misses: string[] = [];
|
|
108
141
|
const result = await Bun.build({
|
|
@@ -113,7 +146,7 @@ export class CsrDevModuleCompiler {
|
|
|
113
146
|
reactFastRefresh: !vendor,
|
|
114
147
|
sourcemap: vendor ? "none" : "external",
|
|
115
148
|
naming: { entry: "[dir]/[name].[ext]", asset: "assets/[name]-[hash].[ext]" },
|
|
116
|
-
publicPath:
|
|
149
|
+
publicPath: this.#routePrefix,
|
|
117
150
|
metafile: true,
|
|
118
151
|
env: "AKAN_PUBLIC_*",
|
|
119
152
|
define: this.#context.define,
|
|
@@ -128,7 +161,9 @@ export class CsrDevModuleCompiler {
|
|
|
128
161
|
const tangled: string[] = [];
|
|
129
162
|
for (const artifact of result.outputs) {
|
|
130
163
|
if (artifact.kind === "asset") {
|
|
131
|
-
|
|
164
|
+
const assetPath = path.join(this.#outDir, artifact.path);
|
|
165
|
+
await Bun.write(assetPath, artifact);
|
|
166
|
+
this.#assets.add(assetPath);
|
|
132
167
|
continue;
|
|
133
168
|
}
|
|
134
169
|
if (artifact.kind !== "entry-point") continue;
|
|
@@ -167,13 +202,20 @@ export class CsrDevModuleCompiler {
|
|
|
167
202
|
if (CsrDevPaths.isVendorFile(args.path)) return undefined;
|
|
168
203
|
const normalized = RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh(
|
|
169
204
|
await Bun.file(args.path).text(),
|
|
205
|
+
{ path: args.path },
|
|
170
206
|
);
|
|
171
207
|
return normalized ? { contents: normalized, loader: "tsx" } : undefined;
|
|
172
208
|
});
|
|
173
209
|
build.onResolve({ filter: /.*/ }, (args) => {
|
|
174
210
|
if (args.kind === "entry-point-build" || !args.importer) return undefined;
|
|
211
|
+
if (this.#context.externals?.includes(args.path))
|
|
212
|
+
return { path: `${CsrDevPaths.vendorPrefix}${args.path}`, external: true };
|
|
175
213
|
const importer = CsrDevPaths.realpath(args.importer);
|
|
176
|
-
const target = this.#resolver.resolve(
|
|
214
|
+
const target = this.#resolver.resolve(
|
|
215
|
+
importer,
|
|
216
|
+
args.path,
|
|
217
|
+
args.kind === "require-call" || args.kind === "require-resolve" ? "require" : "import",
|
|
218
|
+
);
|
|
177
219
|
if (target === CsrDevResolver.inline) return undefined;
|
|
178
220
|
if (target === null) {
|
|
179
221
|
misses.push(`${args.path} from ${this.#paths.idOf(importer)}`);
|
|
@@ -220,11 +262,15 @@ export class CsrDevModuleCompiler {
|
|
|
220
262
|
static #factory(id: string, code: string): { factory: string; helpers: CsrDevSharedHelpers | null } {
|
|
221
263
|
const prefix = CsrDevPaths.modulePrefix;
|
|
222
264
|
let patched = code.replaceAll(`import("${prefix}`, `__akanImport("${prefix}`).replace(/^\/\/# debugId=.*$/m, "");
|
|
265
|
+
//? A file esbuild already bundled (mermaid's parser chunks) defines its own helper under the same name, below Bun's
|
|
266
|
+
//? preamble, and Bun then emits none. Only a copy there is exempt: one in the preamble is Bun's own, changed.
|
|
267
|
+
const marker = patched.search(/^\/\/ /m);
|
|
268
|
+
const body = marker < 0 ? "" : patched.slice(marker);
|
|
223
269
|
for (const [name, params, method] of CsrDevModuleCompiler.#helperPatches) {
|
|
224
270
|
const definition = `var ${name} = ${params} => {`;
|
|
225
271
|
if (patched.includes(definition))
|
|
226
272
|
patched = patched.replace(definition, `var ${name} = __akan.${method}, __bun${name} = ${params} => {`);
|
|
227
|
-
else if (patched.includes(`${name}(`))
|
|
273
|
+
else if (patched.includes(`${name}(`) && !new RegExp(`\\b(?:var|let|const|function)\\s+${name}\\b`).test(body))
|
|
228
274
|
throw new Error(
|
|
229
275
|
`[csr-dev] ${id}: Bun's ${name} helper no longer reads "${definition}"; CsrDevModuleCompiler must follow`,
|
|
230
276
|
);
|