@akanjs/cli 3.0.0-beta.9 → 3.0.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/.build-stamp +1 -1
- package/CodeAgent-zfab7pkn.js +9 -0
- package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
- package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
- package/CodeTui-0zhnr0pw.js +1457 -0
- package/README.ko.md +83 -28
- package/README.md +70 -27
- package/TunnelShare-cwh9ezsm.js +9 -0
- package/{agent.command-0q17pnrs.js → agent.command-aja6z9mn.js} +8 -9
- package/akanCode.js +102 -0
- package/application.command-049ehgx3.js +246 -0
- package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-75n5rpwp.js} +121 -77
- package/buildBatch.proc.js +120 -32
- package/cloud.command-gw4kqxt8.js +95 -0
- package/cloud.script-c723cpcb.js +20 -0
- package/code.command-x9t6z5nw.js +154 -0
- package/commandManifest.json +1 -1
- package/consoleToStderr-h5bryj94.js +7 -0
- package/context.command-yfbe1rtd.js +48 -0
- package/context.runner-erfe68j5.js +21 -0
- package/{dependencyScanner-shs82ynp.js → dependencyScanner-06z2dmrr.js} +2 -2
- package/{devTui-m1atgkc9.js → devTui-q7k7bakd.js} +122 -40
- package/fileSys-wzzrntkw.js +9 -0
- package/guideline.command-kq9gp690.js +42 -0
- package/guidelines/agentRule/agentRule.instruction.md +154 -26
- package/guidelines/conventions/conventions.instruction.md +138 -48
- package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
- package/guidelines/queryRule/queryRule.instruction.md +21 -7
- package/guidelines/runtimeRule/runtimeRule.instruction.md +291 -6
- package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
- package/guidelines/transportRule/transportRule.instruction.md +10 -8
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +20 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
- package/incrementalBuilder.proc.js +564 -109
- package/{index-2tv3fdx4.js → index-150s07n8.js} +1230 -1459
- package/{index-vyvre9en.js → index-25fvrxmy.js} +6 -2
- package/{index-kcxmv48c.js → index-2ahj896a.js} +45 -79
- package/index-2wc41k5w.js +49 -0
- package/index-3hx1z96c.js +87 -0
- package/{index-q0y2jq9j.js → index-52w0fvh8.js} +21 -4
- package/{index-04bk599g.js → index-5g4rkrvt.js} +10 -17
- package/index-5pvxf7z0.js +53 -0
- package/index-5q14pwzd.js +69 -0
- package/{index-vxtad5bd.js → index-66yspb67.js} +5 -8
- package/index-7md2g9vw.js +6 -0
- package/index-7p4c0yer.js +1058 -0
- package/{index-d43r08tf.js → index-8b639zes.js} +77 -150
- package/{index-ktb5njgy.js → index-8kvy8qb4.js} +49 -99
- package/index-94s080qm.js +266 -0
- package/index-9dp031by.js +12 -0
- package/{index-0g7r8xz3.js → index-a37by9hn.js} +114 -232
- package/{index-k2j1hc4r.js → index-b95yhqjr.js} +12 -12
- package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
- package/{index-2xp0kfkt.js → index-br8pkrk1.js} +37 -25
- package/index-c14nsvnv.js +700 -0
- package/index-c6eggbee.js +123 -0
- package/index-d2242ksy.js +99 -0
- package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
- package/{index-a64esmtb.js → index-fkvb79qe.js} +523 -1022
- package/index-h54yj9wr.js +1119 -0
- package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
- package/{index-y6n8nmny.js → index-kn5d4jmg.js} +75 -203
- package/index-m3xsb1ms.js +4300 -0
- package/{index-gyd220p2.js → index-mb59b8x0.js} +554 -577
- package/{index-j9yhg404.js → index-mzjb6zp4.js} +4 -7
- package/index-p6pbz9n5.js +25 -0
- package/{index-sj5vxr8f.js → index-qe71g4ze.js} +983 -898
- package/index-rfcz04w9.js +187 -0
- package/{index-hwkygn9s.js → index-senscydg.js} +331 -36
- package/{index-ffvk10zz.js → index-vs8mefhy.js} +107 -241
- package/index-wa5jbxj1.js +760 -0
- package/index-y5ga5w9t.js +2729 -0
- package/{index-9077wzd2.js → index-y7hx1ygz.js} +43 -26
- package/{index-h7yw2z9n.js → index-zzvvc845.js} +8 -14
- package/index.js +23 -22
- package/{library.command-qzzbj6d8.js → library.command-keqjevpp.js} +4 -5
- package/{localRegistry.command-vyrgg4xw.js → localRegistry.command-pydeq5zf.js} +23 -43
- package/{module.command-rq5hq3ax.js → module.command-dkrnqx3v.js} +8 -12
- package/{package.command-d9hd6v9n.js → package.command-wmc0ys5y.js} +6 -7
- package/package.json +15 -7
- package/{page.command-126d56te.js → page.command-tf3erhrv.js} +4 -5
- package/{primitive.command-taxd1538.js → primitive.command-7hqpegj4.js} +9 -23
- package/prompter-76k56b37.js +8 -0
- package/{quality.command-6gsna0a3.js → quality.command-8122axq3.js} +205 -377
- package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
- package/{repair.command-tq2dgy72.js → repair.command-mcy12h0n.js} +11 -35
- package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
- package/{scalar.command-3bcmrrkv.js → scalar.command-vvvbzttc.js} +7 -11
- package/skills/akan-code/SKILL.md +130 -0
- package/skills/akan-debug/SKILL.md +42 -0
- package/skills/akan-deps/SKILL.md +50 -0
- package/skills/akan-err/SKILL.md +39 -0
- package/skills/akan-module/SKILL.md +45 -0
- package/skills/akan-scaffold/SKILL.md +76 -0
- package/skills/akan-scalar/SKILL.md +52 -0
- package/skills/akan-service/SKILL.md +34 -0
- package/skills/akan-store/SKILL.md +67 -0
- package/skills/akan-ui/SKILL.md +63 -0
- package/skills/akan-validate/SKILL.md +44 -0
- package/{subspace.command-eddzqpx3.js → subspace.command-pzvqfy5h.js} +53 -62
- package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
- package/templates/app/page/_index.tsx +257 -258
- package/templates/app/page/_layout.tsx +5 -1
- package/templates/app/page/_overrides.tsx +12 -0
- package/templates/app/page/styles.css.template +316 -14
- package/templates/app/public/apple-touch-icon.png +0 -0
- package/templates/app/public/favicon.ico +0 -0
- package/templates/app/public/jelly/cloud.webp +0 -0
- package/templates/app/public/jelly/comet.webp +0 -0
- package/templates/app/public/jelly/moon.webp +0 -0
- package/templates/app/public/jelly/planet.webp +0 -0
- package/templates/app/public/jelly/rocket.webp +0 -0
- package/templates/app/public/jelly/star.webp +0 -0
- package/templates/app/public/jelly/sugar.webp +0 -0
- package/templates/app/public/logo.png +0 -0
- package/templates/app/ui/Recipe/index.ts +9 -0
- package/templates/app/ui/Recipe/jellyButton.ts +63 -0
- package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
- package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
- package/templates/appSample/lib/task/Task.Unit.tsx +1 -1
- package/templates/appSample/lib/task/Task.Util.tsx +10 -13
- package/templates/appSample/lib/task/Task.View.tsx +2 -2
- package/templates/appSample/lib/task/Task.Zone.tsx +5 -2
- package/templates/appSample/lib/task/task.abstract.ts +12 -32
- package/templates/appSample/lib/task/task.signal.spec.ts +1 -15
- package/templates/appSample/lib/task/task.signal.test.ts +15 -1
- package/templates/appSample/lib/task/task.signal.ts +10 -8
- package/templates/appSample/page/task/[taskId]/_index.tsx +6 -6
- package/templates/appSample/page/task/[taskId]/edit.tsx +5 -4
- package/templates/appSample/page/task/_index.tsx +4 -3
- package/templates/appSample/page/task/_layout.tsx +1 -1
- package/templates/appSample/page/task/new.tsx +5 -4
- package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
- package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
- package/templates/appSample/ui/GlobalLoading.tsx +2 -1
- package/templates/appSample/ui/QuantityControl.tsx +3 -3
- package/templates/client.ts +7 -30
- package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
- package/templates/crudPages/[__model__Id]/page.tsx +2 -1
- package/templates/crudPages/new/page.tsx +2 -1
- package/templates/crudPages/page.tsx +2 -1
- package/templates/crudSinglePage/page.tsx +2 -1
- package/templates/lib/__lib/lib.dictionary.ts +0 -2
- package/templates/lib/__lib/lib.document.ts +0 -2
- package/templates/lib/sig.ts +8 -30
- package/templates/localDev/docker-compose.yaml.template +3 -14
- package/templates/module/__model__.signal.ts +12 -4
- package/templates/pkgRoot/tsconfig.json.template +2 -7
- package/templates/server.ts +1 -3
- package/templates/service/__model__.abstract.md.template +8 -0
- package/templates/workspaceRoot/.gitignore.template +0 -1
- package/templates/workspaceRoot/AGENTS.md.template +6 -0
- package/templates/workspaceRoot/package.json.template +1 -3
- package/tunnel.command-82zz5xnq.js +129 -0
- package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
- package/typecheck.proc.js +1 -1
- package/workflow.command-y8x3kgjv.js +79 -0
- package/{workspace.command-06wvstgr.js → workspace.command-jvsb7brc.js} +79 -109
- package/abstractCompactor-gmkc0z8j.js +0 -102
- package/application.command-18y0s485.js +0 -204
- package/applicationReleasePackager-4yasxwv9.js +0 -244
- package/capacitorApp-36rbr858.js +0 -12
- package/cloud.command-av97ndh6.js +0 -94
- package/cloud.script-cm11xjdz.js +0 -20
- package/context.command-5v37ef3n.js +0 -81
- package/devTuiView-0hc2emhe.js +0 -9
- package/getDirname-dr3jz1xg.js +0 -7
- package/guideline.command-4pz9g1rt.js +0 -360
- package/guidelines/agentRule/agentRule.generate.json +0 -31
- package/guidelines/componentRule/componentRule.generate.json +0 -31
- package/guidelines/conventions/conventions.generate.json +0 -25
- package/guidelines/cssRule/cssRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
- package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
- package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
- package/guidelines/enumConstant/enumConstant.generate.json +0 -31
- package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
- package/guidelines/fieldRule/fieldRule.generate.json +0 -31
- package/guidelines/framework/framework.generate.json +0 -32
- package/guidelines/mcpRule/mcpRule.generate.json +0 -35
- package/guidelines/modelConstant/modelConstant.generate.json +0 -32
- package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
- package/guidelines/modelDocument/modelDocument.generate.json +0 -31
- package/guidelines/modelService/modelService.generate.json +0 -31
- package/guidelines/modelSignal/modelSignal.generate.json +0 -31
- package/guidelines/modelStore/modelStore.generate.json +0 -31
- package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
- package/guidelines/modelUnit/modelUnit.generate.json +0 -31
- package/guidelines/modelUtil/modelUtil.generate.json +0 -31
- package/guidelines/modelView/modelView.generate.json +0 -31
- package/guidelines/modelZone/modelZone.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
- package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
- package/guidelines/queryRule/queryRule.generate.json +0 -31
- package/guidelines/recipeRule/recipeRule.generate.json +0 -31
- package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
- package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
- package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
- package/guidelines/scalarModule/scalarModule.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
- package/guidelines/ssrRule/ssrRule.generate.json +0 -31
- package/guidelines/transportRule/transportRule.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
- package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
- package/index-47pj3aee.js +0 -76
- package/index-5ystnsd5.js +0 -841
- package/index-6wwzs1bc.js +0 -7
- package/index-a17x49vg.js +0 -229
- package/index-m0xccmmm.js +0 -228
- package/index-meh558jy.js +0 -65
- package/index-rtxxan88.js +0 -471
- package/index-te54x9zm.js +0 -338
- package/index-w8kmx1b6.js +0 -2176
- package/index-wt7qrtd4.js +0 -1516
- package/templates/__scalar/__model__/__model__.abstract.md +0 -28
- package/templates/module/__model__.abstract.md +0 -28
- package/templates/service/__model__.abstract.md +0 -28
- package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
- package/workflow.command-gtdnzvbz.js +0 -108
|
@@ -1,97 +1,32 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
AsyncDefaultExportDetector,
|
|
4
3
|
resolveSsrPageEntriesForApp
|
|
5
|
-
} from "./index-
|
|
4
|
+
} from "./index-150s07n8.js";
|
|
6
5
|
import {
|
|
6
|
+
computeRouteSeedIndex,
|
|
7
|
+
saveRouteSeedIndex,
|
|
7
8
|
BarrelAnalyzer,
|
|
8
|
-
createBarrelImportsPlugin,
|
|
9
9
|
createTsconfigPackageResolver,
|
|
10
10
|
rewriteBarrelImports,
|
|
11
11
|
GraphClientEntryDiscovery,
|
|
12
|
-
|
|
12
|
+
bundleDefine,
|
|
13
13
|
ClientEntriesBundler,
|
|
14
14
|
VENDOR_SPECIFIERS,
|
|
15
|
-
RouteClientBuilder
|
|
16
|
-
|
|
15
|
+
RouteClientBuilder,
|
|
16
|
+
CsrEntryFiles,
|
|
17
|
+
PagesEntrySourceGenerator,
|
|
18
|
+
PagesBundleBuilder
|
|
19
|
+
} from "./index-m3xsb1ms.js";
|
|
20
|
+
import {
|
|
21
|
+
SOURCE_EXTS
|
|
22
|
+
} from "./index-senscydg.js";
|
|
17
23
|
var __require = import.meta.require;
|
|
18
24
|
|
|
19
25
|
// pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
|
|
20
|
-
import path3 from "path";
|
|
21
|
-
|
|
22
|
-
// pkgs/@akanjs/devkit/artifact/routeSeedIndex.ts
|
|
23
26
|
import path from "path";
|
|
24
|
-
import { assertUniqueRoutePatterns, compareRouteSpecificity, parseRouteModuleKey } from "akanjs/common";
|
|
25
|
-
function computeRouteSeedIndex(pageEntries) {
|
|
26
|
-
const layoutsByPrefix = new Map;
|
|
27
|
-
const pagesBySegments = [];
|
|
28
|
-
for (const { key, moduleAbsPath, seedAbsPaths } of pageEntries) {
|
|
29
|
-
const parsed = parseRouteModuleKey(key);
|
|
30
|
-
const files = [path.resolve(moduleAbsPath), ...(seedAbsPaths ?? []).map((seed) => path.resolve(seed))];
|
|
31
|
-
if (parsed.kind === "layout" || parsed.kind === "overrides") {
|
|
32
|
-
const prefix = parsed.routeSegments.join("/");
|
|
33
|
-
const prev = layoutsByPrefix.get(prefix) ?? [];
|
|
34
|
-
layoutsByPrefix.set(prefix, [...prev, ...files]);
|
|
35
|
-
} else if (parsed.kind === "page") {
|
|
36
|
-
pagesBySegments.push({
|
|
37
|
-
key,
|
|
38
|
-
pattern: parsed.pattern,
|
|
39
|
-
segments: parsed.routeSegments,
|
|
40
|
-
files,
|
|
41
|
-
includeOwnLayout: parsed.leaf === "_index"
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
assertUniqueRoutePatterns(pagesBySegments);
|
|
46
|
-
const rootLayouts = layoutsByPrefix.get("") ?? [];
|
|
47
|
-
const globalLayoutFiles = rootLayouts;
|
|
48
|
-
const seedEntries = [];
|
|
49
|
-
for (const { pattern, segments, files, includeOwnLayout } of pagesBySegments) {
|
|
50
|
-
const layouts = [];
|
|
51
|
-
const maxPrefixLength = includeOwnLayout ? segments.length : Math.max(segments.length - 1, 0);
|
|
52
|
-
for (let i = 0;i <= maxPrefixLength; i++) {
|
|
53
|
-
const prefix = segments.slice(0, i).join("/");
|
|
54
|
-
const layoutFiles = layoutsByPrefix.get(prefix);
|
|
55
|
-
if (layoutFiles)
|
|
56
|
-
layouts.push(...layoutFiles);
|
|
57
|
-
}
|
|
58
|
-
const seeds = Array.from(new Set([...layouts, ...files]));
|
|
59
|
-
const routeId = pattern || "/";
|
|
60
|
-
seedEntries.push({ routeId, pattern: routeId, seeds });
|
|
61
|
-
}
|
|
62
|
-
seedEntries.sort((a, b) => compareRouteSpecificity(a.pattern, b.pattern));
|
|
63
|
-
return { entries: seedEntries, globalLayoutFiles };
|
|
64
|
-
}
|
|
65
|
-
var ROUTE_SEED_INDEX_JSON = "route-seed-index.json";
|
|
66
|
-
function serializeRouteSeedIndexForArtifact(index, artifactDir, options = {}) {
|
|
67
|
-
const normalizedArtifactDir = path.resolve(artifactDir);
|
|
68
|
-
if (options.production) {
|
|
69
|
-
return {
|
|
70
|
-
entries: index.entries.map((entry) => ({ routeId: entry.routeId }))
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
return {
|
|
74
|
-
entries: index.entries.map((entry) => ({
|
|
75
|
-
...entry,
|
|
76
|
-
seeds: entry.seeds.map((seed) => serializeArtifactPath(seed, normalizedArtifactDir))
|
|
77
|
-
})),
|
|
78
|
-
globalLayoutFiles: index.globalLayoutFiles.map((file) => serializeArtifactPath(file, normalizedArtifactDir))
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
async function saveRouteSeedIndex(artifactDir, index, options = {}) {
|
|
82
|
-
const absPath = path.join(path.resolve(artifactDir), ROUTE_SEED_INDEX_JSON);
|
|
83
|
-
await Bun.write(absPath, `${JSON.stringify(serializeRouteSeedIndexForArtifact(index, artifactDir, options), null, 2)}
|
|
84
|
-
`);
|
|
85
|
-
return absPath;
|
|
86
|
-
}
|
|
87
|
-
function serializeArtifactPath(artifactPath, artifactDir) {
|
|
88
|
-
if (!path.isAbsolute(artifactPath))
|
|
89
|
-
return artifactPath;
|
|
90
|
-
return path.relative(artifactDir, artifactPath).split(path.sep).join("/");
|
|
91
|
-
}
|
|
92
27
|
|
|
93
28
|
// pkgs/@akanjs/devkit/frontendBuild/routesManifestArtifactSerializer.ts
|
|
94
|
-
import
|
|
29
|
+
import { RoutesManifestStore } from "akanjs/server/artifact/routesManifestStore";
|
|
95
30
|
|
|
96
31
|
class RoutesManifestArtifactSerializer {
|
|
97
32
|
#manifest;
|
|
@@ -99,42 +34,17 @@ class RoutesManifestArtifactSerializer {
|
|
|
99
34
|
#production;
|
|
100
35
|
constructor(manifest, artifactDir, options = {}) {
|
|
101
36
|
this.#manifest = manifest;
|
|
102
|
-
this.#artifactDir =
|
|
37
|
+
this.#artifactDir = artifactDir;
|
|
103
38
|
this.#production = options.production ?? false;
|
|
104
39
|
}
|
|
105
40
|
static serialize(manifest, artifactDir, options = {}) {
|
|
106
41
|
return new RoutesManifestArtifactSerializer(manifest, artifactDir, options).serialize();
|
|
107
42
|
}
|
|
108
43
|
serialize() {
|
|
109
|
-
const serialized =
|
|
110
|
-
...this.#manifest,
|
|
111
|
-
knownEntries: this.#manifest.knownEntries.map((entry) => this.#serializeArtifactPath(entry))
|
|
112
|
-
};
|
|
44
|
+
const serialized = RoutesManifestStore.serialize(this.#manifest, this.#artifactDir);
|
|
113
45
|
if (this.#production)
|
|
114
46
|
delete serialized.knownEntries;
|
|
115
|
-
return
|
|
116
|
-
...serialized,
|
|
117
|
-
clientManifest: Object.fromEntries(Object.entries(this.#manifest.clientManifest).map(([key, row]) => [this.#serializeArtifactPath(key), row])),
|
|
118
|
-
ssrManifest: {
|
|
119
|
-
...this.#manifest.ssrManifest,
|
|
120
|
-
moduleMap: Object.fromEntries(Object.entries(this.#manifest.ssrManifest.moduleMap).map(([entryUrl, byName]) => [
|
|
121
|
-
entryUrl,
|
|
122
|
-
Object.fromEntries(Object.entries(byName).map(([name, entry]) => [
|
|
123
|
-
name,
|
|
124
|
-
{
|
|
125
|
-
...entry,
|
|
126
|
-
id: this.#serializeArtifactPath(entry.id),
|
|
127
|
-
chunks: entry.chunks.map((chunk) => this.#serializeArtifactPath(chunk))
|
|
128
|
-
}
|
|
129
|
-
]))
|
|
130
|
-
]))
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
#serializeArtifactPath(artifactPath) {
|
|
135
|
-
if (!path2.isAbsolute(artifactPath))
|
|
136
|
-
return artifactPath;
|
|
137
|
-
return path2.relative(this.#artifactDir, artifactPath).split(path2.sep).join("/");
|
|
47
|
+
return serialized;
|
|
138
48
|
}
|
|
139
49
|
}
|
|
140
50
|
|
|
@@ -164,27 +74,22 @@ class AllRoutesBuilder {
|
|
|
164
74
|
const seedIndex = computeRouteSeedIndex(pageEntries);
|
|
165
75
|
this.#app.verbose(`[build-all] discovered ${seedIndex.entries.length} routes`);
|
|
166
76
|
this.#discovery = await GraphClientEntryDiscovery.create(this.#app);
|
|
167
|
-
const allEntries =
|
|
168
|
-
const seen = new Set;
|
|
77
|
+
const allEntries = new Set;
|
|
169
78
|
for (const entry of seedIndex.entries) {
|
|
170
79
|
const seeds = Array.from(new Set([...seedIndex.globalLayoutFiles, ...entry.seeds]));
|
|
171
|
-
for (const discovered of await this.#discovery.discover(seeds))
|
|
172
|
-
|
|
173
|
-
continue;
|
|
174
|
-
seen.add(discovered);
|
|
175
|
-
allEntries.push(discovered);
|
|
176
|
-
}
|
|
80
|
+
for (const discovered of await this.#discovery.discover(seeds))
|
|
81
|
+
allEntries.add(discovered);
|
|
177
82
|
this.#routeIds.push(entry.routeId);
|
|
178
83
|
}
|
|
179
|
-
this.#app.verbose(`[build-all] ${allEntries.
|
|
180
|
-
const delta = await this.#buildEntries(allEntries);
|
|
84
|
+
this.#app.verbose(`[build-all] ${allEntries.size} client entries across ${this.#routeIds.length} routes`);
|
|
85
|
+
const delta = await this.#buildEntries([...allEntries]);
|
|
181
86
|
this.#mergeDelta(delta);
|
|
182
87
|
this.#merged.knownEntries = Array.from(this.#knownSet);
|
|
183
88
|
const manifest = {
|
|
184
89
|
routeIds: this.#routeIds,
|
|
185
90
|
...this.#merged
|
|
186
91
|
};
|
|
187
|
-
const manifestPath =
|
|
92
|
+
const manifestPath = path.join(path.resolve(this.#artifactDir), "routes-manifest.json");
|
|
188
93
|
await Bun.write(manifestPath, `${JSON.stringify(RoutesManifestArtifactSerializer.serialize(manifest, this.#artifactDir, {
|
|
189
94
|
production: this.#command === "build"
|
|
190
95
|
}), null, 2)}
|
|
@@ -216,18 +121,231 @@ class AllRoutesBuilder {
|
|
|
216
121
|
return delta;
|
|
217
122
|
}
|
|
218
123
|
#mergeDelta(delta) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
for (const [url, byName] of Object.entries(delta.ssrManifestDelta.moduleMap)) {
|
|
222
|
-
this.#merged.ssrManifest.moduleMap[url] = byName;
|
|
223
|
-
}
|
|
124
|
+
Object.assign(this.#merged.clientManifest, delta.manifestDelta);
|
|
125
|
+
Object.assign(this.#merged.ssrManifest.moduleMap, delta.ssrManifestDelta.moduleMap);
|
|
224
126
|
for (const abs of delta.newEntries)
|
|
225
127
|
this.#knownSet.add(abs);
|
|
226
128
|
}
|
|
227
129
|
}
|
|
228
130
|
|
|
131
|
+
// pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
|
|
132
|
+
import { mkdir, rm, unlink } from "fs/promises";
|
|
133
|
+
import path2 from "path";
|
|
134
|
+
class CsrArtifactBuilder {
|
|
135
|
+
#app;
|
|
136
|
+
#command;
|
|
137
|
+
#lang;
|
|
138
|
+
constructor(app, command = "start", lang = "en") {
|
|
139
|
+
this.#app = app;
|
|
140
|
+
this.#command = command;
|
|
141
|
+
this.#lang = lang;
|
|
142
|
+
}
|
|
143
|
+
async build() {
|
|
144
|
+
const pageKeys = await this.#app.getPageKeys();
|
|
145
|
+
if (pageKeys.length === 0) {
|
|
146
|
+
this.#app.log(`[cli] no route files under ${this.#app.cwdPath}/page \u2014 skipping CSR build`);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const pageEntries = await resolveSsrPageEntriesForApp(this.#app, pageKeys);
|
|
150
|
+
const akanConfig = await this.#app.getConfig();
|
|
151
|
+
const cssAssets = await this.#loadCssAssets();
|
|
152
|
+
const basePaths = [...akanConfig.basePaths];
|
|
153
|
+
const htmlBasePaths = basePaths.length > 0 ? basePaths : [""];
|
|
154
|
+
await rm(this.#outputDir, { recursive: true, force: true });
|
|
155
|
+
await mkdir(this.#generatedDir, { recursive: true });
|
|
156
|
+
const generatedFiles = Object.fromEntries((await Promise.all(htmlBasePaths.map(async (basePath) => [
|
|
157
|
+
this.#createHtmlFile(basePath),
|
|
158
|
+
await this.#createEntryFile(basePath, CsrArtifactBuilder.pageEntriesForBasePath(pageEntries, basePath, basePaths))
|
|
159
|
+
]))).flat());
|
|
160
|
+
const result = await Bun.build({
|
|
161
|
+
target: "browser",
|
|
162
|
+
entrypoints: htmlBasePaths.map((basePath) => this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath))),
|
|
163
|
+
files: generatedFiles,
|
|
164
|
+
root: this.#generatedDir,
|
|
165
|
+
outdir: this.#outputDir,
|
|
166
|
+
splitting: false,
|
|
167
|
+
minify: true,
|
|
168
|
+
env: "AKAN_PUBLIC_*",
|
|
169
|
+
define: bundleDefine(this.#app, this.#command, "csr"),
|
|
170
|
+
optimizeImports: akanConfig.optimizeImports,
|
|
171
|
+
plugins: [PagesBundleBuilder.createCssStubPlugin()]
|
|
172
|
+
});
|
|
173
|
+
if (!result.success) {
|
|
174
|
+
const logs = result.logs.map((log) => log.message).join(`
|
|
175
|
+
`);
|
|
176
|
+
throw new Error(`[csr-build] failed${logs ? `
|
|
177
|
+
${logs}` : ""}`);
|
|
178
|
+
}
|
|
179
|
+
await this.#inlineCsrArtifacts(cssAssets);
|
|
180
|
+
this.#app.verbose(`[csr-build] output -> ${this.#outputDir}`);
|
|
181
|
+
return { outputDir: this.#outputDir };
|
|
182
|
+
}
|
|
183
|
+
static pageEntriesForBasePath(pageEntries, basePath, basePaths) {
|
|
184
|
+
return CsrEntryFiles.pageEntriesForBasePath(pageEntries, basePath, basePaths);
|
|
185
|
+
}
|
|
186
|
+
static htmlFilename(basePath) {
|
|
187
|
+
return `${basePath || "index"}.html`;
|
|
188
|
+
}
|
|
189
|
+
static entryFilename(basePath) {
|
|
190
|
+
return CsrEntryFiles.entryFilename(basePath);
|
|
191
|
+
}
|
|
192
|
+
static basePathOfHtml(htmlPath) {
|
|
193
|
+
const name = path2.basename(htmlPath, ".html");
|
|
194
|
+
return name === "index" ? "" : name;
|
|
195
|
+
}
|
|
196
|
+
get #outputDir() {
|
|
197
|
+
return path2.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, this.#command === "build" ? "csr" : ".akan/artifact/csr");
|
|
198
|
+
}
|
|
199
|
+
get #generatedDir() {
|
|
200
|
+
return path2.join(this.#app.cwdPath, ".akan/generated/csr");
|
|
201
|
+
}
|
|
202
|
+
get #artifactDir() {
|
|
203
|
+
return path2.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact");
|
|
204
|
+
}
|
|
205
|
+
#generatedPath(filename) {
|
|
206
|
+
return path2.join(this.#generatedDir, filename);
|
|
207
|
+
}
|
|
208
|
+
async#createEntryFile(basePath, pageEntries) {
|
|
209
|
+
return [
|
|
210
|
+
this.#generatedPath(CsrArtifactBuilder.entryFilename(basePath)),
|
|
211
|
+
`import { bootCsr } from "akanjs/webkit";
|
|
212
|
+
${await PagesEntrySourceGenerator.generateStatic(pageEntries)}
|
|
213
|
+
void bootCsr(pages);
|
|
214
|
+
`
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
#createHtmlFile(basePath) {
|
|
218
|
+
return [
|
|
219
|
+
this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath)),
|
|
220
|
+
`<!doctype html>
|
|
221
|
+
<html lang="${this.#lang}">
|
|
222
|
+
<head>
|
|
223
|
+
<meta charset="utf-8" />
|
|
224
|
+
<title>${this.#app.name}</title>
|
|
225
|
+
<base href="/" />
|
|
226
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
227
|
+
</head>
|
|
228
|
+
<body>
|
|
229
|
+
<div id="root"></div>
|
|
230
|
+
<script type="module" src="./${CsrArtifactBuilder.entryFilename(basePath)}"></script>
|
|
231
|
+
</body>
|
|
232
|
+
</html>
|
|
233
|
+
`
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
async#loadCssAssets() {
|
|
237
|
+
const artifactFile = Bun.file(path2.join(this.#artifactDir, "base-artifact.json"));
|
|
238
|
+
if (!await artifactFile.exists())
|
|
239
|
+
return {};
|
|
240
|
+
const artifact = await artifactFile.json();
|
|
241
|
+
return artifact.cssAssets ?? {};
|
|
242
|
+
}
|
|
243
|
+
async#inlineCsrArtifacts(cssAssets) {
|
|
244
|
+
const jsFiles = new Set;
|
|
245
|
+
for (const htmlPath of await this.#listOutputFiles((filePath) => filePath.endsWith(".html"))) {
|
|
246
|
+
const htmlFile = Bun.file(htmlPath);
|
|
247
|
+
if (!await htmlFile.exists())
|
|
248
|
+
continue;
|
|
249
|
+
const basePath = CsrArtifactBuilder.basePathOfHtml(htmlPath);
|
|
250
|
+
const cssAsset = cssAssets[basePath];
|
|
251
|
+
if (!cssAsset) {
|
|
252
|
+
this.#app.logger.warn(`[csr-build] base-artifact.json has no compiled stylesheet for ${basePath || "root"}; ${path2.basename(htmlPath)} ships without CSS`);
|
|
253
|
+
}
|
|
254
|
+
const inlined = await this.#inlineHtmlAssets(await htmlFile.text(), htmlPath, cssAsset);
|
|
255
|
+
for (const filePath of inlined.jsFiles)
|
|
256
|
+
jsFiles.add(filePath);
|
|
257
|
+
await Bun.write(htmlPath, inlined.html);
|
|
258
|
+
}
|
|
259
|
+
for (const filePath of jsFiles)
|
|
260
|
+
await unlink(filePath).catch(() => {
|
|
261
|
+
return;
|
|
262
|
+
});
|
|
263
|
+
const remainingAssets = await this.#listOutputFiles((filePath) => filePath.endsWith(".js") || filePath.endsWith(".css"));
|
|
264
|
+
if (remainingAssets.length > 0) {
|
|
265
|
+
throw new Error(`[csr-build] expected single-file HTML, but CSR assets remain:
|
|
266
|
+
${remainingAssets.join(`
|
|
267
|
+
`)}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
async#inlineHtmlAssets(html, htmlPath, cssAsset) {
|
|
271
|
+
let next = html;
|
|
272
|
+
if (cssAsset) {
|
|
273
|
+
const css = await Bun.file(path2.join(this.#artifactDir, cssAsset.cssRelPath)).text();
|
|
274
|
+
next = CsrArtifactBuilder.injectBeforeHeadEnd(next, CsrArtifactBuilder.createInlineStyle(css));
|
|
275
|
+
}
|
|
276
|
+
const jsFiles = [];
|
|
277
|
+
next = await CsrArtifactBuilder.replaceModuleScriptSrc(next, async (src) => {
|
|
278
|
+
const jsPath = CsrArtifactBuilder.resolveHtmlAssetPath(htmlPath, src);
|
|
279
|
+
jsFiles.push(jsPath);
|
|
280
|
+
return await Bun.file(jsPath).text();
|
|
281
|
+
});
|
|
282
|
+
return { html: next, jsFiles };
|
|
283
|
+
}
|
|
284
|
+
async#listOutputFiles(predicate) {
|
|
285
|
+
const glob = new Bun.Glob("**/*");
|
|
286
|
+
const files = [];
|
|
287
|
+
for await (const filePath of glob.scan({ cwd: this.#outputDir, absolute: true })) {
|
|
288
|
+
if (predicate(filePath))
|
|
289
|
+
files.push(filePath);
|
|
290
|
+
}
|
|
291
|
+
return files.sort();
|
|
292
|
+
}
|
|
293
|
+
static injectBeforeHeadEnd(html, snippet) {
|
|
294
|
+
const scannable = CsrArtifactBuilder.blankEmbeddedContent(html);
|
|
295
|
+
const headEnd = scannable.search(/<\/head\s*>/i);
|
|
296
|
+
if (headEnd !== -1)
|
|
297
|
+
return `${html.slice(0, headEnd)}${snippet}
|
|
298
|
+
${html.slice(headEnd)}`;
|
|
299
|
+
const bodyStart = scannable.search(/<body(?:\s|>)/i);
|
|
300
|
+
if (bodyStart !== -1)
|
|
301
|
+
return `${html.slice(0, bodyStart)}${snippet}
|
|
302
|
+
${html.slice(bodyStart)}`;
|
|
303
|
+
return `${html}
|
|
304
|
+
${snippet}`;
|
|
305
|
+
}
|
|
306
|
+
static blankEmbeddedContent(html) {
|
|
307
|
+
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script\s*>|<style\b[^>]*>[\s\S]*?<\/style\s*>|<!--[\s\S]*?-->/gi, (match) => " ".repeat(match.length));
|
|
308
|
+
}
|
|
309
|
+
static createInlineStyle(css) {
|
|
310
|
+
return `<style data-akan-css="active">
|
|
311
|
+
${css.replace(/<\/style/gi, "<\\/style")}
|
|
312
|
+
</style>`;
|
|
313
|
+
}
|
|
314
|
+
static async replaceModuleScriptSrc(html, loadScript) {
|
|
315
|
+
const scriptRe = /<script\b(?=[^>]*\btype=["']module["'])(?=[^>]*\bsrc=["']([^"']+)["'])[^>]*>\s*<\/script>/gi;
|
|
316
|
+
let result = "";
|
|
317
|
+
let lastIndex = 0;
|
|
318
|
+
let matched = false;
|
|
319
|
+
for (const match of html.matchAll(scriptRe)) {
|
|
320
|
+
const full = match[0];
|
|
321
|
+
const src = match[1];
|
|
322
|
+
if (match.index === undefined || !src)
|
|
323
|
+
continue;
|
|
324
|
+
matched = true;
|
|
325
|
+
result += html.slice(lastIndex, match.index);
|
|
326
|
+
result += `<script type="module">
|
|
327
|
+
${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
|
|
328
|
+
</script>`;
|
|
329
|
+
lastIndex = match.index + full.length;
|
|
330
|
+
}
|
|
331
|
+
if (!matched)
|
|
332
|
+
return html;
|
|
333
|
+
return result + html.slice(lastIndex);
|
|
334
|
+
}
|
|
335
|
+
static escapeInlineScript(source) {
|
|
336
|
+
return source.replace(/<\/script/gi, "<\\/script");
|
|
337
|
+
}
|
|
338
|
+
static resolveHtmlAssetPath(htmlPath, src) {
|
|
339
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) {
|
|
340
|
+
throw new Error(`[csr-build] cannot inline external script: ${src}`);
|
|
341
|
+
}
|
|
342
|
+
const normalized = src.startsWith("/") ? src.slice(1) : src;
|
|
343
|
+
return path2.resolve(path2.dirname(htmlPath), normalized);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
229
347
|
// pkgs/@akanjs/devkit/frontendBuild/cssCompiler.ts
|
|
230
|
-
import
|
|
348
|
+
import path4 from "path";
|
|
231
349
|
import { Logger } from "akanjs/common";
|
|
232
350
|
import { compile } from "tailwindcss";
|
|
233
351
|
|
|
@@ -298,7 +416,7 @@ class CssCandidateCache {
|
|
|
298
416
|
}
|
|
299
417
|
|
|
300
418
|
// pkgs/@akanjs/devkit/frontendBuild/cssImportResolver.ts
|
|
301
|
-
import
|
|
419
|
+
import path3 from "path";
|
|
302
420
|
var CSS_IMPORT_EXTS = ["", ".css", "/styles.css", "/index.css"];
|
|
303
421
|
|
|
304
422
|
class CssImportResolver {
|
|
@@ -317,8 +435,8 @@ class CssImportResolver {
|
|
|
317
435
|
async resolve(id, fromBase) {
|
|
318
436
|
for (const resolve of [
|
|
319
437
|
() => this.#resolveWithTsconfig(id),
|
|
320
|
-
() => this.#
|
|
321
|
-
() => this.#
|
|
438
|
+
() => this.#resolveCssWith(fromBase, (base) => Bun.resolveSync(id, base)),
|
|
439
|
+
() => this.#resolveCssWith(fromBase, (base) => __require.resolve(id, { paths: [base] })),
|
|
322
440
|
() => this.#resolvePackageStyle(id, fromBase)
|
|
323
441
|
]) {
|
|
324
442
|
const resolved = await resolve();
|
|
@@ -327,20 +445,10 @@ class CssImportResolver {
|
|
|
327
445
|
}
|
|
328
446
|
return null;
|
|
329
447
|
}
|
|
330
|
-
#
|
|
331
|
-
for (const base of this.#resolutionBases(fromBase)) {
|
|
332
|
-
try {
|
|
333
|
-
const resolved = Bun.resolveSync(id, base);
|
|
334
|
-
if (CssImportResolver.isCssFile(resolved))
|
|
335
|
-
return resolved;
|
|
336
|
-
} catch {}
|
|
337
|
-
}
|
|
338
|
-
return null;
|
|
339
|
-
}
|
|
340
|
-
#resolveWithRequire(id, fromBase) {
|
|
448
|
+
#resolveCssWith(fromBase, resolveFrom) {
|
|
341
449
|
for (const base of this.#resolutionBases(fromBase)) {
|
|
342
450
|
try {
|
|
343
|
-
const resolved =
|
|
451
|
+
const resolved = resolveFrom(base);
|
|
344
452
|
if (CssImportResolver.isCssFile(resolved))
|
|
345
453
|
return resolved;
|
|
346
454
|
} catch {}
|
|
@@ -351,7 +459,7 @@ class CssImportResolver {
|
|
|
351
459
|
const exact = this.#paths[id];
|
|
352
460
|
if (exact) {
|
|
353
461
|
for (const repl of exact) {
|
|
354
|
-
const resolved = await this.#firstExisting(
|
|
462
|
+
const resolved = await this.#firstExisting(path3.resolve(this.#workspaceRoot, repl));
|
|
355
463
|
if (resolved)
|
|
356
464
|
return resolved;
|
|
357
465
|
}
|
|
@@ -362,7 +470,7 @@ class CssImportResolver {
|
|
|
362
470
|
const suffix = id.slice(prefix.length);
|
|
363
471
|
for (const repl of replacements) {
|
|
364
472
|
const replPath = repl.endsWith("/*") ? repl.slice(0, -1) : repl;
|
|
365
|
-
const resolved = await this.#firstExisting(
|
|
473
|
+
const resolved = await this.#firstExisting(path3.resolve(this.#workspaceRoot, replPath + suffix));
|
|
366
474
|
if (resolved)
|
|
367
475
|
return resolved;
|
|
368
476
|
}
|
|
@@ -392,34 +500,34 @@ class CssImportResolver {
|
|
|
392
500
|
try {
|
|
393
501
|
if (!await Bun.file(pkgPath).exists())
|
|
394
502
|
return null;
|
|
395
|
-
const pkgDir =
|
|
503
|
+
const pkgDir = path3.dirname(pkgPath);
|
|
396
504
|
const pkg = await Bun.file(pkgPath).json();
|
|
397
505
|
const subpath = id === pkgName ? "." : `.${id.slice(pkgName.length)}`;
|
|
398
506
|
const exportValue = pkg.exports?.[subpath];
|
|
399
507
|
const exportedEntry = typeof exportValue === "string" ? exportValue : exportValue?.style || exportValue?.import || exportValue?.default;
|
|
400
508
|
if (exportedEntry)
|
|
401
|
-
return await this.#firstExisting(
|
|
509
|
+
return await this.#firstExisting(path3.resolve(pkgDir, exportedEntry));
|
|
402
510
|
if (subpath !== ".")
|
|
403
|
-
return await this.#firstExisting(
|
|
404
|
-
return await this.#firstExisting(
|
|
511
|
+
return await this.#firstExisting(path3.resolve(pkgDir, subpath));
|
|
512
|
+
return await this.#firstExisting(path3.resolve(pkgDir, pkg.exports?.["."]?.style || pkg.style || "index.css"));
|
|
405
513
|
} catch {
|
|
406
514
|
return null;
|
|
407
515
|
}
|
|
408
516
|
}
|
|
409
517
|
#resolutionBases(fromBase) {
|
|
410
|
-
return [fromBase, this.#workspaceRoot,
|
|
518
|
+
return [fromBase, this.#workspaceRoot, path3.dirname(Bun.main), path3.resolve(path3.dirname(Bun.main), "../..")];
|
|
411
519
|
}
|
|
412
520
|
#packageJsonCandidates(pkgName) {
|
|
413
521
|
return [
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
522
|
+
path3.join(this.#workspaceRoot, "pkgs", pkgName, "package.json"),
|
|
523
|
+
path3.join(this.#workspaceRoot, "node_modules", pkgName, "package.json"),
|
|
524
|
+
path3.join(path3.dirname(Bun.main), "node_modules", pkgName, "package.json"),
|
|
525
|
+
path3.join(path3.dirname(Bun.main), "../../", pkgName, "package.json")
|
|
418
526
|
];
|
|
419
527
|
}
|
|
420
528
|
async#firstExisting(basePath) {
|
|
421
529
|
for (const suffix of CSS_IMPORT_EXTS) {
|
|
422
|
-
const candidate = `${basePath}${suffix}
|
|
530
|
+
const candidate = path3.normalize(`${basePath}${suffix}`);
|
|
423
531
|
if (await Bun.file(candidate).exists())
|
|
424
532
|
return candidate;
|
|
425
533
|
}
|
|
@@ -432,12 +540,11 @@ class CssImportResolver {
|
|
|
432
540
|
return parts[0] ?? null;
|
|
433
541
|
}
|
|
434
542
|
static isCssFile(filePath) {
|
|
435
|
-
return
|
|
543
|
+
return path3.extname(filePath) === ".css";
|
|
436
544
|
}
|
|
437
545
|
}
|
|
438
546
|
|
|
439
547
|
// pkgs/@akanjs/devkit/frontendBuild/cssCompiler.ts
|
|
440
|
-
var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
441
548
|
var NON_SOURCE_EXT_RE = /\.(json|svg|png|jpe?g|webp|gif|avif|ico|woff2?|ttf|otf|mp3|mp4|wav)$/i;
|
|
442
549
|
var NODE_MODULES_RE = /[\\/]node_modules[\\/]/;
|
|
443
550
|
var AKANJS_NODE_MODULE_RE = /[\\/]node_modules[\\/]akanjs[\\/]/;
|
|
@@ -451,7 +558,7 @@ class CssCompiler {
|
|
|
451
558
|
this.#app = app;
|
|
452
559
|
}
|
|
453
560
|
get #candidateCachePath() {
|
|
454
|
-
return
|
|
561
|
+
return path4.join(this.#app.cwdPath, ".akan/cache/cssCandidates.json");
|
|
455
562
|
}
|
|
456
563
|
#cssText = null;
|
|
457
564
|
#cssTextByBasePath = null;
|
|
@@ -481,7 +588,7 @@ class CssCompiler {
|
|
|
481
588
|
return filePath;
|
|
482
589
|
}
|
|
483
590
|
for (const ext of SOURCE_EXTS) {
|
|
484
|
-
const filePath =
|
|
591
|
+
const filePath = path4.join(absPathNoExt, `index${ext}`);
|
|
485
592
|
if (await this.#fileExists(filePath))
|
|
486
593
|
return filePath;
|
|
487
594
|
}
|
|
@@ -511,42 +618,33 @@ class CssCompiler {
|
|
|
511
618
|
const pageKeys = await this.#app.getPageKeys({ refresh });
|
|
512
619
|
const basePaths = [...akanConfig.basePaths];
|
|
513
620
|
const rootPageKeys = pageKeys.filter((pageKey) => getPageKeyBasePath(pageKey, basePaths) === null);
|
|
621
|
+
const compileBasePath = async (basePath, basePathPageKeys, label = basePath) => {
|
|
622
|
+
if (basePathPageKeys.length === 0)
|
|
623
|
+
return [basePath, ""];
|
|
624
|
+
const started = Date.now();
|
|
625
|
+
const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: basePathPageKeys });
|
|
626
|
+
const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
|
|
627
|
+
this.importedStylesheetsByBasePath[basePath] = imported;
|
|
628
|
+
this.#logger.verbose(`css base=${label} paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
|
|
629
|
+
return [basePath, css];
|
|
630
|
+
};
|
|
514
631
|
const cssEntries = await Promise.all([
|
|
515
|
-
(
|
|
516
|
-
|
|
517
|
-
return ["", ""];
|
|
518
|
-
const started = Date.now();
|
|
519
|
-
const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: rootPageKeys });
|
|
520
|
-
const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
|
|
521
|
-
this.importedStylesheetsByBasePath[""] = imported;
|
|
522
|
-
this.#logger.verbose(`css base=root paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
|
|
523
|
-
return ["", css];
|
|
524
|
-
})(),
|
|
525
|
-
...basePaths.map(async (basePath) => {
|
|
526
|
-
const basePathPageKeys = pageKeys.filter((pageKey) => getPageKeyBasePath(pageKey, basePaths) === basePath);
|
|
527
|
-
if (basePathPageKeys.length === 0)
|
|
528
|
-
return [basePath, ""];
|
|
529
|
-
const started = Date.now();
|
|
530
|
-
const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: basePathPageKeys });
|
|
531
|
-
const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
|
|
532
|
-
this.importedStylesheetsByBasePath[basePath] = imported;
|
|
533
|
-
this.#logger.verbose(`css base=${basePath} paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
|
|
534
|
-
return [basePath, css];
|
|
535
|
-
})
|
|
632
|
+
compileBasePath("", rootPageKeys, "root"),
|
|
633
|
+
...basePaths.map((basePath) => compileBasePath(basePath, pageKeys.filter((pageKey) => getPageKeyBasePath(pageKey, basePaths) === basePath)))
|
|
536
634
|
]);
|
|
537
635
|
this.#cssTextByBasePath = Object.fromEntries(cssEntries);
|
|
538
636
|
await this.#warnUnreachableStylesheets();
|
|
539
637
|
return this.#cssTextByBasePath;
|
|
540
638
|
}
|
|
541
639
|
async#warnUnreachableStylesheets() {
|
|
542
|
-
const pageDir =
|
|
640
|
+
const pageDir = path4.join(this.#app.cwdPath, "page");
|
|
543
641
|
const glob = new Bun.Glob("**/*.css");
|
|
544
642
|
for await (const cssPath of glob.scan({ cwd: pageDir, absolute: true })) {
|
|
545
|
-
if (cssPath.includes(`${
|
|
643
|
+
if (cssPath.includes(`${path4.sep}(libs)${path4.sep}`))
|
|
546
644
|
continue;
|
|
547
645
|
if (this.#discoveredCssPaths.has(cssPath))
|
|
548
646
|
continue;
|
|
549
|
-
this.#logger.warn(`css ${
|
|
647
|
+
this.#logger.warn(`css ${path4.relative(this.#app.cwdPath, cssPath)} is imported by no route and never compiled`);
|
|
550
648
|
}
|
|
551
649
|
}
|
|
552
650
|
async discoverCss({ refresh } = {}) {
|
|
@@ -558,7 +656,7 @@ class CssCompiler {
|
|
|
558
656
|
pageKeys
|
|
559
657
|
} = {}) {
|
|
560
658
|
pageKeys ??= await this.#app.getPageKeys({ refresh });
|
|
561
|
-
const seeds = pageKeys.map((key) =>
|
|
659
|
+
const seeds = pageKeys.map((key) => path4.resolve(this.#app.cwdPath, "page", key));
|
|
562
660
|
const cssFiles = new Set;
|
|
563
661
|
const sourceFiles = new Set;
|
|
564
662
|
const queue = [...seeds];
|
|
@@ -587,772 +685,198 @@ class CssCompiler {
|
|
|
587
685
|
let imports;
|
|
588
686
|
try {
|
|
589
687
|
imports = this.#transpiler.scanImports(source);
|
|
590
|
-
} catch {
|
|
591
|
-
continue;
|
|
592
|
-
}
|
|
593
|
-
const importerDir = path5.dirname(filePath);
|
|
594
|
-
for (const imp of imports) {
|
|
595
|
-
const spec = imp.path;
|
|
596
|
-
if (!spec)
|
|
597
|
-
continue;
|
|
598
|
-
if (spec.endsWith(".css")) {
|
|
599
|
-
const cssPath = await this.#resolveCssImport(spec, importerDir);
|
|
600
|
-
cssFiles.add(cssPath);
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
if (NON_SOURCE_EXT_RE.test(spec))
|
|
604
|
-
continue;
|
|
605
|
-
const resolved = await this.#resolveSourceImport(spec, importerDir, resolvePackage);
|
|
606
|
-
if (!resolved || sourceFiles.has(resolved) || isIgnoredNodeModuleSource(resolved))
|
|
607
|
-
continue;
|
|
608
|
-
queue.push(resolved);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
const tokenPaths = await this.#libTokenStylesheets(sourceFiles);
|
|
612
|
-
const cssPaths = [...new Set([...tokenPaths, ...cssFiles])];
|
|
613
|
-
for (const cssPath of cssPaths)
|
|
614
|
-
this.#discoveredCssPaths.add(cssPath);
|
|
615
|
-
return { cssPaths, sourcePaths: [...sourceFiles] };
|
|
616
|
-
}
|
|
617
|
-
async#libTokenStylesheets(sourceFiles) {
|
|
618
|
-
const libsRoot = path5.join(this.#app.workspace.workspaceRoot, "libs");
|
|
619
|
-
const libNames = new Set;
|
|
620
|
-
for (const filePath of sourceFiles) {
|
|
621
|
-
const relPath = path5.relative(libsRoot, filePath);
|
|
622
|
-
if (relPath.startsWith("..") || path5.isAbsolute(relPath))
|
|
623
|
-
continue;
|
|
624
|
-
const [libName] = relPath.split(path5.sep);
|
|
625
|
-
if (libName)
|
|
626
|
-
libNames.add(libName);
|
|
627
|
-
}
|
|
628
|
-
const tokenPaths = await Promise.all([...libNames].sort().map(async (libName) => {
|
|
629
|
-
const tokensPath = path5.join(libsRoot, libName, "ui/tokens.css");
|
|
630
|
-
return await this.#fileExists(tokensPath) ? tokensPath : null;
|
|
631
|
-
}));
|
|
632
|
-
return tokenPaths.filter((tokensPath) => !!tokensPath);
|
|
633
|
-
}
|
|
634
|
-
async compileCss(cssPaths, sourcePaths) {
|
|
635
|
-
const { css } = await this.#compileWithImports(cssPaths, sourcePaths);
|
|
636
|
-
return css;
|
|
637
|
-
}
|
|
638
|
-
async#compileWithImports(cssPaths, sourcePaths) {
|
|
639
|
-
if (cssPaths.length === 0)
|
|
640
|
-
return { css: "", imported: [] };
|
|
641
|
-
const compileStarted = Date.now();
|
|
642
|
-
const compilers = await Promise.all(cssPaths.map(async (cssPath) => {
|
|
643
|
-
const css = await Bun.file(cssPath).text();
|
|
644
|
-
const base = path5.dirname(cssPath);
|
|
645
|
-
const imported = new Map;
|
|
646
|
-
const compiler = await compile(css, {
|
|
647
|
-
base,
|
|
648
|
-
loadStylesheet: (id, fromBase) => this.#loadStylesheet(id, fromBase, imported),
|
|
649
|
-
loadModule: (id, fromBase) => this.#loadModule(id, fromBase)
|
|
650
|
-
});
|
|
651
|
-
return { cssPath, compiler, imported };
|
|
652
|
-
}));
|
|
653
|
-
const sourceDirs = new Set;
|
|
654
|
-
for (const entry of compilers) {
|
|
655
|
-
if (!entry)
|
|
656
|
-
continue;
|
|
657
|
-
for (const s of entry.compiler.sources)
|
|
658
|
-
sourceDirs.add(s.base);
|
|
659
|
-
}
|
|
660
|
-
const scanStarted = Date.now();
|
|
661
|
-
const candidates = await this.#scanCandidates(sourcePaths, [...sourceDirs]);
|
|
662
|
-
this.#logger.verbose(`css candidates scanned count=${candidates.length} sources=${sourcePaths.length} dirs=${sourceDirs.size} in ${Date.now() - scanStarted}ms`);
|
|
663
|
-
const parts = [];
|
|
664
|
-
const imported = [];
|
|
665
|
-
for (const entry of compilers) {
|
|
666
|
-
if (!entry)
|
|
667
|
-
continue;
|
|
668
|
-
const part = entry.compiler.build(candidates);
|
|
669
|
-
parts.push(part);
|
|
670
|
-
for (const [cssPath, content] of entry.imported) {
|
|
671
|
-
const declaredNames = declaredCustomProperties(content);
|
|
672
|
-
imported.push({ cssPath, declaredNames });
|
|
673
|
-
if (declaredNames.length === 0 || declaredNames.some((name) => part.includes(`${name}:`)))
|
|
674
|
-
continue;
|
|
675
|
-
this.#logger.warn(`css @import ${cssPath} was loaded by ${entry.cssPath} but none of its ${declaredNames.length} declaration(s) reached the compiled CSS`);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
this.#logger.verbose(`css compiled paths=${cssPaths.length} candidates=${candidates.length} in ${Date.now() - compileStarted}ms`);
|
|
679
|
-
return { css: parts.join(`
|
|
680
|
-
`), imported };
|
|
681
|
-
}
|
|
682
|
-
async#loadStylesheet(id, fromBase, imported) {
|
|
683
|
-
const p = await this.#resolveCssImport(id, fromBase);
|
|
684
|
-
this.#discoveredCssPaths.add(p);
|
|
685
|
-
const content = await Bun.file(p).text();
|
|
686
|
-
imported?.set(p, content);
|
|
687
|
-
this.#logger.verbose(`css import "${id}" from ${fromBase} -> ${p} (${content.length} bytes)`);
|
|
688
|
-
return { path: p, base: path5.dirname(p), content };
|
|
689
|
-
}
|
|
690
|
-
async#resolveCssImport(id, fromBase) {
|
|
691
|
-
if (id.startsWith(".") || id.startsWith("/")) {
|
|
692
|
-
const filePath = path5.resolve(fromBase, id);
|
|
693
|
-
if (await this.#fileExists(filePath))
|
|
694
|
-
return filePath;
|
|
695
|
-
throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase} (no file at ${filePath})`);
|
|
696
|
-
}
|
|
697
|
-
const resolver = await this.#getCssImportResolver();
|
|
698
|
-
const resolved = await resolver.resolve(id, fromBase);
|
|
699
|
-
if (resolved)
|
|
700
|
-
return resolved;
|
|
701
|
-
throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase}`);
|
|
702
|
-
}
|
|
703
|
-
async#getCssImportResolver() {
|
|
704
|
-
if (this.#cssImportResolver)
|
|
705
|
-
return this.#cssImportResolver;
|
|
706
|
-
this.#cssImportResolver = await CssImportResolver.create(this.#app);
|
|
707
|
-
return this.#cssImportResolver;
|
|
708
|
-
}
|
|
709
|
-
async#loadModule(id, fromBase) {
|
|
710
|
-
const p = __require.resolve(id, { paths: [fromBase] });
|
|
711
|
-
const mod = await import(p);
|
|
712
|
-
return { path: p, base: path5.dirname(p), module: mod.default ?? mod };
|
|
713
|
-
}
|
|
714
|
-
#resolveSourceImport(id, fromBase, resolvePackage) {
|
|
715
|
-
const cacheKey = `${fromBase}\x00${id}`;
|
|
716
|
-
let cached = this.#resolvedSpecifierCache.get(cacheKey);
|
|
717
|
-
if (cached)
|
|
718
|
-
return cached;
|
|
719
|
-
cached = this.#resolveSourceImportUncached(id, fromBase, resolvePackage);
|
|
720
|
-
this.#resolvedSpecifierCache.set(cacheKey, cached);
|
|
721
|
-
return cached;
|
|
722
|
-
}
|
|
723
|
-
async#resolveSourceImportUncached(id, fromBase, resolvePackage) {
|
|
724
|
-
if (id.startsWith(".") || id.startsWith("/")) {
|
|
725
|
-
const abs = id.startsWith("/") ? id : path5.resolve(fromBase, id);
|
|
726
|
-
return this.#resolveSourceFileCandidate(abs);
|
|
727
|
-
}
|
|
728
|
-
const pkg = await resolvePackage(id);
|
|
729
|
-
if (pkg)
|
|
730
|
-
return pkg.entryFile;
|
|
731
|
-
for (const resolve of [() => resolveSourceWithBun(id, fromBase), () => resolveSourceWithRequire(id, fromBase)]) {
|
|
732
|
-
const resolved = await resolve();
|
|
733
|
-
if (resolved)
|
|
734
|
-
return resolved;
|
|
735
|
-
}
|
|
736
|
-
return null;
|
|
737
|
-
}
|
|
738
|
-
async#scanCandidates(sourcePaths, dirs) {
|
|
739
|
-
const candidates = new Set;
|
|
740
|
-
const glob = new Bun.Glob("**/*.{tsx,ts,jsx,js,html}");
|
|
741
|
-
const files = new Set(sourcePaths);
|
|
742
|
-
await Promise.all(dirs.map(async (dir) => {
|
|
743
|
-
for await (const file of glob.scan({ cwd: dir, absolute: true })) {
|
|
744
|
-
if (isIgnoredNodeModuleSource(file))
|
|
745
|
-
continue;
|
|
746
|
-
files.add(file);
|
|
747
|
-
}
|
|
748
|
-
}));
|
|
749
|
-
const cache = await new CssCandidateCache(this.#candidateCachePath).load();
|
|
750
|
-
await Promise.all([...files].map(async (file) => {
|
|
751
|
-
for (const candidate of await cache.candidatesFor(file))
|
|
752
|
-
candidates.add(candidate);
|
|
753
|
-
}));
|
|
754
|
-
await cache.save(files);
|
|
755
|
-
this.#logger.verbose(`css candidate cache reused=${cache.reused} rescanned=${cache.rescanned}`);
|
|
756
|
-
return [...candidates];
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
function resolveSourceWithBun(id, fromBase) {
|
|
760
|
-
try {
|
|
761
|
-
const resolved = Bun.resolveSync(id, fromBase);
|
|
762
|
-
return isSourceFile(resolved) ? resolved : null;
|
|
763
|
-
} catch {
|
|
764
|
-
return null;
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
function resolveSourceWithRequire(id, fromBase) {
|
|
768
|
-
try {
|
|
769
|
-
const resolved = __require.resolve(id, { paths: [fromBase] });
|
|
770
|
-
return isSourceFile(resolved) ? resolved : null;
|
|
771
|
-
} catch {
|
|
772
|
-
return null;
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
function isSourceFile(filePath) {
|
|
776
|
-
return SOURCE_EXTS.includes(path5.extname(filePath));
|
|
777
|
-
}
|
|
778
|
-
function isIgnoredNodeModuleSource(filePath) {
|
|
779
|
-
return NODE_MODULES_RE.test(filePath) && !AKANJS_NODE_MODULE_RE.test(filePath);
|
|
780
|
-
}
|
|
781
|
-
function declaredCustomProperties(css) {
|
|
782
|
-
const withoutThemeBlocks = css.replace(/@theme[^{]*\{[^}]*\}/g, "");
|
|
783
|
-
return [...new Set([...withoutThemeBlocks.matchAll(/(?:^|[\s;{])(--[\w-]+)\s*:/g)].map(([, name]) => name))].filter((name) => !!name);
|
|
784
|
-
}
|
|
785
|
-
function getPageKeyBasePath(pageKey, basePaths) {
|
|
786
|
-
const normalized = pageKey.split(path5.sep).join("/").replace(/^\.\//, "");
|
|
787
|
-
const segments = normalized.split("/");
|
|
788
|
-
const firstPublicSegment = segments.find((segment) => segment !== "[lang]" && !/^\(.+\)$/.test(segment));
|
|
789
|
-
return firstPublicSegment && basePaths.includes(firstPublicSegment) ? firstPublicSegment : null;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
// pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
|
|
793
|
-
import path8 from "path";
|
|
794
|
-
|
|
795
|
-
// pkgs/@akanjs/devkit/transforms/externalizeFrameworkPlugin.ts
|
|
796
|
-
import path6 from "path";
|
|
797
|
-
var DEFAULT_INCLUDE = ["akanjs/", "@apps/", "@libs/"];
|
|
798
|
-
var DEFAULT_EXCLUDE_EXACT = new Set(["akanjs/webkit", "@akanjs/cli", "@akanjs/devkit"]);
|
|
799
|
-
var DEFAULT_EXCLUDE_PREFIX = ["@akanjs/cli/", "@akanjs/devkit/"];
|
|
800
|
-
var OPTIONAL_BACKEND_EXTERNAL_EXACT = new Set([
|
|
801
|
-
"@libsql/client",
|
|
802
|
-
"bullmq",
|
|
803
|
-
"ioredis",
|
|
804
|
-
"postgres",
|
|
805
|
-
"protobufjs"
|
|
806
|
-
]);
|
|
807
|
-
var RUNTIME_EXTERNAL_EXACT = new Set([
|
|
808
|
-
"react",
|
|
809
|
-
"react-dom",
|
|
810
|
-
"react/jsx-runtime",
|
|
811
|
-
"react/jsx-dev-runtime",
|
|
812
|
-
"react-server-dom-webpack",
|
|
813
|
-
"react-server-dom-webpack/server.node",
|
|
814
|
-
"react-server-dom-webpack/client.node",
|
|
815
|
-
"react-server-dom-webpack/client.browser"
|
|
816
|
-
]);
|
|
817
|
-
var RUNTIME_EXTERNAL_PREFIX = ["react-dom/", "react-server-dom-webpack/"];
|
|
818
|
-
var CANDIDATE_EXTS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
|
|
819
|
-
async function createExternalizeFrameworkPlugin(options) {
|
|
820
|
-
const tsconfig = await options.app.getTsConfig();
|
|
821
|
-
const includePrefixes = options.include ?? DEFAULT_INCLUDE;
|
|
822
|
-
const extraExact = new Set(options.extra ?? []);
|
|
823
|
-
const workspaceRoot = options.app.workspace.workspaceRoot;
|
|
824
|
-
const tsconfigPaths = tsconfig.compilerOptions.paths ?? {};
|
|
825
|
-
const rootEntries = Object.entries(tsconfigPaths).filter(([k]) => !k.endsWith("/*")).map(([k, v]) => ({ pkg: k, entryFile: v[0] ?? null })).filter((e) => e.entryFile !== null);
|
|
826
|
-
const wildcardEntries = Object.entries(tsconfigPaths).filter(([k]) => k.endsWith("/*")).map(([k, v]) => ({ prefix: k.slice(0, -1), replacements: v })).sort((a, b) => b.prefix.length - a.prefix.length);
|
|
827
|
-
async function resolveWorkspaceSubpath(spec) {
|
|
828
|
-
if (!workspaceRoot)
|
|
829
|
-
return null;
|
|
830
|
-
for (const { prefix, replacements } of wildcardEntries) {
|
|
831
|
-
if (!spec.startsWith(prefix))
|
|
832
|
-
continue;
|
|
833
|
-
const suffix = spec.slice(prefix.length);
|
|
834
|
-
for (const repl of replacements) {
|
|
835
|
-
const replPath = repl?.endsWith("/*") ? repl.slice(0, -1) : repl ?? "";
|
|
836
|
-
if (!replPath)
|
|
837
|
-
continue;
|
|
838
|
-
const candidate = path6.resolve(workspaceRoot, replPath + suffix);
|
|
839
|
-
const hit = await firstExisting(candidate);
|
|
840
|
-
if (hit)
|
|
841
|
-
return hit;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
for (const { pkg, entryFile } of rootEntries) {
|
|
845
|
-
if (spec !== pkg && !spec.startsWith(`${pkg}/`))
|
|
846
|
-
continue;
|
|
847
|
-
if (spec === pkg)
|
|
848
|
-
continue;
|
|
849
|
-
const suffix = spec.slice(pkg.length + 1);
|
|
850
|
-
const pkgDir = path6.dirname(path6.resolve(workspaceRoot, entryFile));
|
|
851
|
-
const candidate = path6.join(pkgDir, suffix);
|
|
852
|
-
const hit = await firstExisting(candidate);
|
|
853
|
-
if (hit)
|
|
854
|
-
return hit;
|
|
855
|
-
}
|
|
856
|
-
return null;
|
|
857
|
-
}
|
|
858
|
-
return {
|
|
859
|
-
name: "akan-externalize-framework",
|
|
860
|
-
setup(build) {
|
|
861
|
-
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
862
|
-
const spec = args.path;
|
|
863
|
-
if (spec === "." || spec === ".." || spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/"))
|
|
864
|
-
return;
|
|
865
|
-
if (extraExact.has(spec) || DEFAULT_EXCLUDE_EXACT.has(spec))
|
|
866
|
-
return { path: spec, external: true };
|
|
867
|
-
for (const prefix of DEFAULT_EXCLUDE_PREFIX) {
|
|
868
|
-
if (spec.startsWith(prefix))
|
|
869
|
-
return { path: spec, external: true };
|
|
870
|
-
}
|
|
871
|
-
if (OPTIONAL_BACKEND_EXTERNAL_EXACT.has(spec))
|
|
872
|
-
return { path: spec, external: true };
|
|
873
|
-
if (RUNTIME_EXTERNAL_EXACT.has(spec))
|
|
874
|
-
return { path: spec, external: true };
|
|
875
|
-
for (const prefix of RUNTIME_EXTERNAL_PREFIX) {
|
|
876
|
-
if (spec.startsWith(prefix))
|
|
877
|
-
return { path: spec, external: true };
|
|
878
|
-
}
|
|
879
|
-
for (const prefix of includePrefixes) {
|
|
880
|
-
if (!spec.startsWith(prefix))
|
|
881
|
-
continue;
|
|
882
|
-
const resolved = await resolveWorkspaceSubpath(spec);
|
|
883
|
-
if (resolved)
|
|
884
|
-
return { path: resolved };
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
return;
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
};
|
|
891
|
-
}
|
|
892
|
-
async function firstExisting(basePath) {
|
|
893
|
-
if (await Bun.file(basePath).exists())
|
|
894
|
-
return basePath;
|
|
895
|
-
for (const ext of CANDIDATE_EXTS) {
|
|
896
|
-
const candidate = `${basePath}${ext}`;
|
|
897
|
-
if (await Bun.file(candidate).exists())
|
|
898
|
-
return candidate;
|
|
899
|
-
}
|
|
900
|
-
for (const ext of CANDIDATE_EXTS) {
|
|
901
|
-
const candidate = path6.join(basePath, `index${ext}`);
|
|
902
|
-
if (await Bun.file(candidate).exists())
|
|
903
|
-
return candidate;
|
|
904
|
-
}
|
|
905
|
-
return null;
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
// pkgs/@akanjs/devkit/transforms/useClientBundlePlugin.ts
|
|
909
|
-
function createUseClientBundlePlugin(options = {}) {
|
|
910
|
-
return {
|
|
911
|
-
name: "akan-use-client-bundle",
|
|
912
|
-
setup(build) {
|
|
913
|
-
build.onLoad({ filter: /\.(tsx|ts|jsx|js)$/ }, async (args) => {
|
|
914
|
-
if (args.path.includes("/node_modules/"))
|
|
915
|
-
return;
|
|
916
|
-
let source;
|
|
917
|
-
try {
|
|
918
|
-
source = await Bun.file(args.path).text();
|
|
919
|
-
} catch {
|
|
920
|
-
return;
|
|
921
|
-
}
|
|
922
|
-
const stubbed = transformUseClient(source, {
|
|
923
|
-
path: args.path,
|
|
924
|
-
workspaceRoot: options.workspaceRoot
|
|
925
|
-
});
|
|
926
|
-
if (stubbed === null)
|
|
927
|
-
return;
|
|
928
|
-
return { contents: stubbed, loader: loaderFor(args.path) };
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
function loaderFor(absPath) {
|
|
934
|
-
if (absPath.endsWith(".tsx"))
|
|
935
|
-
return "tsx";
|
|
936
|
-
if (absPath.endsWith(".jsx"))
|
|
937
|
-
return "jsx";
|
|
938
|
-
if (absPath.endsWith(".ts"))
|
|
939
|
-
return "ts";
|
|
940
|
-
return "js";
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// pkgs/@akanjs/devkit/frontendBuild/pagesEntrySourceGenerator.ts
|
|
944
|
-
import path7 from "path";
|
|
945
|
-
class PagesEntrySourceGenerator {
|
|
946
|
-
#pageEntries;
|
|
947
|
-
constructor(pageEntries) {
|
|
948
|
-
this.#pageEntries = pageEntries;
|
|
949
|
-
}
|
|
950
|
-
static generate(pageEntries) {
|
|
951
|
-
return new PagesEntrySourceGenerator(pageEntries).generate();
|
|
952
|
-
}
|
|
953
|
-
generate() {
|
|
954
|
-
const lines = this.#pageEntries.map(({ key, moduleAbsPath }) => {
|
|
955
|
-
const specifier = PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
|
|
956
|
-
return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(specifier)}),`;
|
|
957
|
-
});
|
|
958
|
-
return `export const pages = {
|
|
959
|
-
${lines.join(`
|
|
960
|
-
`)}
|
|
961
|
-
};
|
|
962
|
-
`;
|
|
963
|
-
}
|
|
964
|
-
static async generateStatic(pageEntries) {
|
|
965
|
-
return await new PagesEntrySourceGenerator(pageEntries).generateStatic();
|
|
966
|
-
}
|
|
967
|
-
async generateStatic() {
|
|
968
|
-
const imports = this.#pageEntries.map(({ moduleAbsPath }, index) => {
|
|
969
|
-
const specifier = PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
|
|
970
|
-
return `import * as page${index} from ${JSON.stringify(specifier)};`;
|
|
971
|
-
});
|
|
972
|
-
const entries = await Promise.all(this.#pageEntries.map(async ({ key, moduleAbsPath }, index) => {
|
|
973
|
-
const isAsyncDefault = await AsyncDefaultExportDetector.detect(moduleAbsPath);
|
|
974
|
-
return ` ${JSON.stringify(key)}: { loader: async () => page${index}, isAsyncDefault: ${isAsyncDefault} },`;
|
|
975
|
-
}));
|
|
976
|
-
return `${imports.join(`
|
|
977
|
-
`)}
|
|
978
|
-
export const pages = {
|
|
979
|
-
${entries.join(`
|
|
980
|
-
`)}
|
|
981
|
-
};
|
|
982
|
-
`;
|
|
983
|
-
}
|
|
984
|
-
static #toImportSpecifier(moduleAbsPath) {
|
|
985
|
-
return path7.resolve(moduleAbsPath).split(path7.sep).join("/");
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
// pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
|
|
990
|
-
var VIRTUAL_PAGES_ENTRY = "akan-pages-entry";
|
|
991
|
-
|
|
992
|
-
class PagesBundleBuilder {
|
|
993
|
-
#app;
|
|
994
|
-
#command;
|
|
995
|
-
#pageEntries;
|
|
996
|
-
#started = Date.now();
|
|
997
|
-
constructor(app, command = "start", pageEntries) {
|
|
998
|
-
this.#app = app;
|
|
999
|
-
this.#command = command;
|
|
1000
|
-
this.#pageEntries = pageEntries;
|
|
1001
|
-
}
|
|
1002
|
-
async build() {
|
|
1003
|
-
const akanConfig = await this.#app.getConfig();
|
|
1004
|
-
const resolvedEntries = this.#pageEntries ?? await resolveSsrPageEntriesForApp(this.#app, await this.#app.getPageKeys());
|
|
1005
|
-
const entrySource = PagesEntrySourceGenerator.generate(resolvedEntries);
|
|
1006
|
-
const workspaceRoot = this.#app.workspace.workspaceRoot;
|
|
1007
|
-
const result = await Bun.build({
|
|
1008
|
-
entrypoints: [VIRTUAL_PAGES_ENTRY],
|
|
1009
|
-
outdir: `${this.#artifactDir}/server`,
|
|
1010
|
-
target: "bun",
|
|
1011
|
-
format: "esm",
|
|
1012
|
-
splitting: this.#splitting,
|
|
1013
|
-
minify: this.#command === "build",
|
|
1014
|
-
naming: {
|
|
1015
|
-
entry: "pages-[hash].[ext]",
|
|
1016
|
-
chunk: "chunks/[name]-[hash].[ext]",
|
|
1017
|
-
asset: "assets/[name]-[hash].[ext]"
|
|
1018
|
-
},
|
|
1019
|
-
define: this.#define(),
|
|
1020
|
-
plugins: [
|
|
1021
|
-
PagesBundleBuilder.createPagesEntryPlugin(entrySource),
|
|
1022
|
-
PagesBundleBuilder.createCssStubPlugin(),
|
|
1023
|
-
PagesBundleBuilder.createServerUseClientFetchPlugin(),
|
|
1024
|
-
await createExternalizeFrameworkPlugin({ app: this.#app, extra: akanConfig.externalLibs }),
|
|
1025
|
-
akanConfig.barrelImports.length > 0 ? await createBarrelImportsPlugin(this.#app, {
|
|
1026
|
-
pipeAfter: (source, args) => transformUseClient(source, {
|
|
1027
|
-
path: args.path,
|
|
1028
|
-
workspaceRoot
|
|
1029
|
-
})
|
|
1030
|
-
}) : createUseClientBundlePlugin({ workspaceRoot })
|
|
1031
|
-
]
|
|
1032
|
-
});
|
|
1033
|
-
if (!result.success)
|
|
1034
|
-
throw new AggregateError(result.logs, "[PagesBundleBuilder] Bun.build failed");
|
|
1035
|
-
const entryArtifact = result.outputs.find((a) => a.kind === "entry-point");
|
|
1036
|
-
if (!entryArtifact)
|
|
1037
|
-
throw new Error("[PagesBundleBuilder] Bun.build emitted no entry-point artifact");
|
|
1038
|
-
const bundlePath = path8.resolve(entryArtifact.path);
|
|
1039
|
-
const buildId = Date.now();
|
|
1040
|
-
const outputBytes = result.outputs.reduce((sum, output) => sum + output.size, 0);
|
|
1041
|
-
const chunkCount = result.outputs.filter((output) => output.kind === "chunk").length;
|
|
1042
|
-
this.#app.verbose(`[PagesBundleBuilder] ${path8.basename(bundlePath)} emitted in ${Date.now() - this.#started}ms splitting=${this.#splitting} entry=${entryArtifact.size} bytes outputs=${result.outputs.length} chunks=${chunkCount} total=${outputBytes} bytes`);
|
|
1043
|
-
return {
|
|
1044
|
-
bundlePath,
|
|
1045
|
-
buildId,
|
|
1046
|
-
splitting: this.#splitting,
|
|
1047
|
-
entryBytes: entryArtifact.size,
|
|
1048
|
-
outputBytes,
|
|
1049
|
-
outputCount: result.outputs.length,
|
|
1050
|
-
chunkCount
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
get #artifactDir() {
|
|
1054
|
-
return `${this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath}/.akan/artifact`;
|
|
1055
|
-
}
|
|
1056
|
-
get #splitting() {
|
|
1057
|
-
return process.env.AKAN_SERVER_PAGES_SPLITTING === "1";
|
|
1058
|
-
}
|
|
1059
|
-
#define() {
|
|
1060
|
-
const nodeEnv = this.#command === "build" ? "production" : "development";
|
|
1061
|
-
return {
|
|
1062
|
-
"process.env.NODE_ENV": JSON.stringify(nodeEnv),
|
|
1063
|
-
"process.env.AKAN_PUBLIC_RENDER_ENV": JSON.stringify("ssr"),
|
|
1064
|
-
...Object.fromEntries(Object.entries(this.#app.getPublicEnv()).map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]))
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
static createPagesEntryPlugin(source) {
|
|
1068
|
-
return {
|
|
1069
|
-
name: "akan-pages-entry",
|
|
1070
|
-
setup(build) {
|
|
1071
|
-
build.onResolve({ filter: /^akan-pages-entry$/ }, () => ({
|
|
1072
|
-
path: VIRTUAL_PAGES_ENTRY,
|
|
1073
|
-
namespace: "akan-virtual"
|
|
1074
|
-
}));
|
|
1075
|
-
build.onLoad({ filter: /^akan-pages-entry$/, namespace: "akan-virtual" }, () => ({
|
|
1076
|
-
contents: source,
|
|
1077
|
-
loader: "tsx"
|
|
1078
|
-
}));
|
|
1079
|
-
}
|
|
1080
|
-
};
|
|
1081
|
-
}
|
|
1082
|
-
static createCssStubPlugin() {
|
|
1083
|
-
return {
|
|
1084
|
-
name: "akan-css-stub",
|
|
1085
|
-
setup(build) {
|
|
1086
|
-
build.onLoad({ filter: /\.css$/ }, () => ({
|
|
1087
|
-
contents: "",
|
|
1088
|
-
loader: "js"
|
|
1089
|
-
}));
|
|
1090
|
-
}
|
|
1091
|
-
};
|
|
1092
|
-
}
|
|
1093
|
-
static createServerUseClientFetchPlugin() {
|
|
1094
|
-
return {
|
|
1095
|
-
name: "akan-server-use-client-fetch",
|
|
1096
|
-
setup(build) {
|
|
1097
|
-
build.onLoad({ filter: /[/\\]lib[/\\]useClient\.(ts|tsx|js|jsx)$/ }, async (args) => {
|
|
1098
|
-
const source = await Bun.file(args.path).text();
|
|
1099
|
-
const transformed = PagesBundleBuilder.transformServerUseClientFetchSource(source);
|
|
1100
|
-
if (transformed === source)
|
|
1101
|
-
return;
|
|
1102
|
-
return { contents: transformed, loader: loaderFor2(args.path) };
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
static transformServerUseClientFetchSource(source) {
|
|
1108
|
-
if (!source.includes(`with { type: "macro" }`) || !source.includes("FetchClient.build"))
|
|
1109
|
-
return source;
|
|
1110
|
-
return source.replace(/import\s+\{\s*getSerializedSignal\s*\}\s+from\s+["']\.\/sig["']\s+with\s+\{\s*type\s*:\s*["']macro["']\s*\};/, `import { fetch as serverFetch } from "./sig";`).replace(/const\s+fetchProto\s*=\s*FetchClient\.build<[^;]+;/, "const fetchProto = FetchClient.build<typeof signal>(cnst, serverFetch.serializedSignal, { Err: pageProto.Err, base: serverFetch });");
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
function loaderFor2(absPath) {
|
|
1114
|
-
if (absPath.endsWith(".tsx"))
|
|
1115
|
-
return "tsx";
|
|
1116
|
-
if (absPath.endsWith(".jsx"))
|
|
1117
|
-
return "jsx";
|
|
1118
|
-
if (absPath.endsWith(".ts"))
|
|
1119
|
-
return "ts";
|
|
1120
|
-
return "js";
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
// pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
|
|
1124
|
-
import { mkdir, rm, unlink } from "fs/promises";
|
|
1125
|
-
import path9 from "path";
|
|
1126
|
-
class CsrArtifactBuilder {
|
|
1127
|
-
#app;
|
|
1128
|
-
#command;
|
|
1129
|
-
#lang;
|
|
1130
|
-
constructor(app, command = "start", lang = "en") {
|
|
1131
|
-
this.#app = app;
|
|
1132
|
-
this.#command = command;
|
|
1133
|
-
this.#lang = lang;
|
|
1134
|
-
}
|
|
1135
|
-
async build() {
|
|
1136
|
-
const pageKeys = await this.#app.getPageKeys();
|
|
1137
|
-
if (pageKeys.length === 0) {
|
|
1138
|
-
this.#app.log(`[cli] no route files under ${this.#app.cwdPath}/page \u2014 skipping CSR build`);
|
|
1139
|
-
return null;
|
|
1140
|
-
}
|
|
1141
|
-
const pageEntries = await resolveSsrPageEntriesForApp(this.#app, pageKeys);
|
|
1142
|
-
const akanConfig = await this.#app.getConfig();
|
|
1143
|
-
const cssAssets = await this.#loadCssAssets();
|
|
1144
|
-
const basePaths = [...akanConfig.basePaths];
|
|
1145
|
-
const htmlBasePaths = basePaths.length > 0 ? basePaths : [""];
|
|
1146
|
-
await rm(this.#outputDir, { recursive: true, force: true });
|
|
1147
|
-
await mkdir(this.#generatedDir, { recursive: true });
|
|
1148
|
-
const generatedFiles = Object.fromEntries((await Promise.all(htmlBasePaths.map(async (basePath) => [
|
|
1149
|
-
this.#createHtmlFile(basePath),
|
|
1150
|
-
await this.#createEntryFile(basePath, CsrArtifactBuilder.pageEntriesForBasePath(pageEntries, basePath, basePaths))
|
|
1151
|
-
]))).flat());
|
|
1152
|
-
const result = await Bun.build({
|
|
1153
|
-
target: "browser",
|
|
1154
|
-
entrypoints: htmlBasePaths.map((basePath) => this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath))),
|
|
1155
|
-
files: generatedFiles,
|
|
1156
|
-
root: this.#generatedDir,
|
|
1157
|
-
outdir: this.#outputDir,
|
|
1158
|
-
splitting: false,
|
|
1159
|
-
minify: true,
|
|
1160
|
-
env: "AKAN_PUBLIC_*",
|
|
1161
|
-
define: this.#define(),
|
|
1162
|
-
optimizeImports: akanConfig.optimizeImports,
|
|
1163
|
-
plugins: [PagesBundleBuilder.createCssStubPlugin()]
|
|
1164
|
-
});
|
|
1165
|
-
if (!result.success) {
|
|
1166
|
-
const logs = result.logs.map((log) => log.message).join(`
|
|
1167
|
-
`);
|
|
1168
|
-
throw new Error(`[csr-build] failed${logs ? `
|
|
1169
|
-
${logs}` : ""}`);
|
|
1170
|
-
}
|
|
1171
|
-
await this.#inlineCsrArtifacts(cssAssets);
|
|
1172
|
-
this.#app.verbose(`[csr-build] output -> ${this.#outputDir}`);
|
|
1173
|
-
return { outputDir: this.#outputDir };
|
|
1174
|
-
}
|
|
1175
|
-
static pageEntriesForBasePath(pageEntries, basePath, basePaths) {
|
|
1176
|
-
return pageEntries.filter((entry) => {
|
|
1177
|
-
const entryBasePath = getPageKeyBasePath(entry.key, basePaths);
|
|
1178
|
-
return entryBasePath === null || entryBasePath === basePath;
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
static htmlFilename(basePath) {
|
|
1182
|
-
return `${basePath || "index"}.html`;
|
|
1183
|
-
}
|
|
1184
|
-
static entryFilename(basePath) {
|
|
1185
|
-
return `${basePath || "index"}.csr.tsx`;
|
|
1186
|
-
}
|
|
1187
|
-
static basePathOfHtml(htmlPath) {
|
|
1188
|
-
const name = path9.basename(htmlPath, ".html");
|
|
1189
|
-
return name === "index" ? "" : name;
|
|
1190
|
-
}
|
|
1191
|
-
get #outputDir() {
|
|
1192
|
-
return path9.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, this.#command === "build" ? "csr" : ".akan/artifact/csr");
|
|
1193
|
-
}
|
|
1194
|
-
get #generatedDir() {
|
|
1195
|
-
return path9.join(this.#app.cwdPath, ".akan/generated/csr");
|
|
1196
|
-
}
|
|
1197
|
-
get #artifactDir() {
|
|
1198
|
-
return path9.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact");
|
|
1199
|
-
}
|
|
1200
|
-
#generatedPath(filename) {
|
|
1201
|
-
return path9.join(this.#generatedDir, filename);
|
|
1202
|
-
}
|
|
1203
|
-
#define() {
|
|
1204
|
-
const nodeEnv = this.#command === "build" ? "production" : "development";
|
|
1205
|
-
return {
|
|
1206
|
-
"process.env.NODE_ENV": JSON.stringify(nodeEnv),
|
|
1207
|
-
"process.env.AKAN_PUBLIC_RENDER_ENV": JSON.stringify("csr"),
|
|
1208
|
-
...Object.fromEntries(Object.entries(this.#app.getPublicEnv()).map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]))
|
|
1209
|
-
};
|
|
1210
|
-
}
|
|
1211
|
-
async#createEntryFile(basePath, pageEntries) {
|
|
1212
|
-
return [
|
|
1213
|
-
this.#generatedPath(CsrArtifactBuilder.entryFilename(basePath)),
|
|
1214
|
-
`import { bootCsr } from "akanjs/webkit";
|
|
1215
|
-
${await PagesEntrySourceGenerator.generateStatic(pageEntries)}
|
|
1216
|
-
void bootCsr(pages);
|
|
1217
|
-
`
|
|
1218
|
-
];
|
|
1219
|
-
}
|
|
1220
|
-
#createHtmlFile(basePath) {
|
|
1221
|
-
return [
|
|
1222
|
-
this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath)),
|
|
1223
|
-
`<!doctype html>
|
|
1224
|
-
<html lang="${this.#lang}">
|
|
1225
|
-
<head>
|
|
1226
|
-
<meta charset="utf-8" />
|
|
1227
|
-
<title>${this.#app.name}</title>
|
|
1228
|
-
<base href="/" />
|
|
1229
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1230
|
-
</head>
|
|
1231
|
-
<body>
|
|
1232
|
-
<div id="root"></div>
|
|
1233
|
-
<script type="module" src="./${CsrArtifactBuilder.entryFilename(basePath)}"></script>
|
|
1234
|
-
</body>
|
|
1235
|
-
</html>
|
|
1236
|
-
`
|
|
1237
|
-
];
|
|
1238
|
-
}
|
|
1239
|
-
async#loadCssAssets() {
|
|
1240
|
-
const artifactFile = Bun.file(path9.join(this.#artifactDir, "base-artifact.json"));
|
|
1241
|
-
if (!await artifactFile.exists())
|
|
1242
|
-
return {};
|
|
1243
|
-
const artifact = await artifactFile.json();
|
|
1244
|
-
return artifact.cssAssets ?? {};
|
|
1245
|
-
}
|
|
1246
|
-
async#inlineCsrArtifacts(cssAssets) {
|
|
1247
|
-
const jsFiles = new Set;
|
|
1248
|
-
for (const htmlPath of await this.#htmlOutputPaths()) {
|
|
1249
|
-
const htmlFile = Bun.file(htmlPath);
|
|
1250
|
-
if (!await htmlFile.exists())
|
|
688
|
+
} catch {
|
|
1251
689
|
continue;
|
|
1252
|
-
const basePath = CsrArtifactBuilder.basePathOfHtml(htmlPath);
|
|
1253
|
-
const cssAsset = cssAssets[basePath];
|
|
1254
|
-
if (!cssAsset) {
|
|
1255
|
-
this.#app.logger.warn(`[csr-build] base-artifact.json has no compiled stylesheet for ${basePath || "root"}; ${path9.basename(htmlPath)} ships without CSS`);
|
|
1256
690
|
}
|
|
1257
|
-
const
|
|
1258
|
-
for (const
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
691
|
+
const importerDir = path4.dirname(filePath);
|
|
692
|
+
for (const imp of imports) {
|
|
693
|
+
const spec = imp.path;
|
|
694
|
+
if (!spec)
|
|
695
|
+
continue;
|
|
696
|
+
if (spec.endsWith(".css")) {
|
|
697
|
+
const cssPath = await this.#resolveCssImport(spec, importerDir);
|
|
698
|
+
cssFiles.add(cssPath);
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (NON_SOURCE_EXT_RE.test(spec))
|
|
702
|
+
continue;
|
|
703
|
+
const resolved = await this.#resolveSourceImport(spec, importerDir, resolvePackage);
|
|
704
|
+
if (!resolved || sourceFiles.has(resolved) || isIgnoredNodeModuleSource(resolved))
|
|
705
|
+
continue;
|
|
706
|
+
queue.push(resolved);
|
|
707
|
+
}
|
|
1271
708
|
}
|
|
709
|
+
const tokenPaths = await this.#libTokenStylesheets(sourceFiles);
|
|
710
|
+
const cssPaths = [...new Set([...tokenPaths, ...cssFiles])];
|
|
711
|
+
for (const cssPath of cssPaths)
|
|
712
|
+
this.#discoveredCssPaths.add(cssPath);
|
|
713
|
+
return { cssPaths, sourcePaths: [...sourceFiles] };
|
|
1272
714
|
}
|
|
1273
|
-
async#
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
715
|
+
async#libTokenStylesheets(sourceFiles) {
|
|
716
|
+
const libsRoot = path4.join(this.#app.workspace.workspaceRoot, "libs");
|
|
717
|
+
const libNames = new Set;
|
|
718
|
+
for (const filePath of sourceFiles) {
|
|
719
|
+
const relPath = path4.relative(libsRoot, filePath);
|
|
720
|
+
if (relPath.startsWith("..") || path4.isAbsolute(relPath))
|
|
721
|
+
continue;
|
|
722
|
+
const [libName] = relPath.split(path4.sep);
|
|
723
|
+
if (libName)
|
|
724
|
+
libNames.add(libName);
|
|
1278
725
|
}
|
|
1279
|
-
const
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
});
|
|
1285
|
-
return { html: next, jsFiles };
|
|
726
|
+
const tokenPaths = await Promise.all([...libNames].sort().map(async (libName) => {
|
|
727
|
+
const tokensPath = path4.join(libsRoot, libName, "ui/tokens.css");
|
|
728
|
+
return await this.#fileExists(tokensPath) ? tokensPath : null;
|
|
729
|
+
}));
|
|
730
|
+
return tokenPaths.filter((tokensPath) => !!tokensPath);
|
|
1286
731
|
}
|
|
1287
|
-
async
|
|
1288
|
-
|
|
732
|
+
async compileCss(cssPaths, sourcePaths) {
|
|
733
|
+
const { css } = await this.#compileWithImports(cssPaths, sourcePaths);
|
|
734
|
+
return css;
|
|
1289
735
|
}
|
|
1290
|
-
async#
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
736
|
+
async#compileWithImports(cssPaths, sourcePaths) {
|
|
737
|
+
if (cssPaths.length === 0)
|
|
738
|
+
return { css: "", imported: [] };
|
|
739
|
+
const compileStarted = Date.now();
|
|
740
|
+
const compilers = await Promise.all(cssPaths.map(async (cssPath) => {
|
|
741
|
+
const css = await Bun.file(cssPath).text();
|
|
742
|
+
const base = path4.dirname(cssPath);
|
|
743
|
+
const imported = new Map;
|
|
744
|
+
const compiler = await compile(css, {
|
|
745
|
+
base,
|
|
746
|
+
loadStylesheet: (id, fromBase) => this.#loadStylesheet(id, fromBase, imported),
|
|
747
|
+
loadModule: (id, fromBase) => this.#loadModule(id, fromBase)
|
|
748
|
+
});
|
|
749
|
+
return { cssPath, compiler, imported };
|
|
750
|
+
}));
|
|
751
|
+
const sourceDirs = new Set;
|
|
752
|
+
for (const entry of compilers) {
|
|
753
|
+
for (const s of entry.compiler.sources)
|
|
754
|
+
sourceDirs.add(s.base);
|
|
1296
755
|
}
|
|
1297
|
-
|
|
756
|
+
const scanStarted = Date.now();
|
|
757
|
+
const candidates = await this.#scanCandidates(sourcePaths, [...sourceDirs]);
|
|
758
|
+
this.#logger.verbose(`css candidates scanned count=${candidates.length} sources=${sourcePaths.length} dirs=${sourceDirs.size} in ${Date.now() - scanStarted}ms`);
|
|
759
|
+
const parts = [];
|
|
760
|
+
const imported = [];
|
|
761
|
+
for (const entry of compilers) {
|
|
762
|
+
const part = entry.compiler.build(candidates);
|
|
763
|
+
parts.push(part);
|
|
764
|
+
for (const [cssPath, content] of entry.imported) {
|
|
765
|
+
const declaredNames = declaredCustomProperties(content);
|
|
766
|
+
imported.push({ cssPath, declaredNames });
|
|
767
|
+
if (declaredNames.length === 0 || declaredNames.some((name) => part.includes(`${name}:`)))
|
|
768
|
+
continue;
|
|
769
|
+
this.#logger.warn(`css @import ${cssPath} was loaded by ${entry.cssPath} but none of its ${declaredNames.length} declaration(s) reached the compiled CSS`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
this.#logger.verbose(`css compiled paths=${cssPaths.length} candidates=${candidates.length} in ${Date.now() - compileStarted}ms`);
|
|
773
|
+
return { css: parts.join(`
|
|
774
|
+
`), imported };
|
|
1298
775
|
}
|
|
1299
|
-
|
|
1300
|
-
const
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
${
|
|
1305
|
-
|
|
1306
|
-
if (bodyStart !== -1)
|
|
1307
|
-
return `${html.slice(0, bodyStart)}${snippet}
|
|
1308
|
-
${html.slice(bodyStart)}`;
|
|
1309
|
-
return `${html}
|
|
1310
|
-
${snippet}`;
|
|
776
|
+
async#loadStylesheet(id, fromBase, imported) {
|
|
777
|
+
const p = await this.#resolveCssImport(id, fromBase);
|
|
778
|
+
this.#discoveredCssPaths.add(p);
|
|
779
|
+
const content = await Bun.file(p).text();
|
|
780
|
+
imported?.set(p, content);
|
|
781
|
+
this.#logger.verbose(`css import "${id}" from ${fromBase} -> ${p} (${content.length} bytes)`);
|
|
782
|
+
return { path: p, base: path4.dirname(p), content };
|
|
1311
783
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
784
|
+
async#resolveCssImport(id, fromBase) {
|
|
785
|
+
if (id.startsWith(".") || id.startsWith("/")) {
|
|
786
|
+
const filePath = path4.resolve(fromBase, id);
|
|
787
|
+
if (await this.#fileExists(filePath))
|
|
788
|
+
return filePath;
|
|
789
|
+
throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase} (no file at ${filePath})`);
|
|
790
|
+
}
|
|
791
|
+
this.#cssImportResolver ??= await CssImportResolver.create(this.#app);
|
|
792
|
+
const resolved = await this.#cssImportResolver.resolve(id, fromBase);
|
|
793
|
+
if (resolved)
|
|
794
|
+
return resolved;
|
|
795
|
+
throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase}`);
|
|
1314
796
|
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
797
|
+
async#loadModule(id, fromBase) {
|
|
798
|
+
const p = __require.resolve(id, { paths: [fromBase] });
|
|
799
|
+
const mod = await import(p);
|
|
800
|
+
return { path: p, base: path4.dirname(p), module: mod.default ?? mod };
|
|
1319
801
|
}
|
|
1320
|
-
|
|
1321
|
-
const
|
|
1322
|
-
let
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
|
|
1334
|
-
</script>`;
|
|
1335
|
-
lastIndex = match.index + full.length;
|
|
802
|
+
#resolveSourceImport(id, fromBase, resolvePackage) {
|
|
803
|
+
const cacheKey = `${fromBase}\x00${id}`;
|
|
804
|
+
let cached = this.#resolvedSpecifierCache.get(cacheKey);
|
|
805
|
+
if (cached)
|
|
806
|
+
return cached;
|
|
807
|
+
cached = this.#resolveSourceImportUncached(id, fromBase, resolvePackage);
|
|
808
|
+
this.#resolvedSpecifierCache.set(cacheKey, cached);
|
|
809
|
+
return cached;
|
|
810
|
+
}
|
|
811
|
+
async#resolveSourceImportUncached(id, fromBase, resolvePackage) {
|
|
812
|
+
if (id.startsWith(".") || id.startsWith("/")) {
|
|
813
|
+
const abs = id.startsWith("/") ? id : path4.resolve(fromBase, id);
|
|
814
|
+
return this.#resolveSourceFileCandidate(abs);
|
|
1336
815
|
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
816
|
+
const pkg = await resolvePackage(id);
|
|
817
|
+
if (pkg)
|
|
818
|
+
return pkg.entryFile;
|
|
819
|
+
for (const resolve of [() => resolveSourceWithBun(id, fromBase), () => resolveSourceWithRequire(id, fromBase)]) {
|
|
820
|
+
const resolved = await resolve();
|
|
821
|
+
if (resolved)
|
|
822
|
+
return resolved;
|
|
823
|
+
}
|
|
824
|
+
return null;
|
|
1340
825
|
}
|
|
1341
|
-
|
|
1342
|
-
|
|
826
|
+
async#scanCandidates(sourcePaths, dirs) {
|
|
827
|
+
const candidates = new Set;
|
|
828
|
+
const glob = new Bun.Glob("**/*.{tsx,ts,jsx,js,html}");
|
|
829
|
+
const files = new Set(sourcePaths);
|
|
830
|
+
await Promise.all(dirs.map(async (dir) => {
|
|
831
|
+
for await (const file of glob.scan({ cwd: dir, absolute: true })) {
|
|
832
|
+
if (isIgnoredNodeModuleSource(file))
|
|
833
|
+
continue;
|
|
834
|
+
files.add(file);
|
|
835
|
+
}
|
|
836
|
+
}));
|
|
837
|
+
const cache = await new CssCandidateCache(this.#candidateCachePath).load();
|
|
838
|
+
await Promise.all([...files].map(async (file) => {
|
|
839
|
+
for (const candidate of await cache.candidatesFor(file))
|
|
840
|
+
candidates.add(candidate);
|
|
841
|
+
}));
|
|
842
|
+
await cache.save(files);
|
|
843
|
+
this.#logger.verbose(`css candidate cache reused=${cache.reused} rescanned=${cache.rescanned}`);
|
|
844
|
+
return [...candidates];
|
|
1343
845
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
846
|
+
}
|
|
847
|
+
function resolveSourceWithBun(id, fromBase) {
|
|
848
|
+
try {
|
|
849
|
+
const resolved = Bun.resolveSync(id, fromBase);
|
|
850
|
+
return isSourceFile(resolved) ? resolved : null;
|
|
851
|
+
} catch {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
function resolveSourceWithRequire(id, fromBase) {
|
|
856
|
+
try {
|
|
857
|
+
const resolved = __require.resolve(id, { paths: [fromBase] });
|
|
858
|
+
return isSourceFile(resolved) ? resolved : null;
|
|
859
|
+
} catch {
|
|
860
|
+
return null;
|
|
1350
861
|
}
|
|
1351
862
|
}
|
|
863
|
+
function isSourceFile(filePath) {
|
|
864
|
+
return SOURCE_EXTS.has(path4.extname(filePath));
|
|
865
|
+
}
|
|
866
|
+
function isIgnoredNodeModuleSource(filePath) {
|
|
867
|
+
return NODE_MODULES_RE.test(filePath) && !AKANJS_NODE_MODULE_RE.test(filePath);
|
|
868
|
+
}
|
|
869
|
+
function declaredCustomProperties(css) {
|
|
870
|
+
const withoutThemeBlocks = css.replace(/@theme[^{]*\{[^}]*\}/g, "");
|
|
871
|
+
return [...new Set([...withoutThemeBlocks.matchAll(/(?:^|[\s;{])(--[\w-]+)\s*:/g)].map(([, name]) => name))].filter((name) => !!name);
|
|
872
|
+
}
|
|
873
|
+
function getPageKeyBasePath(pageKey, basePaths) {
|
|
874
|
+
return CsrEntryFiles.basePathOfPageKey(pageKey, basePaths);
|
|
875
|
+
}
|
|
1352
876
|
|
|
1353
877
|
// pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
|
|
1354
878
|
import { mkdir as mkdir2, stat as stat2 } from "fs/promises";
|
|
1355
|
-
import
|
|
879
|
+
import path5 from "path";
|
|
1356
880
|
import ts from "typescript";
|
|
1357
881
|
var FONT_URL_PREFIX = "/_akan/fonts";
|
|
1358
882
|
var DEFAULT_FONT_SUBSETS = ["latin"];
|
|
@@ -1369,7 +893,7 @@ class FontOptimizer {
|
|
|
1369
893
|
constructor(app, command = "start") {
|
|
1370
894
|
this.#app = app;
|
|
1371
895
|
this.#command = command;
|
|
1372
|
-
this.#artifactRoot =
|
|
896
|
+
this.#artifactRoot = path5.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
|
|
1373
897
|
}
|
|
1374
898
|
async optimize() {
|
|
1375
899
|
const fonts = await this.discoverFonts();
|
|
@@ -1394,7 +918,7 @@ class FontOptimizer {
|
|
|
1394
918
|
return result;
|
|
1395
919
|
}
|
|
1396
920
|
get #cachePath() {
|
|
1397
|
-
return
|
|
921
|
+
return path5.join(this.#artifactRoot, "fontCache.json");
|
|
1398
922
|
}
|
|
1399
923
|
async#buildCacheKey(fonts) {
|
|
1400
924
|
const sources = [];
|
|
@@ -1409,7 +933,7 @@ class FontOptimizer {
|
|
|
1409
933
|
sources.push({ optimizedSrc: face.optimizedSrc, ...stamp });
|
|
1410
934
|
}
|
|
1411
935
|
for (const filePath of font.subsetFiles ?? []) {
|
|
1412
|
-
const abs =
|
|
936
|
+
const abs = path5.isAbsolute(filePath) ? filePath : path5.join(this.#app.cwdPath, filePath);
|
|
1413
937
|
const stamp = await this.#fileStamp(abs);
|
|
1414
938
|
if (!stamp)
|
|
1415
939
|
return null;
|
|
@@ -1435,7 +959,7 @@ class FontOptimizer {
|
|
|
1435
959
|
const cache = await Bun.file(this.#cachePath).json().catch(() => null);
|
|
1436
960
|
if (cache?.version !== FontOptimizer.#cacheVersion || cache.key !== key)
|
|
1437
961
|
return null;
|
|
1438
|
-
const files = cache.files.map((relativePath) =>
|
|
962
|
+
const files = cache.files.map((relativePath) => path5.join(this.#artifactRoot, relativePath));
|
|
1439
963
|
for (const filePath of files) {
|
|
1440
964
|
if (!await Bun.file(filePath).exists())
|
|
1441
965
|
return null;
|
|
@@ -1447,7 +971,7 @@ class FontOptimizer {
|
|
|
1447
971
|
version: FontOptimizer.#cacheVersion,
|
|
1448
972
|
key,
|
|
1449
973
|
css: result.css,
|
|
1450
|
-
files: result.files.map((filePath) =>
|
|
974
|
+
files: result.files.map((filePath) => path5.relative(this.#artifactRoot, filePath))
|
|
1451
975
|
};
|
|
1452
976
|
await Bun.write(this.#cachePath, JSON.stringify(cache));
|
|
1453
977
|
}
|
|
@@ -1455,7 +979,7 @@ class FontOptimizer {
|
|
|
1455
979
|
const pageKeys = await this.#app.getPageKeys();
|
|
1456
980
|
const fonts = [];
|
|
1457
981
|
await Promise.all(pageKeys.map(async (key) => {
|
|
1458
|
-
const filePath =
|
|
982
|
+
const filePath = path5.resolve(this.#app.cwdPath, "page", key);
|
|
1459
983
|
const file = Bun.file(filePath);
|
|
1460
984
|
if (!await file.exists())
|
|
1461
985
|
return;
|
|
@@ -1464,7 +988,7 @@ class FontOptimizer {
|
|
|
1464
988
|
return;
|
|
1465
989
|
fonts.push(...this.#extractFontsExport(source, filePath));
|
|
1466
990
|
}));
|
|
1467
|
-
return
|
|
991
|
+
return [...new Map(fonts.map((font) => [JSON.stringify(font), font])).values()];
|
|
1468
992
|
}
|
|
1469
993
|
async#optimizeFont(font) {
|
|
1470
994
|
const faceCss = [];
|
|
@@ -1474,8 +998,8 @@ class FontOptimizer {
|
|
|
1474
998
|
this.#app.logger.warn(`[font] source not found: ${face.src}`);
|
|
1475
999
|
continue;
|
|
1476
1000
|
}
|
|
1477
|
-
const outputPath =
|
|
1478
|
-
await mkdir2(
|
|
1001
|
+
const outputPath = path5.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
|
|
1002
|
+
await mkdir2(path5.dirname(outputPath), { recursive: true });
|
|
1479
1003
|
const sourceBuffer = Buffer.from(await Bun.file(sourcePath).arrayBuffer());
|
|
1480
1004
|
const outputBuffer = await this.#buildFontBuffer(font, sourceBuffer, sourcePath);
|
|
1481
1005
|
await Bun.write(outputPath, outputBuffer);
|
|
@@ -1497,8 +1021,8 @@ class FontOptimizer {
|
|
|
1497
1021
|
const entries = Array.isArray(value) ? value : [];
|
|
1498
1022
|
const fonts = entries.filter((entry) => this.#isReadableFont(entry));
|
|
1499
1023
|
if (fonts.length !== entries.length || !Array.isArray(value))
|
|
1500
|
-
this.#app.logger.warn(`[font] ${
|
|
1501
|
-
return fonts.map((font) =>
|
|
1024
|
+
this.#app.logger.warn(`[font] ${path5.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`);
|
|
1025
|
+
return fonts.map((font) => ({ ...font, subsets: font.subsets ?? [...DEFAULT_FONT_SUBSETS] }));
|
|
1502
1026
|
}
|
|
1503
1027
|
#isReadableFont(value) {
|
|
1504
1028
|
if (!value || typeof value !== "object")
|
|
@@ -1587,15 +1111,6 @@ class FontOptimizer {
|
|
|
1587
1111
|
return name.text;
|
|
1588
1112
|
return null;
|
|
1589
1113
|
}
|
|
1590
|
-
#dedupeFonts(fonts) {
|
|
1591
|
-
const map = new Map;
|
|
1592
|
-
for (const font of fonts)
|
|
1593
|
-
map.set(JSON.stringify(font), font);
|
|
1594
|
-
return [...map.values()];
|
|
1595
|
-
}
|
|
1596
|
-
#withFontDefaults(font) {
|
|
1597
|
-
return { ...font, subsets: font.subsets ?? [...DEFAULT_FONT_SUBSETS] };
|
|
1598
|
-
}
|
|
1599
1114
|
#getFontSubsets(font) {
|
|
1600
1115
|
return font.subsets ?? DEFAULT_FONT_SUBSETS;
|
|
1601
1116
|
}
|
|
@@ -1608,11 +1123,8 @@ class FontOptimizer {
|
|
|
1608
1123
|
#isFontOptimizationEnabled(font) {
|
|
1609
1124
|
return font.optimize !== false;
|
|
1610
1125
|
}
|
|
1611
|
-
#getFontStyles(font) {
|
|
1612
|
-
return font.styles?.length ? font.styles : ["normal"];
|
|
1613
|
-
}
|
|
1614
1126
|
#getFontFaces(font) {
|
|
1615
|
-
const enabledStyles = new Set(
|
|
1127
|
+
const enabledStyles = new Set(font.styles?.length ? font.styles : ["normal"]);
|
|
1616
1128
|
return font.paths.map((fontPath) => {
|
|
1617
1129
|
const style = fontPath.style ?? "normal";
|
|
1618
1130
|
return {
|
|
@@ -1667,8 +1179,8 @@ class FontOptimizer {
|
|
|
1667
1179
|
return null;
|
|
1668
1180
|
const rel = src.replace(/^\//, "");
|
|
1669
1181
|
const candidates = [
|
|
1670
|
-
this.#command === "build" ?
|
|
1671
|
-
|
|
1182
|
+
this.#command === "build" ? path5.join(this.#app.dist.cwdPath, "public", rel) : null,
|
|
1183
|
+
path5.join(this.#app.cwdPath, "public", rel),
|
|
1672
1184
|
this.#resolveWorkspacePublicPath(rel)
|
|
1673
1185
|
].filter(Boolean);
|
|
1674
1186
|
for (const candidate of candidates) {
|
|
@@ -1685,15 +1197,12 @@ class FontOptimizer {
|
|
|
1685
1197
|
}
|
|
1686
1198
|
async#convertToWoff2(buffer, sourcePath) {
|
|
1687
1199
|
const { createFont } = await import("fonteditor-core");
|
|
1688
|
-
await this.#initWoff2();
|
|
1689
|
-
const font = createFont(buffer, { type: this.#getFontType(sourcePath, buffer) });
|
|
1690
|
-
return font.write({ type: "woff2", toBuffer: true });
|
|
1691
|
-
}
|
|
1692
|
-
async#initWoff2() {
|
|
1693
1200
|
this.#woff2Ready ??= import("fonteditor-core").then(({ woff2 }) => woff2.init()).then(() => {
|
|
1694
1201
|
return;
|
|
1695
1202
|
});
|
|
1696
|
-
|
|
1203
|
+
await this.#woff2Ready;
|
|
1204
|
+
const font = createFont(buffer, { type: this.#getFontType(sourcePath, buffer) });
|
|
1205
|
+
return font.write({ type: "woff2", toBuffer: true });
|
|
1697
1206
|
}
|
|
1698
1207
|
#getFontType(sourcePath, buffer) {
|
|
1699
1208
|
const signature = buffer.toString("ascii", 0, 4);
|
|
@@ -1703,7 +1212,7 @@ class FontOptimizer {
|
|
|
1703
1212
|
return "woff2";
|
|
1704
1213
|
if (signature === "OTTO")
|
|
1705
1214
|
return "otf";
|
|
1706
|
-
const ext =
|
|
1215
|
+
const ext = path5.extname(sourcePath).slice(1).toLowerCase();
|
|
1707
1216
|
if (ext === "otf" || ext === "woff" || ext === "woff2")
|
|
1708
1217
|
return ext;
|
|
1709
1218
|
return "ttf";
|
|
@@ -1712,7 +1221,7 @@ class FontOptimizer {
|
|
|
1712
1221
|
const [root, dep, ...rest] = rel.split("/");
|
|
1713
1222
|
if (root !== "libs" || !dep || rest.length === 0)
|
|
1714
1223
|
return null;
|
|
1715
|
-
return
|
|
1224
|
+
return path5.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
|
|
1716
1225
|
}
|
|
1717
1226
|
async#getSubsetText(font) {
|
|
1718
1227
|
const parts = new Set;
|
|
@@ -1722,7 +1231,7 @@ class FontOptimizer {
|
|
|
1722
1231
|
if (font.subsetText)
|
|
1723
1232
|
parts.add(font.subsetText);
|
|
1724
1233
|
for (const filePath of font.subsetFiles ?? []) {
|
|
1725
|
-
const abs =
|
|
1234
|
+
const abs = path5.isAbsolute(filePath) ? filePath : path5.join(this.#app.cwdPath, filePath);
|
|
1726
1235
|
const file = Bun.file(abs);
|
|
1727
1236
|
if (await file.exists())
|
|
1728
1237
|
parts.add(await file.text());
|
|
@@ -1731,9 +1240,9 @@ class FontOptimizer {
|
|
|
1731
1240
|
}
|
|
1732
1241
|
async#getSubsetPresetText(subset) {
|
|
1733
1242
|
if (subset === "latin")
|
|
1734
|
-
return
|
|
1243
|
+
return FontOptimizer.#rangeText(32, 126);
|
|
1735
1244
|
if (subset === "latin-ext")
|
|
1736
|
-
return `${
|
|
1245
|
+
return `${FontOptimizer.#rangeText(32, 126)}${FontOptimizer.#rangeText(160, 591)}`;
|
|
1737
1246
|
if (subset === "ks-x-1001")
|
|
1738
1247
|
return FontOptimizer.#getKsX1001Text();
|
|
1739
1248
|
if (subset === "auto")
|
|
@@ -1742,7 +1251,7 @@ class FontOptimizer {
|
|
|
1742
1251
|
}
|
|
1743
1252
|
async#collectAutoSubsetText() {
|
|
1744
1253
|
const libPageRoots = (await this.#app.getPageRoots()).filter((root) => root.keyPrefix).map((root) => root.dir);
|
|
1745
|
-
const roots = [...["page", "ui", "lib"].map((dir) =>
|
|
1254
|
+
const roots = [...["page", "ui", "lib"].map((dir) => path5.join(this.#app.cwdPath, dir)), ...libPageRoots];
|
|
1746
1255
|
const glob = new Bun.Glob("**/*.{ts,tsx,js,jsx,html,md}");
|
|
1747
1256
|
const parts = [];
|
|
1748
1257
|
for (const root of [...new Set(roots)].sort()) {
|
|
@@ -1757,12 +1266,6 @@ class FontOptimizer {
|
|
|
1757
1266
|
}
|
|
1758
1267
|
return parts.join("");
|
|
1759
1268
|
}
|
|
1760
|
-
#rangeText(start, end) {
|
|
1761
|
-
let text = "";
|
|
1762
|
-
for (let code = start;code <= end; code++)
|
|
1763
|
-
text += String.fromCodePoint(code);
|
|
1764
|
-
return text;
|
|
1765
|
-
}
|
|
1766
1269
|
static #getKsX1001Text() {
|
|
1767
1270
|
if (FontOptimizer.#ksX1001Text)
|
|
1768
1271
|
return FontOptimizer.#ksX1001Text;
|
|
@@ -1778,11 +1281,11 @@ class FontOptimizer {
|
|
|
1778
1281
|
}
|
|
1779
1282
|
FontOptimizer.#ksX1001Text = [...chars].join("");
|
|
1780
1283
|
} catch {
|
|
1781
|
-
FontOptimizer.#ksX1001Text = FontOptimizer.#
|
|
1284
|
+
FontOptimizer.#ksX1001Text = FontOptimizer.#rangeText(44032, 55203);
|
|
1782
1285
|
}
|
|
1783
1286
|
return FontOptimizer.#ksX1001Text;
|
|
1784
1287
|
}
|
|
1785
|
-
static #
|
|
1288
|
+
static #rangeText(start, end) {
|
|
1786
1289
|
let text = "";
|
|
1787
1290
|
for (let code = start;code <= end; code++)
|
|
1788
1291
|
text += String.fromCodePoint(code);
|
|
@@ -1862,7 +1365,7 @@ ${declarations.map(([prop, value]) => ` ${prop}: ${value};`).join(`
|
|
|
1862
1365
|
|
|
1863
1366
|
// pkgs/@akanjs/devkit/frontendBuild/fontPruner.ts
|
|
1864
1367
|
import { rm as rm2, rmdir, stat as stat3 } from "fs/promises";
|
|
1865
|
-
import
|
|
1368
|
+
import path6 from "path";
|
|
1866
1369
|
import { isFontOptimizationEnabled } from "akanjs/client";
|
|
1867
1370
|
var FONT_GLOB = "**/*.{woff2,woff,ttf,otf,ttc,eot}";
|
|
1868
1371
|
var REFERRER_GLOB = "**/*.{css,js,mjs,cjs,json,html,htm,svg,xml,webmanifest}";
|
|
@@ -1875,8 +1378,8 @@ class FontPruner {
|
|
|
1875
1378
|
constructor(app, { keepFonts = [] } = {}) {
|
|
1876
1379
|
this.#app = app;
|
|
1877
1380
|
this.#keepGlobs = keepFonts;
|
|
1878
|
-
this.#publicRoot =
|
|
1879
|
-
this.#artifactRoot =
|
|
1381
|
+
this.#publicRoot = path6.join(app.dist.cwdPath, "public");
|
|
1382
|
+
this.#artifactRoot = path6.join(app.dist.cwdPath, ".akan/artifact");
|
|
1880
1383
|
}
|
|
1881
1384
|
async prune() {
|
|
1882
1385
|
const empty = { removed: [], kept: [], freedBytes: 0 };
|
|
@@ -1891,7 +1394,7 @@ class FontPruner {
|
|
|
1891
1394
|
for (const font of fonts) {
|
|
1892
1395
|
const target = isFontOptimizationEnabled(font) ? optimizedSrcs : unoptimizedSrcs;
|
|
1893
1396
|
for (const fontPath of font.paths)
|
|
1894
|
-
target.add(
|
|
1397
|
+
target.add(path6.posix.basename(fontPath.src));
|
|
1895
1398
|
}
|
|
1896
1399
|
const result = { removed: [], kept: [], freedBytes: 0 };
|
|
1897
1400
|
const undecided = [];
|
|
@@ -1929,10 +1432,10 @@ class FontPruner {
|
|
|
1929
1432
|
async#collectCandidates() {
|
|
1930
1433
|
const candidates = [];
|
|
1931
1434
|
for await (const rel of new Bun.Glob(FONT_GLOB).scan({ cwd: this.#publicRoot, dot: false })) {
|
|
1932
|
-
const entry = await stat3(
|
|
1435
|
+
const entry = await stat3(path6.join(this.#publicRoot, rel)).catch(() => null);
|
|
1933
1436
|
if (!entry?.isFile())
|
|
1934
1437
|
continue;
|
|
1935
|
-
candidates.push({ rel: rel.split(
|
|
1438
|
+
candidates.push({ rel: rel.split(path6.sep).join("/"), basename: path6.basename(rel), bytes: entry.size });
|
|
1936
1439
|
}
|
|
1937
1440
|
return candidates;
|
|
1938
1441
|
}
|
|
@@ -1942,11 +1445,11 @@ class FontPruner {
|
|
|
1942
1445
|
const bundled = candidates.filter((candidate) => !optimizedSrcs.has(candidate.basename));
|
|
1943
1446
|
const roots = [
|
|
1944
1447
|
{ dir: this.#publicRoot, label: "public", needles: authoritative },
|
|
1945
|
-
{ dir:
|
|
1946
|
-
{ dir:
|
|
1947
|
-
{ dir:
|
|
1948
|
-
{ dir:
|
|
1949
|
-
{ dir:
|
|
1448
|
+
{ dir: path6.join(this.#artifactRoot, "styles"), label: ".akan/artifact/styles", needles: authoritative },
|
|
1449
|
+
{ dir: path6.join(this.#artifactRoot, "client"), label: ".akan/artifact/client", needles: bundled },
|
|
1450
|
+
{ dir: path6.join(this.#artifactRoot, "client-ssr"), label: ".akan/artifact/client-ssr", needles: bundled },
|
|
1451
|
+
{ dir: path6.join(this.#artifactRoot, "server"), label: ".akan/artifact/server", needles: bundled },
|
|
1452
|
+
{ dir: path6.join(this.#app.dist.cwdPath, "csr"), label: "csr", needles: bundled }
|
|
1950
1453
|
];
|
|
1951
1454
|
for (const root of roots) {
|
|
1952
1455
|
const pending = root.needles.filter((candidate) => !referrers.has(candidate.rel));
|
|
@@ -1965,20 +1468,20 @@ class FontPruner {
|
|
|
1965
1468
|
const remaining = needles.filter((needle) => !referrers.has(needle.rel));
|
|
1966
1469
|
if (!remaining.length)
|
|
1967
1470
|
return;
|
|
1968
|
-
const text = await Bun.file(
|
|
1471
|
+
const text = await Bun.file(path6.join(root.dir, rel)).text().catch(() => "");
|
|
1969
1472
|
if (!text)
|
|
1970
1473
|
continue;
|
|
1971
1474
|
for (const needle of remaining) {
|
|
1972
1475
|
if (needle.forms.some((form) => text.includes(form)))
|
|
1973
|
-
referrers.set(needle.rel, `${root.label}/${rel.split(
|
|
1476
|
+
referrers.set(needle.rel, `${root.label}/${rel.split(path6.sep).join("/")}`);
|
|
1974
1477
|
}
|
|
1975
1478
|
}
|
|
1976
1479
|
}
|
|
1977
1480
|
async#remove(files) {
|
|
1978
|
-
await Promise.all(files.map((file) => rm2(
|
|
1979
|
-
const dirs = [...new Set(files.map((file) =>
|
|
1481
|
+
await Promise.all(files.map((file) => rm2(path6.join(this.#publicRoot, file), { force: true })));
|
|
1482
|
+
const dirs = [...new Set(files.map((file) => path6.posix.dirname(file)))].filter((dir) => dir !== "." && dir !== "/").sort((a, b) => b.length - a.length);
|
|
1980
1483
|
for (const dir of dirs)
|
|
1981
|
-
await rmdir(
|
|
1484
|
+
await rmdir(path6.join(this.#publicRoot, dir)).catch(() => {
|
|
1982
1485
|
return;
|
|
1983
1486
|
});
|
|
1984
1487
|
}
|
|
@@ -2026,17 +1529,18 @@ class FontPruner {
|
|
|
2026
1529
|
|
|
2027
1530
|
// pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
|
|
2028
1531
|
import fs from "fs";
|
|
2029
|
-
import
|
|
1532
|
+
import path7 from "path";
|
|
2030
1533
|
import zlib from "zlib";
|
|
2031
1534
|
var COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
|
|
2032
1535
|
var MIN_COMPRESS_BYTES = 1024;
|
|
2033
1536
|
var BROTLI_QUALITY_BY_EXT = { ".css": 11 };
|
|
2034
1537
|
var DEFAULT_BROTLI_QUALITY = 9;
|
|
2035
1538
|
var GZIP_LEVEL = 9;
|
|
1539
|
+
var { formatBytes } = FontPruner;
|
|
2036
1540
|
async function precompressArtifacts(app) {
|
|
2037
1541
|
const roots = [
|
|
2038
|
-
|
|
2039
|
-
|
|
1542
|
+
path7.join(app.dist.cwdPath, ".akan/artifact/client"),
|
|
1543
|
+
path7.join(app.dist.cwdPath, ".akan/artifact/styles")
|
|
2040
1544
|
];
|
|
2041
1545
|
const result = { files: 0, inputBytes: 0, outputBytes: 0, brotliBytes: 0 };
|
|
2042
1546
|
await Promise.all(roots.map((root) => precompressRoot(root, result)));
|
|
@@ -2053,9 +1557,9 @@ async function precompressRoot(root, result) {
|
|
|
2053
1557
|
if (!await shouldPrecompress(filePath))
|
|
2054
1558
|
continue;
|
|
2055
1559
|
const bytes = await Bun.file(filePath).bytes();
|
|
2056
|
-
const buffer =
|
|
1560
|
+
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
2057
1561
|
const gz = Bun.gzipSync(buffer, { level: GZIP_LEVEL });
|
|
2058
|
-
const br = brotliCompress(buffer,
|
|
1562
|
+
const br = brotliCompress(buffer, path7.extname(filePath).toLowerCase());
|
|
2059
1563
|
await Promise.all([Bun.write(`${filePath}.gz`, gz), Bun.write(`${filePath}.br`, br)]);
|
|
2060
1564
|
result.files += 1;
|
|
2061
1565
|
result.inputBytes += bytes.byteLength;
|
|
@@ -2075,26 +1579,16 @@ function brotliCompress(buffer, ext) {
|
|
|
2075
1579
|
async function shouldPrecompress(filePath) {
|
|
2076
1580
|
if (filePath.endsWith(".gz") || filePath.endsWith(".br"))
|
|
2077
1581
|
return false;
|
|
2078
|
-
if (!COMPRESSIBLE_EXTS.has(
|
|
1582
|
+
if (!COMPRESSIBLE_EXTS.has(path7.extname(filePath).toLowerCase()))
|
|
2079
1583
|
return false;
|
|
2080
1584
|
const file = Bun.file(filePath);
|
|
2081
1585
|
if (!await file.exists())
|
|
2082
1586
|
return false;
|
|
2083
1587
|
return file.size >= MIN_COMPRESS_BYTES;
|
|
2084
1588
|
}
|
|
2085
|
-
function toArrayBuffer(bytes) {
|
|
2086
|
-
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
2087
|
-
}
|
|
2088
|
-
function formatBytes(bytes) {
|
|
2089
|
-
if (bytes < 1024)
|
|
2090
|
-
return `${bytes}B`;
|
|
2091
|
-
if (bytes < 1024 * 1024)
|
|
2092
|
-
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
2093
|
-
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
2094
|
-
}
|
|
2095
1589
|
|
|
2096
1590
|
// pkgs/@akanjs/devkit/frontendBuild/ssrBaseArtifactBuilder.ts
|
|
2097
|
-
import
|
|
1591
|
+
import path8 from "path";
|
|
2098
1592
|
import { optimize } from "@tailwindcss/node";
|
|
2099
1593
|
function prepareCssAsset(command, basePath, cssText) {
|
|
2100
1594
|
return optimize(cssText, { file: `${basePath || "root"}.css`, minify: command === "build" }).code;
|
|
@@ -2110,7 +1604,10 @@ class SsrBaseArtifactBuilder {
|
|
|
2110
1604
|
this.#app = app;
|
|
2111
1605
|
this.#command = command;
|
|
2112
1606
|
this.#artifactDir = `${command === "build" ? app.dist.cwdPath : app.cwdPath}/.akan/artifact`;
|
|
2113
|
-
this.#absArtifactDir =
|
|
1607
|
+
this.#absArtifactDir = path8.resolve(this.#artifactDir);
|
|
1608
|
+
}
|
|
1609
|
+
static servedWeb(command, web) {
|
|
1610
|
+
return command === "start" ? { ssr: true, csr: true } : web;
|
|
2114
1611
|
}
|
|
2115
1612
|
async build() {
|
|
2116
1613
|
const akanConfig = await this.#app.getConfig();
|
|
@@ -2132,7 +1629,7 @@ class SsrBaseArtifactBuilder {
|
|
|
2132
1629
|
rscRuntimeSsrManifest,
|
|
2133
1630
|
vendorMap,
|
|
2134
1631
|
cssAssets,
|
|
2135
|
-
pagesBundlePath: this.#command === "build" ?
|
|
1632
|
+
pagesBundlePath: this.#command === "build" ? path8.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
|
|
2136
1633
|
pagesBundleBuildId: pagesBundle.buildId,
|
|
2137
1634
|
domains: [...akanConfig.domains],
|
|
2138
1635
|
subRoutes: Object.fromEntries(Array.from(akanConfig.subRoutes.entries()).map(([basePath, domains]) => [basePath, [...domains]])),
|
|
@@ -2140,27 +1637,28 @@ class SsrBaseArtifactBuilder {
|
|
|
2140
1637
|
branches: [...akanConfig.branches],
|
|
2141
1638
|
i18n: akanConfig.i18n,
|
|
2142
1639
|
imageConfig: akanConfig.images,
|
|
2143
|
-
web: akanConfig.web,
|
|
2144
|
-
deepLinkAssociations: Object.values(akanConfig.
|
|
1640
|
+
web: SsrBaseArtifactBuilder.servedWeb(this.#command, akanConfig.web),
|
|
1641
|
+
deepLinkAssociations: Object.values(akanConfig.native.targets).filter((target) => (target.deepLinks?.domains?.length ?? 0) > 0).map((target) => ({
|
|
2145
1642
|
targetName: target.name,
|
|
2146
|
-
|
|
1643
|
+
iosAppId: SsrBaseArtifactBuilder.#appIdOn(target.appId, "ios"),
|
|
1644
|
+
androidAppId: SsrBaseArtifactBuilder.#appIdOn(target.appId, "android"),
|
|
2147
1645
|
domains: target.deepLinks?.domains ?? [],
|
|
2148
|
-
iosTeamId: target.
|
|
2149
|
-
androidSha256CertFingerprints: target.
|
|
1646
|
+
iosTeamId: target.ios?.teamId,
|
|
1647
|
+
androidSha256CertFingerprints: target.android?.sha256CertFingerprints
|
|
2150
1648
|
}))
|
|
2151
1649
|
};
|
|
2152
|
-
await Bun.write(
|
|
1650
|
+
await Bun.write(path8.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
|
|
2153
1651
|
`);
|
|
2154
1652
|
this.#app.verbose(`[base-artifact] complete in ${Date.now() - this.#started}ms`);
|
|
2155
1653
|
return { artifact, seedIndex, cssCompiler, optimizedFonts };
|
|
2156
1654
|
}
|
|
2157
1655
|
async#buildRuntimeClientEntries() {
|
|
2158
1656
|
const akanServerPath = await this.#resolveAkanServerPath();
|
|
2159
|
-
const rscClientEntry =
|
|
2160
|
-
const rscSegmentOutletEntry =
|
|
1657
|
+
const rscClientEntry = path8.resolve(akanServerPath, "rscClient.tsx");
|
|
1658
|
+
const rscSegmentOutletEntry = path8.resolve(akanServerPath, "rscSegmentOutlet.tsx");
|
|
2161
1659
|
const vendorEntries = VENDOR_SPECIFIERS.map((specifier) => ({
|
|
2162
1660
|
specifier,
|
|
2163
|
-
absPath:
|
|
1661
|
+
absPath: path8.resolve(akanServerPath, "vendor", `${specifier.replaceAll("/", "-").replaceAll(".", "-")}.ts`)
|
|
2164
1662
|
}));
|
|
2165
1663
|
const entries = [rscClientEntry, rscSegmentOutletEntry, ...vendorEntries.map((v) => v.absPath)];
|
|
2166
1664
|
const clientBundle = await new ClientEntriesBundler({ app: this.#app, entries, command: this.#command }).bundle();
|
|
@@ -2197,15 +1695,15 @@ class SsrBaseArtifactBuilder {
|
|
|
2197
1695
|
async#resolveAkanServerPath() {
|
|
2198
1696
|
const candidates = [];
|
|
2199
1697
|
try {
|
|
2200
|
-
candidates.push(
|
|
1698
|
+
candidates.push(path8.dirname(Bun.resolveSync("akanjs/server", this.#app.workspace.workspaceRoot)));
|
|
2201
1699
|
} catch {}
|
|
2202
|
-
candidates.push(
|
|
1700
|
+
candidates.push(path8.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server"), path8.join(this.#app.workspace.workspaceRoot, "node_modules/akanjs/server"));
|
|
2203
1701
|
try {
|
|
2204
|
-
candidates.push(
|
|
1702
|
+
candidates.push(path8.dirname(Bun.resolveSync("akanjs/server", path8.dirname(Bun.main))));
|
|
2205
1703
|
} catch {}
|
|
2206
|
-
candidates.push(
|
|
1704
|
+
candidates.push(path8.join(path8.dirname(Bun.main), "node_modules/akanjs/server"), path8.join(path8.dirname(Bun.main), "../../akanjs/server"), path8.resolve(import.meta.dir, "../../server"), path8.resolve(import.meta.dir, "../server"));
|
|
2207
1705
|
for (const candidate of candidates) {
|
|
2208
|
-
if (await Bun.file(
|
|
1706
|
+
if (await Bun.file(path8.join(candidate, "rscClient.tsx")).exists())
|
|
2209
1707
|
return candidate;
|
|
2210
1708
|
}
|
|
2211
1709
|
throw new Error(`[base-artifact] failed to locate akanjs/server; looked in: ${candidates.join(", ")}`);
|
|
@@ -2234,11 +1732,14 @@ ${preparedCssText}`).toString(36);
|
|
|
2234
1732
|
`styles/${cssAssetName}-${cssHash}.css`,
|
|
2235
1733
|
`/_akan/styles/${cssAssetName}-${cssHash}.css`
|
|
2236
1734
|
];
|
|
2237
|
-
await Bun.write(
|
|
1735
|
+
await Bun.write(path8.join(this.#absArtifactDir, cssRelPath), preparedCssText);
|
|
2238
1736
|
SsrBaseArtifactBuilder.#warnDroppedImports(this.#app, cssRelPath, preparedCssText, imported);
|
|
2239
1737
|
this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath} -> ${cssRelPath}`);
|
|
2240
1738
|
return [basePath, { cssUrl, cssRelPath }];
|
|
2241
1739
|
}
|
|
1740
|
+
static #appIdOn(appId, platform) {
|
|
1741
|
+
return typeof appId === "string" ? appId : appId[platform] ?? appId.default ?? null;
|
|
1742
|
+
}
|
|
2242
1743
|
static #warnDroppedImports(app, cssRelPath, css, imported) {
|
|
2243
1744
|
for (const { cssPath, declaredNames } of imported) {
|
|
2244
1745
|
if (declaredNames.length === 0 || declaredNames.some((name) => css.includes(`${name}:`)))
|
|
@@ -2247,4 +1748,4 @@ ${preparedCssText}`).toString(36);
|
|
|
2247
1748
|
}
|
|
2248
1749
|
}
|
|
2249
1750
|
}
|
|
2250
|
-
export { AllRoutesBuilder,
|
|
1751
|
+
export { AllRoutesBuilder, CsrArtifactBuilder, CssCompiler, FontOptimizer, FontPruner, precompressArtifacts, SsrBaseArtifactBuilder };
|