@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.
Files changed (58) hide show
  1. package/DEV_RUNTIME_KNOBS.md +44 -26
  2. package/akanApp/akanApp.host.ts +113 -23
  3. package/akanApp/devBootLatch.test.ts +39 -0
  4. package/akanApp/devBootLatch.ts +39 -0
  5. package/akanApp/devHostPolicy.test.ts +71 -1
  6. package/akanApp/devHostPolicy.ts +29 -4
  7. package/cloud/globalConfig.ts +3 -17
  8. package/csrE2e/csrE2eHarness.fixture.ts +66 -11
  9. package/csrE2e/csrPatchFirst.e2e.test.ts +87 -0
  10. package/csrE2e/devHmrLatency.e2e.test.ts +391 -0
  11. package/csrE2e/ssrRegistry.e2e.test.ts +644 -0
  12. package/fileSys.ts +16 -1
  13. package/frontendBuild/buildRouteClient.test.ts +61 -0
  14. package/frontendBuild/bundleDefine.test.ts +12 -0
  15. package/frontendBuild/bundleDefine.ts +5 -1
  16. package/frontendBuild/clientEntryDiscovery.test.ts +112 -0
  17. package/frontendBuild/clientEntryDiscovery.ts +117 -70
  18. package/frontendBuild/csrArtifactBuilder.ts +3 -6
  19. package/frontendBuild/csrDevArtifactWriter.test.ts +143 -0
  20. package/frontendBuild/csrDevArtifactWriter.ts +158 -24
  21. package/frontendBuild/csrDevBundler.ts +44 -205
  22. package/frontendBuild/csrDevModuleCompiler.test.ts +89 -0
  23. package/frontendBuild/csrDevModuleCompiler.ts +56 -10
  24. package/frontendBuild/csrDevPatcher.test.ts +572 -0
  25. package/frontendBuild/csrDevPatcher.ts +548 -0
  26. package/frontendBuild/csrDevPaths.ts +81 -1
  27. package/frontendBuild/csrDevResolver.test.ts +239 -0
  28. package/frontendBuild/csrDevResolver.ts +119 -18
  29. package/frontendBuild/csrDevTypes.ts +16 -1
  30. package/frontendBuild/csrEntryFiles.ts +24 -0
  31. package/frontendBuild/cssCompiler.ts +2 -4
  32. package/frontendBuild/devRegistryBundler.ts +192 -0
  33. package/frontendBuild/hmrWatcher.test.ts +51 -0
  34. package/frontendBuild/hmrWatcher.ts +17 -3
  35. package/frontendBuild/index.ts +3 -0
  36. package/frontendBuild/pagesBundleBuilder.ts +24 -1
  37. package/frontendBuild/pagesEntrySourceGenerator.ts +9 -2
  38. package/frontendBuild/registryMetadataFingerprint.test.ts +63 -0
  39. package/frontendBuild/registryMetadataFingerprint.ts +28 -0
  40. package/frontendBuild/routeClientBuilder.ts +79 -18
  41. package/frontendBuild/serverGraphFile.test.ts +77 -0
  42. package/frontendBuild/serverGraphFile.ts +93 -0
  43. package/frontendBuild/ssrDevBundler.ts +85 -0
  44. package/incrementalBuilder/buildBatch.proc.ts +94 -16
  45. package/incrementalBuilder/buildBatchProtocol.ts +6 -3
  46. package/incrementalBuilder/buildBatchRunner.test.ts +31 -0
  47. package/incrementalBuilder/buildBatchRunner.ts +17 -8
  48. package/incrementalBuilder/builderWorkQueue.test.ts +94 -0
  49. package/incrementalBuilder/builderWorkQueue.ts +126 -0
  50. package/incrementalBuilder/incrementalBuilder.host.test.ts +35 -1
  51. package/incrementalBuilder/incrementalBuilder.host.ts +50 -6
  52. package/incrementalBuilder/incrementalBuilder.proc.test.ts +190 -2
  53. package/incrementalBuilder/incrementalBuilder.proc.ts +560 -60
  54. package/integration/devStability.integration.test.ts +25 -7
  55. package/package.json +2 -2
  56. package/transforms/rscUseClientTransform.ts +7 -0
  57. package/transforms/transforms.test.ts +13 -0
  58. 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(startFiles: string[], known: Set<string>): Promise<CsrDevCompiledModule[]> {
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
- const mtimes = new Map(files.map((file) => [file, CsrDevPaths.mtimeOf(file)]));
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(files.map(async (file) => [file, vendor ? "" : await CsrDevPaths.hashOf(file)] as const)),
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: CSR_DEV_ROUTE_PREFIX,
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
- await Bun.write(path.join(this.#outDir, artifact.path), artifact);
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(importer, args.path);
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
  );