@akanjs/cli 3.0.0-beta.24 → 3.0.0-beta.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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-z7a4bdrq.js → application.command-afajz1m5.js} +7 -7
- package/{applicationBuildRunner-n6xfexaa.js → applicationBuildRunner-412y0345.js} +4 -4
- package/buildBatch.proc.js +77 -23
- package/{cloud.command-j0fcz44z.js → cloud.command-yz0h8a2e.js} +12 -12
- package/cloud.script-zez2b8am.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/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-zabxte74.js → index-2wc41k5w.js} +1 -1
- package/{index-khpfpcsd.js → index-48k96p55.js} +27 -7
- package/{index-2gh9sndr.js → index-50rq2qms.js} +4 -4
- package/{index-5b6tavv3.js → index-52w0fvh8.js} +15 -1
- package/{index-zbc2q6a6.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-fzd22vjh.js → index-94s080qm.js} +3 -17
- package/index-cd9qt2t6.js +4110 -0
- package/{index-ymgtbmf1.js → index-cfj73ck1.js} +213 -62
- package/{index-yy4s1j72.js → index-d65fzxvd.js} +1 -1
- package/{index-m0ms3n1c.js → index-da72pfdv.js} +7 -7
- package/{index-4dn1p115.js → index-ggfhz9ab.js} +4 -4
- package/{index-9629w7ns.js → index-hzy4pe24.js} +3 -3
- package/{index-d4mfjgm0.js → index-mq74nzf8.js} +6 -6
- 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-ne50hc2m.js → index-wvebzf0y.js} +456 -1623
- 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-gmj1k6q8.js → localRegistry.command-fm41vdtw.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 +2 -2
- 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-ksenprez.js → subspace.command-y1p56wyv.js} +6 -6
- package/{tunnel.command-54947jck.js → tunnel.command-4g95vhsn.js} +5 -5
- package/{workflow.command-z2p32nmf.js → workflow.command-b91ph8sx.js} +11 -11
- package/{workspace.command-tkn8g0bn.js → workspace.command-d8he10tv.js} +24 -24
- package/TunnelShare-pemaknmd.js +0 -9
- package/cloud.script-g2dcnr8c.js +0 -20
- package/context.runner-ehjs419v.js +0 -21
- package/index-fgq75haa.js +0 -2084
|
@@ -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-cd9qt2t6.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";
|
|
@@ -526,1524 +676,207 @@ class CssCompiler {
|
|
|
526
676
|
}
|
|
527
677
|
let source = content;
|
|
528
678
|
if (akanConfig.barrelImports.length > 0) {
|
|
529
|
-
try {
|
|
530
|
-
const rewritten = await rewriteBarrelImports(content, akanConfig.barrelImports, analyzer);
|
|
531
|
-
if (rewritten !== null)
|
|
532
|
-
source = rewritten;
|
|
533
|
-
} catch {}
|
|
534
|
-
}
|
|
535
|
-
let imports;
|
|
536
|
-
try {
|
|
537
|
-
imports = this.#transpiler.scanImports(source);
|
|
538
|
-
} catch {
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
|
-
const importerDir = path4.dirname(filePath);
|
|
542
|
-
for (const imp of imports) {
|
|
543
|
-
const spec = imp.path;
|
|
544
|
-
if (!spec)
|
|
545
|
-
continue;
|
|
546
|
-
if (spec.endsWith(".css")) {
|
|
547
|
-
const cssPath = await this.#resolveCssImport(spec, importerDir);
|
|
548
|
-
cssFiles.add(cssPath);
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
if (NON_SOURCE_EXT_RE.test(spec))
|
|
552
|
-
continue;
|
|
553
|
-
const resolved = await this.#resolveSourceImport(spec, importerDir, resolvePackage);
|
|
554
|
-
if (!resolved || sourceFiles.has(resolved) || isIgnoredNodeModuleSource(resolved))
|
|
555
|
-
continue;
|
|
556
|
-
queue.push(resolved);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
const tokenPaths = await this.#libTokenStylesheets(sourceFiles);
|
|
560
|
-
const cssPaths = [...new Set([...tokenPaths, ...cssFiles])];
|
|
561
|
-
for (const cssPath of cssPaths)
|
|
562
|
-
this.#discoveredCssPaths.add(cssPath);
|
|
563
|
-
return { cssPaths, sourcePaths: [...sourceFiles] };
|
|
564
|
-
}
|
|
565
|
-
async#libTokenStylesheets(sourceFiles) {
|
|
566
|
-
const libsRoot = path4.join(this.#app.workspace.workspaceRoot, "libs");
|
|
567
|
-
const libNames = new Set;
|
|
568
|
-
for (const filePath of sourceFiles) {
|
|
569
|
-
const relPath = path4.relative(libsRoot, filePath);
|
|
570
|
-
if (relPath.startsWith("..") || path4.isAbsolute(relPath))
|
|
571
|
-
continue;
|
|
572
|
-
const [libName] = relPath.split(path4.sep);
|
|
573
|
-
if (libName)
|
|
574
|
-
libNames.add(libName);
|
|
575
|
-
}
|
|
576
|
-
const tokenPaths = await Promise.all([...libNames].sort().map(async (libName) => {
|
|
577
|
-
const tokensPath = path4.join(libsRoot, libName, "ui/tokens.css");
|
|
578
|
-
return await this.#fileExists(tokensPath) ? tokensPath : null;
|
|
579
|
-
}));
|
|
580
|
-
return tokenPaths.filter((tokensPath) => !!tokensPath);
|
|
581
|
-
}
|
|
582
|
-
async compileCss(cssPaths, sourcePaths) {
|
|
583
|
-
const { css } = await this.#compileWithImports(cssPaths, sourcePaths);
|
|
584
|
-
return css;
|
|
585
|
-
}
|
|
586
|
-
async#compileWithImports(cssPaths, sourcePaths) {
|
|
587
|
-
if (cssPaths.length === 0)
|
|
588
|
-
return { css: "", imported: [] };
|
|
589
|
-
const compileStarted = Date.now();
|
|
590
|
-
const compilers = await Promise.all(cssPaths.map(async (cssPath) => {
|
|
591
|
-
const css = await Bun.file(cssPath).text();
|
|
592
|
-
const base = path4.dirname(cssPath);
|
|
593
|
-
const imported = new Map;
|
|
594
|
-
const compiler = await compile(css, {
|
|
595
|
-
base,
|
|
596
|
-
loadStylesheet: (id, fromBase) => this.#loadStylesheet(id, fromBase, imported),
|
|
597
|
-
loadModule: (id, fromBase) => this.#loadModule(id, fromBase)
|
|
598
|
-
});
|
|
599
|
-
return { cssPath, compiler, imported };
|
|
600
|
-
}));
|
|
601
|
-
const sourceDirs = new Set;
|
|
602
|
-
for (const entry of compilers) {
|
|
603
|
-
for (const s of entry.compiler.sources)
|
|
604
|
-
sourceDirs.add(s.base);
|
|
605
|
-
}
|
|
606
|
-
const scanStarted = Date.now();
|
|
607
|
-
const candidates = await this.#scanCandidates(sourcePaths, [...sourceDirs]);
|
|
608
|
-
this.#logger.verbose(`css candidates scanned count=${candidates.length} sources=${sourcePaths.length} dirs=${sourceDirs.size} in ${Date.now() - scanStarted}ms`);
|
|
609
|
-
const parts = [];
|
|
610
|
-
const imported = [];
|
|
611
|
-
for (const entry of compilers) {
|
|
612
|
-
const part = entry.compiler.build(candidates);
|
|
613
|
-
parts.push(part);
|
|
614
|
-
for (const [cssPath, content] of entry.imported) {
|
|
615
|
-
const declaredNames = declaredCustomProperties(content);
|
|
616
|
-
imported.push({ cssPath, declaredNames });
|
|
617
|
-
if (declaredNames.length === 0 || declaredNames.some((name) => part.includes(`${name}:`)))
|
|
618
|
-
continue;
|
|
619
|
-
this.#logger.warn(`css @import ${cssPath} was loaded by ${entry.cssPath} but none of its ${declaredNames.length} declaration(s) reached the compiled CSS`);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
this.#logger.verbose(`css compiled paths=${cssPaths.length} candidates=${candidates.length} in ${Date.now() - compileStarted}ms`);
|
|
623
|
-
return { css: parts.join(`
|
|
624
|
-
`), imported };
|
|
625
|
-
}
|
|
626
|
-
async#loadStylesheet(id, fromBase, imported) {
|
|
627
|
-
const p = await this.#resolveCssImport(id, fromBase);
|
|
628
|
-
this.#discoveredCssPaths.add(p);
|
|
629
|
-
const content = await Bun.file(p).text();
|
|
630
|
-
imported?.set(p, content);
|
|
631
|
-
this.#logger.verbose(`css import "${id}" from ${fromBase} -> ${p} (${content.length} bytes)`);
|
|
632
|
-
return { path: p, base: path4.dirname(p), content };
|
|
633
|
-
}
|
|
634
|
-
async#resolveCssImport(id, fromBase) {
|
|
635
|
-
if (id.startsWith(".") || id.startsWith("/")) {
|
|
636
|
-
const filePath = path4.resolve(fromBase, id);
|
|
637
|
-
if (await this.#fileExists(filePath))
|
|
638
|
-
return filePath;
|
|
639
|
-
throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase} (no file at ${filePath})`);
|
|
640
|
-
}
|
|
641
|
-
this.#cssImportResolver ??= await CssImportResolver.create(this.#app);
|
|
642
|
-
const resolved = await this.#cssImportResolver.resolve(id, fromBase);
|
|
643
|
-
if (resolved)
|
|
644
|
-
return resolved;
|
|
645
|
-
throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase}`);
|
|
646
|
-
}
|
|
647
|
-
async#loadModule(id, fromBase) {
|
|
648
|
-
const p = __require.resolve(id, { paths: [fromBase] });
|
|
649
|
-
const mod = await import(p);
|
|
650
|
-
return { path: p, base: path4.dirname(p), module: mod.default ?? mod };
|
|
651
|
-
}
|
|
652
|
-
#resolveSourceImport(id, fromBase, resolvePackage) {
|
|
653
|
-
const cacheKey = `${fromBase}\x00${id}`;
|
|
654
|
-
let cached = this.#resolvedSpecifierCache.get(cacheKey);
|
|
655
|
-
if (cached)
|
|
656
|
-
return cached;
|
|
657
|
-
cached = this.#resolveSourceImportUncached(id, fromBase, resolvePackage);
|
|
658
|
-
this.#resolvedSpecifierCache.set(cacheKey, cached);
|
|
659
|
-
return cached;
|
|
660
|
-
}
|
|
661
|
-
async#resolveSourceImportUncached(id, fromBase, resolvePackage) {
|
|
662
|
-
if (id.startsWith(".") || id.startsWith("/")) {
|
|
663
|
-
const abs = id.startsWith("/") ? id : path4.resolve(fromBase, id);
|
|
664
|
-
return this.#resolveSourceFileCandidate(abs);
|
|
665
|
-
}
|
|
666
|
-
const pkg = await resolvePackage(id);
|
|
667
|
-
if (pkg)
|
|
668
|
-
return pkg.entryFile;
|
|
669
|
-
for (const resolve of [() => resolveSourceWithBun(id, fromBase), () => resolveSourceWithRequire(id, fromBase)]) {
|
|
670
|
-
const resolved = await resolve();
|
|
671
|
-
if (resolved)
|
|
672
|
-
return resolved;
|
|
673
|
-
}
|
|
674
|
-
return null;
|
|
675
|
-
}
|
|
676
|
-
async#scanCandidates(sourcePaths, dirs) {
|
|
677
|
-
const candidates = new Set;
|
|
678
|
-
const glob = new Bun.Glob("**/*.{tsx,ts,jsx,js,html}");
|
|
679
|
-
const files = new Set(sourcePaths);
|
|
680
|
-
await Promise.all(dirs.map(async (dir) => {
|
|
681
|
-
for await (const file of glob.scan({ cwd: dir, absolute: true })) {
|
|
682
|
-
if (isIgnoredNodeModuleSource(file))
|
|
683
|
-
continue;
|
|
684
|
-
files.add(file);
|
|
685
|
-
}
|
|
686
|
-
}));
|
|
687
|
-
const cache = await new CssCandidateCache(this.#candidateCachePath).load();
|
|
688
|
-
await Promise.all([...files].map(async (file) => {
|
|
689
|
-
for (const candidate of await cache.candidatesFor(file))
|
|
690
|
-
candidates.add(candidate);
|
|
691
|
-
}));
|
|
692
|
-
await cache.save(files);
|
|
693
|
-
this.#logger.verbose(`css candidate cache reused=${cache.reused} rescanned=${cache.rescanned}`);
|
|
694
|
-
return [...candidates];
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
function resolveSourceWithBun(id, fromBase) {
|
|
698
|
-
try {
|
|
699
|
-
const resolved = Bun.resolveSync(id, fromBase);
|
|
700
|
-
return isSourceFile(resolved) ? resolved : null;
|
|
701
|
-
} catch {
|
|
702
|
-
return null;
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
function resolveSourceWithRequire(id, fromBase) {
|
|
706
|
-
try {
|
|
707
|
-
const resolved = __require.resolve(id, { paths: [fromBase] });
|
|
708
|
-
return isSourceFile(resolved) ? resolved : null;
|
|
709
|
-
} catch {
|
|
710
|
-
return null;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
function isSourceFile(filePath) {
|
|
714
|
-
return SOURCE_EXTS.has(path4.extname(filePath));
|
|
715
|
-
}
|
|
716
|
-
function isIgnoredNodeModuleSource(filePath) {
|
|
717
|
-
return NODE_MODULES_RE.test(filePath) && !AKANJS_NODE_MODULE_RE.test(filePath);
|
|
718
|
-
}
|
|
719
|
-
function declaredCustomProperties(css) {
|
|
720
|
-
const withoutThemeBlocks = css.replace(/@theme[^{]*\{[^}]*\}/g, "");
|
|
721
|
-
return [...new Set([...withoutThemeBlocks.matchAll(/(?:^|[\s;{])(--[\w-]+)\s*:/g)].map(([, name]) => name))].filter((name) => !!name);
|
|
722
|
-
}
|
|
723
|
-
function getPageKeyBasePath(pageKey, basePaths) {
|
|
724
|
-
const normalized = pageKey.split(path4.sep).join("/").replace(/^\.\//, "");
|
|
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
|
-
#asyncDefaults = new Map;
|
|
870
|
-
constructor(pageEntries) {
|
|
871
|
-
this.#pageEntries = pageEntries;
|
|
872
|
-
}
|
|
873
|
-
static generate(pageEntries) {
|
|
874
|
-
return new PagesEntrySourceGenerator(pageEntries).generate();
|
|
875
|
-
}
|
|
876
|
-
generate() {
|
|
877
|
-
const lines = this.#pageEntries.map(({ key, moduleAbsPath }) => {
|
|
878
|
-
const specifier = PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
|
|
879
|
-
return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(specifier)}),`;
|
|
880
|
-
});
|
|
881
|
-
return `export const pages = {
|
|
882
|
-
${lines.join(`
|
|
883
|
-
`)}
|
|
884
|
-
};
|
|
885
|
-
`;
|
|
886
|
-
}
|
|
887
|
-
static async generateStatic(pageEntries, { fromDir } = {}) {
|
|
888
|
-
return await new PagesEntrySourceGenerator(pageEntries).generateStatic({ fromDir });
|
|
889
|
-
}
|
|
890
|
-
async generateStatic({ fromDir } = {}) {
|
|
891
|
-
const imports = this.#pageEntries.map(({ moduleAbsPath }, index) => {
|
|
892
|
-
const specifier = fromDir ? PagesEntrySourceGenerator.#toRelativeSpecifier(fromDir, moduleAbsPath) : PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
|
|
893
|
-
return `import * as page${index} from ${JSON.stringify(specifier)};`;
|
|
894
|
-
});
|
|
895
|
-
const entries = await Promise.all(this.#pageEntries.map(async ({ key, moduleAbsPath }, index) => {
|
|
896
|
-
const isAsyncDefault = await this.#isAsyncDefault(moduleAbsPath);
|
|
897
|
-
return ` ${JSON.stringify(key)}: { loader: async () => page${index}, isAsyncDefault: ${isAsyncDefault} },`;
|
|
898
|
-
}));
|
|
899
|
-
return `${imports.join(`
|
|
900
|
-
`)}
|
|
901
|
-
export const pages = {
|
|
902
|
-
${entries.join(`
|
|
903
|
-
`)}
|
|
904
|
-
};
|
|
905
|
-
`;
|
|
906
|
-
}
|
|
907
|
-
async generateHotReplace({
|
|
908
|
-
fromDir,
|
|
909
|
-
ownerId,
|
|
910
|
-
moduleIds
|
|
911
|
-
}) {
|
|
912
|
-
const entries = await Promise.all(this.#pageEntries.map(async ({ key, moduleAbsPath }) => {
|
|
913
|
-
const specifier = PagesEntrySourceGenerator.#toRelativeSpecifier(fromDir, moduleAbsPath);
|
|
914
|
-
const isAsyncDefault = await this.#isAsyncDefault(moduleAbsPath);
|
|
915
|
-
return ` ${JSON.stringify(key)}: { loader: async () => require(${JSON.stringify(specifier)}), isAsyncDefault: ${isAsyncDefault} },`;
|
|
916
|
-
}));
|
|
917
|
-
return `__akan.accept(${JSON.stringify(ownerId)}, ${JSON.stringify(moduleIds)}, async () => {
|
|
918
|
-
const replaced = await akanWebkit.replacePages({
|
|
919
|
-
${entries.join(`
|
|
920
|
-
`)}
|
|
921
|
-
});
|
|
922
|
-
if (!replaced) throw new Error("the route table no longer matches the pages it was built from");
|
|
923
|
-
});
|
|
924
|
-
`;
|
|
925
|
-
}
|
|
926
|
-
#isAsyncDefault(moduleAbsPath) {
|
|
927
|
-
const known = this.#asyncDefaults.get(moduleAbsPath);
|
|
928
|
-
if (known)
|
|
929
|
-
return known;
|
|
930
|
-
const detected = AsyncDefaultExportDetector.detect(moduleAbsPath);
|
|
931
|
-
this.#asyncDefaults.set(moduleAbsPath, detected);
|
|
932
|
-
return detected;
|
|
933
|
-
}
|
|
934
|
-
static #toImportSpecifier(moduleAbsPath) {
|
|
935
|
-
return path6.resolve(moduleAbsPath).split(path6.sep).join("/");
|
|
936
|
-
}
|
|
937
|
-
static #toRelativeSpecifier(fromDir, moduleAbsPath) {
|
|
938
|
-
const relative = path6.relative(fromDir, path6.resolve(moduleAbsPath)).split(path6.sep).join("/");
|
|
939
|
-
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
|
|
944
|
-
var VIRTUAL_PAGES_ENTRY = "akan-pages-entry";
|
|
945
|
-
|
|
946
|
-
class PagesBundleBuilder {
|
|
947
|
-
#app;
|
|
948
|
-
#command;
|
|
949
|
-
#pageEntries;
|
|
950
|
-
#started = Date.now();
|
|
951
|
-
constructor(app, command = "start", pageEntries) {
|
|
952
|
-
this.#app = app;
|
|
953
|
-
this.#command = command;
|
|
954
|
-
this.#pageEntries = pageEntries;
|
|
955
|
-
}
|
|
956
|
-
async build() {
|
|
957
|
-
const akanConfig = await this.#app.getConfig();
|
|
958
|
-
const resolvedEntries = this.#pageEntries ?? await resolveSsrPageEntriesForApp(this.#app, await this.#app.getPageKeys());
|
|
959
|
-
const entrySource = PagesEntrySourceGenerator.generate(resolvedEntries);
|
|
960
|
-
const workspaceRoot = this.#app.workspace.workspaceRoot;
|
|
961
|
-
const result = await Bun.build({
|
|
962
|
-
entrypoints: [VIRTUAL_PAGES_ENTRY],
|
|
963
|
-
outdir: `${this.#artifactDir}/server`,
|
|
964
|
-
target: "bun",
|
|
965
|
-
format: "esm",
|
|
966
|
-
splitting: this.#splitting,
|
|
967
|
-
minify: this.#command === "build",
|
|
968
|
-
naming: {
|
|
969
|
-
entry: "pages-[hash].[ext]",
|
|
970
|
-
chunk: "chunks/[name]-[hash].[ext]",
|
|
971
|
-
asset: "assets/[name]-[hash].[ext]"
|
|
972
|
-
},
|
|
973
|
-
define: bundleDefine(this.#app, this.#command, "ssr"),
|
|
974
|
-
plugins: [
|
|
975
|
-
PagesBundleBuilder.createPagesEntryPlugin(entrySource),
|
|
976
|
-
PagesBundleBuilder.createCssStubPlugin(),
|
|
977
|
-
PagesBundleBuilder.createServerUseClientFetchPlugin(),
|
|
978
|
-
await createExternalizeFrameworkPlugin({ app: this.#app, extra: akanConfig.externalLibs }),
|
|
979
|
-
akanConfig.barrelImports.length > 0 ? await createBarrelImportsPlugin(this.#app, {
|
|
980
|
-
pipeAfter: (source, args) => transformUseClient(source, {
|
|
981
|
-
path: args.path,
|
|
982
|
-
workspaceRoot
|
|
983
|
-
})
|
|
984
|
-
}) : createUseClientBundlePlugin({ workspaceRoot })
|
|
985
|
-
]
|
|
986
|
-
});
|
|
987
|
-
if (!result.success)
|
|
988
|
-
throw new AggregateError(result.logs, "[PagesBundleBuilder] Bun.build failed");
|
|
989
|
-
const entryArtifact = result.outputs.find((a) => a.kind === "entry-point");
|
|
990
|
-
if (!entryArtifact)
|
|
991
|
-
throw new Error("[PagesBundleBuilder] Bun.build emitted no entry-point artifact");
|
|
992
|
-
const bundlePath = path7.resolve(entryArtifact.path);
|
|
993
|
-
const buildId = Date.now();
|
|
994
|
-
const outputBytes = result.outputs.reduce((sum, output) => sum + output.size, 0);
|
|
995
|
-
const chunkCount = result.outputs.filter((output) => output.kind === "chunk").length;
|
|
996
|
-
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`);
|
|
997
|
-
return {
|
|
998
|
-
bundlePath,
|
|
999
|
-
buildId,
|
|
1000
|
-
splitting: this.#splitting,
|
|
1001
|
-
entryBytes: entryArtifact.size,
|
|
1002
|
-
outputBytes,
|
|
1003
|
-
outputCount: result.outputs.length,
|
|
1004
|
-
chunkCount
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
get #artifactDir() {
|
|
1008
|
-
return `${this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath}/.akan/artifact`;
|
|
1009
|
-
}
|
|
1010
|
-
get #splitting() {
|
|
1011
|
-
return process.env.AKAN_SERVER_PAGES_SPLITTING === "1";
|
|
1012
|
-
}
|
|
1013
|
-
static createPagesEntryPlugin(source) {
|
|
1014
|
-
return {
|
|
1015
|
-
name: "akan-pages-entry",
|
|
1016
|
-
setup(build) {
|
|
1017
|
-
build.onResolve({ filter: /^akan-pages-entry$/ }, () => ({
|
|
1018
|
-
path: VIRTUAL_PAGES_ENTRY,
|
|
1019
|
-
namespace: "akan-virtual"
|
|
1020
|
-
}));
|
|
1021
|
-
build.onLoad({ filter: /^akan-pages-entry$/, namespace: "akan-virtual" }, () => ({
|
|
1022
|
-
contents: source,
|
|
1023
|
-
loader: "tsx"
|
|
1024
|
-
}));
|
|
1025
|
-
}
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
static createCssStubPlugin() {
|
|
1029
|
-
return {
|
|
1030
|
-
name: "akan-css-stub",
|
|
1031
|
-
setup(build) {
|
|
1032
|
-
build.onLoad({ filter: /\.css$/ }, () => ({
|
|
1033
|
-
contents: "",
|
|
1034
|
-
loader: "js"
|
|
1035
|
-
}));
|
|
1036
|
-
}
|
|
1037
|
-
};
|
|
1038
|
-
}
|
|
1039
|
-
static createServerUseClientFetchPlugin() {
|
|
1040
|
-
return {
|
|
1041
|
-
name: "akan-server-use-client-fetch",
|
|
1042
|
-
setup(build) {
|
|
1043
|
-
build.onLoad({ filter: /[/\\]lib[/\\]useClient\.(ts|tsx|js|jsx)$/ }, async (args) => {
|
|
1044
|
-
const source = await Bun.file(args.path).text();
|
|
1045
|
-
const transformed = PagesBundleBuilder.transformServerUseClientFetchSource(source);
|
|
1046
|
-
if (transformed === source)
|
|
1047
|
-
return;
|
|
1048
|
-
return { contents: transformed, loader: loaderFor(args.path) };
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
static transformServerUseClientFetchSource(source) {
|
|
1054
|
-
if (!source.includes(`with { type: "macro" }`) || !source.includes("FetchClient.build"))
|
|
1055
|
-
return source;
|
|
1056
|
-
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 });");
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
// pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
|
|
1061
|
-
import { mkdir, rm, unlink } from "fs/promises";
|
|
1062
|
-
import path8 from "path";
|
|
1063
|
-
class CsrArtifactBuilder {
|
|
1064
|
-
#app;
|
|
1065
|
-
#command;
|
|
1066
|
-
#lang;
|
|
1067
|
-
constructor(app, command = "start", lang = "en") {
|
|
1068
|
-
this.#app = app;
|
|
1069
|
-
this.#command = command;
|
|
1070
|
-
this.#lang = lang;
|
|
1071
|
-
}
|
|
1072
|
-
async build() {
|
|
1073
|
-
const pageKeys = await this.#app.getPageKeys();
|
|
1074
|
-
if (pageKeys.length === 0) {
|
|
1075
|
-
this.#app.log(`[cli] no route files under ${this.#app.cwdPath}/page \u2014 skipping CSR build`);
|
|
1076
|
-
return null;
|
|
1077
|
-
}
|
|
1078
|
-
const pageEntries = await resolveSsrPageEntriesForApp(this.#app, pageKeys);
|
|
1079
|
-
const akanConfig = await this.#app.getConfig();
|
|
1080
|
-
const cssAssets = await this.#loadCssAssets();
|
|
1081
|
-
const basePaths = [...akanConfig.basePaths];
|
|
1082
|
-
const htmlBasePaths = basePaths.length > 0 ? basePaths : [""];
|
|
1083
|
-
await rm(this.#outputDir, { recursive: true, force: true });
|
|
1084
|
-
await mkdir(this.#generatedDir, { recursive: true });
|
|
1085
|
-
const generatedFiles = Object.fromEntries((await Promise.all(htmlBasePaths.map(async (basePath) => [
|
|
1086
|
-
this.#createHtmlFile(basePath),
|
|
1087
|
-
await this.#createEntryFile(basePath, CsrArtifactBuilder.pageEntriesForBasePath(pageEntries, basePath, basePaths))
|
|
1088
|
-
]))).flat());
|
|
1089
|
-
const result = await Bun.build({
|
|
1090
|
-
target: "browser",
|
|
1091
|
-
entrypoints: htmlBasePaths.map((basePath) => this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath))),
|
|
1092
|
-
files: generatedFiles,
|
|
1093
|
-
root: this.#generatedDir,
|
|
1094
|
-
outdir: this.#outputDir,
|
|
1095
|
-
splitting: false,
|
|
1096
|
-
minify: true,
|
|
1097
|
-
env: "AKAN_PUBLIC_*",
|
|
1098
|
-
define: bundleDefine(this.#app, this.#command, "csr"),
|
|
1099
|
-
optimizeImports: akanConfig.optimizeImports,
|
|
1100
|
-
plugins: [PagesBundleBuilder.createCssStubPlugin()]
|
|
1101
|
-
});
|
|
1102
|
-
if (!result.success) {
|
|
1103
|
-
const logs = result.logs.map((log) => log.message).join(`
|
|
1104
|
-
`);
|
|
1105
|
-
throw new Error(`[csr-build] failed${logs ? `
|
|
1106
|
-
${logs}` : ""}`);
|
|
1107
|
-
}
|
|
1108
|
-
await this.#inlineCsrArtifacts(cssAssets);
|
|
1109
|
-
this.#app.verbose(`[csr-build] output -> ${this.#outputDir}`);
|
|
1110
|
-
return { outputDir: this.#outputDir };
|
|
1111
|
-
}
|
|
1112
|
-
static pageEntriesForBasePath(pageEntries, basePath, basePaths) {
|
|
1113
|
-
return pageEntries.filter((entry) => {
|
|
1114
|
-
const entryBasePath = getPageKeyBasePath(entry.key, basePaths);
|
|
1115
|
-
return entryBasePath === null || entryBasePath === basePath;
|
|
1116
|
-
});
|
|
1117
|
-
}
|
|
1118
|
-
static htmlFilename(basePath) {
|
|
1119
|
-
return `${basePath || "index"}.html`;
|
|
1120
|
-
}
|
|
1121
|
-
static entryFilename(basePath) {
|
|
1122
|
-
return `${basePath || "index"}.csr.tsx`;
|
|
1123
|
-
}
|
|
1124
|
-
static basePathOfHtml(htmlPath) {
|
|
1125
|
-
const name = path8.basename(htmlPath, ".html");
|
|
1126
|
-
return name === "index" ? "" : name;
|
|
1127
|
-
}
|
|
1128
|
-
get #outputDir() {
|
|
1129
|
-
return path8.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, this.#command === "build" ? "csr" : ".akan/artifact/csr");
|
|
1130
|
-
}
|
|
1131
|
-
get #generatedDir() {
|
|
1132
|
-
return path8.join(this.#app.cwdPath, ".akan/generated/csr");
|
|
1133
|
-
}
|
|
1134
|
-
get #artifactDir() {
|
|
1135
|
-
return path8.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact");
|
|
1136
|
-
}
|
|
1137
|
-
#generatedPath(filename) {
|
|
1138
|
-
return path8.join(this.#generatedDir, filename);
|
|
1139
|
-
}
|
|
1140
|
-
async#createEntryFile(basePath, pageEntries) {
|
|
1141
|
-
return [
|
|
1142
|
-
this.#generatedPath(CsrArtifactBuilder.entryFilename(basePath)),
|
|
1143
|
-
`import { bootCsr } from "akanjs/webkit";
|
|
1144
|
-
${await PagesEntrySourceGenerator.generateStatic(pageEntries)}
|
|
1145
|
-
void bootCsr(pages);
|
|
1146
|
-
`
|
|
1147
|
-
];
|
|
1148
|
-
}
|
|
1149
|
-
#createHtmlFile(basePath) {
|
|
1150
|
-
return [
|
|
1151
|
-
this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath)),
|
|
1152
|
-
`<!doctype html>
|
|
1153
|
-
<html lang="${this.#lang}">
|
|
1154
|
-
<head>
|
|
1155
|
-
<meta charset="utf-8" />
|
|
1156
|
-
<title>${this.#app.name}</title>
|
|
1157
|
-
<base href="/" />
|
|
1158
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1159
|
-
</head>
|
|
1160
|
-
<body>
|
|
1161
|
-
<div id="root"></div>
|
|
1162
|
-
<script type="module" src="./${CsrArtifactBuilder.entryFilename(basePath)}"></script>
|
|
1163
|
-
</body>
|
|
1164
|
-
</html>
|
|
1165
|
-
`
|
|
1166
|
-
];
|
|
1167
|
-
}
|
|
1168
|
-
async#loadCssAssets() {
|
|
1169
|
-
const artifactFile = Bun.file(path8.join(this.#artifactDir, "base-artifact.json"));
|
|
1170
|
-
if (!await artifactFile.exists())
|
|
1171
|
-
return {};
|
|
1172
|
-
const artifact = await artifactFile.json();
|
|
1173
|
-
return artifact.cssAssets ?? {};
|
|
1174
|
-
}
|
|
1175
|
-
async#inlineCsrArtifacts(cssAssets) {
|
|
1176
|
-
const jsFiles = new Set;
|
|
1177
|
-
for (const htmlPath of await this.#listOutputFiles((filePath) => filePath.endsWith(".html"))) {
|
|
1178
|
-
const htmlFile = Bun.file(htmlPath);
|
|
1179
|
-
if (!await htmlFile.exists())
|
|
1180
|
-
continue;
|
|
1181
|
-
const basePath = CsrArtifactBuilder.basePathOfHtml(htmlPath);
|
|
1182
|
-
const cssAsset = cssAssets[basePath];
|
|
1183
|
-
if (!cssAsset) {
|
|
1184
|
-
this.#app.logger.warn(`[csr-build] base-artifact.json has no compiled stylesheet for ${basePath || "root"}; ${path8.basename(htmlPath)} ships without CSS`);
|
|
1185
|
-
}
|
|
1186
|
-
const inlined = await this.#inlineHtmlAssets(await htmlFile.text(), htmlPath, cssAsset);
|
|
1187
|
-
for (const filePath of inlined.jsFiles)
|
|
1188
|
-
jsFiles.add(filePath);
|
|
1189
|
-
await Bun.write(htmlPath, inlined.html);
|
|
1190
|
-
}
|
|
1191
|
-
for (const filePath of jsFiles)
|
|
1192
|
-
await unlink(filePath).catch(() => {
|
|
1193
|
-
return;
|
|
1194
|
-
});
|
|
1195
|
-
const remainingAssets = await this.#listOutputFiles((filePath) => filePath.endsWith(".js") || filePath.endsWith(".css"));
|
|
1196
|
-
if (remainingAssets.length > 0) {
|
|
1197
|
-
throw new Error(`[csr-build] expected single-file HTML, but CSR assets remain:
|
|
1198
|
-
${remainingAssets.join(`
|
|
1199
|
-
`)}`);
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
async#inlineHtmlAssets(html, htmlPath, cssAsset) {
|
|
1203
|
-
let next = html;
|
|
1204
|
-
if (cssAsset) {
|
|
1205
|
-
const css = await Bun.file(path8.join(this.#artifactDir, cssAsset.cssRelPath)).text();
|
|
1206
|
-
next = CsrArtifactBuilder.injectBeforeHeadEnd(next, CsrArtifactBuilder.createInlineStyle(css));
|
|
1207
|
-
}
|
|
1208
|
-
const jsFiles = [];
|
|
1209
|
-
next = await CsrArtifactBuilder.replaceModuleScriptSrc(next, async (src) => {
|
|
1210
|
-
const jsPath = CsrArtifactBuilder.resolveHtmlAssetPath(htmlPath, src);
|
|
1211
|
-
jsFiles.push(jsPath);
|
|
1212
|
-
return await Bun.file(jsPath).text();
|
|
1213
|
-
});
|
|
1214
|
-
return { html: next, jsFiles };
|
|
1215
|
-
}
|
|
1216
|
-
async#listOutputFiles(predicate) {
|
|
1217
|
-
const glob = new Bun.Glob("**/*");
|
|
1218
|
-
const files = [];
|
|
1219
|
-
for await (const filePath of glob.scan({ cwd: this.#outputDir, absolute: true })) {
|
|
1220
|
-
if (predicate(filePath))
|
|
1221
|
-
files.push(filePath);
|
|
1222
|
-
}
|
|
1223
|
-
return files.sort();
|
|
1224
|
-
}
|
|
1225
|
-
static injectBeforeHeadEnd(html, snippet) {
|
|
1226
|
-
const scannable = CsrArtifactBuilder.blankEmbeddedContent(html);
|
|
1227
|
-
const headEnd = scannable.search(/<\/head\s*>/i);
|
|
1228
|
-
if (headEnd !== -1)
|
|
1229
|
-
return `${html.slice(0, headEnd)}${snippet}
|
|
1230
|
-
${html.slice(headEnd)}`;
|
|
1231
|
-
const bodyStart = scannable.search(/<body(?:\s|>)/i);
|
|
1232
|
-
if (bodyStart !== -1)
|
|
1233
|
-
return `${html.slice(0, bodyStart)}${snippet}
|
|
1234
|
-
${html.slice(bodyStart)}`;
|
|
1235
|
-
return `${html}
|
|
1236
|
-
${snippet}`;
|
|
1237
|
-
}
|
|
1238
|
-
static blankEmbeddedContent(html) {
|
|
1239
|
-
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script\s*>|<style\b[^>]*>[\s\S]*?<\/style\s*>|<!--[\s\S]*?-->/gi, (match) => " ".repeat(match.length));
|
|
1240
|
-
}
|
|
1241
|
-
static createInlineStyle(css) {
|
|
1242
|
-
return `<style data-akan-css="active">
|
|
1243
|
-
${css.replace(/<\/style/gi, "<\\/style")}
|
|
1244
|
-
</style>`;
|
|
1245
|
-
}
|
|
1246
|
-
static async replaceModuleScriptSrc(html, loadScript) {
|
|
1247
|
-
const scriptRe = /<script\b(?=[^>]*\btype=["']module["'])(?=[^>]*\bsrc=["']([^"']+)["'])[^>]*>\s*<\/script>/gi;
|
|
1248
|
-
let result = "";
|
|
1249
|
-
let lastIndex = 0;
|
|
1250
|
-
let matched = false;
|
|
1251
|
-
for (const match of html.matchAll(scriptRe)) {
|
|
1252
|
-
const full = match[0];
|
|
1253
|
-
const src = match[1];
|
|
1254
|
-
if (match.index === undefined || !src)
|
|
1255
|
-
continue;
|
|
1256
|
-
matched = true;
|
|
1257
|
-
result += html.slice(lastIndex, match.index);
|
|
1258
|
-
result += `<script type="module">
|
|
1259
|
-
${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
|
|
1260
|
-
</script>`;
|
|
1261
|
-
lastIndex = match.index + full.length;
|
|
1262
|
-
}
|
|
1263
|
-
if (!matched)
|
|
1264
|
-
return html;
|
|
1265
|
-
return result + html.slice(lastIndex);
|
|
1266
|
-
}
|
|
1267
|
-
static escapeInlineScript(source) {
|
|
1268
|
-
return source.replace(/<\/script/gi, "<\\/script");
|
|
1269
|
-
}
|
|
1270
|
-
static resolveHtmlAssetPath(htmlPath, src) {
|
|
1271
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) {
|
|
1272
|
-
throw new Error(`[csr-build] cannot inline external script: ${src}`);
|
|
1273
|
-
}
|
|
1274
|
-
const normalized = src.startsWith("/") ? src.slice(1) : src;
|
|
1275
|
-
return path8.resolve(path8.dirname(htmlPath), normalized);
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
// pkgs/@akanjs/devkit/frontendBuild/csrDevBundler.ts
|
|
1280
|
-
import fs3 from "fs";
|
|
1281
|
-
import { mkdir as mkdir3 } from "fs/promises";
|
|
1282
|
-
import path13 from "path";
|
|
1283
|
-
import { CSR_DEV_DIRNAME, CSR_DEV_MANIFEST_FILE as CSR_DEV_MANIFEST_FILE2 } from "akanjs/server/hmr/csrDevManifest";
|
|
1284
|
-
import { isAkanRuntimeMetadataFile } from "akanjs/server/hmr/runtimeMetadataFile";
|
|
1285
|
-
|
|
1286
|
-
// pkgs/@akanjs/devkit/frontendBuild/csrDevArtifactWriter.ts
|
|
1287
|
-
import { mkdir as mkdir2, readdir, rename, rm as rm2 } from "fs/promises";
|
|
1288
|
-
import path10 from "path";
|
|
1289
|
-
import {
|
|
1290
|
-
CSR_DEV_APP_FILE,
|
|
1291
|
-
CSR_DEV_MANIFEST_FILE,
|
|
1292
|
-
CSR_DEV_ROUTE_PREFIX,
|
|
1293
|
-
csrDevModuleFile
|
|
1294
|
-
} from "akanjs/server/hmr/csrDevManifest";
|
|
1295
|
-
|
|
1296
|
-
// pkgs/@akanjs/devkit/frontendBuild/csrDevPaths.ts
|
|
1297
|
-
import fs from "fs";
|
|
1298
|
-
import path9 from "path";
|
|
1299
|
-
|
|
1300
|
-
class CsrDevPaths {
|
|
1301
|
-
static modulePrefix = "akan-module:";
|
|
1302
|
-
static stubPrefix = "stub:";
|
|
1303
|
-
root;
|
|
1304
|
-
constructor(workspaceRoot) {
|
|
1305
|
-
this.root = CsrDevPaths.realpath(workspaceRoot);
|
|
1306
|
-
}
|
|
1307
|
-
idOf(file) {
|
|
1308
|
-
const real = CsrDevPaths.realpath(file);
|
|
1309
|
-
const relative = path9.relative(this.root, real);
|
|
1310
|
-
const id = relative.startsWith("..") || path9.isAbsolute(relative) ? real : relative;
|
|
1311
|
-
return id.split(path9.sep).join("/");
|
|
1312
|
-
}
|
|
1313
|
-
fileOf(id) {
|
|
1314
|
-
return path9.isAbsolute(id) ? id : path9.join(this.root, id);
|
|
1315
|
-
}
|
|
1316
|
-
static isStub(target) {
|
|
1317
|
-
return target.startsWith(CsrDevPaths.stubPrefix);
|
|
1318
|
-
}
|
|
1319
|
-
static isVendorFile(file) {
|
|
1320
|
-
return file.includes(`${path9.sep}node_modules${path9.sep}`);
|
|
1321
|
-
}
|
|
1322
|
-
static isScript(file) {
|
|
1323
|
-
return /\.[cm]?[jt]sx?$/.test(file);
|
|
1324
|
-
}
|
|
1325
|
-
static mtimeOf(file) {
|
|
1326
|
-
return fs.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? -1;
|
|
1327
|
-
}
|
|
1328
|
-
static async hashOf(file) {
|
|
1329
|
-
const source = Bun.file(file);
|
|
1330
|
-
return await source.exists() ? Bun.hash(await source.arrayBuffer()).toString(36) : "";
|
|
1331
|
-
}
|
|
1332
|
-
static realpath(file) {
|
|
1333
|
-
return fs.existsSync(file) ? fs.realpathSync(file) : path9.resolve(file);
|
|
1334
|
-
}
|
|
1335
|
-
static tryResolve(specifier, from) {
|
|
1336
|
-
try {
|
|
1337
|
-
return Bun.resolveSync(specifier, from);
|
|
1338
|
-
} catch {
|
|
1339
|
-
return null;
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
static lineCount(text) {
|
|
1343
|
-
let count = 0;
|
|
1344
|
-
for (let index = text.indexOf(`
|
|
1345
|
-
`);index !== -1; index = text.indexOf(`
|
|
1346
|
-
`, index + 1))
|
|
1347
|
-
count += 1;
|
|
1348
|
-
return count;
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
// pkgs/@akanjs/devkit/frontendBuild/csrDevArtifactWriter.ts
|
|
1353
|
-
class CsrDevArtifactWriter {
|
|
1354
|
-
static #keptPatches = 40;
|
|
1355
|
-
static #keptVendors = 3;
|
|
1356
|
-
#outDir;
|
|
1357
|
-
constructor(outDir) {
|
|
1358
|
-
this.#outDir = outDir;
|
|
1359
|
-
}
|
|
1360
|
-
async reset() {
|
|
1361
|
-
await rm2(this.#outDir, { recursive: true, force: true });
|
|
1362
|
-
await mkdir2(path10.join(this.#outDir, "modules"), { recursive: true });
|
|
1363
|
-
}
|
|
1364
|
-
async readJson(name) {
|
|
1365
|
-
const file = Bun.file(path10.join(this.#outDir, name));
|
|
1366
|
-
if (!await file.exists())
|
|
1367
|
-
return null;
|
|
1368
|
-
return await file.json().catch(() => null);
|
|
1369
|
-
}
|
|
1370
|
-
async writeJson(name, value) {
|
|
1371
|
-
await this.#writeAtomic(name, JSON.stringify(value));
|
|
1372
|
-
}
|
|
1373
|
-
async writeModules(compiled) {
|
|
1374
|
-
await mkdir2(path10.join(this.#outDir, "modules"), { recursive: true });
|
|
1375
|
-
const helpers = new Map(compiled.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
|
|
1376
|
-
await Promise.all([
|
|
1377
|
-
...compiled.flatMap((module) => [
|
|
1378
|
-
Bun.write(this.#modulePath(module.id, ".js"), module.factory),
|
|
1379
|
-
...module.sourceMap ? [Bun.write(this.#modulePath(module.id, ".js.map"), module.sourceMap)] : []
|
|
1380
|
-
]),
|
|
1381
|
-
...[...helpers.values()].map((shared) => Bun.write(this.#helpersPath(shared.hash), shared.definition))
|
|
1382
|
-
]);
|
|
1383
|
-
}
|
|
1384
|
-
async forgetModules(ids) {
|
|
1385
|
-
await Promise.all(ids.flatMap((id) => [
|
|
1386
|
-
rm2(this.#modulePath(id, ".js"), { force: true }),
|
|
1387
|
-
rm2(this.#modulePath(id, ".js.map"), { force: true })
|
|
1388
|
-
]));
|
|
1389
|
-
}
|
|
1390
|
-
async writeVendor(graph) {
|
|
1391
|
-
const ids = Object.keys(graph.modules).filter((id) => graph.modules[id]?.vendor).sort();
|
|
1392
|
-
const stubs = new Set(Object.values(graph.modules).flatMap((module) => module.deps.filter(CsrDevPaths.isStub)));
|
|
1393
|
-
const lines = [await this.#helperDefinitions(graph, ids), ...await this.#defineLines(ids)];
|
|
1394
|
-
for (const stub of [...stubs].sort()) {
|
|
1395
|
-
const message = `[akan-csr] ${stub.slice(CsrDevPaths.stubPrefix.length)} is a Node built-in the browser does not have`;
|
|
1396
|
-
lines.push(`__akan.define(${JSON.stringify(stub)}, function () {
|
|
1397
|
-
throw new Error(${JSON.stringify(message)});
|
|
1398
|
-
});
|
|
1399
|
-
`);
|
|
1400
|
-
}
|
|
1401
|
-
const code = lines.join("");
|
|
1402
|
-
const vendorFile = `vendor-${Bun.hash(code).toString(36)}.js`;
|
|
1403
|
-
await this.#writeAtomic(vendorFile, code);
|
|
1404
|
-
return vendorFile;
|
|
1405
|
-
}
|
|
1406
|
-
async writeApp(graph, generation) {
|
|
1407
|
-
const ids = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor).sort();
|
|
1408
|
-
const lines = await this.#defineLines(ids);
|
|
1409
|
-
const blocks = ids.map((id, index) => [id, lines[index] ?? ""]);
|
|
1410
|
-
const start = `__akan.start(${JSON.stringify({ generation, refresh: graph.refresh })});
|
|
1411
|
-
`;
|
|
1412
|
-
await this.#writeWithSourceMap(CSR_DEV_APP_FILE, await this.#helperDefinitions(graph, ids), blocks, start);
|
|
1413
|
-
}
|
|
1414
|
-
async writePatch(generation, modules) {
|
|
1415
|
-
const patchFile = `patch-${generation}.js`;
|
|
1416
|
-
const blocks = modules.map((module, index) => [module.id, `${JSON.stringify(module.id)}: ${module.factory}${index < modules.length - 1 ? `,
|
|
1417
|
-
` : `
|
|
1418
|
-
`}`]);
|
|
1419
|
-
const helpers = new Map(modules.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
|
|
1420
|
-
const header = `${[...helpers.values()].map((shared) => shared.definition).join("")}__akan.update(${generation}, {
|
|
1421
|
-
`;
|
|
1422
|
-
await this.#writeWithSourceMap(patchFile, header, blocks, `});
|
|
1423
|
-
`);
|
|
1424
|
-
return `${CSR_DEV_ROUTE_PREFIX}${patchFile}`;
|
|
1425
|
-
}
|
|
1426
|
-
async writeState(graph, manifest) {
|
|
1427
|
-
await this.writeJson("graph.json", graph);
|
|
1428
|
-
await this.writeJson(CSR_DEV_MANIFEST_FILE, manifest);
|
|
1429
|
-
}
|
|
1430
|
-
async prune(vendorFile, generation) {
|
|
1431
|
-
const names = await readdir(this.#outDir);
|
|
1432
|
-
const stale = names.filter((name) => {
|
|
1433
|
-
const patch = /^patch-(\d+)\.js(?:\.layout\.json)?$/.exec(name);
|
|
1434
|
-
return patch ? Number(patch[1]) <= generation - CsrDevArtifactWriter.#keptPatches : false;
|
|
1435
|
-
});
|
|
1436
|
-
const vendors = names.filter((name) => /^vendor-[\w-]+\.js$/.test(name) && name !== vendorFile).map((name) => ({ name, mtimeMs: CsrDevPaths.mtimeOf(path10.join(this.#outDir, name)) })).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(CsrDevArtifactWriter.#keptVendors - 1).map(({ name }) => name);
|
|
1437
|
-
await Promise.all([...stale, ...vendors].map((name) => rm2(path10.join(this.#outDir, name), { force: true })));
|
|
1438
|
-
}
|
|
1439
|
-
async#helperDefinitions(graph, ids) {
|
|
1440
|
-
const hashes = [...new Set(ids.flatMap((id) => graph.modules[id]?.helpers ?? []))].sort();
|
|
1441
|
-
return (await Promise.all(hashes.map((hash) => Bun.file(this.#helpersPath(hash)).text()))).join("");
|
|
1442
|
-
}
|
|
1443
|
-
async#defineLines(ids) {
|
|
1444
|
-
return await Promise.all(ids.map(async (id) => `__akan.define(${JSON.stringify(id)}, ${await Bun.file(this.#modulePath(id, ".js")).text()});
|
|
1445
|
-
`));
|
|
1446
|
-
}
|
|
1447
|
-
async#writeWithSourceMap(name, header, blocks, footer) {
|
|
1448
|
-
const layout = { lineCount: 0, modules: [] };
|
|
1449
|
-
let line = CsrDevPaths.lineCount(header);
|
|
1450
|
-
for (const [id, text] of blocks) {
|
|
1451
|
-
layout.modules.push([id, line + 1]);
|
|
1452
|
-
line += CsrDevPaths.lineCount(text);
|
|
1453
|
-
}
|
|
1454
|
-
const code = `${header}${blocks.map(([, text]) => text).join("")}${footer}//# sourceMappingURL=${name}.map
|
|
1455
|
-
`;
|
|
1456
|
-
layout.lineCount = CsrDevPaths.lineCount(code);
|
|
1457
|
-
await this.writeJson(`${name}.layout.json`, layout);
|
|
1458
|
-
await this.#writeAtomic(name, code);
|
|
1459
|
-
}
|
|
1460
|
-
async#writeAtomic(name, content) {
|
|
1461
|
-
const target = path10.join(this.#outDir, name);
|
|
1462
|
-
const temp = `${target}.${process.pid}.tmp`;
|
|
1463
|
-
await Bun.write(temp, content);
|
|
1464
|
-
await rename(temp, target);
|
|
1465
|
-
}
|
|
1466
|
-
#modulePath(id, extension) {
|
|
1467
|
-
return path10.join(this.#outDir, csrDevModuleFile(id, extension));
|
|
1468
|
-
}
|
|
1469
|
-
#helpersPath(hash) {
|
|
1470
|
-
return path10.join(this.#outDir, "modules", `helpers-${hash}.js`);
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
// pkgs/@akanjs/devkit/frontendBuild/csrDevModuleCompiler.ts
|
|
1475
|
-
import path12 from "path";
|
|
1476
|
-
import { CSR_DEV_ROUTE_PREFIX as CSR_DEV_ROUTE_PREFIX2 } from "akanjs/server/hmr/csrDevManifest";
|
|
1477
|
-
|
|
1478
|
-
// pkgs/@akanjs/devkit/frontendBuild/csrDevResolver.ts
|
|
1479
|
-
import fs2 from "fs";
|
|
1480
|
-
import path11 from "path";
|
|
1481
|
-
class CsrDevResolver {
|
|
1482
|
-
static inline = "inline";
|
|
1483
|
-
#paths;
|
|
1484
|
-
#context;
|
|
1485
|
-
#entryFiles;
|
|
1486
|
-
#resolution = new Map;
|
|
1487
|
-
#fallbacks = new Set;
|
|
1488
|
-
#prepassDone = false;
|
|
1489
|
-
constructor({ paths, context, entryFiles, resolution = {} }) {
|
|
1490
|
-
this.#paths = paths;
|
|
1491
|
-
this.#context = context;
|
|
1492
|
-
this.#entryFiles = entryFiles;
|
|
1493
|
-
for (const [importer, bySpecifier] of Object.entries(resolution))
|
|
1494
|
-
this.#resolution.set(paths.fileOf(importer), new Map(Object.entries(bySpecifier).map(([specifier, target]) => [
|
|
1495
|
-
specifier,
|
|
1496
|
-
CsrDevPaths.isStub(target) ? target : paths.fileOf(target)
|
|
1497
|
-
])));
|
|
1498
|
-
}
|
|
1499
|
-
get prepassDone() {
|
|
1500
|
-
return this.#prepassDone;
|
|
1501
|
-
}
|
|
1502
|
-
get fallbacks() {
|
|
1503
|
-
return this.#fallbacks;
|
|
1504
|
-
}
|
|
1505
|
-
async prepass() {
|
|
1506
|
-
const result = await Bun.build({
|
|
1507
|
-
entrypoints: [...this.#entryFiles, this.#context.refreshFile],
|
|
1508
|
-
target: "browser",
|
|
1509
|
-
metafile: true,
|
|
1510
|
-
env: "AKAN_PUBLIC_*",
|
|
1511
|
-
define: this.#context.define,
|
|
1512
|
-
optimizeImports: this.#context.optimizeImports,
|
|
1513
|
-
plugins: [PagesBundleBuilder.createCssStubPlugin()]
|
|
1514
|
-
});
|
|
1515
|
-
const { inputs } = result.metafile;
|
|
1516
|
-
for (const [input, { imports }] of Object.entries(inputs)) {
|
|
1517
|
-
const importer = CsrDevPaths.realpath(path11.resolve(input));
|
|
1518
|
-
for (const record of imports) {
|
|
1519
|
-
if (!record.original)
|
|
679
|
+
try {
|
|
680
|
+
const rewritten = await rewriteBarrelImports(content, akanConfig.barrelImports, analyzer);
|
|
681
|
+
if (rewritten !== null)
|
|
682
|
+
source = rewritten;
|
|
683
|
+
} catch {}
|
|
684
|
+
}
|
|
685
|
+
let imports;
|
|
686
|
+
try {
|
|
687
|
+
imports = this.#transpiler.scanImports(source);
|
|
688
|
+
} catch {
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
const importerDir = path4.dirname(filePath);
|
|
692
|
+
for (const imp of imports) {
|
|
693
|
+
const spec = imp.path;
|
|
694
|
+
if (!spec)
|
|
1520
695
|
continue;
|
|
1521
|
-
if (
|
|
1522
|
-
|
|
1523
|
-
|
|
696
|
+
if (spec.endsWith(".css")) {
|
|
697
|
+
const cssPath = await this.#resolveCssImport(spec, importerDir);
|
|
698
|
+
cssFiles.add(cssPath);
|
|
1524
699
|
continue;
|
|
1525
700
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
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);
|
|
1529
707
|
}
|
|
1530
708
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
const known = this.#resolution.get(importer)?.get(specifier);
|
|
1537
|
-
const usable = known && (CsrDevPaths.isStub(known) || fs2.existsSync(known)) ? known : null;
|
|
1538
|
-
const target = usable ?? this.#resolveUnknown(importer, specifier);
|
|
1539
|
-
if (target === null || CsrDevPaths.isStub(target))
|
|
1540
|
-
return target;
|
|
1541
|
-
if (!CsrDevPaths.isScript(target))
|
|
1542
|
-
return CsrDevResolver.inline;
|
|
1543
|
-
if (!usable)
|
|
1544
|
-
this.#remember(importer, specifier, target);
|
|
1545
|
-
return target;
|
|
1546
|
-
}
|
|
1547
|
-
isRegistryModule(file) {
|
|
1548
|
-
if (!CsrDevPaths.isScript(file))
|
|
1549
|
-
return false;
|
|
1550
|
-
const packageName = CsrDevResolver.#packageNameOf(file);
|
|
1551
|
-
if (packageName === null)
|
|
1552
|
-
return true;
|
|
1553
|
-
return !this.#context.optimizeImports.some((pattern) => {
|
|
1554
|
-
const base = CsrDevResolver.#patternBase(pattern);
|
|
1555
|
-
return base === packageName || base.startsWith(`${packageName}/`);
|
|
1556
|
-
});
|
|
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] };
|
|
1557
714
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
if (specifier.startsWith("node:"))
|
|
1569
|
-
return `${CsrDevPaths.stubPrefix}${specifier}`;
|
|
1570
|
-
const relative = specifier.startsWith(".") || path11.isAbsolute(specifier);
|
|
1571
|
-
if (!relative && !this.#prepassDone)
|
|
1572
|
-
return null;
|
|
1573
|
-
if (!relative) {
|
|
1574
|
-
const sibling = this.#resolvedBySibling(importer, specifier);
|
|
1575
|
-
if (sibling)
|
|
1576
|
-
return sibling;
|
|
1577
|
-
}
|
|
1578
|
-
const resolved = CsrDevPaths.tryResolve(specifier, path11.dirname(importer));
|
|
1579
|
-
if (!resolved)
|
|
1580
|
-
throw new Error(`[csr-dev] cannot resolve "${specifier}" from ${this.#paths.idOf(importer)}`);
|
|
1581
|
-
if (!path11.isAbsolute(resolved))
|
|
1582
|
-
return `${CsrDevPaths.stubPrefix}${specifier}`;
|
|
1583
|
-
if (!relative)
|
|
1584
|
-
this.#fallbacks.add(specifier);
|
|
1585
|
-
return CsrDevPaths.realpath(resolved);
|
|
1586
|
-
}
|
|
1587
|
-
#resolvedBySibling(importer, specifier) {
|
|
1588
|
-
const scope = CsrDevResolver.#packageScopeOf(importer);
|
|
1589
|
-
for (const [other, bySpecifier] of this.#resolution) {
|
|
1590
|
-
const target = bySpecifier.get(specifier);
|
|
1591
|
-
if (target && CsrDevResolver.#packageScopeOf(other) === scope)
|
|
1592
|
-
return target;
|
|
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);
|
|
1593
725
|
}
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
}
|
|
1600
|
-
#isInlineSpecifier(specifier) {
|
|
1601
|
-
if (specifier.startsWith(".") || path11.isAbsolute(specifier) || specifier.startsWith("node:"))
|
|
1602
|
-
return false;
|
|
1603
|
-
return this.#context.optimizeImports.some((pattern) => {
|
|
1604
|
-
const base = CsrDevResolver.#patternBase(pattern);
|
|
1605
|
-
return specifier === base || specifier.startsWith(`${base}/`);
|
|
1606
|
-
});
|
|
1607
|
-
}
|
|
1608
|
-
static #patternBase(pattern) {
|
|
1609
|
-
return pattern.endsWith("/*") ? pattern.slice(0, -2) : pattern;
|
|
1610
|
-
}
|
|
1611
|
-
static #packageNameOf(file) {
|
|
1612
|
-
const marker = `${path11.sep}node_modules${path11.sep}`;
|
|
1613
|
-
const index = file.lastIndexOf(marker);
|
|
1614
|
-
if (index < 0)
|
|
1615
|
-
return null;
|
|
1616
|
-
const [scope, name] = file.slice(index + marker.length).split(path11.sep);
|
|
1617
|
-
return (scope?.startsWith("@") ? `${scope}/${name}` : scope) || null;
|
|
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);
|
|
1618
731
|
}
|
|
1619
|
-
|
|
1620
|
-
const
|
|
1621
|
-
|
|
1622
|
-
if (index < 0)
|
|
1623
|
-
return "";
|
|
1624
|
-
const [scope, name] = file.slice(index + marker.length).split(path11.sep);
|
|
1625
|
-
return file.slice(0, index + marker.length) + (scope?.startsWith("@") ? `${scope}${path11.sep}${name}` : scope);
|
|
732
|
+
async compileCss(cssPaths, sourcePaths) {
|
|
733
|
+
const { css } = await this.#compileWithImports(cssPaths, sourcePaths);
|
|
734
|
+
return css;
|
|
1626
735
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
this.#context = context;
|
|
1647
|
-
this.#outDir = outDir;
|
|
1648
|
-
}
|
|
1649
|
-
async compile(startFiles, known) {
|
|
1650
|
-
const compiled = new Map;
|
|
1651
|
-
const seen = new Set([...known, ...startFiles]);
|
|
1652
|
-
let frontier = [...new Set(startFiles)];
|
|
1653
|
-
while (frontier.length > 0) {
|
|
1654
|
-
const round = await this.#compileRound(frontier);
|
|
1655
|
-
if (round.misses.length > 0 && !this.#resolver.prepassDone) {
|
|
1656
|
-
this.#app.verbose(`[csr-dev] ${round.misses.length} unresolved import(s); rerunning the resolution build`);
|
|
1657
|
-
await this.#resolver.prepass();
|
|
1658
|
-
continue;
|
|
1659
|
-
}
|
|
1660
|
-
const next = [];
|
|
1661
|
-
for (const module of round.modules) {
|
|
1662
|
-
compiled.set(module.file, module);
|
|
1663
|
-
for (const dep of module.deps) {
|
|
1664
|
-
if (CsrDevPaths.isStub(dep) || seen.has(dep))
|
|
1665
|
-
continue;
|
|
1666
|
-
seen.add(dep);
|
|
1667
|
-
next.push(dep);
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
frontier = next;
|
|
1671
|
-
}
|
|
1672
|
-
const { fallbacks } = this.#resolver;
|
|
1673
|
-
if (fallbacks.size > 0)
|
|
1674
|
-
this.#app.verbose(`[csr-dev] ${fallbacks.size} import(s) outside the browser build's graph resolved with Bun's runtime resolver: ${[...fallbacks].slice(0, 5).join(", ")}${fallbacks.size > 5 ? ", ..." : ""}`);
|
|
1675
|
-
return [...compiled.values()];
|
|
1676
|
-
}
|
|
1677
|
-
async#compileRound(files) {
|
|
1678
|
-
const appFiles = files.filter((file) => !CsrDevPaths.isVendorFile(file));
|
|
1679
|
-
const vendorFiles = files.filter((file) => CsrDevPaths.isVendorFile(file));
|
|
1680
|
-
const rounds = await Promise.all([
|
|
1681
|
-
appFiles.length > 0 ? this.#bunBuild(appFiles, false) : null,
|
|
1682
|
-
vendorFiles.length > 0 ? this.#bunBuild(vendorFiles, true) : null
|
|
1683
|
-
]);
|
|
1684
|
-
const modules = rounds.flatMap((round) => round?.modules ?? []);
|
|
1685
|
-
const misses = rounds.flatMap((round) => round?.misses ?? []);
|
|
1686
|
-
for (const file of rounds.flatMap((round) => round?.tangled ?? [])) {
|
|
1687
|
-
const single = await this.#bunBuild([file], CsrDevPaths.isVendorFile(file));
|
|
1688
|
-
if (single.tangled.length > 0)
|
|
1689
|
-
throw new Error(`[csr-dev] ${this.#paths.idOf(file)} kept inlining another module`);
|
|
1690
|
-
modules.push(...single.modules);
|
|
1691
|
-
misses.push(...single.misses);
|
|
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);
|
|
1692
755
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
const
|
|
1697
|
-
const
|
|
1698
|
-
const
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
publicPath: CSR_DEV_ROUTE_PREFIX2,
|
|
1708
|
-
metafile: true,
|
|
1709
|
-
env: "AKAN_PUBLIC_*",
|
|
1710
|
-
define: this.#context.define,
|
|
1711
|
-
optimizeImports: this.#context.optimizeImports,
|
|
1712
|
-
plugins: [this.#registryPlugin(vendor, misses)]
|
|
1713
|
-
});
|
|
1714
|
-
const { outputs } = result.metafile;
|
|
1715
|
-
const sourceMaps = new Map(result.outputs.filter((artifact) => artifact.kind === "sourcemap").map((artifact) => [artifact.path, artifact]));
|
|
1716
|
-
const modules = [];
|
|
1717
|
-
const tangled = [];
|
|
1718
|
-
for (const artifact of result.outputs) {
|
|
1719
|
-
if (artifact.kind === "asset") {
|
|
1720
|
-
await Bun.write(path12.join(this.#outDir, artifact.path), artifact);
|
|
1721
|
-
continue;
|
|
1722
|
-
}
|
|
1723
|
-
if (artifact.kind !== "entry-point")
|
|
1724
|
-
continue;
|
|
1725
|
-
const meta = outputs[artifact.path];
|
|
1726
|
-
if (!meta?.entryPoint)
|
|
1727
|
-
throw new Error(`[csr-dev] no entry point recorded for ${artifact.path}`);
|
|
1728
|
-
const file = CsrDevPaths.realpath(path12.resolve(meta.entryPoint));
|
|
1729
|
-
const inputs = Object.keys(meta.inputs).map((input) => CsrDevPaths.realpath(path12.resolve(input)));
|
|
1730
|
-
if (inputs.some((input) => input !== file && this.#resolver.isRegistryModule(input))) {
|
|
1731
|
-
tangled.push(file);
|
|
1732
|
-
continue;
|
|
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`);
|
|
1733
770
|
}
|
|
1734
|
-
const code = await artifact.text();
|
|
1735
|
-
const id = this.#paths.idOf(file);
|
|
1736
|
-
const sourceMap = sourceMaps.get(`${artifact.path}.map`);
|
|
1737
|
-
modules.push({
|
|
1738
|
-
id,
|
|
1739
|
-
file,
|
|
1740
|
-
vendor,
|
|
1741
|
-
...CsrDevModuleCompiler.#factory(id, code),
|
|
1742
|
-
sourceMap: sourceMap ? this.#rebaseSourceMap(await sourceMap.text()) : undefined,
|
|
1743
|
-
deps: this.#emittedDeps(code),
|
|
1744
|
-
mtimeMs: mtimes.get(file) ?? CsrDevPaths.mtimeOf(file),
|
|
1745
|
-
hash: hashes.get(file) ?? ""
|
|
1746
|
-
});
|
|
1747
771
|
}
|
|
1748
|
-
|
|
772
|
+
this.#logger.verbose(`css compiled paths=${cssPaths.length} candidates=${candidates.length} in ${Date.now() - compileStarted}ms`);
|
|
773
|
+
return { css: parts.join(`
|
|
774
|
+
`), imported };
|
|
1749
775
|
}
|
|
1750
|
-
#
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
if (CsrDevPaths.isVendorFile(args.path))
|
|
1758
|
-
return;
|
|
1759
|
-
const normalized = RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh(await Bun.file(args.path).text());
|
|
1760
|
-
return normalized ? { contents: normalized, loader: "tsx" } : undefined;
|
|
1761
|
-
});
|
|
1762
|
-
build.onResolve({ filter: /.*/ }, (args) => {
|
|
1763
|
-
if (args.kind === "entry-point-build" || !args.importer)
|
|
1764
|
-
return;
|
|
1765
|
-
const importer = CsrDevPaths.realpath(args.importer);
|
|
1766
|
-
const target = this.#resolver.resolve(importer, args.path);
|
|
1767
|
-
if (target === CsrDevResolver.inline)
|
|
1768
|
-
return;
|
|
1769
|
-
if (target === null) {
|
|
1770
|
-
misses.push(`${args.path} from ${this.#paths.idOf(importer)}`);
|
|
1771
|
-
return { path: `akan-miss:${args.path}`, external: true };
|
|
1772
|
-
}
|
|
1773
|
-
const external = CsrDevPaths.isStub(target) ? target : `${CsrDevPaths.modulePrefix}${this.#paths.idOf(target)}`;
|
|
1774
|
-
return { path: external, external: true };
|
|
1775
|
-
});
|
|
1776
|
-
}
|
|
1777
|
-
};
|
|
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 };
|
|
1778
783
|
}
|
|
1779
|
-
#
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
}
|
|
1786
|
-
#emittedDeps(code) {
|
|
1787
|
-
const deps = new Set;
|
|
1788
|
-
const references = /\b(?:require|import|__akanImport)\("((?:akan-module|stub):[^"]+)"\)/g;
|
|
1789
|
-
for (const [, reference] of code.matchAll(references)) {
|
|
1790
|
-
if (!reference)
|
|
1791
|
-
continue;
|
|
1792
|
-
deps.add(CsrDevPaths.isStub(reference) ? reference : this.#paths.fileOf(reference.slice(CsrDevPaths.modulePrefix.length)));
|
|
1793
|
-
}
|
|
1794
|
-
return [...deps];
|
|
1795
|
-
}
|
|
1796
|
-
static #factory(id, code) {
|
|
1797
|
-
const prefix = CsrDevPaths.modulePrefix;
|
|
1798
|
-
let patched = code.replaceAll(`import("${prefix}`, `__akanImport("${prefix}`).replace(/^\/\/# debugId=.*$/m, "");
|
|
1799
|
-
for (const [name, params, method] of CsrDevModuleCompiler.#helperPatches) {
|
|
1800
|
-
const definition = `var ${name} = ${params} => {`;
|
|
1801
|
-
if (patched.includes(definition))
|
|
1802
|
-
patched = patched.replace(definition, `var ${name} = __akan.${method}, __bun${name} = ${params} => {`);
|
|
1803
|
-
else if (patched.includes(`${name}(`))
|
|
1804
|
-
throw new Error(`[csr-dev] ${id}: Bun's ${name} helper no longer reads "${definition}"; CsrDevModuleCompiler must follow`);
|
|
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})`);
|
|
1805
790
|
}
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
factory: `function (require, module, exports, $RefreshReg$, $RefreshSig$, __akanImport) {
|
|
1812
|
-
${shared?.code ?? patched}
|
|
1813
|
-
}`,
|
|
1814
|
-
helpers: shared?.helpers ?? null
|
|
1815
|
-
};
|
|
1816
|
-
}
|
|
1817
|
-
static #shareHelpers(code) {
|
|
1818
|
-
const marker = code.search(/^\/\/ /m);
|
|
1819
|
-
if (marker <= 0)
|
|
1820
|
-
return null;
|
|
1821
|
-
const preamble = code.slice(0, marker);
|
|
1822
|
-
const names = [...preamble.matchAll(/^(?:var|let|const|function)\s+([\w$]+)/gm)].map(([, name]) => name).filter((name) => !!name);
|
|
1823
|
-
if (names.length === 0 || CsrDevModuleCompiler.#factoryArgument.test(preamble))
|
|
1824
|
-
return null;
|
|
1825
|
-
const hash = Bun.hash(preamble).toString(36);
|
|
1826
|
-
const binding = `var { ${names.join(", ")} } = __akan.helpers(${JSON.stringify(hash)});`;
|
|
1827
|
-
return {
|
|
1828
|
-
code: `${binding}${`
|
|
1829
|
-
`.repeat(CsrDevPaths.lineCount(preamble))}${code.slice(marker)}`,
|
|
1830
|
-
helpers: {
|
|
1831
|
-
hash,
|
|
1832
|
-
definition: `__akan.defineHelpers(${JSON.stringify(hash)}, function () {
|
|
1833
|
-
${preamble}return { ${names.join(", ")} };
|
|
1834
|
-
});
|
|
1835
|
-
`
|
|
1836
|
-
}
|
|
1837
|
-
};
|
|
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}`);
|
|
1838
796
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
static #formatVersion = 3;
|
|
1844
|
-
#app;
|
|
1845
|
-
#paths;
|
|
1846
|
-
#outDir;
|
|
1847
|
-
#entryDir;
|
|
1848
|
-
#writer;
|
|
1849
|
-
constructor(app) {
|
|
1850
|
-
this.#app = app;
|
|
1851
|
-
this.#paths = new CsrDevPaths(app.workspace.workspaceRoot);
|
|
1852
|
-
this.#outDir = path13.join(app.cwdPath, ".akan/artifact", CSR_DEV_DIRNAME);
|
|
1853
|
-
this.#entryDir = path13.join(app.cwdPath, ".akan/generated", CSR_DEV_DIRNAME);
|
|
1854
|
-
this.#writer = new CsrDevArtifactWriter(this.#outDir);
|
|
1855
|
-
}
|
|
1856
|
-
async update(changedFiles = []) {
|
|
1857
|
-
const context = await this.#context();
|
|
1858
|
-
if (context.pageEntries.length === 0)
|
|
1859
|
-
return null;
|
|
1860
|
-
const manifest = await this.#writer.readJson(CSR_DEV_MANIFEST_FILE2);
|
|
1861
|
-
const graph = await this.#writer.readJson("graph.json");
|
|
1862
|
-
const generation = (manifest?.generation ?? 0) + 1;
|
|
1863
|
-
if (!manifest || !graph || graph.version !== 1 || graph.configKey !== context.configKey)
|
|
1864
|
-
return await this.#fullBuild(context, generation, manifest ? "the dev bundle config changed" : "first build");
|
|
1865
|
-
const metadataFile = changedFiles.find(isAkanRuntimeMetadataFile);
|
|
1866
|
-
if (metadataFile)
|
|
1867
|
-
return await this.#fullBuild(context, generation, `${path13.basename(metadataFile)} changed`);
|
|
1868
|
-
return await this.#incrementalBuild(context, graph, manifest, changedFiles, generation);
|
|
1869
|
-
}
|
|
1870
|
-
async#context() {
|
|
1871
|
-
const akanConfig = await this.#app.getConfig();
|
|
1872
|
-
const pageEntries = await resolveSsrPageEntriesForApp(this.#app, await this.#app.getPageKeys());
|
|
1873
|
-
const basePaths = [...akanConfig.basePaths];
|
|
1874
|
-
const htmlBasePaths = basePaths.length > 0 ? basePaths : [""];
|
|
1875
|
-
const define = bundleDefine(this.#app, "start", "csr");
|
|
1876
|
-
const optimizeImports = [...akanConfig.optimizeImports];
|
|
1877
|
-
const configKey = Bun.hash(JSON.stringify([CsrDevBundler.#formatVersion, Bun.version, define, optimizeImports, htmlBasePaths])).toString(36);
|
|
1878
|
-
return {
|
|
1879
|
-
pageEntries,
|
|
1880
|
-
basePaths,
|
|
1881
|
-
htmlBasePaths,
|
|
1882
|
-
define,
|
|
1883
|
-
optimizeImports,
|
|
1884
|
-
configKey,
|
|
1885
|
-
refreshFile: this.#resolveRefreshRuntime()
|
|
1886
|
-
};
|
|
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 };
|
|
1887
801
|
}
|
|
1888
|
-
#
|
|
1889
|
-
const
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
const resolved = CsrDevPaths.tryResolve("react-refresh/runtime", base);
|
|
1897
|
-
if (resolved)
|
|
1898
|
-
return CsrDevPaths.realpath(resolved);
|
|
1899
|
-
}
|
|
1900
|
-
throw new Error(`[csr-dev] react-refresh/runtime is not resolvable from ${bases.join(", ")}`);
|
|
1901
|
-
}
|
|
1902
|
-
async#fullBuild(context, generation, reason) {
|
|
1903
|
-
await this.#writer.reset();
|
|
1904
|
-
const entries = await this.#writeEntries(context);
|
|
1905
|
-
const entryFiles = Object.values(entries.files);
|
|
1906
|
-
const resolver = new CsrDevResolver({ paths: this.#paths, context, entryFiles });
|
|
1907
|
-
await resolver.prepass();
|
|
1908
|
-
const compiled = await this.#compiler(context, resolver).compile([...entryFiles, context.refreshFile], new Set);
|
|
1909
|
-
const graph = {
|
|
1910
|
-
version: 1,
|
|
1911
|
-
configKey: context.configKey,
|
|
1912
|
-
refresh: this.#paths.idOf(context.refreshFile),
|
|
1913
|
-
entries: this.#entryIds(entries.files),
|
|
1914
|
-
modules: {},
|
|
1915
|
-
resolution: {},
|
|
1916
|
-
pending: []
|
|
1917
|
-
};
|
|
1918
|
-
this.#merge(graph, resolver, compiled);
|
|
1919
|
-
await this.#writer.writeModules(compiled);
|
|
1920
|
-
const vendorFile = await this.#writer.writeVendor(graph);
|
|
1921
|
-
await this.#writer.writeApp(graph, generation);
|
|
1922
|
-
await this.#writer.writeState(graph, { version: 1, generation, vendorFile, entries: graph.entries });
|
|
1923
|
-
await this.#writer.prune(vendorFile, generation);
|
|
1924
|
-
return { generation, reload: true, reason, changedIds: [], moduleCount: Object.keys(graph.modules).length };
|
|
1925
|
-
}
|
|
1926
|
-
async#incrementalBuild(context, graph, manifest, changedFiles, generation) {
|
|
1927
|
-
const entries = await this.#writeEntries(context);
|
|
1928
|
-
const entryIds = this.#entryIds(entries.files);
|
|
1929
|
-
await this.#forgetDeletedModules(graph);
|
|
1930
|
-
const changed = await this.#changedModules(graph, changedFiles);
|
|
1931
|
-
for (const file of entries.changed)
|
|
1932
|
-
changed.add(this.#paths.idOf(file));
|
|
1933
|
-
const routesMoved = entries.changed.length > 0 || JSON.stringify(entryIds) !== JSON.stringify(graph.entries);
|
|
1934
|
-
if (changed.size === 0)
|
|
1935
|
-
return null;
|
|
1936
|
-
const resolver = new CsrDevResolver({
|
|
1937
|
-
paths: this.#paths,
|
|
1938
|
-
context,
|
|
1939
|
-
entryFiles: Object.values(entries.files),
|
|
1940
|
-
resolution: graph.resolution
|
|
1941
|
-
});
|
|
1942
|
-
const known = new Set(Object.keys(graph.modules).map((id) => this.#paths.fileOf(id)));
|
|
1943
|
-
let compiled;
|
|
1944
|
-
try {
|
|
1945
|
-
compiled = await this.#compiler(context, resolver).compile([...changed].map((id) => this.#paths.fileOf(id)), known);
|
|
1946
|
-
} catch (error) {
|
|
1947
|
-
graph.pending = [...changed];
|
|
1948
|
-
await this.#writer.writeJson("graph.json", graph);
|
|
1949
|
-
throw error;
|
|
1950
|
-
}
|
|
1951
|
-
const vendorJoined = compiled.some((module) => module.vendor && !graph.modules[module.id]);
|
|
1952
|
-
this.#merge(graph, resolver, compiled);
|
|
1953
|
-
graph.entries = entryIds;
|
|
1954
|
-
await this.#writer.writeModules(compiled);
|
|
1955
|
-
const vendorFile = vendorJoined ? await this.#writer.writeVendor(graph) : manifest.vendorFile;
|
|
1956
|
-
await this.#writer.writeApp(graph, generation);
|
|
1957
|
-
const changedIds = compiled.filter((module) => !module.vendor).map((module) => module.id);
|
|
1958
|
-
const constantId = changedIds.find((id) => id.endsWith(".constant.ts"));
|
|
1959
|
-
const reason = vendorJoined ? "an npm module joined the graph" : routesMoved ? "the route table changed" : constantId ? `${path13.basename(constantId)} changed` : undefined;
|
|
1960
|
-
const patchUrl = reason ? undefined : await this.#writer.writePatch(generation, compiled.filter((module) => !module.vendor));
|
|
1961
|
-
await this.#writer.writeState(graph, { ...manifest, generation, vendorFile, entries: entryIds });
|
|
1962
|
-
await this.#writer.prune(vendorFile, generation);
|
|
1963
|
-
return {
|
|
1964
|
-
generation,
|
|
1965
|
-
reload: !!reason,
|
|
1966
|
-
reason,
|
|
1967
|
-
patchUrl,
|
|
1968
|
-
changedIds,
|
|
1969
|
-
moduleCount: Object.keys(graph.modules).length
|
|
1970
|
-
};
|
|
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;
|
|
1971
810
|
}
|
|
1972
|
-
async#
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
if (graph.modules[id])
|
|
1977
|
-
changed.add(id);
|
|
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);
|
|
1978
815
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
changed.add(id);
|
|
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;
|
|
1987
823
|
}
|
|
1988
|
-
return
|
|
824
|
+
return null;
|
|
1989
825
|
}
|
|
1990
|
-
async#
|
|
1991
|
-
const
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
await
|
|
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];
|
|
1995
845
|
}
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
const
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
const generator = new PagesEntrySourceGenerator(entryPages);
|
|
2004
|
-
const pages = await generator.generateStatic({ fromDir: this.#entryDir });
|
|
2005
|
-
const hot = await generator.generateHotReplace({
|
|
2006
|
-
fromDir: this.#entryDir,
|
|
2007
|
-
ownerId: this.#paths.idOf(file),
|
|
2008
|
-
moduleIds: entryPages.map(({ moduleAbsPath }) => this.#paths.idOf(moduleAbsPath))
|
|
2009
|
-
});
|
|
2010
|
-
const source = `import * as akanWebkit from "akanjs/webkit";
|
|
2011
|
-
${pages}
|
|
2012
|
-
void akanWebkit.bootCsr(pages);
|
|
2013
|
-
${hot}`;
|
|
2014
|
-
const unchanged = await Bun.file(file).exists() && await Bun.file(file).text() === source;
|
|
2015
|
-
if (!unchanged)
|
|
2016
|
-
await Bun.write(file, source);
|
|
2017
|
-
const entryFile = CsrDevPaths.realpath(file);
|
|
2018
|
-
files[basePath] = entryFile;
|
|
2019
|
-
if (!unchanged)
|
|
2020
|
-
changed.push(entryFile);
|
|
2021
|
-
}
|
|
2022
|
-
return { files, changed };
|
|
2023
|
-
}
|
|
2024
|
-
#compiler(context, resolver) {
|
|
2025
|
-
return new CsrDevModuleCompiler({ app: this.#app, paths: this.#paths, resolver, context, outDir: this.#outDir });
|
|
2026
|
-
}
|
|
2027
|
-
#merge(graph, resolver, compiled) {
|
|
2028
|
-
for (const module of compiled)
|
|
2029
|
-
graph.modules[module.id] = {
|
|
2030
|
-
vendor: module.vendor,
|
|
2031
|
-
mtimeMs: module.mtimeMs,
|
|
2032
|
-
hash: module.hash,
|
|
2033
|
-
deps: module.deps.map((dep) => CsrDevPaths.isStub(dep) ? dep : this.#paths.idOf(dep)),
|
|
2034
|
-
...module.helpers ? { helpers: module.helpers.hash } : {}
|
|
2035
|
-
};
|
|
2036
|
-
graph.pending = [];
|
|
2037
|
-
graph.resolution = resolver.serialize();
|
|
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;
|
|
2038
853
|
}
|
|
2039
|
-
|
|
2040
|
-
|
|
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;
|
|
2041
861
|
}
|
|
2042
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
|
+
}
|
|
2043
876
|
|
|
2044
877
|
// pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
|
|
2045
|
-
import { mkdir as
|
|
2046
|
-
import
|
|
878
|
+
import { mkdir as mkdir2, stat as stat2 } from "fs/promises";
|
|
879
|
+
import path5 from "path";
|
|
2047
880
|
import ts from "typescript";
|
|
2048
881
|
var FONT_URL_PREFIX = "/_akan/fonts";
|
|
2049
882
|
var DEFAULT_FONT_SUBSETS = ["latin"];
|
|
@@ -2060,7 +893,7 @@ class FontOptimizer {
|
|
|
2060
893
|
constructor(app, command = "start") {
|
|
2061
894
|
this.#app = app;
|
|
2062
895
|
this.#command = command;
|
|
2063
|
-
this.#artifactRoot =
|
|
896
|
+
this.#artifactRoot = path5.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
|
|
2064
897
|
}
|
|
2065
898
|
async optimize() {
|
|
2066
899
|
const fonts = await this.discoverFonts();
|
|
@@ -2085,7 +918,7 @@ class FontOptimizer {
|
|
|
2085
918
|
return result;
|
|
2086
919
|
}
|
|
2087
920
|
get #cachePath() {
|
|
2088
|
-
return
|
|
921
|
+
return path5.join(this.#artifactRoot, "fontCache.json");
|
|
2089
922
|
}
|
|
2090
923
|
async#buildCacheKey(fonts) {
|
|
2091
924
|
const sources = [];
|
|
@@ -2100,7 +933,7 @@ class FontOptimizer {
|
|
|
2100
933
|
sources.push({ optimizedSrc: face.optimizedSrc, ...stamp });
|
|
2101
934
|
}
|
|
2102
935
|
for (const filePath of font.subsetFiles ?? []) {
|
|
2103
|
-
const abs =
|
|
936
|
+
const abs = path5.isAbsolute(filePath) ? filePath : path5.join(this.#app.cwdPath, filePath);
|
|
2104
937
|
const stamp = await this.#fileStamp(abs);
|
|
2105
938
|
if (!stamp)
|
|
2106
939
|
return null;
|
|
@@ -2126,7 +959,7 @@ class FontOptimizer {
|
|
|
2126
959
|
const cache = await Bun.file(this.#cachePath).json().catch(() => null);
|
|
2127
960
|
if (cache?.version !== FontOptimizer.#cacheVersion || cache.key !== key)
|
|
2128
961
|
return null;
|
|
2129
|
-
const files = cache.files.map((relativePath) =>
|
|
962
|
+
const files = cache.files.map((relativePath) => path5.join(this.#artifactRoot, relativePath));
|
|
2130
963
|
for (const filePath of files) {
|
|
2131
964
|
if (!await Bun.file(filePath).exists())
|
|
2132
965
|
return null;
|
|
@@ -2138,7 +971,7 @@ class FontOptimizer {
|
|
|
2138
971
|
version: FontOptimizer.#cacheVersion,
|
|
2139
972
|
key,
|
|
2140
973
|
css: result.css,
|
|
2141
|
-
files: result.files.map((filePath) =>
|
|
974
|
+
files: result.files.map((filePath) => path5.relative(this.#artifactRoot, filePath))
|
|
2142
975
|
};
|
|
2143
976
|
await Bun.write(this.#cachePath, JSON.stringify(cache));
|
|
2144
977
|
}
|
|
@@ -2146,7 +979,7 @@ class FontOptimizer {
|
|
|
2146
979
|
const pageKeys = await this.#app.getPageKeys();
|
|
2147
980
|
const fonts = [];
|
|
2148
981
|
await Promise.all(pageKeys.map(async (key) => {
|
|
2149
|
-
const filePath =
|
|
982
|
+
const filePath = path5.resolve(this.#app.cwdPath, "page", key);
|
|
2150
983
|
const file = Bun.file(filePath);
|
|
2151
984
|
if (!await file.exists())
|
|
2152
985
|
return;
|
|
@@ -2165,8 +998,8 @@ class FontOptimizer {
|
|
|
2165
998
|
this.#app.logger.warn(`[font] source not found: ${face.src}`);
|
|
2166
999
|
continue;
|
|
2167
1000
|
}
|
|
2168
|
-
const outputPath =
|
|
2169
|
-
await
|
|
1001
|
+
const outputPath = path5.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
|
|
1002
|
+
await mkdir2(path5.dirname(outputPath), { recursive: true });
|
|
2170
1003
|
const sourceBuffer = Buffer.from(await Bun.file(sourcePath).arrayBuffer());
|
|
2171
1004
|
const outputBuffer = await this.#buildFontBuffer(font, sourceBuffer, sourcePath);
|
|
2172
1005
|
await Bun.write(outputPath, outputBuffer);
|
|
@@ -2188,7 +1021,7 @@ class FontOptimizer {
|
|
|
2188
1021
|
const entries = Array.isArray(value) ? value : [];
|
|
2189
1022
|
const fonts = entries.filter((entry) => this.#isReadableFont(entry));
|
|
2190
1023
|
if (fonts.length !== entries.length || !Array.isArray(value))
|
|
2191
|
-
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`);
|
|
2192
1025
|
return fonts.map((font) => ({ ...font, subsets: font.subsets ?? [...DEFAULT_FONT_SUBSETS] }));
|
|
2193
1026
|
}
|
|
2194
1027
|
#isReadableFont(value) {
|
|
@@ -2346,8 +1179,8 @@ class FontOptimizer {
|
|
|
2346
1179
|
return null;
|
|
2347
1180
|
const rel = src.replace(/^\//, "");
|
|
2348
1181
|
const candidates = [
|
|
2349
|
-
this.#command === "build" ?
|
|
2350
|
-
|
|
1182
|
+
this.#command === "build" ? path5.join(this.#app.dist.cwdPath, "public", rel) : null,
|
|
1183
|
+
path5.join(this.#app.cwdPath, "public", rel),
|
|
2351
1184
|
this.#resolveWorkspacePublicPath(rel)
|
|
2352
1185
|
].filter(Boolean);
|
|
2353
1186
|
for (const candidate of candidates) {
|
|
@@ -2379,7 +1212,7 @@ class FontOptimizer {
|
|
|
2379
1212
|
return "woff2";
|
|
2380
1213
|
if (signature === "OTTO")
|
|
2381
1214
|
return "otf";
|
|
2382
|
-
const ext =
|
|
1215
|
+
const ext = path5.extname(sourcePath).slice(1).toLowerCase();
|
|
2383
1216
|
if (ext === "otf" || ext === "woff" || ext === "woff2")
|
|
2384
1217
|
return ext;
|
|
2385
1218
|
return "ttf";
|
|
@@ -2388,7 +1221,7 @@ class FontOptimizer {
|
|
|
2388
1221
|
const [root, dep, ...rest] = rel.split("/");
|
|
2389
1222
|
if (root !== "libs" || !dep || rest.length === 0)
|
|
2390
1223
|
return null;
|
|
2391
|
-
return
|
|
1224
|
+
return path5.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
|
|
2392
1225
|
}
|
|
2393
1226
|
async#getSubsetText(font) {
|
|
2394
1227
|
const parts = new Set;
|
|
@@ -2398,7 +1231,7 @@ class FontOptimizer {
|
|
|
2398
1231
|
if (font.subsetText)
|
|
2399
1232
|
parts.add(font.subsetText);
|
|
2400
1233
|
for (const filePath of font.subsetFiles ?? []) {
|
|
2401
|
-
const abs =
|
|
1234
|
+
const abs = path5.isAbsolute(filePath) ? filePath : path5.join(this.#app.cwdPath, filePath);
|
|
2402
1235
|
const file = Bun.file(abs);
|
|
2403
1236
|
if (await file.exists())
|
|
2404
1237
|
parts.add(await file.text());
|
|
@@ -2418,7 +1251,7 @@ class FontOptimizer {
|
|
|
2418
1251
|
}
|
|
2419
1252
|
async#collectAutoSubsetText() {
|
|
2420
1253
|
const libPageRoots = (await this.#app.getPageRoots()).filter((root) => root.keyPrefix).map((root) => root.dir);
|
|
2421
|
-
const roots = [...["page", "ui", "lib"].map((dir) =>
|
|
1254
|
+
const roots = [...["page", "ui", "lib"].map((dir) => path5.join(this.#app.cwdPath, dir)), ...libPageRoots];
|
|
2422
1255
|
const glob = new Bun.Glob("**/*.{ts,tsx,js,jsx,html,md}");
|
|
2423
1256
|
const parts = [];
|
|
2424
1257
|
for (const root of [...new Set(roots)].sort()) {
|
|
@@ -2531,8 +1364,8 @@ ${declarations.map(([prop, value]) => ` ${prop}: ${value};`).join(`
|
|
|
2531
1364
|
}
|
|
2532
1365
|
|
|
2533
1366
|
// pkgs/@akanjs/devkit/frontendBuild/fontPruner.ts
|
|
2534
|
-
import { rm as
|
|
2535
|
-
import
|
|
1367
|
+
import { rm as rm2, rmdir, stat as stat3 } from "fs/promises";
|
|
1368
|
+
import path6 from "path";
|
|
2536
1369
|
import { isFontOptimizationEnabled } from "akanjs/client";
|
|
2537
1370
|
var FONT_GLOB = "**/*.{woff2,woff,ttf,otf,ttc,eot}";
|
|
2538
1371
|
var REFERRER_GLOB = "**/*.{css,js,mjs,cjs,json,html,htm,svg,xml,webmanifest}";
|
|
@@ -2545,8 +1378,8 @@ class FontPruner {
|
|
|
2545
1378
|
constructor(app, { keepFonts = [] } = {}) {
|
|
2546
1379
|
this.#app = app;
|
|
2547
1380
|
this.#keepGlobs = keepFonts;
|
|
2548
|
-
this.#publicRoot =
|
|
2549
|
-
this.#artifactRoot =
|
|
1381
|
+
this.#publicRoot = path6.join(app.dist.cwdPath, "public");
|
|
1382
|
+
this.#artifactRoot = path6.join(app.dist.cwdPath, ".akan/artifact");
|
|
2550
1383
|
}
|
|
2551
1384
|
async prune() {
|
|
2552
1385
|
const empty = { removed: [], kept: [], freedBytes: 0 };
|
|
@@ -2561,7 +1394,7 @@ class FontPruner {
|
|
|
2561
1394
|
for (const font of fonts) {
|
|
2562
1395
|
const target = isFontOptimizationEnabled(font) ? optimizedSrcs : unoptimizedSrcs;
|
|
2563
1396
|
for (const fontPath of font.paths)
|
|
2564
|
-
target.add(
|
|
1397
|
+
target.add(path6.posix.basename(fontPath.src));
|
|
2565
1398
|
}
|
|
2566
1399
|
const result = { removed: [], kept: [], freedBytes: 0 };
|
|
2567
1400
|
const undecided = [];
|
|
@@ -2599,10 +1432,10 @@ class FontPruner {
|
|
|
2599
1432
|
async#collectCandidates() {
|
|
2600
1433
|
const candidates = [];
|
|
2601
1434
|
for await (const rel of new Bun.Glob(FONT_GLOB).scan({ cwd: this.#publicRoot, dot: false })) {
|
|
2602
|
-
const entry = await stat3(
|
|
1435
|
+
const entry = await stat3(path6.join(this.#publicRoot, rel)).catch(() => null);
|
|
2603
1436
|
if (!entry?.isFile())
|
|
2604
1437
|
continue;
|
|
2605
|
-
candidates.push({ rel: rel.split(
|
|
1438
|
+
candidates.push({ rel: rel.split(path6.sep).join("/"), basename: path6.basename(rel), bytes: entry.size });
|
|
2606
1439
|
}
|
|
2607
1440
|
return candidates;
|
|
2608
1441
|
}
|
|
@@ -2612,11 +1445,11 @@ class FontPruner {
|
|
|
2612
1445
|
const bundled = candidates.filter((candidate) => !optimizedSrcs.has(candidate.basename));
|
|
2613
1446
|
const roots = [
|
|
2614
1447
|
{ dir: this.#publicRoot, label: "public", needles: authoritative },
|
|
2615
|
-
{ dir:
|
|
2616
|
-
{ dir:
|
|
2617
|
-
{ dir:
|
|
2618
|
-
{ dir:
|
|
2619
|
-
{ 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 }
|
|
2620
1453
|
];
|
|
2621
1454
|
for (const root of roots) {
|
|
2622
1455
|
const pending = root.needles.filter((candidate) => !referrers.has(candidate.rel));
|
|
@@ -2635,20 +1468,20 @@ class FontPruner {
|
|
|
2635
1468
|
const remaining = needles.filter((needle) => !referrers.has(needle.rel));
|
|
2636
1469
|
if (!remaining.length)
|
|
2637
1470
|
return;
|
|
2638
|
-
const text = await Bun.file(
|
|
1471
|
+
const text = await Bun.file(path6.join(root.dir, rel)).text().catch(() => "");
|
|
2639
1472
|
if (!text)
|
|
2640
1473
|
continue;
|
|
2641
1474
|
for (const needle of remaining) {
|
|
2642
1475
|
if (needle.forms.some((form) => text.includes(form)))
|
|
2643
|
-
referrers.set(needle.rel, `${root.label}/${rel.split(
|
|
1476
|
+
referrers.set(needle.rel, `${root.label}/${rel.split(path6.sep).join("/")}`);
|
|
2644
1477
|
}
|
|
2645
1478
|
}
|
|
2646
1479
|
}
|
|
2647
1480
|
async#remove(files) {
|
|
2648
|
-
await Promise.all(files.map((file) =>
|
|
2649
|
-
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);
|
|
2650
1483
|
for (const dir of dirs)
|
|
2651
|
-
await rmdir(
|
|
1484
|
+
await rmdir(path6.join(this.#publicRoot, dir)).catch(() => {
|
|
2652
1485
|
return;
|
|
2653
1486
|
});
|
|
2654
1487
|
}
|
|
@@ -2695,8 +1528,8 @@ class FontPruner {
|
|
|
2695
1528
|
}
|
|
2696
1529
|
|
|
2697
1530
|
// pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
|
|
2698
|
-
import
|
|
2699
|
-
import
|
|
1531
|
+
import fs from "fs";
|
|
1532
|
+
import path7 from "path";
|
|
2700
1533
|
import zlib from "zlib";
|
|
2701
1534
|
var COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
|
|
2702
1535
|
var MIN_COMPRESS_BYTES = 1024;
|
|
@@ -2706,8 +1539,8 @@ var GZIP_LEVEL = 9;
|
|
|
2706
1539
|
var { formatBytes } = FontPruner;
|
|
2707
1540
|
async function precompressArtifacts(app) {
|
|
2708
1541
|
const roots = [
|
|
2709
|
-
|
|
2710
|
-
|
|
1542
|
+
path7.join(app.dist.cwdPath, ".akan/artifact/client"),
|
|
1543
|
+
path7.join(app.dist.cwdPath, ".akan/artifact/styles")
|
|
2711
1544
|
];
|
|
2712
1545
|
const result = { files: 0, inputBytes: 0, outputBytes: 0, brotliBytes: 0 };
|
|
2713
1546
|
await Promise.all(roots.map((root) => precompressRoot(root, result)));
|
|
@@ -2717,7 +1550,7 @@ async function precompressArtifacts(app) {
|
|
|
2717
1550
|
return result;
|
|
2718
1551
|
}
|
|
2719
1552
|
async function precompressRoot(root, result) {
|
|
2720
|
-
if (!
|
|
1553
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory())
|
|
2721
1554
|
return;
|
|
2722
1555
|
const glob = new Bun.Glob("**/*");
|
|
2723
1556
|
for await (const filePath of glob.scan({ cwd: root, absolute: true })) {
|
|
@@ -2726,7 +1559,7 @@ async function precompressRoot(root, result) {
|
|
|
2726
1559
|
const bytes = await Bun.file(filePath).bytes();
|
|
2727
1560
|
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
2728
1561
|
const gz = Bun.gzipSync(buffer, { level: GZIP_LEVEL });
|
|
2729
|
-
const br = brotliCompress(buffer,
|
|
1562
|
+
const br = brotliCompress(buffer, path7.extname(filePath).toLowerCase());
|
|
2730
1563
|
await Promise.all([Bun.write(`${filePath}.gz`, gz), Bun.write(`${filePath}.br`, br)]);
|
|
2731
1564
|
result.files += 1;
|
|
2732
1565
|
result.inputBytes += bytes.byteLength;
|
|
@@ -2746,7 +1579,7 @@ function brotliCompress(buffer, ext) {
|
|
|
2746
1579
|
async function shouldPrecompress(filePath) {
|
|
2747
1580
|
if (filePath.endsWith(".gz") || filePath.endsWith(".br"))
|
|
2748
1581
|
return false;
|
|
2749
|
-
if (!COMPRESSIBLE_EXTS.has(
|
|
1582
|
+
if (!COMPRESSIBLE_EXTS.has(path7.extname(filePath).toLowerCase()))
|
|
2750
1583
|
return false;
|
|
2751
1584
|
const file = Bun.file(filePath);
|
|
2752
1585
|
if (!await file.exists())
|
|
@@ -2755,7 +1588,7 @@ async function shouldPrecompress(filePath) {
|
|
|
2755
1588
|
}
|
|
2756
1589
|
|
|
2757
1590
|
// pkgs/@akanjs/devkit/frontendBuild/ssrBaseArtifactBuilder.ts
|
|
2758
|
-
import
|
|
1591
|
+
import path8 from "path";
|
|
2759
1592
|
import { optimize } from "@tailwindcss/node";
|
|
2760
1593
|
function prepareCssAsset(command, basePath, cssText) {
|
|
2761
1594
|
return optimize(cssText, { file: `${basePath || "root"}.css`, minify: command === "build" }).code;
|
|
@@ -2771,7 +1604,7 @@ class SsrBaseArtifactBuilder {
|
|
|
2771
1604
|
this.#app = app;
|
|
2772
1605
|
this.#command = command;
|
|
2773
1606
|
this.#artifactDir = `${command === "build" ? app.dist.cwdPath : app.cwdPath}/.akan/artifact`;
|
|
2774
|
-
this.#absArtifactDir =
|
|
1607
|
+
this.#absArtifactDir = path8.resolve(this.#artifactDir);
|
|
2775
1608
|
}
|
|
2776
1609
|
static servedWeb(command, web) {
|
|
2777
1610
|
return command === "start" ? { ssr: true, csr: true } : web;
|
|
@@ -2796,7 +1629,7 @@ class SsrBaseArtifactBuilder {
|
|
|
2796
1629
|
rscRuntimeSsrManifest,
|
|
2797
1630
|
vendorMap,
|
|
2798
1631
|
cssAssets,
|
|
2799
|
-
pagesBundlePath: this.#command === "build" ?
|
|
1632
|
+
pagesBundlePath: this.#command === "build" ? path8.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
|
|
2800
1633
|
pagesBundleBuildId: pagesBundle.buildId,
|
|
2801
1634
|
domains: [...akanConfig.domains],
|
|
2802
1635
|
subRoutes: Object.fromEntries(Array.from(akanConfig.subRoutes.entries()).map(([basePath, domains]) => [basePath, [...domains]])),
|
|
@@ -2813,18 +1646,18 @@ class SsrBaseArtifactBuilder {
|
|
|
2813
1646
|
androidSha256CertFingerprints: target.deepLinks?.android?.sha256CertFingerprints
|
|
2814
1647
|
}))
|
|
2815
1648
|
};
|
|
2816
|
-
await Bun.write(
|
|
1649
|
+
await Bun.write(path8.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
|
|
2817
1650
|
`);
|
|
2818
1651
|
this.#app.verbose(`[base-artifact] complete in ${Date.now() - this.#started}ms`);
|
|
2819
1652
|
return { artifact, seedIndex, cssCompiler, optimizedFonts };
|
|
2820
1653
|
}
|
|
2821
1654
|
async#buildRuntimeClientEntries() {
|
|
2822
1655
|
const akanServerPath = await this.#resolveAkanServerPath();
|
|
2823
|
-
const rscClientEntry =
|
|
2824
|
-
const rscSegmentOutletEntry =
|
|
1656
|
+
const rscClientEntry = path8.resolve(akanServerPath, "rscClient.tsx");
|
|
1657
|
+
const rscSegmentOutletEntry = path8.resolve(akanServerPath, "rscSegmentOutlet.tsx");
|
|
2825
1658
|
const vendorEntries = VENDOR_SPECIFIERS.map((specifier) => ({
|
|
2826
1659
|
specifier,
|
|
2827
|
-
absPath:
|
|
1660
|
+
absPath: path8.resolve(akanServerPath, "vendor", `${specifier.replaceAll("/", "-").replaceAll(".", "-")}.ts`)
|
|
2828
1661
|
}));
|
|
2829
1662
|
const entries = [rscClientEntry, rscSegmentOutletEntry, ...vendorEntries.map((v) => v.absPath)];
|
|
2830
1663
|
const clientBundle = await new ClientEntriesBundler({ app: this.#app, entries, command: this.#command }).bundle();
|
|
@@ -2861,15 +1694,15 @@ class SsrBaseArtifactBuilder {
|
|
|
2861
1694
|
async#resolveAkanServerPath() {
|
|
2862
1695
|
const candidates = [];
|
|
2863
1696
|
try {
|
|
2864
|
-
candidates.push(
|
|
1697
|
+
candidates.push(path8.dirname(Bun.resolveSync("akanjs/server", this.#app.workspace.workspaceRoot)));
|
|
2865
1698
|
} catch {}
|
|
2866
|
-
candidates.push(
|
|
1699
|
+
candidates.push(path8.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server"), path8.join(this.#app.workspace.workspaceRoot, "node_modules/akanjs/server"));
|
|
2867
1700
|
try {
|
|
2868
|
-
candidates.push(
|
|
1701
|
+
candidates.push(path8.dirname(Bun.resolveSync("akanjs/server", path8.dirname(Bun.main))));
|
|
2869
1702
|
} catch {}
|
|
2870
|
-
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"));
|
|
2871
1704
|
for (const candidate of candidates) {
|
|
2872
|
-
if (await Bun.file(
|
|
1705
|
+
if (await Bun.file(path8.join(candidate, "rscClient.tsx")).exists())
|
|
2873
1706
|
return candidate;
|
|
2874
1707
|
}
|
|
2875
1708
|
throw new Error(`[base-artifact] failed to locate akanjs/server; looked in: ${candidates.join(", ")}`);
|
|
@@ -2898,7 +1731,7 @@ ${preparedCssText}`).toString(36);
|
|
|
2898
1731
|
`styles/${cssAssetName}-${cssHash}.css`,
|
|
2899
1732
|
`/_akan/styles/${cssAssetName}-${cssHash}.css`
|
|
2900
1733
|
];
|
|
2901
|
-
await Bun.write(
|
|
1734
|
+
await Bun.write(path8.join(this.#absArtifactDir, cssRelPath), preparedCssText);
|
|
2902
1735
|
SsrBaseArtifactBuilder.#warnDroppedImports(this.#app, cssRelPath, preparedCssText, imported);
|
|
2903
1736
|
this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath} -> ${cssRelPath}`);
|
|
2904
1737
|
return [basePath, { cssUrl, cssRelPath }];
|
|
@@ -2911,4 +1744,4 @@ ${preparedCssText}`).toString(36);
|
|
|
2911
1744
|
}
|
|
2912
1745
|
}
|
|
2913
1746
|
}
|
|
2914
|
-
export {
|
|
1747
|
+
export { AllRoutesBuilder, CsrArtifactBuilder, CssCompiler, FontOptimizer, FontPruner, precompressArtifacts, SsrBaseArtifactBuilder };
|