@akanjs/cli 3.0.0-beta.23 → 3.0.0-beta.25
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-0dh9fzzg.js → CodeAgent-p65jja35.js} +2 -2
- package/{CodeTui-drwdvwg9.js → CodeTui-5xr8kxx4.js} +1 -1
- package/TunnelShare-cwh9ezsm.js +9 -0
- package/{agent.command-ttn09rx3.js → agent.command-rp505zxa.js} +7 -7
- package/akanCode.js +2 -2
- package/{application.command-b6rck8d6.js → application.command-2x9jqy0k.js} +7 -7
- package/{applicationBuildRunner-vh74vrhg.js → applicationBuildRunner-tm0vba5t.js} +4 -4
- package/buildBatch.proc.js +99 -12
- package/{cloud.command-gwdypy7k.js → cloud.command-j6m607bp.js} +12 -12
- package/cloud.script-r5t7nhee.js +20 -0
- package/{code.command-1jgk14tc.js → code.command-jhrjttm1.js} +7 -7
- package/{context.command-4vpjmg22.js → context.command-xncdvv7n.js} +14 -14
- package/context.runner-pjy89k2s.js +21 -0
- package/{dependencyScanner-ye2e6pdz.js → dependencyScanner-06z2dmrr.js} +2 -2
- package/{fileSys-5ngz93ga.js → fileSys-wzzrntkw.js} +1 -1
- package/{guideline.command-7jk47fgq.js → guideline.command-d5qmxx29.js} +4 -4
- package/guidelines/agentRule/agentRule.instruction.md +7 -0
- package/guidelines/conventions/conventions.instruction.md +7 -5
- package/incrementalBuilder.proc.js +544 -72
- package/{index-ga6hygxr.js → index-0n9q1f23.js} +3 -3
- package/{index-3t7g9e4r.js → index-2ahj896a.js} +1 -1
- package/{index-smme4r47.js → index-2wc41k5w.js} +1 -1
- package/{index-khpfpcsd.js → index-48k96p55.js} +27 -7
- package/{index-776sswhv.js → index-4mgbzgb5.js} +281 -651
- package/{index-2gh9sndr.js → index-50rq2qms.js} +4 -4
- package/{index-5b6tavv3.js → index-52w0fvh8.js} +15 -1
- package/{index-yjkte9aj.js → index-56xwsgyn.js} +3 -3
- package/{index-rmcw8yc8.js → index-58cyna2s.js} +2 -2
- package/{index-1db2cw0y.js → index-5mm3g24t.js} +2 -2
- package/{index-wp43xrjv.js → index-6dt6zft6.js} +4 -4
- package/{index-vj1cwwy5.js → index-7yxndzea.js} +7 -7
- package/{index-q44jbw48.js → index-860zjnr1.js} +1 -1
- package/{index-jn07hxrr.js → index-8k93esfr.js} +6 -6
- package/{index-22d57v4c.js → index-94s080qm.js} +3 -3
- package/{index-yy4s1j72.js → index-d65fzxvd.js} +1 -1
- package/{index-m0ms3n1c.js → index-da72pfdv.js} +7 -7
- package/index-dt1x62ec.js +4108 -0
- package/{index-4dn1p115.js → index-ggfhz9ab.js} +4 -4
- package/{index-9629w7ns.js → index-hzy4pe24.js} +3 -3
- package/{index-jg5e15db.js → index-pb10njz7.js} +192 -58
- package/{index-mgfx3mfs.js → index-per2kqy9.js} +4 -4
- package/{index-aaz7n8s2.js → index-qm4fppfk.js} +4 -4
- package/{index-ya5qbmyg.js → index-t02g6dpn.js} +3 -3
- package/{index-hb8sgepf.js → index-tpdn30ya.js} +1 -1
- package/{index-rnt66vym.js → index-v4hnkqz9.js} +1 -1
- package/{index-pp54p5zk.js → index-xfgdmm5d.js} +2 -2
- package/{index-tgjy8xf3.js → index-zazvn45w.js} +4 -4
- package/{index-0bq14vr1.js → index-zzvvc845.js} +1 -1
- package/index.js +22 -22
- package/{library.command-ccb1cp92.js → library.command-sv6g1h2g.js} +4 -4
- package/{localRegistry.command-cxvgn7fr.js → localRegistry.command-4kj56531.js} +11 -11
- package/{module.command-5k6h407d.js → module.command-ccdeder9.js} +6 -6
- package/{package.command-e6ntvpez.js → package.command-xmw605b1.js} +5 -5
- package/package.json +4 -4
- package/{page.command-v4f90wb8.js → page.command-ky7vafsn.js} +4 -4
- package/{primitive.command-g6km57q5.js → primitive.command-bh6j8nfs.js} +7 -7
- package/{prompter-ptaa3nk0.js → prompter-76k56b37.js} +2 -2
- package/{quality.command-de3p36pz.js → quality.command-rqqrpe98.js} +3 -3
- package/{repair.command-d3x1javq.js → repair.command-n9nsm9qe.js} +6 -6
- package/{scalar.command-7ezrac2b.js → scalar.command-t09gmck8.js} +5 -5
- package/{subspace.command-0cep72ce.js → subspace.command-ygfdqpbj.js} +6 -6
- package/templates/workspaceRoot/package.json.template +1 -3
- package/{tunnel.command-hsrv2mny.js → tunnel.command-4g95vhsn.js} +5 -5
- package/{workflow.command-z2p32nmf.js → workflow.command-b91ph8sx.js} +11 -11
- package/{workspace.command-axwscbnn.js → workspace.command-p44d21qf.js} +24 -24
- package/README.ko.md +0 -72
- package/README.md +0 -88
- package/TunnelShare-scf4e4pd.js +0 -9
- package/cloud.script-at2beffs.js +0 -20
- package/context.runner-ehjs419v.js +0 -21
- package/index-fgq75haa.js +0 -2084
- package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
|
@@ -1,95 +1,29 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
AsyncDefaultExportDetector,
|
|
4
3
|
resolveSsrPageEntriesForApp
|
|
5
|
-
} from "./index-
|
|
4
|
+
} from "./index-0n9q1f23.js";
|
|
6
5
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
loaderFor,
|
|
6
|
+
computeRouteSeedIndex,
|
|
7
|
+
saveRouteSeedIndex,
|
|
11
8
|
BarrelAnalyzer,
|
|
12
|
-
createBarrelImportsPlugin,
|
|
13
9
|
createTsconfigPackageResolver,
|
|
14
10
|
rewriteBarrelImports,
|
|
15
11
|
GraphClientEntryDiscovery,
|
|
16
|
-
transformUseClient,
|
|
17
12
|
bundleDefine,
|
|
18
13
|
ClientEntriesBundler,
|
|
19
14
|
VENDOR_SPECIFIERS,
|
|
20
|
-
RouteClientBuilder
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
import path from "path";
|
|
26
|
-
import { assertUniqueRoutePatterns, compareRouteSpecificity, parseRouteModuleKey } from "akanjs/common";
|
|
15
|
+
RouteClientBuilder,
|
|
16
|
+
CsrEntryFiles,
|
|
17
|
+
PagesEntrySourceGenerator,
|
|
18
|
+
PagesBundleBuilder
|
|
19
|
+
} from "./index-dt1x62ec.js";
|
|
27
20
|
import {
|
|
28
|
-
|
|
29
|
-
} from "
|
|
30
|
-
|
|
31
|
-
const layoutsByPrefix = new Map;
|
|
32
|
-
const pagesBySegments = [];
|
|
33
|
-
for (const { key, moduleAbsPath, seedAbsPaths } of pageEntries) {
|
|
34
|
-
const parsed = parseRouteModuleKey(key);
|
|
35
|
-
const files = [path.resolve(moduleAbsPath), ...(seedAbsPaths ?? []).map((seed) => path.resolve(seed))];
|
|
36
|
-
if (parsed.kind === "layout" || parsed.kind === "overrides") {
|
|
37
|
-
layoutsByPrefix.getOrInsert(parsed.routeSegments.join("/"), []).push(...files);
|
|
38
|
-
} else if (parsed.kind === "page") {
|
|
39
|
-
pagesBySegments.push({
|
|
40
|
-
key,
|
|
41
|
-
pattern: parsed.pattern,
|
|
42
|
-
segments: parsed.routeSegments,
|
|
43
|
-
files,
|
|
44
|
-
includeOwnLayout: parsed.leaf === "_index"
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
assertUniqueRoutePatterns(pagesBySegments);
|
|
49
|
-
const globalLayoutFiles = layoutsByPrefix.get("") ?? [];
|
|
50
|
-
const seedEntries = [];
|
|
51
|
-
for (const { pattern, segments, files, includeOwnLayout } of pagesBySegments) {
|
|
52
|
-
const layouts = [];
|
|
53
|
-
const maxPrefixLength = includeOwnLayout ? segments.length : Math.max(segments.length - 1, 0);
|
|
54
|
-
for (let i = 0;i <= maxPrefixLength; i++) {
|
|
55
|
-
const prefix = segments.slice(0, i).join("/");
|
|
56
|
-
const layoutFiles = layoutsByPrefix.get(prefix);
|
|
57
|
-
if (layoutFiles)
|
|
58
|
-
layouts.push(...layoutFiles);
|
|
59
|
-
}
|
|
60
|
-
const seeds = Array.from(new Set([...layouts, ...files]));
|
|
61
|
-
const routeId = pattern || "/";
|
|
62
|
-
seedEntries.push({ routeId, pattern: routeId, seeds });
|
|
63
|
-
}
|
|
64
|
-
seedEntries.sort((a, b) => compareRouteSpecificity(a.pattern, b.pattern));
|
|
65
|
-
return { entries: seedEntries, globalLayoutFiles };
|
|
66
|
-
}
|
|
67
|
-
function serializeRouteSeedIndexForArtifact(index, artifactDir, options = {}) {
|
|
68
|
-
const normalizedArtifactDir = path.resolve(artifactDir);
|
|
69
|
-
if (options.production)
|
|
70
|
-
return { entries: index.entries.map((entry) => ({ routeId: entry.routeId })) };
|
|
71
|
-
return {
|
|
72
|
-
entries: index.entries.map((entry) => ({
|
|
73
|
-
...entry,
|
|
74
|
-
seeds: entry.seeds.map((seed) => serializeArtifactPath(seed, normalizedArtifactDir))
|
|
75
|
-
})),
|
|
76
|
-
globalLayoutFiles: index.globalLayoutFiles.map((file) => serializeArtifactPath(file, normalizedArtifactDir))
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
async function saveRouteSeedIndex(artifactDir, index, options = {}) {
|
|
80
|
-
const absPath = path.join(path.resolve(artifactDir), ROUTE_SEED_INDEX_JSON);
|
|
81
|
-
await Bun.write(absPath, `${JSON.stringify(serializeRouteSeedIndexForArtifact(index, artifactDir, options), null, 2)}
|
|
82
|
-
`);
|
|
83
|
-
return absPath;
|
|
84
|
-
}
|
|
85
|
-
function serializeArtifactPath(artifactPath, artifactDir) {
|
|
86
|
-
if (!path.isAbsolute(artifactPath))
|
|
87
|
-
return artifactPath;
|
|
88
|
-
return path.relative(artifactDir, artifactPath).split(path.sep).join("/");
|
|
89
|
-
}
|
|
21
|
+
SOURCE_EXTS
|
|
22
|
+
} from "./index-48k96p55.js";
|
|
23
|
+
var __require = import.meta.require;
|
|
90
24
|
|
|
91
25
|
// pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
|
|
92
|
-
import
|
|
26
|
+
import path from "path";
|
|
93
27
|
|
|
94
28
|
// pkgs/@akanjs/devkit/frontendBuild/routesManifestArtifactSerializer.ts
|
|
95
29
|
import { RoutesManifestStore } from "akanjs/server/artifact/routesManifestStore";
|
|
@@ -155,7 +89,7 @@ class AllRoutesBuilder {
|
|
|
155
89
|
routeIds: this.#routeIds,
|
|
156
90
|
...this.#merged
|
|
157
91
|
};
|
|
158
|
-
const manifestPath =
|
|
92
|
+
const manifestPath = path.join(path.resolve(this.#artifactDir), "routes-manifest.json");
|
|
159
93
|
await Bun.write(manifestPath, `${JSON.stringify(RoutesManifestArtifactSerializer.serialize(manifest, this.#artifactDir, {
|
|
160
94
|
production: this.#command === "build"
|
|
161
95
|
}), null, 2)}
|
|
@@ -194,6 +128,222 @@ class AllRoutesBuilder {
|
|
|
194
128
|
}
|
|
195
129
|
}
|
|
196
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
|
+
|
|
197
347
|
// pkgs/@akanjs/devkit/frontendBuild/cssCompiler.ts
|
|
198
348
|
import path4 from "path";
|
|
199
349
|
import { Logger } from "akanjs/common";
|
|
@@ -721,532 +871,12 @@ function declaredCustomProperties(css) {
|
|
|
721
871
|
return [...new Set([...withoutThemeBlocks.matchAll(/(?:^|[\s;{])(--[\w-]+)\s*:/g)].map(([, name]) => name))].filter((name) => !!name);
|
|
722
872
|
}
|
|
723
873
|
function getPageKeyBasePath(pageKey, basePaths) {
|
|
724
|
-
|
|
725
|
-
const segments = normalized.split("/");
|
|
726
|
-
const firstPublicSegment = segments.find((segment) => segment !== "[lang]" && !/^\(.+\)$/.test(segment));
|
|
727
|
-
return firstPublicSegment && basePaths.includes(firstPublicSegment) ? firstPublicSegment : null;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
// pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
|
|
731
|
-
import path7 from "path";
|
|
732
|
-
|
|
733
|
-
// pkgs/@akanjs/devkit/transforms/externalizeFrameworkPlugin.ts
|
|
734
|
-
import path5 from "path";
|
|
735
|
-
var DEFAULT_INCLUDE = ["akanjs/", "@apps/", "@libs/"];
|
|
736
|
-
var DEFAULT_EXCLUDE_EXACT = new Set(["akanjs/webkit", "@akanjs/cli", "@akanjs/devkit"]);
|
|
737
|
-
var DEFAULT_EXCLUDE_PREFIX = ["@akanjs/cli/", "@akanjs/devkit/"];
|
|
738
|
-
var OPTIONAL_BACKEND_EXTERNAL_EXACT = new Set([
|
|
739
|
-
"@libsql/client",
|
|
740
|
-
"bullmq",
|
|
741
|
-
"ioredis",
|
|
742
|
-
"postgres",
|
|
743
|
-
"protobufjs"
|
|
744
|
-
]);
|
|
745
|
-
var RUNTIME_EXTERNAL_EXACT = new Set([
|
|
746
|
-
"react",
|
|
747
|
-
"react-dom",
|
|
748
|
-
"react/jsx-runtime",
|
|
749
|
-
"react/jsx-dev-runtime",
|
|
750
|
-
"react-server-dom-webpack",
|
|
751
|
-
"react-server-dom-webpack/server.node",
|
|
752
|
-
"react-server-dom-webpack/client.node",
|
|
753
|
-
"react-server-dom-webpack/client.browser"
|
|
754
|
-
]);
|
|
755
|
-
var RUNTIME_EXTERNAL_PREFIX = ["react-dom/", "react-server-dom-webpack/"];
|
|
756
|
-
var CANDIDATE_EXTS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
|
|
757
|
-
async function createExternalizeFrameworkPlugin(options) {
|
|
758
|
-
const tsconfig = await options.app.getTsConfig();
|
|
759
|
-
const includePrefixes = options.include ?? DEFAULT_INCLUDE;
|
|
760
|
-
const extraExact = new Set(options.extra ?? []);
|
|
761
|
-
const workspaceRoot = options.app.workspace.workspaceRoot;
|
|
762
|
-
const tsconfigPaths = tsconfig.compilerOptions.paths ?? {};
|
|
763
|
-
const rootEntries = Object.entries(tsconfigPaths).filter(([k]) => !k.endsWith("/*")).map(([k, v]) => ({ pkg: k, entryFile: v[0] ?? null })).filter((e) => e.entryFile !== null);
|
|
764
|
-
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);
|
|
765
|
-
async function resolveWorkspaceSubpath(spec) {
|
|
766
|
-
if (!workspaceRoot)
|
|
767
|
-
return null;
|
|
768
|
-
for (const { prefix, replacements } of wildcardEntries) {
|
|
769
|
-
if (!spec.startsWith(prefix))
|
|
770
|
-
continue;
|
|
771
|
-
const suffix = spec.slice(prefix.length);
|
|
772
|
-
for (const repl of replacements) {
|
|
773
|
-
const replPath = repl?.endsWith("/*") ? repl.slice(0, -1) : repl ?? "";
|
|
774
|
-
if (!replPath)
|
|
775
|
-
continue;
|
|
776
|
-
const candidate = path5.resolve(workspaceRoot, replPath + suffix);
|
|
777
|
-
const hit = await firstExisting(candidate);
|
|
778
|
-
if (hit)
|
|
779
|
-
return hit;
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
for (const { pkg, entryFile } of rootEntries) {
|
|
783
|
-
if (!spec.startsWith(`${pkg}/`))
|
|
784
|
-
continue;
|
|
785
|
-
const suffix = spec.slice(pkg.length + 1);
|
|
786
|
-
const pkgDir = path5.dirname(path5.resolve(workspaceRoot, entryFile));
|
|
787
|
-
const candidate = path5.join(pkgDir, suffix);
|
|
788
|
-
const hit = await firstExisting(candidate);
|
|
789
|
-
if (hit)
|
|
790
|
-
return hit;
|
|
791
|
-
}
|
|
792
|
-
return null;
|
|
793
|
-
}
|
|
794
|
-
return {
|
|
795
|
-
name: "akan-externalize-framework",
|
|
796
|
-
setup(build) {
|
|
797
|
-
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
798
|
-
const spec = args.path;
|
|
799
|
-
if (spec === "." || spec === ".." || spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/"))
|
|
800
|
-
return;
|
|
801
|
-
if (extraExact.has(spec) || DEFAULT_EXCLUDE_EXACT.has(spec))
|
|
802
|
-
return { path: spec, external: true };
|
|
803
|
-
for (const prefix of DEFAULT_EXCLUDE_PREFIX) {
|
|
804
|
-
if (spec.startsWith(prefix))
|
|
805
|
-
return { path: spec, external: true };
|
|
806
|
-
}
|
|
807
|
-
if (OPTIONAL_BACKEND_EXTERNAL_EXACT.has(spec))
|
|
808
|
-
return { path: spec, external: true };
|
|
809
|
-
if (RUNTIME_EXTERNAL_EXACT.has(spec))
|
|
810
|
-
return { path: spec, external: true };
|
|
811
|
-
for (const prefix of RUNTIME_EXTERNAL_PREFIX) {
|
|
812
|
-
if (spec.startsWith(prefix))
|
|
813
|
-
return { path: spec, external: true };
|
|
814
|
-
}
|
|
815
|
-
if (!includePrefixes.some((prefix) => spec.startsWith(prefix)))
|
|
816
|
-
return;
|
|
817
|
-
const resolved = await resolveWorkspaceSubpath(spec);
|
|
818
|
-
return resolved ? { path: resolved } : undefined;
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
async function firstExisting(basePath) {
|
|
824
|
-
if (await Bun.file(basePath).exists())
|
|
825
|
-
return basePath;
|
|
826
|
-
for (const ext of CANDIDATE_EXTS) {
|
|
827
|
-
const candidate = `${basePath}${ext}`;
|
|
828
|
-
if (await Bun.file(candidate).exists())
|
|
829
|
-
return candidate;
|
|
830
|
-
}
|
|
831
|
-
for (const ext of CANDIDATE_EXTS) {
|
|
832
|
-
const candidate = path5.join(basePath, `index${ext}`);
|
|
833
|
-
if (await Bun.file(candidate).exists())
|
|
834
|
-
return candidate;
|
|
835
|
-
}
|
|
836
|
-
return null;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
// pkgs/@akanjs/devkit/transforms/useClientBundlePlugin.ts
|
|
840
|
-
function createUseClientBundlePlugin(options = {}) {
|
|
841
|
-
return {
|
|
842
|
-
name: "akan-use-client-bundle",
|
|
843
|
-
setup(build) {
|
|
844
|
-
build.onLoad({ filter: /\.(tsx|ts|jsx|js)$/ }, async (args) => {
|
|
845
|
-
if (args.path.includes("/node_modules/"))
|
|
846
|
-
return;
|
|
847
|
-
let source;
|
|
848
|
-
try {
|
|
849
|
-
source = await Bun.file(args.path).text();
|
|
850
|
-
} catch {
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
const stubbed = transformUseClient(source, {
|
|
854
|
-
path: args.path,
|
|
855
|
-
workspaceRoot: options.workspaceRoot
|
|
856
|
-
});
|
|
857
|
-
if (stubbed === null)
|
|
858
|
-
return;
|
|
859
|
-
return { contents: stubbed, loader: loaderFor(args.path) };
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
// pkgs/@akanjs/devkit/frontendBuild/pagesEntrySourceGenerator.ts
|
|
866
|
-
import path6 from "path";
|
|
867
|
-
class PagesEntrySourceGenerator {
|
|
868
|
-
#pageEntries;
|
|
869
|
-
constructor(pageEntries) {
|
|
870
|
-
this.#pageEntries = pageEntries;
|
|
871
|
-
}
|
|
872
|
-
static generate(pageEntries) {
|
|
873
|
-
return new PagesEntrySourceGenerator(pageEntries).generate();
|
|
874
|
-
}
|
|
875
|
-
generate() {
|
|
876
|
-
const lines = this.#pageEntries.map(({ key, moduleAbsPath }) => {
|
|
877
|
-
const specifier = PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
|
|
878
|
-
return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(specifier)}),`;
|
|
879
|
-
});
|
|
880
|
-
return `export const pages = {
|
|
881
|
-
${lines.join(`
|
|
882
|
-
`)}
|
|
883
|
-
};
|
|
884
|
-
`;
|
|
885
|
-
}
|
|
886
|
-
static async generateStatic(pageEntries) {
|
|
887
|
-
return await new PagesEntrySourceGenerator(pageEntries).generateStatic();
|
|
888
|
-
}
|
|
889
|
-
async generateStatic() {
|
|
890
|
-
const imports = this.#pageEntries.map(({ moduleAbsPath }, index) => {
|
|
891
|
-
const specifier = PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
|
|
892
|
-
return `import * as page${index} from ${JSON.stringify(specifier)};`;
|
|
893
|
-
});
|
|
894
|
-
const entries = await Promise.all(this.#pageEntries.map(async ({ key, moduleAbsPath }, index) => {
|
|
895
|
-
const isAsyncDefault = await AsyncDefaultExportDetector.detect(moduleAbsPath);
|
|
896
|
-
return ` ${JSON.stringify(key)}: { loader: async () => page${index}, isAsyncDefault: ${isAsyncDefault} },`;
|
|
897
|
-
}));
|
|
898
|
-
return `${imports.join(`
|
|
899
|
-
`)}
|
|
900
|
-
export const pages = {
|
|
901
|
-
${entries.join(`
|
|
902
|
-
`)}
|
|
903
|
-
};
|
|
904
|
-
`;
|
|
905
|
-
}
|
|
906
|
-
static #toImportSpecifier(moduleAbsPath) {
|
|
907
|
-
return path6.resolve(moduleAbsPath).split(path6.sep).join("/");
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
|
|
912
|
-
var VIRTUAL_PAGES_ENTRY = "akan-pages-entry";
|
|
913
|
-
|
|
914
|
-
class PagesBundleBuilder {
|
|
915
|
-
#app;
|
|
916
|
-
#command;
|
|
917
|
-
#pageEntries;
|
|
918
|
-
#started = Date.now();
|
|
919
|
-
constructor(app, command = "start", pageEntries) {
|
|
920
|
-
this.#app = app;
|
|
921
|
-
this.#command = command;
|
|
922
|
-
this.#pageEntries = pageEntries;
|
|
923
|
-
}
|
|
924
|
-
async build() {
|
|
925
|
-
const akanConfig = await this.#app.getConfig();
|
|
926
|
-
const resolvedEntries = this.#pageEntries ?? await resolveSsrPageEntriesForApp(this.#app, await this.#app.getPageKeys());
|
|
927
|
-
const entrySource = PagesEntrySourceGenerator.generate(resolvedEntries);
|
|
928
|
-
const workspaceRoot = this.#app.workspace.workspaceRoot;
|
|
929
|
-
const result = await Bun.build({
|
|
930
|
-
entrypoints: [VIRTUAL_PAGES_ENTRY],
|
|
931
|
-
outdir: `${this.#artifactDir}/server`,
|
|
932
|
-
target: "bun",
|
|
933
|
-
format: "esm",
|
|
934
|
-
splitting: this.#splitting,
|
|
935
|
-
minify: this.#command === "build",
|
|
936
|
-
naming: {
|
|
937
|
-
entry: "pages-[hash].[ext]",
|
|
938
|
-
chunk: "chunks/[name]-[hash].[ext]",
|
|
939
|
-
asset: "assets/[name]-[hash].[ext]"
|
|
940
|
-
},
|
|
941
|
-
define: bundleDefine(this.#app, this.#command, "ssr"),
|
|
942
|
-
plugins: [
|
|
943
|
-
PagesBundleBuilder.createPagesEntryPlugin(entrySource),
|
|
944
|
-
PagesBundleBuilder.createCssStubPlugin(),
|
|
945
|
-
PagesBundleBuilder.createServerUseClientFetchPlugin(),
|
|
946
|
-
await createExternalizeFrameworkPlugin({ app: this.#app, extra: akanConfig.externalLibs }),
|
|
947
|
-
akanConfig.barrelImports.length > 0 ? await createBarrelImportsPlugin(this.#app, {
|
|
948
|
-
pipeAfter: (source, args) => transformUseClient(source, {
|
|
949
|
-
path: args.path,
|
|
950
|
-
workspaceRoot
|
|
951
|
-
})
|
|
952
|
-
}) : createUseClientBundlePlugin({ workspaceRoot })
|
|
953
|
-
]
|
|
954
|
-
});
|
|
955
|
-
if (!result.success)
|
|
956
|
-
throw new AggregateError(result.logs, "[PagesBundleBuilder] Bun.build failed");
|
|
957
|
-
const entryArtifact = result.outputs.find((a) => a.kind === "entry-point");
|
|
958
|
-
if (!entryArtifact)
|
|
959
|
-
throw new Error("[PagesBundleBuilder] Bun.build emitted no entry-point artifact");
|
|
960
|
-
const bundlePath = path7.resolve(entryArtifact.path);
|
|
961
|
-
const buildId = Date.now();
|
|
962
|
-
const outputBytes = result.outputs.reduce((sum, output) => sum + output.size, 0);
|
|
963
|
-
const chunkCount = result.outputs.filter((output) => output.kind === "chunk").length;
|
|
964
|
-
this.#app.verbose(`[PagesBundleBuilder] ${path7.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`);
|
|
965
|
-
return {
|
|
966
|
-
bundlePath,
|
|
967
|
-
buildId,
|
|
968
|
-
splitting: this.#splitting,
|
|
969
|
-
entryBytes: entryArtifact.size,
|
|
970
|
-
outputBytes,
|
|
971
|
-
outputCount: result.outputs.length,
|
|
972
|
-
chunkCount
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
get #artifactDir() {
|
|
976
|
-
return `${this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath}/.akan/artifact`;
|
|
977
|
-
}
|
|
978
|
-
get #splitting() {
|
|
979
|
-
return process.env.AKAN_SERVER_PAGES_SPLITTING === "1";
|
|
980
|
-
}
|
|
981
|
-
static createPagesEntryPlugin(source) {
|
|
982
|
-
return {
|
|
983
|
-
name: "akan-pages-entry",
|
|
984
|
-
setup(build) {
|
|
985
|
-
build.onResolve({ filter: /^akan-pages-entry$/ }, () => ({
|
|
986
|
-
path: VIRTUAL_PAGES_ENTRY,
|
|
987
|
-
namespace: "akan-virtual"
|
|
988
|
-
}));
|
|
989
|
-
build.onLoad({ filter: /^akan-pages-entry$/, namespace: "akan-virtual" }, () => ({
|
|
990
|
-
contents: source,
|
|
991
|
-
loader: "tsx"
|
|
992
|
-
}));
|
|
993
|
-
}
|
|
994
|
-
};
|
|
995
|
-
}
|
|
996
|
-
static createCssStubPlugin() {
|
|
997
|
-
return {
|
|
998
|
-
name: "akan-css-stub",
|
|
999
|
-
setup(build) {
|
|
1000
|
-
build.onLoad({ filter: /\.css$/ }, () => ({
|
|
1001
|
-
contents: "",
|
|
1002
|
-
loader: "js"
|
|
1003
|
-
}));
|
|
1004
|
-
}
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
static createServerUseClientFetchPlugin() {
|
|
1008
|
-
return {
|
|
1009
|
-
name: "akan-server-use-client-fetch",
|
|
1010
|
-
setup(build) {
|
|
1011
|
-
build.onLoad({ filter: /[/\\]lib[/\\]useClient\.(ts|tsx|js|jsx)$/ }, async (args) => {
|
|
1012
|
-
const source = await Bun.file(args.path).text();
|
|
1013
|
-
const transformed = PagesBundleBuilder.transformServerUseClientFetchSource(source);
|
|
1014
|
-
if (transformed === source)
|
|
1015
|
-
return;
|
|
1016
|
-
return { contents: transformed, loader: loaderFor(args.path) };
|
|
1017
|
-
});
|
|
1018
|
-
}
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
static transformServerUseClientFetchSource(source) {
|
|
1022
|
-
if (!source.includes(`with { type: "macro" }`) || !source.includes("FetchClient.build"))
|
|
1023
|
-
return source;
|
|
1024
|
-
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 });");
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
// pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
|
|
1029
|
-
import { mkdir, rm, unlink } from "fs/promises";
|
|
1030
|
-
import path8 from "path";
|
|
1031
|
-
class CsrArtifactBuilder {
|
|
1032
|
-
#app;
|
|
1033
|
-
#command;
|
|
1034
|
-
#lang;
|
|
1035
|
-
constructor(app, command = "start", lang = "en") {
|
|
1036
|
-
this.#app = app;
|
|
1037
|
-
this.#command = command;
|
|
1038
|
-
this.#lang = lang;
|
|
1039
|
-
}
|
|
1040
|
-
async build() {
|
|
1041
|
-
const pageKeys = await this.#app.getPageKeys();
|
|
1042
|
-
if (pageKeys.length === 0) {
|
|
1043
|
-
this.#app.log(`[cli] no route files under ${this.#app.cwdPath}/page \u2014 skipping CSR build`);
|
|
1044
|
-
return null;
|
|
1045
|
-
}
|
|
1046
|
-
const pageEntries = await resolveSsrPageEntriesForApp(this.#app, pageKeys);
|
|
1047
|
-
const akanConfig = await this.#app.getConfig();
|
|
1048
|
-
const cssAssets = await this.#loadCssAssets();
|
|
1049
|
-
const basePaths = [...akanConfig.basePaths];
|
|
1050
|
-
const htmlBasePaths = basePaths.length > 0 ? basePaths : [""];
|
|
1051
|
-
await rm(this.#outputDir, { recursive: true, force: true });
|
|
1052
|
-
await mkdir(this.#generatedDir, { recursive: true });
|
|
1053
|
-
const generatedFiles = Object.fromEntries((await Promise.all(htmlBasePaths.map(async (basePath) => [
|
|
1054
|
-
this.#createHtmlFile(basePath),
|
|
1055
|
-
await this.#createEntryFile(basePath, CsrArtifactBuilder.pageEntriesForBasePath(pageEntries, basePath, basePaths))
|
|
1056
|
-
]))).flat());
|
|
1057
|
-
const result = await Bun.build({
|
|
1058
|
-
target: "browser",
|
|
1059
|
-
entrypoints: htmlBasePaths.map((basePath) => this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath))),
|
|
1060
|
-
files: generatedFiles,
|
|
1061
|
-
root: this.#generatedDir,
|
|
1062
|
-
outdir: this.#outputDir,
|
|
1063
|
-
splitting: false,
|
|
1064
|
-
minify: true,
|
|
1065
|
-
env: "AKAN_PUBLIC_*",
|
|
1066
|
-
define: bundleDefine(this.#app, this.#command, "csr"),
|
|
1067
|
-
optimizeImports: akanConfig.optimizeImports,
|
|
1068
|
-
plugins: [PagesBundleBuilder.createCssStubPlugin()]
|
|
1069
|
-
});
|
|
1070
|
-
if (!result.success) {
|
|
1071
|
-
const logs = result.logs.map((log) => log.message).join(`
|
|
1072
|
-
`);
|
|
1073
|
-
throw new Error(`[csr-build] failed${logs ? `
|
|
1074
|
-
${logs}` : ""}`);
|
|
1075
|
-
}
|
|
1076
|
-
await this.#inlineCsrArtifacts(cssAssets);
|
|
1077
|
-
this.#app.verbose(`[csr-build] output -> ${this.#outputDir}`);
|
|
1078
|
-
return { outputDir: this.#outputDir };
|
|
1079
|
-
}
|
|
1080
|
-
static pageEntriesForBasePath(pageEntries, basePath, basePaths) {
|
|
1081
|
-
return pageEntries.filter((entry) => {
|
|
1082
|
-
const entryBasePath = getPageKeyBasePath(entry.key, basePaths);
|
|
1083
|
-
return entryBasePath === null || entryBasePath === basePath;
|
|
1084
|
-
});
|
|
1085
|
-
}
|
|
1086
|
-
static htmlFilename(basePath) {
|
|
1087
|
-
return `${basePath || "index"}.html`;
|
|
1088
|
-
}
|
|
1089
|
-
static entryFilename(basePath) {
|
|
1090
|
-
return `${basePath || "index"}.csr.tsx`;
|
|
1091
|
-
}
|
|
1092
|
-
static basePathOfHtml(htmlPath) {
|
|
1093
|
-
const name = path8.basename(htmlPath, ".html");
|
|
1094
|
-
return name === "index" ? "" : name;
|
|
1095
|
-
}
|
|
1096
|
-
get #outputDir() {
|
|
1097
|
-
return path8.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, this.#command === "build" ? "csr" : ".akan/artifact/csr");
|
|
1098
|
-
}
|
|
1099
|
-
get #generatedDir() {
|
|
1100
|
-
return path8.join(this.#app.cwdPath, ".akan/generated/csr");
|
|
1101
|
-
}
|
|
1102
|
-
get #artifactDir() {
|
|
1103
|
-
return path8.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact");
|
|
1104
|
-
}
|
|
1105
|
-
#generatedPath(filename) {
|
|
1106
|
-
return path8.join(this.#generatedDir, filename);
|
|
1107
|
-
}
|
|
1108
|
-
async#createEntryFile(basePath, pageEntries) {
|
|
1109
|
-
return [
|
|
1110
|
-
this.#generatedPath(CsrArtifactBuilder.entryFilename(basePath)),
|
|
1111
|
-
`import { bootCsr } from "akanjs/webkit";
|
|
1112
|
-
${await PagesEntrySourceGenerator.generateStatic(pageEntries)}
|
|
1113
|
-
void bootCsr(pages);
|
|
1114
|
-
`
|
|
1115
|
-
];
|
|
1116
|
-
}
|
|
1117
|
-
#createHtmlFile(basePath) {
|
|
1118
|
-
return [
|
|
1119
|
-
this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath)),
|
|
1120
|
-
`<!doctype html>
|
|
1121
|
-
<html lang="${this.#lang}">
|
|
1122
|
-
<head>
|
|
1123
|
-
<meta charset="utf-8" />
|
|
1124
|
-
<title>${this.#app.name}</title>
|
|
1125
|
-
<base href="/" />
|
|
1126
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1127
|
-
</head>
|
|
1128
|
-
<body>
|
|
1129
|
-
<div id="root"></div>
|
|
1130
|
-
<script type="module" src="./${CsrArtifactBuilder.entryFilename(basePath)}"></script>
|
|
1131
|
-
</body>
|
|
1132
|
-
</html>
|
|
1133
|
-
`
|
|
1134
|
-
];
|
|
1135
|
-
}
|
|
1136
|
-
async#loadCssAssets() {
|
|
1137
|
-
const artifactFile = Bun.file(path8.join(this.#artifactDir, "base-artifact.json"));
|
|
1138
|
-
if (!await artifactFile.exists())
|
|
1139
|
-
return {};
|
|
1140
|
-
const artifact = await artifactFile.json();
|
|
1141
|
-
return artifact.cssAssets ?? {};
|
|
1142
|
-
}
|
|
1143
|
-
async#inlineCsrArtifacts(cssAssets) {
|
|
1144
|
-
const jsFiles = new Set;
|
|
1145
|
-
for (const htmlPath of await this.#listOutputFiles((filePath) => filePath.endsWith(".html"))) {
|
|
1146
|
-
const htmlFile = Bun.file(htmlPath);
|
|
1147
|
-
if (!await htmlFile.exists())
|
|
1148
|
-
continue;
|
|
1149
|
-
const basePath = CsrArtifactBuilder.basePathOfHtml(htmlPath);
|
|
1150
|
-
const cssAsset = cssAssets[basePath];
|
|
1151
|
-
if (!cssAsset) {
|
|
1152
|
-
this.#app.logger.warn(`[csr-build] base-artifact.json has no compiled stylesheet for ${basePath || "root"}; ${path8.basename(htmlPath)} ships without CSS`);
|
|
1153
|
-
}
|
|
1154
|
-
const inlined = await this.#inlineHtmlAssets(await htmlFile.text(), htmlPath, cssAsset);
|
|
1155
|
-
for (const filePath of inlined.jsFiles)
|
|
1156
|
-
jsFiles.add(filePath);
|
|
1157
|
-
await Bun.write(htmlPath, inlined.html);
|
|
1158
|
-
}
|
|
1159
|
-
for (const filePath of jsFiles)
|
|
1160
|
-
await unlink(filePath).catch(() => {
|
|
1161
|
-
return;
|
|
1162
|
-
});
|
|
1163
|
-
const remainingAssets = await this.#listOutputFiles((filePath) => filePath.endsWith(".js") || filePath.endsWith(".css"));
|
|
1164
|
-
if (remainingAssets.length > 0) {
|
|
1165
|
-
throw new Error(`[csr-build] expected single-file HTML, but CSR assets remain:
|
|
1166
|
-
${remainingAssets.join(`
|
|
1167
|
-
`)}`);
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
async#inlineHtmlAssets(html, htmlPath, cssAsset) {
|
|
1171
|
-
let next = html;
|
|
1172
|
-
if (cssAsset) {
|
|
1173
|
-
const css = await Bun.file(path8.join(this.#artifactDir, cssAsset.cssRelPath)).text();
|
|
1174
|
-
next = CsrArtifactBuilder.injectBeforeHeadEnd(next, CsrArtifactBuilder.createInlineStyle(css));
|
|
1175
|
-
}
|
|
1176
|
-
const jsFiles = [];
|
|
1177
|
-
next = await CsrArtifactBuilder.replaceModuleScriptSrc(next, async (src) => {
|
|
1178
|
-
const jsPath = CsrArtifactBuilder.resolveHtmlAssetPath(htmlPath, src);
|
|
1179
|
-
jsFiles.push(jsPath);
|
|
1180
|
-
return await Bun.file(jsPath).text();
|
|
1181
|
-
});
|
|
1182
|
-
return { html: next, jsFiles };
|
|
1183
|
-
}
|
|
1184
|
-
async#listOutputFiles(predicate) {
|
|
1185
|
-
const glob = new Bun.Glob("**/*");
|
|
1186
|
-
const files = [];
|
|
1187
|
-
for await (const filePath of glob.scan({ cwd: this.#outputDir, absolute: true })) {
|
|
1188
|
-
if (predicate(filePath))
|
|
1189
|
-
files.push(filePath);
|
|
1190
|
-
}
|
|
1191
|
-
return files.sort();
|
|
1192
|
-
}
|
|
1193
|
-
static injectBeforeHeadEnd(html, snippet) {
|
|
1194
|
-
const scannable = CsrArtifactBuilder.blankEmbeddedContent(html);
|
|
1195
|
-
const headEnd = scannable.search(/<\/head\s*>/i);
|
|
1196
|
-
if (headEnd !== -1)
|
|
1197
|
-
return `${html.slice(0, headEnd)}${snippet}
|
|
1198
|
-
${html.slice(headEnd)}`;
|
|
1199
|
-
const bodyStart = scannable.search(/<body(?:\s|>)/i);
|
|
1200
|
-
if (bodyStart !== -1)
|
|
1201
|
-
return `${html.slice(0, bodyStart)}${snippet}
|
|
1202
|
-
${html.slice(bodyStart)}`;
|
|
1203
|
-
return `${html}
|
|
1204
|
-
${snippet}`;
|
|
1205
|
-
}
|
|
1206
|
-
static blankEmbeddedContent(html) {
|
|
1207
|
-
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script\s*>|<style\b[^>]*>[\s\S]*?<\/style\s*>|<!--[\s\S]*?-->/gi, (match) => " ".repeat(match.length));
|
|
1208
|
-
}
|
|
1209
|
-
static createInlineStyle(css) {
|
|
1210
|
-
return `<style data-akan-css="active">
|
|
1211
|
-
${css.replace(/<\/style/gi, "<\\/style")}
|
|
1212
|
-
</style>`;
|
|
1213
|
-
}
|
|
1214
|
-
static async replaceModuleScriptSrc(html, loadScript) {
|
|
1215
|
-
const scriptRe = /<script\b(?=[^>]*\btype=["']module["'])(?=[^>]*\bsrc=["']([^"']+)["'])[^>]*>\s*<\/script>/gi;
|
|
1216
|
-
let result = "";
|
|
1217
|
-
let lastIndex = 0;
|
|
1218
|
-
let matched = false;
|
|
1219
|
-
for (const match of html.matchAll(scriptRe)) {
|
|
1220
|
-
const full = match[0];
|
|
1221
|
-
const src = match[1];
|
|
1222
|
-
if (match.index === undefined || !src)
|
|
1223
|
-
continue;
|
|
1224
|
-
matched = true;
|
|
1225
|
-
result += html.slice(lastIndex, match.index);
|
|
1226
|
-
result += `<script type="module">
|
|
1227
|
-
${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
|
|
1228
|
-
</script>`;
|
|
1229
|
-
lastIndex = match.index + full.length;
|
|
1230
|
-
}
|
|
1231
|
-
if (!matched)
|
|
1232
|
-
return html;
|
|
1233
|
-
return result + html.slice(lastIndex);
|
|
1234
|
-
}
|
|
1235
|
-
static escapeInlineScript(source) {
|
|
1236
|
-
return source.replace(/<\/script/gi, "<\\/script");
|
|
1237
|
-
}
|
|
1238
|
-
static resolveHtmlAssetPath(htmlPath, src) {
|
|
1239
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) {
|
|
1240
|
-
throw new Error(`[csr-build] cannot inline external script: ${src}`);
|
|
1241
|
-
}
|
|
1242
|
-
const normalized = src.startsWith("/") ? src.slice(1) : src;
|
|
1243
|
-
return path8.resolve(path8.dirname(htmlPath), normalized);
|
|
1244
|
-
}
|
|
874
|
+
return CsrEntryFiles.basePathOfPageKey(pageKey, basePaths);
|
|
1245
875
|
}
|
|
1246
876
|
|
|
1247
877
|
// pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
|
|
1248
878
|
import { mkdir as mkdir2, stat as stat2 } from "fs/promises";
|
|
1249
|
-
import
|
|
879
|
+
import path5 from "path";
|
|
1250
880
|
import ts from "typescript";
|
|
1251
881
|
var FONT_URL_PREFIX = "/_akan/fonts";
|
|
1252
882
|
var DEFAULT_FONT_SUBSETS = ["latin"];
|
|
@@ -1263,7 +893,7 @@ class FontOptimizer {
|
|
|
1263
893
|
constructor(app, command = "start") {
|
|
1264
894
|
this.#app = app;
|
|
1265
895
|
this.#command = command;
|
|
1266
|
-
this.#artifactRoot =
|
|
896
|
+
this.#artifactRoot = path5.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
|
|
1267
897
|
}
|
|
1268
898
|
async optimize() {
|
|
1269
899
|
const fonts = await this.discoverFonts();
|
|
@@ -1288,7 +918,7 @@ class FontOptimizer {
|
|
|
1288
918
|
return result;
|
|
1289
919
|
}
|
|
1290
920
|
get #cachePath() {
|
|
1291
|
-
return
|
|
921
|
+
return path5.join(this.#artifactRoot, "fontCache.json");
|
|
1292
922
|
}
|
|
1293
923
|
async#buildCacheKey(fonts) {
|
|
1294
924
|
const sources = [];
|
|
@@ -1303,7 +933,7 @@ class FontOptimizer {
|
|
|
1303
933
|
sources.push({ optimizedSrc: face.optimizedSrc, ...stamp });
|
|
1304
934
|
}
|
|
1305
935
|
for (const filePath of font.subsetFiles ?? []) {
|
|
1306
|
-
const abs =
|
|
936
|
+
const abs = path5.isAbsolute(filePath) ? filePath : path5.join(this.#app.cwdPath, filePath);
|
|
1307
937
|
const stamp = await this.#fileStamp(abs);
|
|
1308
938
|
if (!stamp)
|
|
1309
939
|
return null;
|
|
@@ -1329,7 +959,7 @@ class FontOptimizer {
|
|
|
1329
959
|
const cache = await Bun.file(this.#cachePath).json().catch(() => null);
|
|
1330
960
|
if (cache?.version !== FontOptimizer.#cacheVersion || cache.key !== key)
|
|
1331
961
|
return null;
|
|
1332
|
-
const files = cache.files.map((relativePath) =>
|
|
962
|
+
const files = cache.files.map((relativePath) => path5.join(this.#artifactRoot, relativePath));
|
|
1333
963
|
for (const filePath of files) {
|
|
1334
964
|
if (!await Bun.file(filePath).exists())
|
|
1335
965
|
return null;
|
|
@@ -1341,7 +971,7 @@ class FontOptimizer {
|
|
|
1341
971
|
version: FontOptimizer.#cacheVersion,
|
|
1342
972
|
key,
|
|
1343
973
|
css: result.css,
|
|
1344
|
-
files: result.files.map((filePath) =>
|
|
974
|
+
files: result.files.map((filePath) => path5.relative(this.#artifactRoot, filePath))
|
|
1345
975
|
};
|
|
1346
976
|
await Bun.write(this.#cachePath, JSON.stringify(cache));
|
|
1347
977
|
}
|
|
@@ -1349,7 +979,7 @@ class FontOptimizer {
|
|
|
1349
979
|
const pageKeys = await this.#app.getPageKeys();
|
|
1350
980
|
const fonts = [];
|
|
1351
981
|
await Promise.all(pageKeys.map(async (key) => {
|
|
1352
|
-
const filePath =
|
|
982
|
+
const filePath = path5.resolve(this.#app.cwdPath, "page", key);
|
|
1353
983
|
const file = Bun.file(filePath);
|
|
1354
984
|
if (!await file.exists())
|
|
1355
985
|
return;
|
|
@@ -1368,8 +998,8 @@ class FontOptimizer {
|
|
|
1368
998
|
this.#app.logger.warn(`[font] source not found: ${face.src}`);
|
|
1369
999
|
continue;
|
|
1370
1000
|
}
|
|
1371
|
-
const outputPath =
|
|
1372
|
-
await mkdir2(
|
|
1001
|
+
const outputPath = path5.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
|
|
1002
|
+
await mkdir2(path5.dirname(outputPath), { recursive: true });
|
|
1373
1003
|
const sourceBuffer = Buffer.from(await Bun.file(sourcePath).arrayBuffer());
|
|
1374
1004
|
const outputBuffer = await this.#buildFontBuffer(font, sourceBuffer, sourcePath);
|
|
1375
1005
|
await Bun.write(outputPath, outputBuffer);
|
|
@@ -1391,7 +1021,7 @@ class FontOptimizer {
|
|
|
1391
1021
|
const entries = Array.isArray(value) ? value : [];
|
|
1392
1022
|
const fonts = entries.filter((entry) => this.#isReadableFont(entry));
|
|
1393
1023
|
if (fonts.length !== entries.length || !Array.isArray(value))
|
|
1394
|
-
this.#app.logger.warn(`[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`);
|
|
1395
1025
|
return fonts.map((font) => ({ ...font, subsets: font.subsets ?? [...DEFAULT_FONT_SUBSETS] }));
|
|
1396
1026
|
}
|
|
1397
1027
|
#isReadableFont(value) {
|
|
@@ -1549,8 +1179,8 @@ class FontOptimizer {
|
|
|
1549
1179
|
return null;
|
|
1550
1180
|
const rel = src.replace(/^\//, "");
|
|
1551
1181
|
const candidates = [
|
|
1552
|
-
this.#command === "build" ?
|
|
1553
|
-
|
|
1182
|
+
this.#command === "build" ? path5.join(this.#app.dist.cwdPath, "public", rel) : null,
|
|
1183
|
+
path5.join(this.#app.cwdPath, "public", rel),
|
|
1554
1184
|
this.#resolveWorkspacePublicPath(rel)
|
|
1555
1185
|
].filter(Boolean);
|
|
1556
1186
|
for (const candidate of candidates) {
|
|
@@ -1582,7 +1212,7 @@ class FontOptimizer {
|
|
|
1582
1212
|
return "woff2";
|
|
1583
1213
|
if (signature === "OTTO")
|
|
1584
1214
|
return "otf";
|
|
1585
|
-
const ext =
|
|
1215
|
+
const ext = path5.extname(sourcePath).slice(1).toLowerCase();
|
|
1586
1216
|
if (ext === "otf" || ext === "woff" || ext === "woff2")
|
|
1587
1217
|
return ext;
|
|
1588
1218
|
return "ttf";
|
|
@@ -1591,7 +1221,7 @@ class FontOptimizer {
|
|
|
1591
1221
|
const [root, dep, ...rest] = rel.split("/");
|
|
1592
1222
|
if (root !== "libs" || !dep || rest.length === 0)
|
|
1593
1223
|
return null;
|
|
1594
|
-
return
|
|
1224
|
+
return path5.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
|
|
1595
1225
|
}
|
|
1596
1226
|
async#getSubsetText(font) {
|
|
1597
1227
|
const parts = new Set;
|
|
@@ -1601,7 +1231,7 @@ class FontOptimizer {
|
|
|
1601
1231
|
if (font.subsetText)
|
|
1602
1232
|
parts.add(font.subsetText);
|
|
1603
1233
|
for (const filePath of font.subsetFiles ?? []) {
|
|
1604
|
-
const abs =
|
|
1234
|
+
const abs = path5.isAbsolute(filePath) ? filePath : path5.join(this.#app.cwdPath, filePath);
|
|
1605
1235
|
const file = Bun.file(abs);
|
|
1606
1236
|
if (await file.exists())
|
|
1607
1237
|
parts.add(await file.text());
|
|
@@ -1621,7 +1251,7 @@ class FontOptimizer {
|
|
|
1621
1251
|
}
|
|
1622
1252
|
async#collectAutoSubsetText() {
|
|
1623
1253
|
const libPageRoots = (await this.#app.getPageRoots()).filter((root) => root.keyPrefix).map((root) => root.dir);
|
|
1624
|
-
const roots = [...["page", "ui", "lib"].map((dir) =>
|
|
1254
|
+
const roots = [...["page", "ui", "lib"].map((dir) => path5.join(this.#app.cwdPath, dir)), ...libPageRoots];
|
|
1625
1255
|
const glob = new Bun.Glob("**/*.{ts,tsx,js,jsx,html,md}");
|
|
1626
1256
|
const parts = [];
|
|
1627
1257
|
for (const root of [...new Set(roots)].sort()) {
|
|
@@ -1735,7 +1365,7 @@ ${declarations.map(([prop, value]) => ` ${prop}: ${value};`).join(`
|
|
|
1735
1365
|
|
|
1736
1366
|
// pkgs/@akanjs/devkit/frontendBuild/fontPruner.ts
|
|
1737
1367
|
import { rm as rm2, rmdir, stat as stat3 } from "fs/promises";
|
|
1738
|
-
import
|
|
1368
|
+
import path6 from "path";
|
|
1739
1369
|
import { isFontOptimizationEnabled } from "akanjs/client";
|
|
1740
1370
|
var FONT_GLOB = "**/*.{woff2,woff,ttf,otf,ttc,eot}";
|
|
1741
1371
|
var REFERRER_GLOB = "**/*.{css,js,mjs,cjs,json,html,htm,svg,xml,webmanifest}";
|
|
@@ -1748,8 +1378,8 @@ class FontPruner {
|
|
|
1748
1378
|
constructor(app, { keepFonts = [] } = {}) {
|
|
1749
1379
|
this.#app = app;
|
|
1750
1380
|
this.#keepGlobs = keepFonts;
|
|
1751
|
-
this.#publicRoot =
|
|
1752
|
-
this.#artifactRoot =
|
|
1381
|
+
this.#publicRoot = path6.join(app.dist.cwdPath, "public");
|
|
1382
|
+
this.#artifactRoot = path6.join(app.dist.cwdPath, ".akan/artifact");
|
|
1753
1383
|
}
|
|
1754
1384
|
async prune() {
|
|
1755
1385
|
const empty = { removed: [], kept: [], freedBytes: 0 };
|
|
@@ -1764,7 +1394,7 @@ class FontPruner {
|
|
|
1764
1394
|
for (const font of fonts) {
|
|
1765
1395
|
const target = isFontOptimizationEnabled(font) ? optimizedSrcs : unoptimizedSrcs;
|
|
1766
1396
|
for (const fontPath of font.paths)
|
|
1767
|
-
target.add(
|
|
1397
|
+
target.add(path6.posix.basename(fontPath.src));
|
|
1768
1398
|
}
|
|
1769
1399
|
const result = { removed: [], kept: [], freedBytes: 0 };
|
|
1770
1400
|
const undecided = [];
|
|
@@ -1802,10 +1432,10 @@ class FontPruner {
|
|
|
1802
1432
|
async#collectCandidates() {
|
|
1803
1433
|
const candidates = [];
|
|
1804
1434
|
for await (const rel of new Bun.Glob(FONT_GLOB).scan({ cwd: this.#publicRoot, dot: false })) {
|
|
1805
|
-
const entry = await stat3(
|
|
1435
|
+
const entry = await stat3(path6.join(this.#publicRoot, rel)).catch(() => null);
|
|
1806
1436
|
if (!entry?.isFile())
|
|
1807
1437
|
continue;
|
|
1808
|
-
candidates.push({ rel: rel.split(
|
|
1438
|
+
candidates.push({ rel: rel.split(path6.sep).join("/"), basename: path6.basename(rel), bytes: entry.size });
|
|
1809
1439
|
}
|
|
1810
1440
|
return candidates;
|
|
1811
1441
|
}
|
|
@@ -1815,11 +1445,11 @@ class FontPruner {
|
|
|
1815
1445
|
const bundled = candidates.filter((candidate) => !optimizedSrcs.has(candidate.basename));
|
|
1816
1446
|
const roots = [
|
|
1817
1447
|
{ dir: this.#publicRoot, label: "public", needles: authoritative },
|
|
1818
|
-
{ dir:
|
|
1819
|
-
{ dir:
|
|
1820
|
-
{ dir:
|
|
1821
|
-
{ dir:
|
|
1822
|
-
{ 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 }
|
|
1823
1453
|
];
|
|
1824
1454
|
for (const root of roots) {
|
|
1825
1455
|
const pending = root.needles.filter((candidate) => !referrers.has(candidate.rel));
|
|
@@ -1838,20 +1468,20 @@ class FontPruner {
|
|
|
1838
1468
|
const remaining = needles.filter((needle) => !referrers.has(needle.rel));
|
|
1839
1469
|
if (!remaining.length)
|
|
1840
1470
|
return;
|
|
1841
|
-
const text = await Bun.file(
|
|
1471
|
+
const text = await Bun.file(path6.join(root.dir, rel)).text().catch(() => "");
|
|
1842
1472
|
if (!text)
|
|
1843
1473
|
continue;
|
|
1844
1474
|
for (const needle of remaining) {
|
|
1845
1475
|
if (needle.forms.some((form) => text.includes(form)))
|
|
1846
|
-
referrers.set(needle.rel, `${root.label}/${rel.split(
|
|
1476
|
+
referrers.set(needle.rel, `${root.label}/${rel.split(path6.sep).join("/")}`);
|
|
1847
1477
|
}
|
|
1848
1478
|
}
|
|
1849
1479
|
}
|
|
1850
1480
|
async#remove(files) {
|
|
1851
|
-
await Promise.all(files.map((file) => rm2(
|
|
1852
|
-
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);
|
|
1853
1483
|
for (const dir of dirs)
|
|
1854
|
-
await rmdir(
|
|
1484
|
+
await rmdir(path6.join(this.#publicRoot, dir)).catch(() => {
|
|
1855
1485
|
return;
|
|
1856
1486
|
});
|
|
1857
1487
|
}
|
|
@@ -1899,7 +1529,7 @@ class FontPruner {
|
|
|
1899
1529
|
|
|
1900
1530
|
// pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
|
|
1901
1531
|
import fs from "fs";
|
|
1902
|
-
import
|
|
1532
|
+
import path7 from "path";
|
|
1903
1533
|
import zlib from "zlib";
|
|
1904
1534
|
var COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
|
|
1905
1535
|
var MIN_COMPRESS_BYTES = 1024;
|
|
@@ -1909,8 +1539,8 @@ var GZIP_LEVEL = 9;
|
|
|
1909
1539
|
var { formatBytes } = FontPruner;
|
|
1910
1540
|
async function precompressArtifacts(app) {
|
|
1911
1541
|
const roots = [
|
|
1912
|
-
|
|
1913
|
-
|
|
1542
|
+
path7.join(app.dist.cwdPath, ".akan/artifact/client"),
|
|
1543
|
+
path7.join(app.dist.cwdPath, ".akan/artifact/styles")
|
|
1914
1544
|
];
|
|
1915
1545
|
const result = { files: 0, inputBytes: 0, outputBytes: 0, brotliBytes: 0 };
|
|
1916
1546
|
await Promise.all(roots.map((root) => precompressRoot(root, result)));
|
|
@@ -1929,7 +1559,7 @@ async function precompressRoot(root, result) {
|
|
|
1929
1559
|
const bytes = await Bun.file(filePath).bytes();
|
|
1930
1560
|
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
1931
1561
|
const gz = Bun.gzipSync(buffer, { level: GZIP_LEVEL });
|
|
1932
|
-
const br = brotliCompress(buffer,
|
|
1562
|
+
const br = brotliCompress(buffer, path7.extname(filePath).toLowerCase());
|
|
1933
1563
|
await Promise.all([Bun.write(`${filePath}.gz`, gz), Bun.write(`${filePath}.br`, br)]);
|
|
1934
1564
|
result.files += 1;
|
|
1935
1565
|
result.inputBytes += bytes.byteLength;
|
|
@@ -1949,7 +1579,7 @@ function brotliCompress(buffer, ext) {
|
|
|
1949
1579
|
async function shouldPrecompress(filePath) {
|
|
1950
1580
|
if (filePath.endsWith(".gz") || filePath.endsWith(".br"))
|
|
1951
1581
|
return false;
|
|
1952
|
-
if (!COMPRESSIBLE_EXTS.has(
|
|
1582
|
+
if (!COMPRESSIBLE_EXTS.has(path7.extname(filePath).toLowerCase()))
|
|
1953
1583
|
return false;
|
|
1954
1584
|
const file = Bun.file(filePath);
|
|
1955
1585
|
if (!await file.exists())
|
|
@@ -1958,7 +1588,7 @@ async function shouldPrecompress(filePath) {
|
|
|
1958
1588
|
}
|
|
1959
1589
|
|
|
1960
1590
|
// pkgs/@akanjs/devkit/frontendBuild/ssrBaseArtifactBuilder.ts
|
|
1961
|
-
import
|
|
1591
|
+
import path8 from "path";
|
|
1962
1592
|
import { optimize } from "@tailwindcss/node";
|
|
1963
1593
|
function prepareCssAsset(command, basePath, cssText) {
|
|
1964
1594
|
return optimize(cssText, { file: `${basePath || "root"}.css`, minify: command === "build" }).code;
|
|
@@ -1974,7 +1604,7 @@ class SsrBaseArtifactBuilder {
|
|
|
1974
1604
|
this.#app = app;
|
|
1975
1605
|
this.#command = command;
|
|
1976
1606
|
this.#artifactDir = `${command === "build" ? app.dist.cwdPath : app.cwdPath}/.akan/artifact`;
|
|
1977
|
-
this.#absArtifactDir =
|
|
1607
|
+
this.#absArtifactDir = path8.resolve(this.#artifactDir);
|
|
1978
1608
|
}
|
|
1979
1609
|
static servedWeb(command, web) {
|
|
1980
1610
|
return command === "start" ? { ssr: true, csr: true } : web;
|
|
@@ -1999,7 +1629,7 @@ class SsrBaseArtifactBuilder {
|
|
|
1999
1629
|
rscRuntimeSsrManifest,
|
|
2000
1630
|
vendorMap,
|
|
2001
1631
|
cssAssets,
|
|
2002
|
-
pagesBundlePath: this.#command === "build" ?
|
|
1632
|
+
pagesBundlePath: this.#command === "build" ? path8.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
|
|
2003
1633
|
pagesBundleBuildId: pagesBundle.buildId,
|
|
2004
1634
|
domains: [...akanConfig.domains],
|
|
2005
1635
|
subRoutes: Object.fromEntries(Array.from(akanConfig.subRoutes.entries()).map(([basePath, domains]) => [basePath, [...domains]])),
|
|
@@ -2016,18 +1646,18 @@ class SsrBaseArtifactBuilder {
|
|
|
2016
1646
|
androidSha256CertFingerprints: target.deepLinks?.android?.sha256CertFingerprints
|
|
2017
1647
|
}))
|
|
2018
1648
|
};
|
|
2019
|
-
await Bun.write(
|
|
1649
|
+
await Bun.write(path8.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
|
|
2020
1650
|
`);
|
|
2021
1651
|
this.#app.verbose(`[base-artifact] complete in ${Date.now() - this.#started}ms`);
|
|
2022
1652
|
return { artifact, seedIndex, cssCompiler, optimizedFonts };
|
|
2023
1653
|
}
|
|
2024
1654
|
async#buildRuntimeClientEntries() {
|
|
2025
1655
|
const akanServerPath = await this.#resolveAkanServerPath();
|
|
2026
|
-
const rscClientEntry =
|
|
2027
|
-
const rscSegmentOutletEntry =
|
|
1656
|
+
const rscClientEntry = path8.resolve(akanServerPath, "rscClient.tsx");
|
|
1657
|
+
const rscSegmentOutletEntry = path8.resolve(akanServerPath, "rscSegmentOutlet.tsx");
|
|
2028
1658
|
const vendorEntries = VENDOR_SPECIFIERS.map((specifier) => ({
|
|
2029
1659
|
specifier,
|
|
2030
|
-
absPath:
|
|
1660
|
+
absPath: path8.resolve(akanServerPath, "vendor", `${specifier.replaceAll("/", "-").replaceAll(".", "-")}.ts`)
|
|
2031
1661
|
}));
|
|
2032
1662
|
const entries = [rscClientEntry, rscSegmentOutletEntry, ...vendorEntries.map((v) => v.absPath)];
|
|
2033
1663
|
const clientBundle = await new ClientEntriesBundler({ app: this.#app, entries, command: this.#command }).bundle();
|
|
@@ -2064,15 +1694,15 @@ class SsrBaseArtifactBuilder {
|
|
|
2064
1694
|
async#resolveAkanServerPath() {
|
|
2065
1695
|
const candidates = [];
|
|
2066
1696
|
try {
|
|
2067
|
-
candidates.push(
|
|
1697
|
+
candidates.push(path8.dirname(Bun.resolveSync("akanjs/server", this.#app.workspace.workspaceRoot)));
|
|
2068
1698
|
} catch {}
|
|
2069
|
-
candidates.push(
|
|
1699
|
+
candidates.push(path8.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server"), path8.join(this.#app.workspace.workspaceRoot, "node_modules/akanjs/server"));
|
|
2070
1700
|
try {
|
|
2071
|
-
candidates.push(
|
|
1701
|
+
candidates.push(path8.dirname(Bun.resolveSync("akanjs/server", path8.dirname(Bun.main))));
|
|
2072
1702
|
} catch {}
|
|
2073
|
-
candidates.push(
|
|
1703
|
+
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"));
|
|
2074
1704
|
for (const candidate of candidates) {
|
|
2075
|
-
if (await Bun.file(
|
|
1705
|
+
if (await Bun.file(path8.join(candidate, "rscClient.tsx")).exists())
|
|
2076
1706
|
return candidate;
|
|
2077
1707
|
}
|
|
2078
1708
|
throw new Error(`[base-artifact] failed to locate akanjs/server; looked in: ${candidates.join(", ")}`);
|
|
@@ -2101,7 +1731,7 @@ ${preparedCssText}`).toString(36);
|
|
|
2101
1731
|
`styles/${cssAssetName}-${cssHash}.css`,
|
|
2102
1732
|
`/_akan/styles/${cssAssetName}-${cssHash}.css`
|
|
2103
1733
|
];
|
|
2104
|
-
await Bun.write(
|
|
1734
|
+
await Bun.write(path8.join(this.#absArtifactDir, cssRelPath), preparedCssText);
|
|
2105
1735
|
SsrBaseArtifactBuilder.#warnDroppedImports(this.#app, cssRelPath, preparedCssText, imported);
|
|
2106
1736
|
this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath} -> ${cssRelPath}`);
|
|
2107
1737
|
return [basePath, { cssUrl, cssRelPath }];
|
|
@@ -2114,4 +1744,4 @@ ${preparedCssText}`).toString(36);
|
|
|
2114
1744
|
}
|
|
2115
1745
|
}
|
|
2116
1746
|
}
|
|
2117
|
-
export {
|
|
1747
|
+
export { AllRoutesBuilder, CsrArtifactBuilder, CssCompiler, FontOptimizer, FontPruner, precompressArtifacts, SsrBaseArtifactBuilder };
|