@akanjs/cli 3.0.0-beta.22 → 3.0.0-beta.24
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/.build-stamp +1 -1
- package/{TunnelShare-scf4e4pd.js → TunnelShare-pemaknmd.js} +2 -2
- package/{application.command-mm5v2gzp.js → application.command-z7a4bdrq.js} +1 -1
- package/{applicationBuildRunner-38exmejd.js → applicationBuildRunner-n6xfexaa.js} +1 -1
- package/buildBatch.proc.js +36 -3
- package/{cloud.command-f1pnvp2a.js → cloud.command-j0fcz44z.js} +4 -4
- package/{cloud.script-d61mcf0q.js → cloud.script-g2dcnr8c.js} +4 -4
- package/guidelines/agentRule/agentRule.instruction.md +7 -0
- package/guidelines/queryRule/queryRule.instruction.md +4 -0
- package/{index-sz0myv9k.js → index-d4mfjgm0.js} +3 -3
- package/{index-22d57v4c.js → index-fzd22vjh.js} +15 -1
- package/{index-aadpbnt2.js → index-ne50hc2m.js} +861 -61
- package/{index-m79y8b2h.js → index-ymgtbmf1.js} +3 -2
- package/{index-smme4r47.js → index-zabxte74.js} +1 -1
- package/{index-yjkte9aj.js → index-zbc2q6a6.js} +1 -1
- package/index.js +6 -6
- package/{localRegistry.command-pgbr7vjj.js → localRegistry.command-gmj1k6q8.js} +3 -3
- package/package.json +3 -3
- package/{subspace.command-g2tq9nrn.js → subspace.command-ksenprez.js} +2 -2
- package/templates/workspaceRoot/package.json.template +1 -3
- package/{tunnel.command-hsrv2mny.js → tunnel.command-54947jck.js} +2 -2
- package/{workspace.command-s82wd61q.js → workspace.command-tkn8g0bn.js} +4 -4
- package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
|
@@ -866,6 +866,7 @@ function createUseClientBundlePlugin(options = {}) {
|
|
|
866
866
|
import path6 from "path";
|
|
867
867
|
class PagesEntrySourceGenerator {
|
|
868
868
|
#pageEntries;
|
|
869
|
+
#asyncDefaults = new Map;
|
|
869
870
|
constructor(pageEntries) {
|
|
870
871
|
this.#pageEntries = pageEntries;
|
|
871
872
|
}
|
|
@@ -883,16 +884,16 @@ ${lines.join(`
|
|
|
883
884
|
};
|
|
884
885
|
`;
|
|
885
886
|
}
|
|
886
|
-
static async generateStatic(pageEntries) {
|
|
887
|
-
return await new PagesEntrySourceGenerator(pageEntries).generateStatic();
|
|
887
|
+
static async generateStatic(pageEntries, { fromDir } = {}) {
|
|
888
|
+
return await new PagesEntrySourceGenerator(pageEntries).generateStatic({ fromDir });
|
|
888
889
|
}
|
|
889
|
-
async generateStatic() {
|
|
890
|
+
async generateStatic({ fromDir } = {}) {
|
|
890
891
|
const imports = this.#pageEntries.map(({ moduleAbsPath }, index) => {
|
|
891
|
-
const specifier = PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
|
|
892
|
+
const specifier = fromDir ? PagesEntrySourceGenerator.#toRelativeSpecifier(fromDir, moduleAbsPath) : PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
|
|
892
893
|
return `import * as page${index} from ${JSON.stringify(specifier)};`;
|
|
893
894
|
});
|
|
894
895
|
const entries = await Promise.all(this.#pageEntries.map(async ({ key, moduleAbsPath }, index) => {
|
|
895
|
-
const isAsyncDefault = await
|
|
896
|
+
const isAsyncDefault = await this.#isAsyncDefault(moduleAbsPath);
|
|
896
897
|
return ` ${JSON.stringify(key)}: { loader: async () => page${index}, isAsyncDefault: ${isAsyncDefault} },`;
|
|
897
898
|
}));
|
|
898
899
|
return `${imports.join(`
|
|
@@ -903,9 +904,40 @@ ${entries.join(`
|
|
|
903
904
|
};
|
|
904
905
|
`;
|
|
905
906
|
}
|
|
907
|
+
async generateHotReplace({
|
|
908
|
+
fromDir,
|
|
909
|
+
ownerId,
|
|
910
|
+
moduleIds
|
|
911
|
+
}) {
|
|
912
|
+
const entries = await Promise.all(this.#pageEntries.map(async ({ key, moduleAbsPath }) => {
|
|
913
|
+
const specifier = PagesEntrySourceGenerator.#toRelativeSpecifier(fromDir, moduleAbsPath);
|
|
914
|
+
const isAsyncDefault = await this.#isAsyncDefault(moduleAbsPath);
|
|
915
|
+
return ` ${JSON.stringify(key)}: { loader: async () => require(${JSON.stringify(specifier)}), isAsyncDefault: ${isAsyncDefault} },`;
|
|
916
|
+
}));
|
|
917
|
+
return `__akan.accept(${JSON.stringify(ownerId)}, ${JSON.stringify(moduleIds)}, async () => {
|
|
918
|
+
const replaced = await akanWebkit.replacePages({
|
|
919
|
+
${entries.join(`
|
|
920
|
+
`)}
|
|
921
|
+
});
|
|
922
|
+
if (!replaced) throw new Error("the route table no longer matches the pages it was built from");
|
|
923
|
+
});
|
|
924
|
+
`;
|
|
925
|
+
}
|
|
926
|
+
#isAsyncDefault(moduleAbsPath) {
|
|
927
|
+
const known = this.#asyncDefaults.get(moduleAbsPath);
|
|
928
|
+
if (known)
|
|
929
|
+
return known;
|
|
930
|
+
const detected = AsyncDefaultExportDetector.detect(moduleAbsPath);
|
|
931
|
+
this.#asyncDefaults.set(moduleAbsPath, detected);
|
|
932
|
+
return detected;
|
|
933
|
+
}
|
|
906
934
|
static #toImportSpecifier(moduleAbsPath) {
|
|
907
935
|
return path6.resolve(moduleAbsPath).split(path6.sep).join("/");
|
|
908
936
|
}
|
|
937
|
+
static #toRelativeSpecifier(fromDir, moduleAbsPath) {
|
|
938
|
+
const relative = path6.relative(fromDir, path6.resolve(moduleAbsPath)).split(path6.sep).join("/");
|
|
939
|
+
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
940
|
+
}
|
|
909
941
|
}
|
|
910
942
|
|
|
911
943
|
// pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
|
|
@@ -1244,9 +1276,774 @@ ${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
|
|
|
1244
1276
|
}
|
|
1245
1277
|
}
|
|
1246
1278
|
|
|
1247
|
-
// pkgs/@akanjs/devkit/frontendBuild/
|
|
1248
|
-
import
|
|
1279
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevBundler.ts
|
|
1280
|
+
import fs3 from "fs";
|
|
1281
|
+
import { mkdir as mkdir3 } from "fs/promises";
|
|
1282
|
+
import path13 from "path";
|
|
1283
|
+
import { CSR_DEV_DIRNAME, CSR_DEV_MANIFEST_FILE as CSR_DEV_MANIFEST_FILE2 } from "akanjs/server/hmr/csrDevManifest";
|
|
1284
|
+
import { isAkanRuntimeMetadataFile } from "akanjs/server/hmr/runtimeMetadataFile";
|
|
1285
|
+
|
|
1286
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevArtifactWriter.ts
|
|
1287
|
+
import { mkdir as mkdir2, readdir, rename, rm as rm2 } from "fs/promises";
|
|
1288
|
+
import path10 from "path";
|
|
1289
|
+
import {
|
|
1290
|
+
CSR_DEV_APP_FILE,
|
|
1291
|
+
CSR_DEV_MANIFEST_FILE,
|
|
1292
|
+
CSR_DEV_ROUTE_PREFIX,
|
|
1293
|
+
csrDevModuleFile
|
|
1294
|
+
} from "akanjs/server/hmr/csrDevManifest";
|
|
1295
|
+
|
|
1296
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevPaths.ts
|
|
1297
|
+
import fs from "fs";
|
|
1249
1298
|
import path9 from "path";
|
|
1299
|
+
|
|
1300
|
+
class CsrDevPaths {
|
|
1301
|
+
static modulePrefix = "akan-module:";
|
|
1302
|
+
static stubPrefix = "stub:";
|
|
1303
|
+
root;
|
|
1304
|
+
constructor(workspaceRoot) {
|
|
1305
|
+
this.root = CsrDevPaths.realpath(workspaceRoot);
|
|
1306
|
+
}
|
|
1307
|
+
idOf(file) {
|
|
1308
|
+
const real = CsrDevPaths.realpath(file);
|
|
1309
|
+
const relative = path9.relative(this.root, real);
|
|
1310
|
+
const id = relative.startsWith("..") || path9.isAbsolute(relative) ? real : relative;
|
|
1311
|
+
return id.split(path9.sep).join("/");
|
|
1312
|
+
}
|
|
1313
|
+
fileOf(id) {
|
|
1314
|
+
return path9.isAbsolute(id) ? id : path9.join(this.root, id);
|
|
1315
|
+
}
|
|
1316
|
+
static isStub(target) {
|
|
1317
|
+
return target.startsWith(CsrDevPaths.stubPrefix);
|
|
1318
|
+
}
|
|
1319
|
+
static isVendorFile(file) {
|
|
1320
|
+
return file.includes(`${path9.sep}node_modules${path9.sep}`);
|
|
1321
|
+
}
|
|
1322
|
+
static isScript(file) {
|
|
1323
|
+
return /\.[cm]?[jt]sx?$/.test(file);
|
|
1324
|
+
}
|
|
1325
|
+
static mtimeOf(file) {
|
|
1326
|
+
return fs.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? -1;
|
|
1327
|
+
}
|
|
1328
|
+
static async hashOf(file) {
|
|
1329
|
+
const source = Bun.file(file);
|
|
1330
|
+
return await source.exists() ? Bun.hash(await source.arrayBuffer()).toString(36) : "";
|
|
1331
|
+
}
|
|
1332
|
+
static realpath(file) {
|
|
1333
|
+
return fs.existsSync(file) ? fs.realpathSync(file) : path9.resolve(file);
|
|
1334
|
+
}
|
|
1335
|
+
static tryResolve(specifier, from) {
|
|
1336
|
+
try {
|
|
1337
|
+
return Bun.resolveSync(specifier, from);
|
|
1338
|
+
} catch {
|
|
1339
|
+
return null;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
static lineCount(text) {
|
|
1343
|
+
let count = 0;
|
|
1344
|
+
for (let index = text.indexOf(`
|
|
1345
|
+
`);index !== -1; index = text.indexOf(`
|
|
1346
|
+
`, index + 1))
|
|
1347
|
+
count += 1;
|
|
1348
|
+
return count;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevArtifactWriter.ts
|
|
1353
|
+
class CsrDevArtifactWriter {
|
|
1354
|
+
static #keptPatches = 40;
|
|
1355
|
+
static #keptVendors = 3;
|
|
1356
|
+
#outDir;
|
|
1357
|
+
constructor(outDir) {
|
|
1358
|
+
this.#outDir = outDir;
|
|
1359
|
+
}
|
|
1360
|
+
async reset() {
|
|
1361
|
+
await rm2(this.#outDir, { recursive: true, force: true });
|
|
1362
|
+
await mkdir2(path10.join(this.#outDir, "modules"), { recursive: true });
|
|
1363
|
+
}
|
|
1364
|
+
async readJson(name) {
|
|
1365
|
+
const file = Bun.file(path10.join(this.#outDir, name));
|
|
1366
|
+
if (!await file.exists())
|
|
1367
|
+
return null;
|
|
1368
|
+
return await file.json().catch(() => null);
|
|
1369
|
+
}
|
|
1370
|
+
async writeJson(name, value) {
|
|
1371
|
+
await this.#writeAtomic(name, JSON.stringify(value));
|
|
1372
|
+
}
|
|
1373
|
+
async writeModules(compiled) {
|
|
1374
|
+
await mkdir2(path10.join(this.#outDir, "modules"), { recursive: true });
|
|
1375
|
+
const helpers = new Map(compiled.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
|
|
1376
|
+
await Promise.all([
|
|
1377
|
+
...compiled.flatMap((module) => [
|
|
1378
|
+
Bun.write(this.#modulePath(module.id, ".js"), module.factory),
|
|
1379
|
+
...module.sourceMap ? [Bun.write(this.#modulePath(module.id, ".js.map"), module.sourceMap)] : []
|
|
1380
|
+
]),
|
|
1381
|
+
...[...helpers.values()].map((shared) => Bun.write(this.#helpersPath(shared.hash), shared.definition))
|
|
1382
|
+
]);
|
|
1383
|
+
}
|
|
1384
|
+
async forgetModules(ids) {
|
|
1385
|
+
await Promise.all(ids.flatMap((id) => [
|
|
1386
|
+
rm2(this.#modulePath(id, ".js"), { force: true }),
|
|
1387
|
+
rm2(this.#modulePath(id, ".js.map"), { force: true })
|
|
1388
|
+
]));
|
|
1389
|
+
}
|
|
1390
|
+
async writeVendor(graph) {
|
|
1391
|
+
const ids = Object.keys(graph.modules).filter((id) => graph.modules[id]?.vendor).sort();
|
|
1392
|
+
const stubs = new Set(Object.values(graph.modules).flatMap((module) => module.deps.filter(CsrDevPaths.isStub)));
|
|
1393
|
+
const lines = [await this.#helperDefinitions(graph, ids), ...await this.#defineLines(ids)];
|
|
1394
|
+
for (const stub of [...stubs].sort()) {
|
|
1395
|
+
const message = `[akan-csr] ${stub.slice(CsrDevPaths.stubPrefix.length)} is a Node built-in the browser does not have`;
|
|
1396
|
+
lines.push(`__akan.define(${JSON.stringify(stub)}, function () {
|
|
1397
|
+
throw new Error(${JSON.stringify(message)});
|
|
1398
|
+
});
|
|
1399
|
+
`);
|
|
1400
|
+
}
|
|
1401
|
+
const code = lines.join("");
|
|
1402
|
+
const vendorFile = `vendor-${Bun.hash(code).toString(36)}.js`;
|
|
1403
|
+
await this.#writeAtomic(vendorFile, code);
|
|
1404
|
+
return vendorFile;
|
|
1405
|
+
}
|
|
1406
|
+
async writeApp(graph, generation) {
|
|
1407
|
+
const ids = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor).sort();
|
|
1408
|
+
const lines = await this.#defineLines(ids);
|
|
1409
|
+
const blocks = ids.map((id, index) => [id, lines[index] ?? ""]);
|
|
1410
|
+
const start = `__akan.start(${JSON.stringify({ generation, refresh: graph.refresh })});
|
|
1411
|
+
`;
|
|
1412
|
+
await this.#writeWithSourceMap(CSR_DEV_APP_FILE, await this.#helperDefinitions(graph, ids), blocks, start);
|
|
1413
|
+
}
|
|
1414
|
+
async writePatch(generation, modules) {
|
|
1415
|
+
const patchFile = `patch-${generation}.js`;
|
|
1416
|
+
const blocks = modules.map((module, index) => [module.id, `${JSON.stringify(module.id)}: ${module.factory}${index < modules.length - 1 ? `,
|
|
1417
|
+
` : `
|
|
1418
|
+
`}`]);
|
|
1419
|
+
const helpers = new Map(modules.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
|
|
1420
|
+
const header = `${[...helpers.values()].map((shared) => shared.definition).join("")}__akan.update(${generation}, {
|
|
1421
|
+
`;
|
|
1422
|
+
await this.#writeWithSourceMap(patchFile, header, blocks, `});
|
|
1423
|
+
`);
|
|
1424
|
+
return `${CSR_DEV_ROUTE_PREFIX}${patchFile}`;
|
|
1425
|
+
}
|
|
1426
|
+
async writeState(graph, manifest) {
|
|
1427
|
+
await this.writeJson("graph.json", graph);
|
|
1428
|
+
await this.writeJson(CSR_DEV_MANIFEST_FILE, manifest);
|
|
1429
|
+
}
|
|
1430
|
+
async prune(vendorFile, generation) {
|
|
1431
|
+
const names = await readdir(this.#outDir);
|
|
1432
|
+
const stale = names.filter((name) => {
|
|
1433
|
+
const patch = /^patch-(\d+)\.js(?:\.layout\.json)?$/.exec(name);
|
|
1434
|
+
return patch ? Number(patch[1]) <= generation - CsrDevArtifactWriter.#keptPatches : false;
|
|
1435
|
+
});
|
|
1436
|
+
const vendors = names.filter((name) => /^vendor-[\w-]+\.js$/.test(name) && name !== vendorFile).map((name) => ({ name, mtimeMs: CsrDevPaths.mtimeOf(path10.join(this.#outDir, name)) })).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(CsrDevArtifactWriter.#keptVendors - 1).map(({ name }) => name);
|
|
1437
|
+
await Promise.all([...stale, ...vendors].map((name) => rm2(path10.join(this.#outDir, name), { force: true })));
|
|
1438
|
+
}
|
|
1439
|
+
async#helperDefinitions(graph, ids) {
|
|
1440
|
+
const hashes = [...new Set(ids.flatMap((id) => graph.modules[id]?.helpers ?? []))].sort();
|
|
1441
|
+
return (await Promise.all(hashes.map((hash) => Bun.file(this.#helpersPath(hash)).text()))).join("");
|
|
1442
|
+
}
|
|
1443
|
+
async#defineLines(ids) {
|
|
1444
|
+
return await Promise.all(ids.map(async (id) => `__akan.define(${JSON.stringify(id)}, ${await Bun.file(this.#modulePath(id, ".js")).text()});
|
|
1445
|
+
`));
|
|
1446
|
+
}
|
|
1447
|
+
async#writeWithSourceMap(name, header, blocks, footer) {
|
|
1448
|
+
const layout = { lineCount: 0, modules: [] };
|
|
1449
|
+
let line = CsrDevPaths.lineCount(header);
|
|
1450
|
+
for (const [id, text] of blocks) {
|
|
1451
|
+
layout.modules.push([id, line + 1]);
|
|
1452
|
+
line += CsrDevPaths.lineCount(text);
|
|
1453
|
+
}
|
|
1454
|
+
const code = `${header}${blocks.map(([, text]) => text).join("")}${footer}//# sourceMappingURL=${name}.map
|
|
1455
|
+
`;
|
|
1456
|
+
layout.lineCount = CsrDevPaths.lineCount(code);
|
|
1457
|
+
await this.writeJson(`${name}.layout.json`, layout);
|
|
1458
|
+
await this.#writeAtomic(name, code);
|
|
1459
|
+
}
|
|
1460
|
+
async#writeAtomic(name, content) {
|
|
1461
|
+
const target = path10.join(this.#outDir, name);
|
|
1462
|
+
const temp = `${target}.${process.pid}.tmp`;
|
|
1463
|
+
await Bun.write(temp, content);
|
|
1464
|
+
await rename(temp, target);
|
|
1465
|
+
}
|
|
1466
|
+
#modulePath(id, extension) {
|
|
1467
|
+
return path10.join(this.#outDir, csrDevModuleFile(id, extension));
|
|
1468
|
+
}
|
|
1469
|
+
#helpersPath(hash) {
|
|
1470
|
+
return path10.join(this.#outDir, "modules", `helpers-${hash}.js`);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevModuleCompiler.ts
|
|
1475
|
+
import path12 from "path";
|
|
1476
|
+
import { CSR_DEV_ROUTE_PREFIX as CSR_DEV_ROUTE_PREFIX2 } from "akanjs/server/hmr/csrDevManifest";
|
|
1477
|
+
|
|
1478
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevResolver.ts
|
|
1479
|
+
import fs2 from "fs";
|
|
1480
|
+
import path11 from "path";
|
|
1481
|
+
class CsrDevResolver {
|
|
1482
|
+
static inline = "inline";
|
|
1483
|
+
#paths;
|
|
1484
|
+
#context;
|
|
1485
|
+
#entryFiles;
|
|
1486
|
+
#resolution = new Map;
|
|
1487
|
+
#fallbacks = new Set;
|
|
1488
|
+
#prepassDone = false;
|
|
1489
|
+
constructor({ paths, context, entryFiles, resolution = {} }) {
|
|
1490
|
+
this.#paths = paths;
|
|
1491
|
+
this.#context = context;
|
|
1492
|
+
this.#entryFiles = entryFiles;
|
|
1493
|
+
for (const [importer, bySpecifier] of Object.entries(resolution))
|
|
1494
|
+
this.#resolution.set(paths.fileOf(importer), new Map(Object.entries(bySpecifier).map(([specifier, target]) => [
|
|
1495
|
+
specifier,
|
|
1496
|
+
CsrDevPaths.isStub(target) ? target : paths.fileOf(target)
|
|
1497
|
+
])));
|
|
1498
|
+
}
|
|
1499
|
+
get prepassDone() {
|
|
1500
|
+
return this.#prepassDone;
|
|
1501
|
+
}
|
|
1502
|
+
get fallbacks() {
|
|
1503
|
+
return this.#fallbacks;
|
|
1504
|
+
}
|
|
1505
|
+
async prepass() {
|
|
1506
|
+
const result = await Bun.build({
|
|
1507
|
+
entrypoints: [...this.#entryFiles, this.#context.refreshFile],
|
|
1508
|
+
target: "browser",
|
|
1509
|
+
metafile: true,
|
|
1510
|
+
env: "AKAN_PUBLIC_*",
|
|
1511
|
+
define: this.#context.define,
|
|
1512
|
+
optimizeImports: this.#context.optimizeImports,
|
|
1513
|
+
plugins: [PagesBundleBuilder.createCssStubPlugin()]
|
|
1514
|
+
});
|
|
1515
|
+
const { inputs } = result.metafile;
|
|
1516
|
+
for (const [input, { imports }] of Object.entries(inputs)) {
|
|
1517
|
+
const importer = CsrDevPaths.realpath(path11.resolve(input));
|
|
1518
|
+
for (const record of imports) {
|
|
1519
|
+
if (!record.original)
|
|
1520
|
+
continue;
|
|
1521
|
+
if (record.external) {
|
|
1522
|
+
if (record.original.startsWith("node:"))
|
|
1523
|
+
this.#remember(importer, record.original, `${CsrDevPaths.stubPrefix}${record.original}`);
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
const target = path11.resolve(record.path);
|
|
1527
|
+
if (fs2.existsSync(target))
|
|
1528
|
+
this.#remember(importer, record.original, CsrDevPaths.realpath(target));
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
this.#prepassDone = true;
|
|
1532
|
+
}
|
|
1533
|
+
resolve(importer, specifier) {
|
|
1534
|
+
if (this.#isInlineSpecifier(specifier))
|
|
1535
|
+
return CsrDevResolver.inline;
|
|
1536
|
+
const known = this.#resolution.get(importer)?.get(specifier);
|
|
1537
|
+
const usable = known && (CsrDevPaths.isStub(known) || fs2.existsSync(known)) ? known : null;
|
|
1538
|
+
const target = usable ?? this.#resolveUnknown(importer, specifier);
|
|
1539
|
+
if (target === null || CsrDevPaths.isStub(target))
|
|
1540
|
+
return target;
|
|
1541
|
+
if (!CsrDevPaths.isScript(target))
|
|
1542
|
+
return CsrDevResolver.inline;
|
|
1543
|
+
if (!usable)
|
|
1544
|
+
this.#remember(importer, specifier, target);
|
|
1545
|
+
return target;
|
|
1546
|
+
}
|
|
1547
|
+
isRegistryModule(file) {
|
|
1548
|
+
if (!CsrDevPaths.isScript(file))
|
|
1549
|
+
return false;
|
|
1550
|
+
const packageName = CsrDevResolver.#packageNameOf(file);
|
|
1551
|
+
if (packageName === null)
|
|
1552
|
+
return true;
|
|
1553
|
+
return !this.#context.optimizeImports.some((pattern) => {
|
|
1554
|
+
const base = CsrDevResolver.#patternBase(pattern);
|
|
1555
|
+
return base === packageName || base.startsWith(`${packageName}/`);
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
serialize() {
|
|
1559
|
+
return Object.fromEntries([...this.#resolution].map(([importer, bySpecifier]) => [
|
|
1560
|
+
this.#paths.idOf(importer),
|
|
1561
|
+
Object.fromEntries([...bySpecifier].map(([specifier, target]) => [
|
|
1562
|
+
specifier,
|
|
1563
|
+
CsrDevPaths.isStub(target) ? target : this.#paths.idOf(target)
|
|
1564
|
+
]))
|
|
1565
|
+
]));
|
|
1566
|
+
}
|
|
1567
|
+
#resolveUnknown(importer, specifier) {
|
|
1568
|
+
if (specifier.startsWith("node:"))
|
|
1569
|
+
return `${CsrDevPaths.stubPrefix}${specifier}`;
|
|
1570
|
+
const relative = specifier.startsWith(".") || path11.isAbsolute(specifier);
|
|
1571
|
+
if (!relative && !this.#prepassDone)
|
|
1572
|
+
return null;
|
|
1573
|
+
if (!relative) {
|
|
1574
|
+
const sibling = this.#resolvedBySibling(importer, specifier);
|
|
1575
|
+
if (sibling)
|
|
1576
|
+
return sibling;
|
|
1577
|
+
}
|
|
1578
|
+
const resolved = CsrDevPaths.tryResolve(specifier, path11.dirname(importer));
|
|
1579
|
+
if (!resolved)
|
|
1580
|
+
throw new Error(`[csr-dev] cannot resolve "${specifier}" from ${this.#paths.idOf(importer)}`);
|
|
1581
|
+
if (!path11.isAbsolute(resolved))
|
|
1582
|
+
return `${CsrDevPaths.stubPrefix}${specifier}`;
|
|
1583
|
+
if (!relative)
|
|
1584
|
+
this.#fallbacks.add(specifier);
|
|
1585
|
+
return CsrDevPaths.realpath(resolved);
|
|
1586
|
+
}
|
|
1587
|
+
#resolvedBySibling(importer, specifier) {
|
|
1588
|
+
const scope = CsrDevResolver.#packageScopeOf(importer);
|
|
1589
|
+
for (const [other, bySpecifier] of this.#resolution) {
|
|
1590
|
+
const target = bySpecifier.get(specifier);
|
|
1591
|
+
if (target && CsrDevResolver.#packageScopeOf(other) === scope)
|
|
1592
|
+
return target;
|
|
1593
|
+
}
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
#remember(importer, specifier, target) {
|
|
1597
|
+
const byImporter = this.#resolution.get(importer) ?? new Map;
|
|
1598
|
+
this.#resolution.set(importer, byImporter.set(specifier, target));
|
|
1599
|
+
}
|
|
1600
|
+
#isInlineSpecifier(specifier) {
|
|
1601
|
+
if (specifier.startsWith(".") || path11.isAbsolute(specifier) || specifier.startsWith("node:"))
|
|
1602
|
+
return false;
|
|
1603
|
+
return this.#context.optimizeImports.some((pattern) => {
|
|
1604
|
+
const base = CsrDevResolver.#patternBase(pattern);
|
|
1605
|
+
return specifier === base || specifier.startsWith(`${base}/`);
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
static #patternBase(pattern) {
|
|
1609
|
+
return pattern.endsWith("/*") ? pattern.slice(0, -2) : pattern;
|
|
1610
|
+
}
|
|
1611
|
+
static #packageNameOf(file) {
|
|
1612
|
+
const marker = `${path11.sep}node_modules${path11.sep}`;
|
|
1613
|
+
const index = file.lastIndexOf(marker);
|
|
1614
|
+
if (index < 0)
|
|
1615
|
+
return null;
|
|
1616
|
+
const [scope, name] = file.slice(index + marker.length).split(path11.sep);
|
|
1617
|
+
return (scope?.startsWith("@") ? `${scope}/${name}` : scope) || null;
|
|
1618
|
+
}
|
|
1619
|
+
static #packageScopeOf(file) {
|
|
1620
|
+
const marker = `${path11.sep}node_modules${path11.sep}`;
|
|
1621
|
+
const index = file.lastIndexOf(marker);
|
|
1622
|
+
if (index < 0)
|
|
1623
|
+
return "";
|
|
1624
|
+
const [scope, name] = file.slice(index + marker.length).split(path11.sep);
|
|
1625
|
+
return file.slice(0, index + marker.length) + (scope?.startsWith("@") ? `${scope}${path11.sep}${name}` : scope);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevModuleCompiler.ts
|
|
1630
|
+
class CsrDevModuleCompiler {
|
|
1631
|
+
static #storeRoot = /(?:^|\/)lib\/st\.ts$/;
|
|
1632
|
+
static #factoryArgument = /(?<![\w$.])(?:require|module|exports|__akanImport|\$RefreshReg\$|\$RefreshSig\$)(?![\w$]|\s*:)/;
|
|
1633
|
+
static #helperPatches = [
|
|
1634
|
+
["__toESM", "(mod, isNodeMode, target)", "toESM"],
|
|
1635
|
+
["__reExport", "(target, mod, secondTarget)", "reExport"]
|
|
1636
|
+
];
|
|
1637
|
+
#app;
|
|
1638
|
+
#paths;
|
|
1639
|
+
#resolver;
|
|
1640
|
+
#context;
|
|
1641
|
+
#outDir;
|
|
1642
|
+
constructor({ app, paths, resolver, context, outDir }) {
|
|
1643
|
+
this.#app = app;
|
|
1644
|
+
this.#paths = paths;
|
|
1645
|
+
this.#resolver = resolver;
|
|
1646
|
+
this.#context = context;
|
|
1647
|
+
this.#outDir = outDir;
|
|
1648
|
+
}
|
|
1649
|
+
async compile(startFiles, known) {
|
|
1650
|
+
const compiled = new Map;
|
|
1651
|
+
const seen = new Set([...known, ...startFiles]);
|
|
1652
|
+
let frontier = [...new Set(startFiles)];
|
|
1653
|
+
while (frontier.length > 0) {
|
|
1654
|
+
const round = await this.#compileRound(frontier);
|
|
1655
|
+
if (round.misses.length > 0 && !this.#resolver.prepassDone) {
|
|
1656
|
+
this.#app.verbose(`[csr-dev] ${round.misses.length} unresolved import(s); rerunning the resolution build`);
|
|
1657
|
+
await this.#resolver.prepass();
|
|
1658
|
+
continue;
|
|
1659
|
+
}
|
|
1660
|
+
const next = [];
|
|
1661
|
+
for (const module of round.modules) {
|
|
1662
|
+
compiled.set(module.file, module);
|
|
1663
|
+
for (const dep of module.deps) {
|
|
1664
|
+
if (CsrDevPaths.isStub(dep) || seen.has(dep))
|
|
1665
|
+
continue;
|
|
1666
|
+
seen.add(dep);
|
|
1667
|
+
next.push(dep);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
frontier = next;
|
|
1671
|
+
}
|
|
1672
|
+
const { fallbacks } = this.#resolver;
|
|
1673
|
+
if (fallbacks.size > 0)
|
|
1674
|
+
this.#app.verbose(`[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 ? ", ..." : ""}`);
|
|
1675
|
+
return [...compiled.values()];
|
|
1676
|
+
}
|
|
1677
|
+
async#compileRound(files) {
|
|
1678
|
+
const appFiles = files.filter((file) => !CsrDevPaths.isVendorFile(file));
|
|
1679
|
+
const vendorFiles = files.filter((file) => CsrDevPaths.isVendorFile(file));
|
|
1680
|
+
const rounds = await Promise.all([
|
|
1681
|
+
appFiles.length > 0 ? this.#bunBuild(appFiles, false) : null,
|
|
1682
|
+
vendorFiles.length > 0 ? this.#bunBuild(vendorFiles, true) : null
|
|
1683
|
+
]);
|
|
1684
|
+
const modules = rounds.flatMap((round) => round?.modules ?? []);
|
|
1685
|
+
const misses = rounds.flatMap((round) => round?.misses ?? []);
|
|
1686
|
+
for (const file of rounds.flatMap((round) => round?.tangled ?? [])) {
|
|
1687
|
+
const single = await this.#bunBuild([file], CsrDevPaths.isVendorFile(file));
|
|
1688
|
+
if (single.tangled.length > 0)
|
|
1689
|
+
throw new Error(`[csr-dev] ${this.#paths.idOf(file)} kept inlining another module`);
|
|
1690
|
+
modules.push(...single.modules);
|
|
1691
|
+
misses.push(...single.misses);
|
|
1692
|
+
}
|
|
1693
|
+
return { modules, misses };
|
|
1694
|
+
}
|
|
1695
|
+
async#bunBuild(files, vendor) {
|
|
1696
|
+
const mtimes = new Map(files.map((file) => [file, CsrDevPaths.mtimeOf(file)]));
|
|
1697
|
+
const hashes = new Map(await Promise.all(files.map(async (file) => [file, vendor ? "" : await CsrDevPaths.hashOf(file)])));
|
|
1698
|
+
const misses = [];
|
|
1699
|
+
const result = await Bun.build({
|
|
1700
|
+
entrypoints: files,
|
|
1701
|
+
root: this.#paths.root,
|
|
1702
|
+
target: "browser",
|
|
1703
|
+
format: "cjs",
|
|
1704
|
+
reactFastRefresh: !vendor,
|
|
1705
|
+
sourcemap: vendor ? "none" : "external",
|
|
1706
|
+
naming: { entry: "[dir]/[name].[ext]", asset: "assets/[name]-[hash].[ext]" },
|
|
1707
|
+
publicPath: CSR_DEV_ROUTE_PREFIX2,
|
|
1708
|
+
metafile: true,
|
|
1709
|
+
env: "AKAN_PUBLIC_*",
|
|
1710
|
+
define: this.#context.define,
|
|
1711
|
+
optimizeImports: this.#context.optimizeImports,
|
|
1712
|
+
plugins: [this.#registryPlugin(vendor, misses)]
|
|
1713
|
+
});
|
|
1714
|
+
const { outputs } = result.metafile;
|
|
1715
|
+
const sourceMaps = new Map(result.outputs.filter((artifact) => artifact.kind === "sourcemap").map((artifact) => [artifact.path, artifact]));
|
|
1716
|
+
const modules = [];
|
|
1717
|
+
const tangled = [];
|
|
1718
|
+
for (const artifact of result.outputs) {
|
|
1719
|
+
if (artifact.kind === "asset") {
|
|
1720
|
+
await Bun.write(path12.join(this.#outDir, artifact.path), artifact);
|
|
1721
|
+
continue;
|
|
1722
|
+
}
|
|
1723
|
+
if (artifact.kind !== "entry-point")
|
|
1724
|
+
continue;
|
|
1725
|
+
const meta = outputs[artifact.path];
|
|
1726
|
+
if (!meta?.entryPoint)
|
|
1727
|
+
throw new Error(`[csr-dev] no entry point recorded for ${artifact.path}`);
|
|
1728
|
+
const file = CsrDevPaths.realpath(path12.resolve(meta.entryPoint));
|
|
1729
|
+
const inputs = Object.keys(meta.inputs).map((input) => CsrDevPaths.realpath(path12.resolve(input)));
|
|
1730
|
+
if (inputs.some((input) => input !== file && this.#resolver.isRegistryModule(input))) {
|
|
1731
|
+
tangled.push(file);
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
const code = await artifact.text();
|
|
1735
|
+
const id = this.#paths.idOf(file);
|
|
1736
|
+
const sourceMap = sourceMaps.get(`${artifact.path}.map`);
|
|
1737
|
+
modules.push({
|
|
1738
|
+
id,
|
|
1739
|
+
file,
|
|
1740
|
+
vendor,
|
|
1741
|
+
...CsrDevModuleCompiler.#factory(id, code),
|
|
1742
|
+
sourceMap: sourceMap ? this.#rebaseSourceMap(await sourceMap.text()) : undefined,
|
|
1743
|
+
deps: this.#emittedDeps(code),
|
|
1744
|
+
mtimeMs: mtimes.get(file) ?? CsrDevPaths.mtimeOf(file),
|
|
1745
|
+
hash: hashes.get(file) ?? ""
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
return { modules, misses, tangled };
|
|
1749
|
+
}
|
|
1750
|
+
#registryPlugin(vendor, misses) {
|
|
1751
|
+
return {
|
|
1752
|
+
name: "akan-csr-registry",
|
|
1753
|
+
setup: (build) => {
|
|
1754
|
+
build.onLoad({ filter: /\.css$/ }, () => ({ contents: "", loader: "js" }));
|
|
1755
|
+
if (!vendor)
|
|
1756
|
+
build.onLoad({ filter: /\.tsx$/ }, async (args) => {
|
|
1757
|
+
if (CsrDevPaths.isVendorFile(args.path))
|
|
1758
|
+
return;
|
|
1759
|
+
const normalized = RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh(await Bun.file(args.path).text());
|
|
1760
|
+
return normalized ? { contents: normalized, loader: "tsx" } : undefined;
|
|
1761
|
+
});
|
|
1762
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
1763
|
+
if (args.kind === "entry-point-build" || !args.importer)
|
|
1764
|
+
return;
|
|
1765
|
+
const importer = CsrDevPaths.realpath(args.importer);
|
|
1766
|
+
const target = this.#resolver.resolve(importer, args.path);
|
|
1767
|
+
if (target === CsrDevResolver.inline)
|
|
1768
|
+
return;
|
|
1769
|
+
if (target === null) {
|
|
1770
|
+
misses.push(`${args.path} from ${this.#paths.idOf(importer)}`);
|
|
1771
|
+
return { path: `akan-miss:${args.path}`, external: true };
|
|
1772
|
+
}
|
|
1773
|
+
const external = CsrDevPaths.isStub(target) ? target : `${CsrDevPaths.modulePrefix}${this.#paths.idOf(target)}`;
|
|
1774
|
+
return { path: external, external: true };
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
#rebaseSourceMap(text) {
|
|
1780
|
+
const map = JSON.parse(text);
|
|
1781
|
+
const files = map.sources.map((source) => path12.resolve(source));
|
|
1782
|
+
map.sources = files.map((file) => `/${this.#paths.idOf(file)}`);
|
|
1783
|
+
map.sourcesContent = files.map((file, index) => CsrDevPaths.isVendorFile(file) ? null : map.sourcesContent?.[index] ?? null);
|
|
1784
|
+
return JSON.stringify(map);
|
|
1785
|
+
}
|
|
1786
|
+
#emittedDeps(code) {
|
|
1787
|
+
const deps = new Set;
|
|
1788
|
+
const references = /\b(?:require|import|__akanImport)\("((?:akan-module|stub):[^"]+)"\)/g;
|
|
1789
|
+
for (const [, reference] of code.matchAll(references)) {
|
|
1790
|
+
if (!reference)
|
|
1791
|
+
continue;
|
|
1792
|
+
deps.add(CsrDevPaths.isStub(reference) ? reference : this.#paths.fileOf(reference.slice(CsrDevPaths.modulePrefix.length)));
|
|
1793
|
+
}
|
|
1794
|
+
return [...deps];
|
|
1795
|
+
}
|
|
1796
|
+
static #factory(id, code) {
|
|
1797
|
+
const prefix = CsrDevPaths.modulePrefix;
|
|
1798
|
+
let patched = code.replaceAll(`import("${prefix}`, `__akanImport("${prefix}`).replace(/^\/\/# debugId=.*$/m, "");
|
|
1799
|
+
for (const [name, params, method] of CsrDevModuleCompiler.#helperPatches) {
|
|
1800
|
+
const definition = `var ${name} = ${params} => {`;
|
|
1801
|
+
if (patched.includes(definition))
|
|
1802
|
+
patched = patched.replace(definition, `var ${name} = __akan.${method}, __bun${name} = ${params} => {`);
|
|
1803
|
+
else if (patched.includes(`${name}(`))
|
|
1804
|
+
throw new Error(`[csr-dev] ${id}: Bun's ${name} helper no longer reads "${definition}"; CsrDevModuleCompiler must follow`);
|
|
1805
|
+
}
|
|
1806
|
+
if (CsrDevModuleCompiler.#storeRoot.test(id))
|
|
1807
|
+
patched += `
|
|
1808
|
+
module.hot.accept();`;
|
|
1809
|
+
const shared = CsrDevModuleCompiler.#shareHelpers(patched);
|
|
1810
|
+
return {
|
|
1811
|
+
factory: `function (require, module, exports, $RefreshReg$, $RefreshSig$, __akanImport) {
|
|
1812
|
+
${shared?.code ?? patched}
|
|
1813
|
+
}`,
|
|
1814
|
+
helpers: shared?.helpers ?? null
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
static #shareHelpers(code) {
|
|
1818
|
+
const marker = code.search(/^\/\/ /m);
|
|
1819
|
+
if (marker <= 0)
|
|
1820
|
+
return null;
|
|
1821
|
+
const preamble = code.slice(0, marker);
|
|
1822
|
+
const names = [...preamble.matchAll(/^(?:var|let|const|function)\s+([\w$]+)/gm)].map(([, name]) => name).filter((name) => !!name);
|
|
1823
|
+
if (names.length === 0 || CsrDevModuleCompiler.#factoryArgument.test(preamble))
|
|
1824
|
+
return null;
|
|
1825
|
+
const hash = Bun.hash(preamble).toString(36);
|
|
1826
|
+
const binding = `var { ${names.join(", ")} } = __akan.helpers(${JSON.stringify(hash)});`;
|
|
1827
|
+
return {
|
|
1828
|
+
code: `${binding}${`
|
|
1829
|
+
`.repeat(CsrDevPaths.lineCount(preamble))}${code.slice(marker)}`,
|
|
1830
|
+
helpers: {
|
|
1831
|
+
hash,
|
|
1832
|
+
definition: `__akan.defineHelpers(${JSON.stringify(hash)}, function () {
|
|
1833
|
+
${preamble}return { ${names.join(", ")} };
|
|
1834
|
+
});
|
|
1835
|
+
`
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrDevBundler.ts
|
|
1842
|
+
class CsrDevBundler {
|
|
1843
|
+
static #formatVersion = 3;
|
|
1844
|
+
#app;
|
|
1845
|
+
#paths;
|
|
1846
|
+
#outDir;
|
|
1847
|
+
#entryDir;
|
|
1848
|
+
#writer;
|
|
1849
|
+
constructor(app) {
|
|
1850
|
+
this.#app = app;
|
|
1851
|
+
this.#paths = new CsrDevPaths(app.workspace.workspaceRoot);
|
|
1852
|
+
this.#outDir = path13.join(app.cwdPath, ".akan/artifact", CSR_DEV_DIRNAME);
|
|
1853
|
+
this.#entryDir = path13.join(app.cwdPath, ".akan/generated", CSR_DEV_DIRNAME);
|
|
1854
|
+
this.#writer = new CsrDevArtifactWriter(this.#outDir);
|
|
1855
|
+
}
|
|
1856
|
+
async update(changedFiles = []) {
|
|
1857
|
+
const context = await this.#context();
|
|
1858
|
+
if (context.pageEntries.length === 0)
|
|
1859
|
+
return null;
|
|
1860
|
+
const manifest = await this.#writer.readJson(CSR_DEV_MANIFEST_FILE2);
|
|
1861
|
+
const graph = await this.#writer.readJson("graph.json");
|
|
1862
|
+
const generation = (manifest?.generation ?? 0) + 1;
|
|
1863
|
+
if (!manifest || !graph || graph.version !== 1 || graph.configKey !== context.configKey)
|
|
1864
|
+
return await this.#fullBuild(context, generation, manifest ? "the dev bundle config changed" : "first build");
|
|
1865
|
+
const metadataFile = changedFiles.find(isAkanRuntimeMetadataFile);
|
|
1866
|
+
if (metadataFile)
|
|
1867
|
+
return await this.#fullBuild(context, generation, `${path13.basename(metadataFile)} changed`);
|
|
1868
|
+
return await this.#incrementalBuild(context, graph, manifest, changedFiles, generation);
|
|
1869
|
+
}
|
|
1870
|
+
async#context() {
|
|
1871
|
+
const akanConfig = await this.#app.getConfig();
|
|
1872
|
+
const pageEntries = await resolveSsrPageEntriesForApp(this.#app, await this.#app.getPageKeys());
|
|
1873
|
+
const basePaths = [...akanConfig.basePaths];
|
|
1874
|
+
const htmlBasePaths = basePaths.length > 0 ? basePaths : [""];
|
|
1875
|
+
const define = bundleDefine(this.#app, "start", "csr");
|
|
1876
|
+
const optimizeImports = [...akanConfig.optimizeImports];
|
|
1877
|
+
const configKey = Bun.hash(JSON.stringify([CsrDevBundler.#formatVersion, Bun.version, define, optimizeImports, htmlBasePaths])).toString(36);
|
|
1878
|
+
return {
|
|
1879
|
+
pageEntries,
|
|
1880
|
+
basePaths,
|
|
1881
|
+
htmlBasePaths,
|
|
1882
|
+
define,
|
|
1883
|
+
optimizeImports,
|
|
1884
|
+
configKey,
|
|
1885
|
+
refreshFile: this.#resolveRefreshRuntime()
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
#resolveRefreshRuntime() {
|
|
1889
|
+
const akanWebkit = CsrDevPaths.tryResolve("akanjs/webkit", this.#app.cwdPath);
|
|
1890
|
+
const bases = [
|
|
1891
|
+
this.#app.cwdPath,
|
|
1892
|
+
this.#paths.root,
|
|
1893
|
+
...akanWebkit ? [path13.dirname(path13.dirname(akanWebkit))] : []
|
|
1894
|
+
];
|
|
1895
|
+
for (const base of bases) {
|
|
1896
|
+
const resolved = CsrDevPaths.tryResolve("react-refresh/runtime", base);
|
|
1897
|
+
if (resolved)
|
|
1898
|
+
return CsrDevPaths.realpath(resolved);
|
|
1899
|
+
}
|
|
1900
|
+
throw new Error(`[csr-dev] react-refresh/runtime is not resolvable from ${bases.join(", ")}`);
|
|
1901
|
+
}
|
|
1902
|
+
async#fullBuild(context, generation, reason) {
|
|
1903
|
+
await this.#writer.reset();
|
|
1904
|
+
const entries = await this.#writeEntries(context);
|
|
1905
|
+
const entryFiles = Object.values(entries.files);
|
|
1906
|
+
const resolver = new CsrDevResolver({ paths: this.#paths, context, entryFiles });
|
|
1907
|
+
await resolver.prepass();
|
|
1908
|
+
const compiled = await this.#compiler(context, resolver).compile([...entryFiles, context.refreshFile], new Set);
|
|
1909
|
+
const graph = {
|
|
1910
|
+
version: 1,
|
|
1911
|
+
configKey: context.configKey,
|
|
1912
|
+
refresh: this.#paths.idOf(context.refreshFile),
|
|
1913
|
+
entries: this.#entryIds(entries.files),
|
|
1914
|
+
modules: {},
|
|
1915
|
+
resolution: {},
|
|
1916
|
+
pending: []
|
|
1917
|
+
};
|
|
1918
|
+
this.#merge(graph, resolver, compiled);
|
|
1919
|
+
await this.#writer.writeModules(compiled);
|
|
1920
|
+
const vendorFile = await this.#writer.writeVendor(graph);
|
|
1921
|
+
await this.#writer.writeApp(graph, generation);
|
|
1922
|
+
await this.#writer.writeState(graph, { version: 1, generation, vendorFile, entries: graph.entries });
|
|
1923
|
+
await this.#writer.prune(vendorFile, generation);
|
|
1924
|
+
return { generation, reload: true, reason, changedIds: [], moduleCount: Object.keys(graph.modules).length };
|
|
1925
|
+
}
|
|
1926
|
+
async#incrementalBuild(context, graph, manifest, changedFiles, generation) {
|
|
1927
|
+
const entries = await this.#writeEntries(context);
|
|
1928
|
+
const entryIds = this.#entryIds(entries.files);
|
|
1929
|
+
await this.#forgetDeletedModules(graph);
|
|
1930
|
+
const changed = await this.#changedModules(graph, changedFiles);
|
|
1931
|
+
for (const file of entries.changed)
|
|
1932
|
+
changed.add(this.#paths.idOf(file));
|
|
1933
|
+
const routesMoved = entries.changed.length > 0 || JSON.stringify(entryIds) !== JSON.stringify(graph.entries);
|
|
1934
|
+
if (changed.size === 0)
|
|
1935
|
+
return null;
|
|
1936
|
+
const resolver = new CsrDevResolver({
|
|
1937
|
+
paths: this.#paths,
|
|
1938
|
+
context,
|
|
1939
|
+
entryFiles: Object.values(entries.files),
|
|
1940
|
+
resolution: graph.resolution
|
|
1941
|
+
});
|
|
1942
|
+
const known = new Set(Object.keys(graph.modules).map((id) => this.#paths.fileOf(id)));
|
|
1943
|
+
let compiled;
|
|
1944
|
+
try {
|
|
1945
|
+
compiled = await this.#compiler(context, resolver).compile([...changed].map((id) => this.#paths.fileOf(id)), known);
|
|
1946
|
+
} catch (error) {
|
|
1947
|
+
graph.pending = [...changed];
|
|
1948
|
+
await this.#writer.writeJson("graph.json", graph);
|
|
1949
|
+
throw error;
|
|
1950
|
+
}
|
|
1951
|
+
const vendorJoined = compiled.some((module) => module.vendor && !graph.modules[module.id]);
|
|
1952
|
+
this.#merge(graph, resolver, compiled);
|
|
1953
|
+
graph.entries = entryIds;
|
|
1954
|
+
await this.#writer.writeModules(compiled);
|
|
1955
|
+
const vendorFile = vendorJoined ? await this.#writer.writeVendor(graph) : manifest.vendorFile;
|
|
1956
|
+
await this.#writer.writeApp(graph, generation);
|
|
1957
|
+
const changedIds = compiled.filter((module) => !module.vendor).map((module) => module.id);
|
|
1958
|
+
const constantId = changedIds.find((id) => id.endsWith(".constant.ts"));
|
|
1959
|
+
const reason = vendorJoined ? "an npm module joined the graph" : routesMoved ? "the route table changed" : constantId ? `${path13.basename(constantId)} changed` : undefined;
|
|
1960
|
+
const patchUrl = reason ? undefined : await this.#writer.writePatch(generation, compiled.filter((module) => !module.vendor));
|
|
1961
|
+
await this.#writer.writeState(graph, { ...manifest, generation, vendorFile, entries: entryIds });
|
|
1962
|
+
await this.#writer.prune(vendorFile, generation);
|
|
1963
|
+
return {
|
|
1964
|
+
generation,
|
|
1965
|
+
reload: !!reason,
|
|
1966
|
+
reason,
|
|
1967
|
+
patchUrl,
|
|
1968
|
+
changedIds,
|
|
1969
|
+
moduleCount: Object.keys(graph.modules).length
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
async#changedModules(graph, changedFiles) {
|
|
1973
|
+
const changed = new Set(graph.pending.filter((id) => graph.modules[id]));
|
|
1974
|
+
for (const file of changedFiles) {
|
|
1975
|
+
const id = this.#paths.idOf(file);
|
|
1976
|
+
if (graph.modules[id])
|
|
1977
|
+
changed.add(id);
|
|
1978
|
+
}
|
|
1979
|
+
for (const [id, module] of Object.entries(graph.modules)) {
|
|
1980
|
+
if (module.vendor || changed.has(id))
|
|
1981
|
+
continue;
|
|
1982
|
+
const file = this.#paths.fileOf(id);
|
|
1983
|
+
if (CsrDevPaths.mtimeOf(file) === module.mtimeMs)
|
|
1984
|
+
continue;
|
|
1985
|
+
if (await CsrDevPaths.hashOf(file) !== module.hash)
|
|
1986
|
+
changed.add(id);
|
|
1987
|
+
}
|
|
1988
|
+
return changed;
|
|
1989
|
+
}
|
|
1990
|
+
async#forgetDeletedModules(graph) {
|
|
1991
|
+
const deleted = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor && !fs3.existsSync(this.#paths.fileOf(id)));
|
|
1992
|
+
for (const id of deleted)
|
|
1993
|
+
delete graph.modules[id];
|
|
1994
|
+
await this.#writer.forgetModules(deleted);
|
|
1995
|
+
}
|
|
1996
|
+
async#writeEntries(context) {
|
|
1997
|
+
await mkdir3(this.#entryDir, { recursive: true });
|
|
1998
|
+
const files = {};
|
|
1999
|
+
const changed = [];
|
|
2000
|
+
for (const basePath of context.htmlBasePaths) {
|
|
2001
|
+
const file = path13.join(this.#entryDir, CsrArtifactBuilder.entryFilename(basePath));
|
|
2002
|
+
const entryPages = CsrArtifactBuilder.pageEntriesForBasePath(context.pageEntries, basePath, context.basePaths);
|
|
2003
|
+
const generator = new PagesEntrySourceGenerator(entryPages);
|
|
2004
|
+
const pages = await generator.generateStatic({ fromDir: this.#entryDir });
|
|
2005
|
+
const hot = await generator.generateHotReplace({
|
|
2006
|
+
fromDir: this.#entryDir,
|
|
2007
|
+
ownerId: this.#paths.idOf(file),
|
|
2008
|
+
moduleIds: entryPages.map(({ moduleAbsPath }) => this.#paths.idOf(moduleAbsPath))
|
|
2009
|
+
});
|
|
2010
|
+
const source = `import * as akanWebkit from "akanjs/webkit";
|
|
2011
|
+
${pages}
|
|
2012
|
+
void akanWebkit.bootCsr(pages);
|
|
2013
|
+
${hot}`;
|
|
2014
|
+
const unchanged = await Bun.file(file).exists() && await Bun.file(file).text() === source;
|
|
2015
|
+
if (!unchanged)
|
|
2016
|
+
await Bun.write(file, source);
|
|
2017
|
+
const entryFile = CsrDevPaths.realpath(file);
|
|
2018
|
+
files[basePath] = entryFile;
|
|
2019
|
+
if (!unchanged)
|
|
2020
|
+
changed.push(entryFile);
|
|
2021
|
+
}
|
|
2022
|
+
return { files, changed };
|
|
2023
|
+
}
|
|
2024
|
+
#compiler(context, resolver) {
|
|
2025
|
+
return new CsrDevModuleCompiler({ app: this.#app, paths: this.#paths, resolver, context, outDir: this.#outDir });
|
|
2026
|
+
}
|
|
2027
|
+
#merge(graph, resolver, compiled) {
|
|
2028
|
+
for (const module of compiled)
|
|
2029
|
+
graph.modules[module.id] = {
|
|
2030
|
+
vendor: module.vendor,
|
|
2031
|
+
mtimeMs: module.mtimeMs,
|
|
2032
|
+
hash: module.hash,
|
|
2033
|
+
deps: module.deps.map((dep) => CsrDevPaths.isStub(dep) ? dep : this.#paths.idOf(dep)),
|
|
2034
|
+
...module.helpers ? { helpers: module.helpers.hash } : {}
|
|
2035
|
+
};
|
|
2036
|
+
graph.pending = [];
|
|
2037
|
+
graph.resolution = resolver.serialize();
|
|
2038
|
+
}
|
|
2039
|
+
#entryIds(files) {
|
|
2040
|
+
return Object.fromEntries(Object.entries(files).map(([basePath, file]) => [basePath, this.#paths.idOf(file)]));
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
// pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
|
|
2045
|
+
import { mkdir as mkdir4, stat as stat2 } from "fs/promises";
|
|
2046
|
+
import path14 from "path";
|
|
1250
2047
|
import ts from "typescript";
|
|
1251
2048
|
var FONT_URL_PREFIX = "/_akan/fonts";
|
|
1252
2049
|
var DEFAULT_FONT_SUBSETS = ["latin"];
|
|
@@ -1263,7 +2060,7 @@ class FontOptimizer {
|
|
|
1263
2060
|
constructor(app, command = "start") {
|
|
1264
2061
|
this.#app = app;
|
|
1265
2062
|
this.#command = command;
|
|
1266
|
-
this.#artifactRoot =
|
|
2063
|
+
this.#artifactRoot = path14.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
|
|
1267
2064
|
}
|
|
1268
2065
|
async optimize() {
|
|
1269
2066
|
const fonts = await this.discoverFonts();
|
|
@@ -1288,7 +2085,7 @@ class FontOptimizer {
|
|
|
1288
2085
|
return result;
|
|
1289
2086
|
}
|
|
1290
2087
|
get #cachePath() {
|
|
1291
|
-
return
|
|
2088
|
+
return path14.join(this.#artifactRoot, "fontCache.json");
|
|
1292
2089
|
}
|
|
1293
2090
|
async#buildCacheKey(fonts) {
|
|
1294
2091
|
const sources = [];
|
|
@@ -1303,7 +2100,7 @@ class FontOptimizer {
|
|
|
1303
2100
|
sources.push({ optimizedSrc: face.optimizedSrc, ...stamp });
|
|
1304
2101
|
}
|
|
1305
2102
|
for (const filePath of font.subsetFiles ?? []) {
|
|
1306
|
-
const abs =
|
|
2103
|
+
const abs = path14.isAbsolute(filePath) ? filePath : path14.join(this.#app.cwdPath, filePath);
|
|
1307
2104
|
const stamp = await this.#fileStamp(abs);
|
|
1308
2105
|
if (!stamp)
|
|
1309
2106
|
return null;
|
|
@@ -1329,7 +2126,7 @@ class FontOptimizer {
|
|
|
1329
2126
|
const cache = await Bun.file(this.#cachePath).json().catch(() => null);
|
|
1330
2127
|
if (cache?.version !== FontOptimizer.#cacheVersion || cache.key !== key)
|
|
1331
2128
|
return null;
|
|
1332
|
-
const files = cache.files.map((relativePath) =>
|
|
2129
|
+
const files = cache.files.map((relativePath) => path14.join(this.#artifactRoot, relativePath));
|
|
1333
2130
|
for (const filePath of files) {
|
|
1334
2131
|
if (!await Bun.file(filePath).exists())
|
|
1335
2132
|
return null;
|
|
@@ -1341,7 +2138,7 @@ class FontOptimizer {
|
|
|
1341
2138
|
version: FontOptimizer.#cacheVersion,
|
|
1342
2139
|
key,
|
|
1343
2140
|
css: result.css,
|
|
1344
|
-
files: result.files.map((filePath) =>
|
|
2141
|
+
files: result.files.map((filePath) => path14.relative(this.#artifactRoot, filePath))
|
|
1345
2142
|
};
|
|
1346
2143
|
await Bun.write(this.#cachePath, JSON.stringify(cache));
|
|
1347
2144
|
}
|
|
@@ -1349,7 +2146,7 @@ class FontOptimizer {
|
|
|
1349
2146
|
const pageKeys = await this.#app.getPageKeys();
|
|
1350
2147
|
const fonts = [];
|
|
1351
2148
|
await Promise.all(pageKeys.map(async (key) => {
|
|
1352
|
-
const filePath =
|
|
2149
|
+
const filePath = path14.resolve(this.#app.cwdPath, "page", key);
|
|
1353
2150
|
const file = Bun.file(filePath);
|
|
1354
2151
|
if (!await file.exists())
|
|
1355
2152
|
return;
|
|
@@ -1368,8 +2165,8 @@ class FontOptimizer {
|
|
|
1368
2165
|
this.#app.logger.warn(`[font] source not found: ${face.src}`);
|
|
1369
2166
|
continue;
|
|
1370
2167
|
}
|
|
1371
|
-
const outputPath =
|
|
1372
|
-
await
|
|
2168
|
+
const outputPath = path14.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
|
|
2169
|
+
await mkdir4(path14.dirname(outputPath), { recursive: true });
|
|
1373
2170
|
const sourceBuffer = Buffer.from(await Bun.file(sourcePath).arrayBuffer());
|
|
1374
2171
|
const outputBuffer = await this.#buildFontBuffer(font, sourceBuffer, sourcePath);
|
|
1375
2172
|
await Bun.write(outputPath, outputBuffer);
|
|
@@ -1391,7 +2188,7 @@ class FontOptimizer {
|
|
|
1391
2188
|
const entries = Array.isArray(value) ? value : [];
|
|
1392
2189
|
const fonts = entries.filter((entry) => this.#isReadableFont(entry));
|
|
1393
2190
|
if (fonts.length !== entries.length || !Array.isArray(value))
|
|
1394
|
-
this.#app.logger.warn(`[font] ${
|
|
2191
|
+
this.#app.logger.warn(`[font] ${path14.relative(this.#app.cwdPath, filePath)} declares fonts the build cannot read without evaluating the module \u2014 write the list inline, or nothing is subset for it and every /_akan/fonts request 404s`);
|
|
1395
2192
|
return fonts.map((font) => ({ ...font, subsets: font.subsets ?? [...DEFAULT_FONT_SUBSETS] }));
|
|
1396
2193
|
}
|
|
1397
2194
|
#isReadableFont(value) {
|
|
@@ -1549,8 +2346,8 @@ class FontOptimizer {
|
|
|
1549
2346
|
return null;
|
|
1550
2347
|
const rel = src.replace(/^\//, "");
|
|
1551
2348
|
const candidates = [
|
|
1552
|
-
this.#command === "build" ?
|
|
1553
|
-
|
|
2349
|
+
this.#command === "build" ? path14.join(this.#app.dist.cwdPath, "public", rel) : null,
|
|
2350
|
+
path14.join(this.#app.cwdPath, "public", rel),
|
|
1554
2351
|
this.#resolveWorkspacePublicPath(rel)
|
|
1555
2352
|
].filter(Boolean);
|
|
1556
2353
|
for (const candidate of candidates) {
|
|
@@ -1582,7 +2379,7 @@ class FontOptimizer {
|
|
|
1582
2379
|
return "woff2";
|
|
1583
2380
|
if (signature === "OTTO")
|
|
1584
2381
|
return "otf";
|
|
1585
|
-
const ext =
|
|
2382
|
+
const ext = path14.extname(sourcePath).slice(1).toLowerCase();
|
|
1586
2383
|
if (ext === "otf" || ext === "woff" || ext === "woff2")
|
|
1587
2384
|
return ext;
|
|
1588
2385
|
return "ttf";
|
|
@@ -1591,7 +2388,7 @@ class FontOptimizer {
|
|
|
1591
2388
|
const [root, dep, ...rest] = rel.split("/");
|
|
1592
2389
|
if (root !== "libs" || !dep || rest.length === 0)
|
|
1593
2390
|
return null;
|
|
1594
|
-
return
|
|
2391
|
+
return path14.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
|
|
1595
2392
|
}
|
|
1596
2393
|
async#getSubsetText(font) {
|
|
1597
2394
|
const parts = new Set;
|
|
@@ -1601,7 +2398,7 @@ class FontOptimizer {
|
|
|
1601
2398
|
if (font.subsetText)
|
|
1602
2399
|
parts.add(font.subsetText);
|
|
1603
2400
|
for (const filePath of font.subsetFiles ?? []) {
|
|
1604
|
-
const abs =
|
|
2401
|
+
const abs = path14.isAbsolute(filePath) ? filePath : path14.join(this.#app.cwdPath, filePath);
|
|
1605
2402
|
const file = Bun.file(abs);
|
|
1606
2403
|
if (await file.exists())
|
|
1607
2404
|
parts.add(await file.text());
|
|
@@ -1621,7 +2418,7 @@ class FontOptimizer {
|
|
|
1621
2418
|
}
|
|
1622
2419
|
async#collectAutoSubsetText() {
|
|
1623
2420
|
const libPageRoots = (await this.#app.getPageRoots()).filter((root) => root.keyPrefix).map((root) => root.dir);
|
|
1624
|
-
const roots = [...["page", "ui", "lib"].map((dir) =>
|
|
2421
|
+
const roots = [...["page", "ui", "lib"].map((dir) => path14.join(this.#app.cwdPath, dir)), ...libPageRoots];
|
|
1625
2422
|
const glob = new Bun.Glob("**/*.{ts,tsx,js,jsx,html,md}");
|
|
1626
2423
|
const parts = [];
|
|
1627
2424
|
for (const root of [...new Set(roots)].sort()) {
|
|
@@ -1734,8 +2531,8 @@ ${declarations.map(([prop, value]) => ` ${prop}: ${value};`).join(`
|
|
|
1734
2531
|
}
|
|
1735
2532
|
|
|
1736
2533
|
// pkgs/@akanjs/devkit/frontendBuild/fontPruner.ts
|
|
1737
|
-
import { rm as
|
|
1738
|
-
import
|
|
2534
|
+
import { rm as rm3, rmdir, stat as stat3 } from "fs/promises";
|
|
2535
|
+
import path15 from "path";
|
|
1739
2536
|
import { isFontOptimizationEnabled } from "akanjs/client";
|
|
1740
2537
|
var FONT_GLOB = "**/*.{woff2,woff,ttf,otf,ttc,eot}";
|
|
1741
2538
|
var REFERRER_GLOB = "**/*.{css,js,mjs,cjs,json,html,htm,svg,xml,webmanifest}";
|
|
@@ -1748,8 +2545,8 @@ class FontPruner {
|
|
|
1748
2545
|
constructor(app, { keepFonts = [] } = {}) {
|
|
1749
2546
|
this.#app = app;
|
|
1750
2547
|
this.#keepGlobs = keepFonts;
|
|
1751
|
-
this.#publicRoot =
|
|
1752
|
-
this.#artifactRoot =
|
|
2548
|
+
this.#publicRoot = path15.join(app.dist.cwdPath, "public");
|
|
2549
|
+
this.#artifactRoot = path15.join(app.dist.cwdPath, ".akan/artifact");
|
|
1753
2550
|
}
|
|
1754
2551
|
async prune() {
|
|
1755
2552
|
const empty = { removed: [], kept: [], freedBytes: 0 };
|
|
@@ -1764,7 +2561,7 @@ class FontPruner {
|
|
|
1764
2561
|
for (const font of fonts) {
|
|
1765
2562
|
const target = isFontOptimizationEnabled(font) ? optimizedSrcs : unoptimizedSrcs;
|
|
1766
2563
|
for (const fontPath of font.paths)
|
|
1767
|
-
target.add(
|
|
2564
|
+
target.add(path15.posix.basename(fontPath.src));
|
|
1768
2565
|
}
|
|
1769
2566
|
const result = { removed: [], kept: [], freedBytes: 0 };
|
|
1770
2567
|
const undecided = [];
|
|
@@ -1802,10 +2599,10 @@ class FontPruner {
|
|
|
1802
2599
|
async#collectCandidates() {
|
|
1803
2600
|
const candidates = [];
|
|
1804
2601
|
for await (const rel of new Bun.Glob(FONT_GLOB).scan({ cwd: this.#publicRoot, dot: false })) {
|
|
1805
|
-
const entry = await stat3(
|
|
2602
|
+
const entry = await stat3(path15.join(this.#publicRoot, rel)).catch(() => null);
|
|
1806
2603
|
if (!entry?.isFile())
|
|
1807
2604
|
continue;
|
|
1808
|
-
candidates.push({ rel: rel.split(
|
|
2605
|
+
candidates.push({ rel: rel.split(path15.sep).join("/"), basename: path15.basename(rel), bytes: entry.size });
|
|
1809
2606
|
}
|
|
1810
2607
|
return candidates;
|
|
1811
2608
|
}
|
|
@@ -1815,11 +2612,11 @@ class FontPruner {
|
|
|
1815
2612
|
const bundled = candidates.filter((candidate) => !optimizedSrcs.has(candidate.basename));
|
|
1816
2613
|
const roots = [
|
|
1817
2614
|
{ dir: this.#publicRoot, label: "public", needles: authoritative },
|
|
1818
|
-
{ dir:
|
|
1819
|
-
{ dir:
|
|
1820
|
-
{ dir:
|
|
1821
|
-
{ dir:
|
|
1822
|
-
{ dir:
|
|
2615
|
+
{ dir: path15.join(this.#artifactRoot, "styles"), label: ".akan/artifact/styles", needles: authoritative },
|
|
2616
|
+
{ dir: path15.join(this.#artifactRoot, "client"), label: ".akan/artifact/client", needles: bundled },
|
|
2617
|
+
{ dir: path15.join(this.#artifactRoot, "client-ssr"), label: ".akan/artifact/client-ssr", needles: bundled },
|
|
2618
|
+
{ dir: path15.join(this.#artifactRoot, "server"), label: ".akan/artifact/server", needles: bundled },
|
|
2619
|
+
{ dir: path15.join(this.#app.dist.cwdPath, "csr"), label: "csr", needles: bundled }
|
|
1823
2620
|
];
|
|
1824
2621
|
for (const root of roots) {
|
|
1825
2622
|
const pending = root.needles.filter((candidate) => !referrers.has(candidate.rel));
|
|
@@ -1838,20 +2635,20 @@ class FontPruner {
|
|
|
1838
2635
|
const remaining = needles.filter((needle) => !referrers.has(needle.rel));
|
|
1839
2636
|
if (!remaining.length)
|
|
1840
2637
|
return;
|
|
1841
|
-
const text = await Bun.file(
|
|
2638
|
+
const text = await Bun.file(path15.join(root.dir, rel)).text().catch(() => "");
|
|
1842
2639
|
if (!text)
|
|
1843
2640
|
continue;
|
|
1844
2641
|
for (const needle of remaining) {
|
|
1845
2642
|
if (needle.forms.some((form) => text.includes(form)))
|
|
1846
|
-
referrers.set(needle.rel, `${root.label}/${rel.split(
|
|
2643
|
+
referrers.set(needle.rel, `${root.label}/${rel.split(path15.sep).join("/")}`);
|
|
1847
2644
|
}
|
|
1848
2645
|
}
|
|
1849
2646
|
}
|
|
1850
2647
|
async#remove(files) {
|
|
1851
|
-
await Promise.all(files.map((file) =>
|
|
1852
|
-
const dirs = [...new Set(files.map((file) =>
|
|
2648
|
+
await Promise.all(files.map((file) => rm3(path15.join(this.#publicRoot, file), { force: true })));
|
|
2649
|
+
const dirs = [...new Set(files.map((file) => path15.posix.dirname(file)))].filter((dir) => dir !== "." && dir !== "/").sort((a, b) => b.length - a.length);
|
|
1853
2650
|
for (const dir of dirs)
|
|
1854
|
-
await rmdir(
|
|
2651
|
+
await rmdir(path15.join(this.#publicRoot, dir)).catch(() => {
|
|
1855
2652
|
return;
|
|
1856
2653
|
});
|
|
1857
2654
|
}
|
|
@@ -1898,8 +2695,8 @@ class FontPruner {
|
|
|
1898
2695
|
}
|
|
1899
2696
|
|
|
1900
2697
|
// pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
|
|
1901
|
-
import
|
|
1902
|
-
import
|
|
2698
|
+
import fs4 from "fs";
|
|
2699
|
+
import path16 from "path";
|
|
1903
2700
|
import zlib from "zlib";
|
|
1904
2701
|
var COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
|
|
1905
2702
|
var MIN_COMPRESS_BYTES = 1024;
|
|
@@ -1909,8 +2706,8 @@ var GZIP_LEVEL = 9;
|
|
|
1909
2706
|
var { formatBytes } = FontPruner;
|
|
1910
2707
|
async function precompressArtifacts(app) {
|
|
1911
2708
|
const roots = [
|
|
1912
|
-
|
|
1913
|
-
|
|
2709
|
+
path16.join(app.dist.cwdPath, ".akan/artifact/client"),
|
|
2710
|
+
path16.join(app.dist.cwdPath, ".akan/artifact/styles")
|
|
1914
2711
|
];
|
|
1915
2712
|
const result = { files: 0, inputBytes: 0, outputBytes: 0, brotliBytes: 0 };
|
|
1916
2713
|
await Promise.all(roots.map((root) => precompressRoot(root, result)));
|
|
@@ -1920,7 +2717,7 @@ async function precompressArtifacts(app) {
|
|
|
1920
2717
|
return result;
|
|
1921
2718
|
}
|
|
1922
2719
|
async function precompressRoot(root, result) {
|
|
1923
|
-
if (!
|
|
2720
|
+
if (!fs4.existsSync(root) || !fs4.statSync(root).isDirectory())
|
|
1924
2721
|
return;
|
|
1925
2722
|
const glob = new Bun.Glob("**/*");
|
|
1926
2723
|
for await (const filePath of glob.scan({ cwd: root, absolute: true })) {
|
|
@@ -1929,7 +2726,7 @@ async function precompressRoot(root, result) {
|
|
|
1929
2726
|
const bytes = await Bun.file(filePath).bytes();
|
|
1930
2727
|
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
1931
2728
|
const gz = Bun.gzipSync(buffer, { level: GZIP_LEVEL });
|
|
1932
|
-
const br = brotliCompress(buffer,
|
|
2729
|
+
const br = brotliCompress(buffer, path16.extname(filePath).toLowerCase());
|
|
1933
2730
|
await Promise.all([Bun.write(`${filePath}.gz`, gz), Bun.write(`${filePath}.br`, br)]);
|
|
1934
2731
|
result.files += 1;
|
|
1935
2732
|
result.inputBytes += bytes.byteLength;
|
|
@@ -1949,7 +2746,7 @@ function brotliCompress(buffer, ext) {
|
|
|
1949
2746
|
async function shouldPrecompress(filePath) {
|
|
1950
2747
|
if (filePath.endsWith(".gz") || filePath.endsWith(".br"))
|
|
1951
2748
|
return false;
|
|
1952
|
-
if (!COMPRESSIBLE_EXTS.has(
|
|
2749
|
+
if (!COMPRESSIBLE_EXTS.has(path16.extname(filePath).toLowerCase()))
|
|
1953
2750
|
return false;
|
|
1954
2751
|
const file = Bun.file(filePath);
|
|
1955
2752
|
if (!await file.exists())
|
|
@@ -1958,7 +2755,7 @@ async function shouldPrecompress(filePath) {
|
|
|
1958
2755
|
}
|
|
1959
2756
|
|
|
1960
2757
|
// pkgs/@akanjs/devkit/frontendBuild/ssrBaseArtifactBuilder.ts
|
|
1961
|
-
import
|
|
2758
|
+
import path17 from "path";
|
|
1962
2759
|
import { optimize } from "@tailwindcss/node";
|
|
1963
2760
|
function prepareCssAsset(command, basePath, cssText) {
|
|
1964
2761
|
return optimize(cssText, { file: `${basePath || "root"}.css`, minify: command === "build" }).code;
|
|
@@ -1974,7 +2771,10 @@ class SsrBaseArtifactBuilder {
|
|
|
1974
2771
|
this.#app = app;
|
|
1975
2772
|
this.#command = command;
|
|
1976
2773
|
this.#artifactDir = `${command === "build" ? app.dist.cwdPath : app.cwdPath}/.akan/artifact`;
|
|
1977
|
-
this.#absArtifactDir =
|
|
2774
|
+
this.#absArtifactDir = path17.resolve(this.#artifactDir);
|
|
2775
|
+
}
|
|
2776
|
+
static servedWeb(command, web) {
|
|
2777
|
+
return command === "start" ? { ssr: true, csr: true } : web;
|
|
1978
2778
|
}
|
|
1979
2779
|
async build() {
|
|
1980
2780
|
const akanConfig = await this.#app.getConfig();
|
|
@@ -1996,7 +2796,7 @@ class SsrBaseArtifactBuilder {
|
|
|
1996
2796
|
rscRuntimeSsrManifest,
|
|
1997
2797
|
vendorMap,
|
|
1998
2798
|
cssAssets,
|
|
1999
|
-
pagesBundlePath: this.#command === "build" ?
|
|
2799
|
+
pagesBundlePath: this.#command === "build" ? path17.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
|
|
2000
2800
|
pagesBundleBuildId: pagesBundle.buildId,
|
|
2001
2801
|
domains: [...akanConfig.domains],
|
|
2002
2802
|
subRoutes: Object.fromEntries(Array.from(akanConfig.subRoutes.entries()).map(([basePath, domains]) => [basePath, [...domains]])),
|
|
@@ -2004,7 +2804,7 @@ class SsrBaseArtifactBuilder {
|
|
|
2004
2804
|
branches: [...akanConfig.branches],
|
|
2005
2805
|
i18n: akanConfig.i18n,
|
|
2006
2806
|
imageConfig: akanConfig.images,
|
|
2007
|
-
web: akanConfig.web,
|
|
2807
|
+
web: SsrBaseArtifactBuilder.servedWeb(this.#command, akanConfig.web),
|
|
2008
2808
|
deepLinkAssociations: Object.values(akanConfig.mobile.targets).filter((target) => (target.deepLinks?.domains?.length ?? 0) > 0).map((target) => ({
|
|
2009
2809
|
targetName: target.name,
|
|
2010
2810
|
appId: target.appId,
|
|
@@ -2013,18 +2813,18 @@ class SsrBaseArtifactBuilder {
|
|
|
2013
2813
|
androidSha256CertFingerprints: target.deepLinks?.android?.sha256CertFingerprints
|
|
2014
2814
|
}))
|
|
2015
2815
|
};
|
|
2016
|
-
await Bun.write(
|
|
2816
|
+
await Bun.write(path17.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
|
|
2017
2817
|
`);
|
|
2018
2818
|
this.#app.verbose(`[base-artifact] complete in ${Date.now() - this.#started}ms`);
|
|
2019
2819
|
return { artifact, seedIndex, cssCompiler, optimizedFonts };
|
|
2020
2820
|
}
|
|
2021
2821
|
async#buildRuntimeClientEntries() {
|
|
2022
2822
|
const akanServerPath = await this.#resolveAkanServerPath();
|
|
2023
|
-
const rscClientEntry =
|
|
2024
|
-
const rscSegmentOutletEntry =
|
|
2823
|
+
const rscClientEntry = path17.resolve(akanServerPath, "rscClient.tsx");
|
|
2824
|
+
const rscSegmentOutletEntry = path17.resolve(akanServerPath, "rscSegmentOutlet.tsx");
|
|
2025
2825
|
const vendorEntries = VENDOR_SPECIFIERS.map((specifier) => ({
|
|
2026
2826
|
specifier,
|
|
2027
|
-
absPath:
|
|
2827
|
+
absPath: path17.resolve(akanServerPath, "vendor", `${specifier.replaceAll("/", "-").replaceAll(".", "-")}.ts`)
|
|
2028
2828
|
}));
|
|
2029
2829
|
const entries = [rscClientEntry, rscSegmentOutletEntry, ...vendorEntries.map((v) => v.absPath)];
|
|
2030
2830
|
const clientBundle = await new ClientEntriesBundler({ app: this.#app, entries, command: this.#command }).bundle();
|
|
@@ -2061,15 +2861,15 @@ class SsrBaseArtifactBuilder {
|
|
|
2061
2861
|
async#resolveAkanServerPath() {
|
|
2062
2862
|
const candidates = [];
|
|
2063
2863
|
try {
|
|
2064
|
-
candidates.push(
|
|
2864
|
+
candidates.push(path17.dirname(Bun.resolveSync("akanjs/server", this.#app.workspace.workspaceRoot)));
|
|
2065
2865
|
} catch {}
|
|
2066
|
-
candidates.push(
|
|
2866
|
+
candidates.push(path17.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server"), path17.join(this.#app.workspace.workspaceRoot, "node_modules/akanjs/server"));
|
|
2067
2867
|
try {
|
|
2068
|
-
candidates.push(
|
|
2868
|
+
candidates.push(path17.dirname(Bun.resolveSync("akanjs/server", path17.dirname(Bun.main))));
|
|
2069
2869
|
} catch {}
|
|
2070
|
-
candidates.push(
|
|
2870
|
+
candidates.push(path17.join(path17.dirname(Bun.main), "node_modules/akanjs/server"), path17.join(path17.dirname(Bun.main), "../../akanjs/server"), path17.resolve(import.meta.dir, "../../server"), path17.resolve(import.meta.dir, "../server"));
|
|
2071
2871
|
for (const candidate of candidates) {
|
|
2072
|
-
if (await Bun.file(
|
|
2872
|
+
if (await Bun.file(path17.join(candidate, "rscClient.tsx")).exists())
|
|
2073
2873
|
return candidate;
|
|
2074
2874
|
}
|
|
2075
2875
|
throw new Error(`[base-artifact] failed to locate akanjs/server; looked in: ${candidates.join(", ")}`);
|
|
@@ -2098,7 +2898,7 @@ ${preparedCssText}`).toString(36);
|
|
|
2098
2898
|
`styles/${cssAssetName}-${cssHash}.css`,
|
|
2099
2899
|
`/_akan/styles/${cssAssetName}-${cssHash}.css`
|
|
2100
2900
|
];
|
|
2101
|
-
await Bun.write(
|
|
2901
|
+
await Bun.write(path17.join(this.#absArtifactDir, cssRelPath), preparedCssText);
|
|
2102
2902
|
SsrBaseArtifactBuilder.#warnDroppedImports(this.#app, cssRelPath, preparedCssText, imported);
|
|
2103
2903
|
this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath} -> ${cssRelPath}`);
|
|
2104
2904
|
return [basePath, { cssUrl, cssRelPath }];
|
|
@@ -2111,4 +2911,4 @@ ${preparedCssText}`).toString(36);
|
|
|
2111
2911
|
}
|
|
2112
2912
|
}
|
|
2113
2913
|
}
|
|
2114
|
-
export { computeRouteSeedIndex, saveRouteSeedIndex, AllRoutesBuilder, CssCompiler, PagesBundleBuilder, CsrArtifactBuilder, FontOptimizer, FontPruner, precompressArtifacts, SsrBaseArtifactBuilder };
|
|
2914
|
+
export { computeRouteSeedIndex, saveRouteSeedIndex, AllRoutesBuilder, CssCompiler, PagesBundleBuilder, CsrArtifactBuilder, CsrDevBundler, FontOptimizer, FontPruner, precompressArtifacts, SsrBaseArtifactBuilder };
|