@blinkk/root 1.0.0-alpha.14 → 1.0.0-alpha.15
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/dist/cli.js +273 -224
- package/dist/cli.js.map +1 -1
- package/dist/{config-3a70dc3b.d.ts → config-1674bc63.d.ts} +27 -7
- package/dist/core.d.ts +1 -1
- package/dist/core.js.map +1 -1
- package/dist/render.d.ts +1 -1
- package/dist/render.js +13 -11
- package/dist/render.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,14 +4,19 @@ import {
|
|
|
4
4
|
} from "./chunk-ZB7R6ZOY.js";
|
|
5
5
|
|
|
6
6
|
// src/cli/commands/dev.ts
|
|
7
|
-
import
|
|
7
|
+
import path5 from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { default as express } from "express";
|
|
10
10
|
import { createServer as createViteServer } from "vite";
|
|
11
11
|
|
|
12
12
|
// src/render/vite-plugin-root.ts
|
|
13
|
-
import
|
|
13
|
+
import path3 from "node:path";
|
|
14
|
+
|
|
15
|
+
// src/core/elements.ts
|
|
16
|
+
import fs2 from "node:fs";
|
|
17
|
+
import path2 from "node:path";
|
|
14
18
|
import glob from "tiny-glob";
|
|
19
|
+
import { searchForWorkspaceRoot } from "vite";
|
|
15
20
|
|
|
16
21
|
// src/core/fsutils.ts
|
|
17
22
|
import { promises as fs } from "node:fs";
|
|
@@ -58,60 +63,84 @@ async function isDirectory(dirpath) {
|
|
|
58
63
|
function fileExists(filepath) {
|
|
59
64
|
return fs.access(filepath).then(() => true).catch(() => false);
|
|
60
65
|
}
|
|
66
|
+
async function directoryContains(dirpath, subpath) {
|
|
67
|
+
const outer = await fs.realpath(dirpath);
|
|
68
|
+
const inner = await fs.realpath(subpath);
|
|
69
|
+
const rel = path.relative(outer, inner);
|
|
70
|
+
return !rel.startsWith("..");
|
|
71
|
+
}
|
|
61
72
|
|
|
62
|
-
// src/
|
|
63
|
-
|
|
64
|
-
var HTML_ELEMENTS_REGEX = /<(\w[\w-]+\w)/g;
|
|
65
|
-
function pluginRoot(options) {
|
|
73
|
+
// src/core/elements.ts
|
|
74
|
+
async function getElements(rootConfig) {
|
|
66
75
|
var _a, _b;
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const rootDir = (options == null ? void 0 : options.rootDir) || process.cwd();
|
|
70
|
-
const rootConfig = (options == null ? void 0 : options.rootConfig) || {};
|
|
76
|
+
const rootDir = rootConfig.rootDir;
|
|
77
|
+
const workspaceRoot = searchForWorkspaceRoot(rootDir);
|
|
71
78
|
const elementsDirs = [path2.join(rootDir, "elements")];
|
|
72
|
-
const
|
|
73
|
-
|
|
79
|
+
const elementsInclude = ((_a = rootConfig.elements) == null ? void 0 : _a.include) || [];
|
|
80
|
+
const excludePatterns = ((_b = rootConfig.elements) == null ? void 0 : _b.exclude) || [];
|
|
81
|
+
const excludeElement = (moduleId) => {
|
|
82
|
+
return excludePatterns.some((pattern) => Boolean(moduleId.match(pattern)));
|
|
83
|
+
};
|
|
84
|
+
for (const dirPath of elementsInclude) {
|
|
74
85
|
const elementsDir = path2.resolve(rootDir, dirPath);
|
|
75
|
-
if (!
|
|
86
|
+
if (!directoryContains(rootDir, elementsDir)) {
|
|
76
87
|
throw new Error(
|
|
77
|
-
`the elements dir (${dirPath}) should be
|
|
88
|
+
`the elements dir (${dirPath}) should be within the project's workspace (${workspaceRoot})`
|
|
78
89
|
);
|
|
79
90
|
}
|
|
80
91
|
elementsDirs.push(elementsDir);
|
|
81
|
-
}
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (isJsFile(parts.base)) {
|
|
99
|
-
const fullPath = path2.join(dirPath, file);
|
|
100
|
-
const moduleId = fullPath.slice(rootDir.length);
|
|
101
|
-
if (!excludeElement(moduleId)) {
|
|
102
|
-
elementMap[parts.name] = moduleId;
|
|
103
|
-
}
|
|
92
|
+
}
|
|
93
|
+
const elementMap = {};
|
|
94
|
+
for (const dirPath of elementsDirs) {
|
|
95
|
+
if (await isDirectory(dirPath)) {
|
|
96
|
+
const elementFiles = await glob("**/*", { cwd: dirPath });
|
|
97
|
+
elementFiles.forEach((file) => {
|
|
98
|
+
const parts = path2.parse(file);
|
|
99
|
+
if (isJsFile(parts.base)) {
|
|
100
|
+
const filePath = path2.join(dirPath, file);
|
|
101
|
+
const src = path2.relative(rootDir, filePath);
|
|
102
|
+
const realPath = fs2.realpathSync(filePath);
|
|
103
|
+
if (!excludeElement(src)) {
|
|
104
|
+
elementMap[parts.name] = {
|
|
105
|
+
src,
|
|
106
|
+
filePath,
|
|
107
|
+
realPath
|
|
108
|
+
};
|
|
104
109
|
}
|
|
105
|
-
}
|
|
106
|
-
})
|
|
107
|
-
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
108
113
|
}
|
|
114
|
+
return elementMap;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/render/vite-plugin-root.ts
|
|
118
|
+
var JSX_ELEMENTS_REGEX = /jsxs?\("(\w[\w-]+\w)"/g;
|
|
119
|
+
var HTML_ELEMENTS_REGEX = /<(\w[\w-]+\w)/g;
|
|
120
|
+
async function pluginRoot(options) {
|
|
121
|
+
const elementsVirtualId = "virtual:root-elements";
|
|
122
|
+
const resolvedElementsVirtualId = "\0" + elementsVirtualId;
|
|
123
|
+
const rootConfig = options.rootConfig;
|
|
124
|
+
let tagNameToElement;
|
|
125
|
+
const customElementFiles = /* @__PURE__ */ new Set();
|
|
126
|
+
async function updateElementMap() {
|
|
127
|
+
tagNameToElement = await getElements(rootConfig);
|
|
128
|
+
customElementFiles.clear();
|
|
129
|
+
Object.values(tagNameToElement).forEach((elementModule) => {
|
|
130
|
+
customElementFiles.add(elementModule.filePath);
|
|
131
|
+
customElementFiles.add(elementModule.realPath);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
await updateElementMap();
|
|
109
135
|
async function getElementImport(tagname) {
|
|
110
|
-
if (!
|
|
136
|
+
if (!tagNameToElement) {
|
|
111
137
|
await updateElementMap();
|
|
112
138
|
}
|
|
113
|
-
if (tagname in
|
|
114
|
-
|
|
139
|
+
if (tagname in tagNameToElement) {
|
|
140
|
+
const elementModule = tagNameToElement[tagname];
|
|
141
|
+
if (elementModule.filePath === elementModule.realPath) {
|
|
142
|
+
return elementModule.src;
|
|
143
|
+
}
|
|
115
144
|
}
|
|
116
145
|
return null;
|
|
117
146
|
}
|
|
@@ -119,9 +148,7 @@ function pluginRoot(options) {
|
|
|
119
148
|
if (!isJsFile(id)) {
|
|
120
149
|
return false;
|
|
121
150
|
}
|
|
122
|
-
return
|
|
123
|
-
return id.startsWith(elementsDir);
|
|
124
|
-
});
|
|
151
|
+
return customElementFiles.has(id);
|
|
125
152
|
}
|
|
126
153
|
return {
|
|
127
154
|
name: "vite-plugin-root",
|
|
@@ -134,13 +161,13 @@ function pluginRoot(options) {
|
|
|
134
161
|
async load(id) {
|
|
135
162
|
if (id === resolvedElementsVirtualId) {
|
|
136
163
|
await updateElementMap();
|
|
137
|
-
return `export const elementsMap = ${JSON.stringify(
|
|
164
|
+
return `export const elementsMap = ${JSON.stringify(tagNameToElement)}`;
|
|
138
165
|
}
|
|
139
166
|
return null;
|
|
140
167
|
},
|
|
141
168
|
async transform(src, id) {
|
|
142
169
|
if (isCustomElement(id)) {
|
|
143
|
-
const idParts =
|
|
170
|
+
const idParts = path3.parse(id);
|
|
144
171
|
const deps = /* @__PURE__ */ new Set();
|
|
145
172
|
const tagnames = [
|
|
146
173
|
...src.matchAll(JSX_ELEMENTS_REGEX),
|
|
@@ -166,7 +193,7 @@ function pluginRoot(options) {
|
|
|
166
193
|
})
|
|
167
194
|
);
|
|
168
195
|
if (importUrls.length > 0) {
|
|
169
|
-
const importLines = importUrls.map((url) => `import '
|
|
196
|
+
const importLines = importUrls.map((url) => `import '/${url}';`).join("\n");
|
|
170
197
|
const code = `${src}
|
|
171
198
|
|
|
172
199
|
// autogenerated by root:
|
|
@@ -182,28 +209,54 @@ ${importLines}
|
|
|
182
209
|
}
|
|
183
210
|
|
|
184
211
|
// src/render/asset-map/dev-asset-map.ts
|
|
185
|
-
import
|
|
212
|
+
import path4 from "node:path";
|
|
213
|
+
import { searchForWorkspaceRoot as searchForWorkspaceRoot2 } from "vite";
|
|
186
214
|
var DevServerAssetMap = class {
|
|
187
|
-
constructor(moduleGraph) {
|
|
215
|
+
constructor(rootConfig, moduleGraph) {
|
|
216
|
+
this.rootConfig = rootConfig;
|
|
188
217
|
this.moduleGraph = moduleGraph;
|
|
189
218
|
}
|
|
190
|
-
async get(
|
|
191
|
-
const
|
|
192
|
-
|
|
219
|
+
async get(src) {
|
|
220
|
+
const file = path4.resolve(this.rootConfig.rootDir, src);
|
|
221
|
+
const viteModules = this.moduleGraph.getModulesByFile(file);
|
|
222
|
+
if (viteModules && viteModules.size > 0) {
|
|
223
|
+
const [viteModule] = viteModules;
|
|
224
|
+
return new DevServerAsset(src, {
|
|
225
|
+
assetMap: this,
|
|
226
|
+
viteModule
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (file.startsWith(this.rootConfig.rootDir)) {
|
|
230
|
+
const assetUrl = file.slice(this.rootConfig.rootDir.length);
|
|
193
231
|
return {
|
|
194
|
-
|
|
195
|
-
assetUrl
|
|
232
|
+
src,
|
|
233
|
+
assetUrl,
|
|
196
234
|
getCssDeps: async () => [],
|
|
197
|
-
getJsDeps: async () => [
|
|
235
|
+
getJsDeps: async () => [assetUrl]
|
|
198
236
|
};
|
|
199
237
|
}
|
|
200
|
-
|
|
238
|
+
const workspaceRoot = searchForWorkspaceRoot2(this.rootConfig.rootDir);
|
|
239
|
+
if (await directoryContains(workspaceRoot, file)) {
|
|
240
|
+
const assetUrl = `/@fs/${file}`;
|
|
241
|
+
return {
|
|
242
|
+
src,
|
|
243
|
+
assetUrl,
|
|
244
|
+
getCssDeps: async () => [],
|
|
245
|
+
getJsDeps: async () => [assetUrl]
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
console.log(`could not find asset in asset map: ${src}`);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
filePathToSrc(file) {
|
|
252
|
+
return path4.relative(this.rootConfig.rootDir, file);
|
|
201
253
|
}
|
|
202
254
|
};
|
|
203
255
|
var DevServerAsset = class {
|
|
204
|
-
constructor(
|
|
205
|
-
this.
|
|
206
|
-
this.
|
|
256
|
+
constructor(src, options) {
|
|
257
|
+
this.src = src;
|
|
258
|
+
this.assetMap = options.assetMap;
|
|
259
|
+
this.viteModule = options.viteModule;
|
|
207
260
|
this.moduleId = this.viteModule.id;
|
|
208
261
|
this.assetUrl = this.viteModule.url;
|
|
209
262
|
}
|
|
@@ -233,13 +286,17 @@ var DevServerAsset = class {
|
|
|
233
286
|
return;
|
|
234
287
|
}
|
|
235
288
|
visited.add(asset.moduleId);
|
|
236
|
-
const parts =
|
|
289
|
+
const parts = path4.parse(asset.assetUrl);
|
|
237
290
|
if ([".js", ".jsx", ".ts", ".tsx"].includes(parts.ext) && asset.moduleId.includes("/elements/")) {
|
|
238
291
|
urls.add(asset.assetUrl);
|
|
239
292
|
}
|
|
240
293
|
asset.getImportedModules().forEach((viteModule) => {
|
|
241
|
-
if (viteModule.
|
|
242
|
-
const
|
|
294
|
+
if (viteModule.file) {
|
|
295
|
+
const src = this.assetMap.filePathToSrc(viteModule.file);
|
|
296
|
+
const importedAsset = new DevServerAsset(src, {
|
|
297
|
+
assetMap: this.assetMap,
|
|
298
|
+
viteModule
|
|
299
|
+
});
|
|
243
300
|
this.collectJs(importedAsset, urls, visited);
|
|
244
301
|
}
|
|
245
302
|
});
|
|
@@ -255,15 +312,19 @@ var DevServerAsset = class {
|
|
|
255
312
|
return;
|
|
256
313
|
}
|
|
257
314
|
visited.add(asset.assetUrl);
|
|
258
|
-
if (asset.
|
|
259
|
-
const parts =
|
|
315
|
+
if (asset.src.endsWith(".scss")) {
|
|
316
|
+
const parts = path4.parse(asset.src);
|
|
260
317
|
if (!parts.name.startsWith("_")) {
|
|
261
318
|
urls.add(asset.assetUrl);
|
|
262
319
|
}
|
|
263
320
|
}
|
|
264
321
|
asset.getImportedModules().forEach((viteModule) => {
|
|
265
|
-
if (viteModule.
|
|
266
|
-
const
|
|
322
|
+
if (viteModule.file) {
|
|
323
|
+
const src = this.assetMap.filePathToSrc(viteModule.file);
|
|
324
|
+
const importedAsset = new DevServerAsset(src, {
|
|
325
|
+
assetMap: this.assetMap,
|
|
326
|
+
viteModule
|
|
327
|
+
});
|
|
267
328
|
this.collectCss(importedAsset, urls, visited);
|
|
268
329
|
}
|
|
269
330
|
});
|
|
@@ -283,9 +344,9 @@ async function loadRootConfig(rootDir) {
|
|
|
283
344
|
const configBundle = await bundleRequire({
|
|
284
345
|
filepath: configPath
|
|
285
346
|
});
|
|
286
|
-
return configBundle.mod.default || {};
|
|
347
|
+
return Object.assign({}, configBundle.mod.default || {}, { rootDir });
|
|
287
348
|
}
|
|
288
|
-
return {};
|
|
349
|
+
return { rootDir };
|
|
289
350
|
}
|
|
290
351
|
|
|
291
352
|
// src/render/html-minify.ts
|
|
@@ -318,27 +379,66 @@ function rootProjectMiddleware(options) {
|
|
|
318
379
|
};
|
|
319
380
|
}
|
|
320
381
|
|
|
382
|
+
// src/cli/ports.ts
|
|
383
|
+
import { createServer } from "node:net";
|
|
384
|
+
function isPortOpen(port) {
|
|
385
|
+
return new Promise((resolve, reject) => {
|
|
386
|
+
const server = createServer();
|
|
387
|
+
server.on("error", (err) => {
|
|
388
|
+
if (err.code === "EADDRINUSE") {
|
|
389
|
+
resolve(false);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
reject(err);
|
|
393
|
+
});
|
|
394
|
+
server.on("close", () => {
|
|
395
|
+
resolve(true);
|
|
396
|
+
});
|
|
397
|
+
server.listen(port, () => {
|
|
398
|
+
server.close((err) => {
|
|
399
|
+
if (err) {
|
|
400
|
+
console.log(`error closing server: ${err}`);
|
|
401
|
+
reject(err);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
async function findOpenPort(min, max) {
|
|
408
|
+
let port = min;
|
|
409
|
+
while (port <= max) {
|
|
410
|
+
const isOpen = await isPortOpen(port);
|
|
411
|
+
if (isOpen) {
|
|
412
|
+
return port;
|
|
413
|
+
}
|
|
414
|
+
port += 1;
|
|
415
|
+
}
|
|
416
|
+
throw new Error(`no ports open between ${min} and ${max}`);
|
|
417
|
+
}
|
|
418
|
+
|
|
321
419
|
// src/cli/commands/dev.ts
|
|
322
|
-
var __dirname =
|
|
420
|
+
var __dirname = path5.dirname(fileURLToPath(import.meta.url));
|
|
323
421
|
async function dev(rootProjectDir) {
|
|
324
422
|
process.env.NODE_ENV = "development";
|
|
325
|
-
const rootDir =
|
|
326
|
-
const
|
|
327
|
-
const port =
|
|
423
|
+
const rootDir = path5.resolve(rootProjectDir || process.cwd());
|
|
424
|
+
const defaultPort = parseInt(process.env.PORT || "4007");
|
|
425
|
+
const port = await findOpenPort(defaultPort, defaultPort + 10);
|
|
328
426
|
console.log();
|
|
329
427
|
console.log(`${dim("\u2503")} project: ${rootDir}`);
|
|
330
428
|
console.log(`${dim("\u2503")} server: http://localhost:${port}`);
|
|
331
429
|
console.log(`${dim("\u2503")} mode: development`);
|
|
332
430
|
console.log();
|
|
431
|
+
const server = await createServer2({ rootDir, port });
|
|
333
432
|
server.listen(port);
|
|
334
433
|
}
|
|
335
|
-
async function
|
|
336
|
-
const rootDir =
|
|
434
|
+
async function createServer2(options) {
|
|
435
|
+
const rootDir = path5.resolve((options == null ? void 0 : options.rootDir) || process.cwd());
|
|
337
436
|
const rootConfig = await loadRootConfig(rootDir);
|
|
437
|
+
const port = options == null ? void 0 : options.port;
|
|
338
438
|
const server = express();
|
|
339
439
|
server.disable("x-powered-by");
|
|
340
440
|
server.use(rootProjectMiddleware({ rootDir, rootConfig }));
|
|
341
|
-
server.use(await viteServerMiddleware({ rootDir, rootConfig }));
|
|
441
|
+
server.use(await viteServerMiddleware({ rootDir, rootConfig, port }));
|
|
342
442
|
server.use(rootDevRendererMiddleware());
|
|
343
443
|
const plugins = rootConfig.plugins || [];
|
|
344
444
|
await configureServerPlugins(
|
|
@@ -358,56 +458,31 @@ async function createServer(options) {
|
|
|
358
458
|
return server;
|
|
359
459
|
}
|
|
360
460
|
async function viteServerMiddleware(options) {
|
|
361
|
-
var _a, _b
|
|
461
|
+
var _a, _b;
|
|
362
462
|
const rootDir = options.rootDir;
|
|
363
463
|
const rootConfig = options.rootConfig;
|
|
364
464
|
const viteConfig = rootConfig.vite || {};
|
|
465
|
+
let hmrOptions = (_a = viteConfig.server) == null ? void 0 : _a.hmr;
|
|
466
|
+
if (typeof hmrOptions === "undefined" && options.port) {
|
|
467
|
+
hmrOptions = { port: options.port + 10 };
|
|
468
|
+
}
|
|
365
469
|
const routeFiles = [];
|
|
366
|
-
if (await isDirectory(
|
|
470
|
+
if (await isDirectory(path5.join(rootDir, "routes"))) {
|
|
367
471
|
const pageFiles = await glob2("routes/**/*", { cwd: rootDir });
|
|
368
472
|
pageFiles.forEach((file) => {
|
|
369
|
-
const parts =
|
|
473
|
+
const parts = path5.parse(file);
|
|
370
474
|
if (!parts.name.startsWith("_") && isJsFile(parts.base)) {
|
|
371
475
|
routeFiles.push(file);
|
|
372
476
|
}
|
|
373
477
|
});
|
|
374
478
|
}
|
|
375
|
-
const
|
|
376
|
-
const
|
|
377
|
-
const excludePatterns = ((_b = rootConfig.elements) == null ? void 0 : _b.exclude) || [];
|
|
378
|
-
const excludeElement = (moduleId) => {
|
|
379
|
-
return excludePatterns.some((pattern) => Boolean(moduleId.match(pattern)));
|
|
380
|
-
};
|
|
381
|
-
for (const dirPath of elementsInclude) {
|
|
382
|
-
const elementsDir = path4.resolve(rootDir, dirPath);
|
|
383
|
-
if (!elementsDir.startsWith(rootDir)) {
|
|
384
|
-
throw new Error(
|
|
385
|
-
`the elements dir (${dirPath}) should be relative to the project's root dir (${rootDir})`
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
elementsDirs.push(elementsDir);
|
|
389
|
-
}
|
|
390
|
-
const elements = [];
|
|
391
|
-
for (const dirPath of elementsDirs) {
|
|
392
|
-
if (await isDirectory(dirPath)) {
|
|
393
|
-
const elementFiles = await glob2("**/*", { cwd: dirPath });
|
|
394
|
-
elementFiles.forEach((file) => {
|
|
395
|
-
const parts = path4.parse(file);
|
|
396
|
-
if (isJsFile(parts.base)) {
|
|
397
|
-
const fullPath = path4.join(dirPath, file);
|
|
398
|
-
const moduleId = fullPath.slice(rootDir.length);
|
|
399
|
-
if (!excludeElement(moduleId)) {
|
|
400
|
-
elements.push(moduleId.slice(1));
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
}
|
|
479
|
+
const elementsMap = await getElements(rootConfig);
|
|
480
|
+
const elements = Object.values(elementsMap).map((mod) => mod.src);
|
|
406
481
|
const bundleScripts = [];
|
|
407
|
-
if (await isDirectory(
|
|
408
|
-
const bundleFiles = await glob2(
|
|
482
|
+
if (await isDirectory(path5.join(rootDir, "bundles"))) {
|
|
483
|
+
const bundleFiles = await glob2("bundles/*", { cwd: rootDir });
|
|
409
484
|
bundleFiles.forEach((file) => {
|
|
410
|
-
const parts =
|
|
485
|
+
const parts = path5.parse(file);
|
|
411
486
|
if (isJsFile(parts.base)) {
|
|
412
487
|
bundleScripts.push(file);
|
|
413
488
|
}
|
|
@@ -417,10 +492,11 @@ async function viteServerMiddleware(options) {
|
|
|
417
492
|
...viteConfig,
|
|
418
493
|
mode: "development",
|
|
419
494
|
root: rootDir,
|
|
420
|
-
publicDir:
|
|
495
|
+
publicDir: path5.join(rootDir, "public"),
|
|
421
496
|
server: {
|
|
422
497
|
...viteConfig.server || {},
|
|
423
|
-
middlewareMode: true
|
|
498
|
+
middlewareMode: true,
|
|
499
|
+
hmr: hmrOptions
|
|
424
500
|
},
|
|
425
501
|
appType: "custom",
|
|
426
502
|
optimizeDeps: {
|
|
@@ -429,7 +505,7 @@ async function viteServerMiddleware(options) {
|
|
|
429
505
|
...routeFiles,
|
|
430
506
|
...elements,
|
|
431
507
|
...bundleScripts,
|
|
432
|
-
...((
|
|
508
|
+
...((_b = viteConfig.optimizeDeps) == null ? void 0 : _b.include) || []
|
|
433
509
|
]
|
|
434
510
|
},
|
|
435
511
|
ssr: {
|
|
@@ -442,23 +518,23 @@ async function viteServerMiddleware(options) {
|
|
|
442
518
|
jsxImportSource: "preact"
|
|
443
519
|
},
|
|
444
520
|
plugins: [
|
|
445
|
-
pluginRoot({
|
|
521
|
+
pluginRoot({ rootConfig }),
|
|
446
522
|
...viteConfig.plugins || [],
|
|
447
523
|
...getVitePlugins(rootConfig.plugins || [])
|
|
448
524
|
]
|
|
449
525
|
});
|
|
450
|
-
return
|
|
526
|
+
return (req, res, next) => {
|
|
451
527
|
req.viteServer = viteServer;
|
|
452
528
|
viteServer.middlewares(req, res, next);
|
|
453
529
|
};
|
|
454
530
|
}
|
|
455
531
|
function rootDevRendererMiddleware() {
|
|
456
|
-
const renderModulePath =
|
|
532
|
+
const renderModulePath = path5.resolve(__dirname, "./render.js");
|
|
457
533
|
return async (req, _, next) => {
|
|
458
534
|
const rootConfig = req.rootConfig;
|
|
459
535
|
const viteServer = req.viteServer;
|
|
460
536
|
const render = await viteServer.ssrLoadModule(renderModulePath);
|
|
461
|
-
const assetMap = new DevServerAssetMap(viteServer.moduleGraph);
|
|
537
|
+
const assetMap = new DevServerAssetMap(rootConfig, viteServer.moduleGraph);
|
|
462
538
|
req.renderer = new render.Renderer(rootConfig, { assetMap });
|
|
463
539
|
next();
|
|
464
540
|
};
|
|
@@ -501,7 +577,7 @@ function rootDevServerMiddleware() {
|
|
|
501
577
|
function rootDevServer404Middleware() {
|
|
502
578
|
return async (req, res) => {
|
|
503
579
|
const url = req.originalUrl;
|
|
504
|
-
const ext =
|
|
580
|
+
const ext = path5.extname(url);
|
|
505
581
|
const renderer = req.renderer;
|
|
506
582
|
if (!ext) {
|
|
507
583
|
const data = await renderer.renderDevServer404();
|
|
@@ -514,7 +590,7 @@ function rootDevServer404Middleware() {
|
|
|
514
590
|
}
|
|
515
591
|
|
|
516
592
|
// src/cli/commands/build.ts
|
|
517
|
-
import
|
|
593
|
+
import path6 from "node:path";
|
|
518
594
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
519
595
|
import fsExtra2 from "fs-extra";
|
|
520
596
|
import glob3 from "tiny-glob";
|
|
@@ -523,37 +599,40 @@ import { build as viteBuild } from "vite";
|
|
|
523
599
|
// src/render/asset-map/build-asset-map.ts
|
|
524
600
|
var BuildAssetMap = class {
|
|
525
601
|
constructor() {
|
|
526
|
-
this.
|
|
602
|
+
this.srcToAsset = /* @__PURE__ */ new Map();
|
|
527
603
|
}
|
|
528
|
-
async get(
|
|
529
|
-
|
|
604
|
+
async get(src) {
|
|
605
|
+
const asset = this.srcToAsset.get(src);
|
|
606
|
+
if (asset) {
|
|
607
|
+
return asset;
|
|
608
|
+
}
|
|
609
|
+
console.log(`could not find build asset: ${src}`);
|
|
610
|
+
return null;
|
|
530
611
|
}
|
|
531
612
|
add(asset) {
|
|
532
|
-
this.
|
|
613
|
+
this.srcToAsset.set(asset.src, asset);
|
|
533
614
|
}
|
|
534
615
|
toJson() {
|
|
535
616
|
const result = {};
|
|
536
|
-
for (const
|
|
537
|
-
result[
|
|
617
|
+
for (const src of this.srcToAsset.keys()) {
|
|
618
|
+
result[src] = this.srcToAsset.get(src).toJson();
|
|
538
619
|
}
|
|
539
620
|
return result;
|
|
540
621
|
}
|
|
541
622
|
static fromViteManifest(viteManifest, elementMap) {
|
|
542
623
|
const assetMap = new BuildAssetMap();
|
|
543
|
-
const
|
|
624
|
+
const elementFiles = /* @__PURE__ */ new Set();
|
|
544
625
|
Object.values(elementMap).forEach(
|
|
545
|
-
(
|
|
626
|
+
(elementModule) => elementFiles.add(elementModule.src)
|
|
546
627
|
);
|
|
547
628
|
Object.keys(viteManifest).forEach((manifestKey) => {
|
|
548
|
-
const
|
|
629
|
+
const src = manifestKey;
|
|
549
630
|
const manifestChunk = viteManifest[manifestKey];
|
|
550
|
-
const isElement =
|
|
631
|
+
const isElement = elementFiles.has(src);
|
|
551
632
|
const assetData = {
|
|
552
|
-
|
|
633
|
+
src,
|
|
553
634
|
assetUrl: `/${manifestChunk.file}`,
|
|
554
|
-
importedModules:
|
|
555
|
-
(relPath) => `/${relPath}`
|
|
556
|
-
),
|
|
635
|
+
importedModules: manifestChunk.imports || [],
|
|
557
636
|
importedCss: (manifestChunk.css || []).map((relPath) => `/${relPath}`),
|
|
558
637
|
isElement
|
|
559
638
|
};
|
|
@@ -573,7 +652,7 @@ var BuildAssetMap = class {
|
|
|
573
652
|
var BuildAsset = class {
|
|
574
653
|
constructor(assetMap, assetData) {
|
|
575
654
|
this.assetMap = assetMap;
|
|
576
|
-
this.
|
|
655
|
+
this.src = assetData.src;
|
|
577
656
|
this.assetUrl = assetData.assetUrl;
|
|
578
657
|
this.importedModules = assetData.importedModules;
|
|
579
658
|
this.importedCss = assetData.importedCss;
|
|
@@ -595,19 +674,19 @@ var BuildAsset = class {
|
|
|
595
674
|
if (!asset) {
|
|
596
675
|
return;
|
|
597
676
|
}
|
|
598
|
-
if (!asset.
|
|
677
|
+
if (!asset.src) {
|
|
599
678
|
return;
|
|
600
679
|
}
|
|
601
|
-
if (visited.has(asset.
|
|
680
|
+
if (visited.has(asset.src)) {
|
|
602
681
|
return;
|
|
603
682
|
}
|
|
604
|
-
visited.add(asset.
|
|
683
|
+
visited.add(asset.src);
|
|
605
684
|
if (asset.isElement) {
|
|
606
685
|
urls.add(asset.assetUrl);
|
|
607
686
|
}
|
|
608
687
|
await Promise.all(
|
|
609
|
-
asset.importedModules.map(async (
|
|
610
|
-
const importedAsset = await this.assetMap.get(
|
|
688
|
+
asset.importedModules.map(async (src) => {
|
|
689
|
+
const importedAsset = await this.assetMap.get(src);
|
|
611
690
|
this.collectJs(importedAsset, urls, visited);
|
|
612
691
|
})
|
|
613
692
|
);
|
|
@@ -619,10 +698,10 @@ var BuildAsset = class {
|
|
|
619
698
|
if (!asset.assetUrl) {
|
|
620
699
|
return;
|
|
621
700
|
}
|
|
622
|
-
if (visited.has(asset.
|
|
701
|
+
if (visited.has(asset.src)) {
|
|
623
702
|
return;
|
|
624
703
|
}
|
|
625
|
-
visited.add(asset.
|
|
704
|
+
visited.add(asset.src);
|
|
626
705
|
if (asset.importedCss) {
|
|
627
706
|
asset.importedCss.forEach((cssUrl) => urls.add(cssUrl));
|
|
628
707
|
}
|
|
@@ -635,7 +714,7 @@ var BuildAsset = class {
|
|
|
635
714
|
}
|
|
636
715
|
toJson() {
|
|
637
716
|
return {
|
|
638
|
-
|
|
717
|
+
src: this.src,
|
|
639
718
|
assetUrl: this.assetUrl,
|
|
640
719
|
importedModules: this.importedModules,
|
|
641
720
|
importedCss: this.importedCss,
|
|
@@ -646,12 +725,11 @@ var BuildAsset = class {
|
|
|
646
725
|
|
|
647
726
|
// src/cli/commands/build.ts
|
|
648
727
|
import { dim as dim2 } from "kleur/colors";
|
|
649
|
-
var __dirname2 =
|
|
728
|
+
var __dirname2 = path6.dirname(fileURLToPath2(import.meta.url));
|
|
650
729
|
async function build(rootProjectDir, options) {
|
|
651
|
-
|
|
652
|
-
const rootDir = path5.resolve(rootProjectDir || process.cwd());
|
|
730
|
+
const rootDir = path6.resolve(rootProjectDir || process.cwd());
|
|
653
731
|
const rootConfig = await loadRootConfig(rootDir);
|
|
654
|
-
const distDir =
|
|
732
|
+
const distDir = path6.join(rootDir, "dist");
|
|
655
733
|
const ssrOnly = (options == null ? void 0 : options.ssrOnly) || false;
|
|
656
734
|
const mode = (options == null ? void 0 : options.mode) || "production";
|
|
657
735
|
console.log();
|
|
@@ -661,62 +739,33 @@ async function build(rootProjectDir, options) {
|
|
|
661
739
|
console.log();
|
|
662
740
|
await rmDir(distDir);
|
|
663
741
|
await makeDir(distDir);
|
|
664
|
-
const
|
|
665
|
-
if (await isDirectory(
|
|
666
|
-
const pageFiles = await glob3(
|
|
742
|
+
const routeFiles = [];
|
|
743
|
+
if (await isDirectory(path6.join(rootDir, "routes"))) {
|
|
744
|
+
const pageFiles = await glob3("routes/**/*", { cwd: rootDir });
|
|
667
745
|
pageFiles.forEach((file) => {
|
|
668
|
-
const parts =
|
|
746
|
+
const parts = path6.parse(file);
|
|
669
747
|
if (!parts.name.startsWith("_") && isJsFile(parts.base)) {
|
|
670
|
-
|
|
748
|
+
routeFiles.push(path6.resolve(rootDir, file));
|
|
671
749
|
}
|
|
672
750
|
});
|
|
673
751
|
}
|
|
674
|
-
const
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
return excludePatterns.some((pattern) => Boolean(moduleId.match(pattern)));
|
|
679
|
-
};
|
|
680
|
-
for (const dirPath of elementsInclude) {
|
|
681
|
-
const elementsDir = path5.resolve(rootDir, dirPath);
|
|
682
|
-
if (!elementsDir.startsWith(rootDir)) {
|
|
683
|
-
throw new Error(
|
|
684
|
-
`the elements dir (${dirPath}) should be relative to the project's root dir (${rootDir})`
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
elementsDirs.push(elementsDir);
|
|
688
|
-
}
|
|
689
|
-
const elements = [];
|
|
690
|
-
const elementMap = {};
|
|
691
|
-
for (const dirPath of elementsDirs) {
|
|
692
|
-
if (await isDirectory(dirPath)) {
|
|
693
|
-
const elementFiles = await glob3("**/*", { cwd: dirPath });
|
|
694
|
-
elementFiles.forEach((file) => {
|
|
695
|
-
const parts = path5.parse(file);
|
|
696
|
-
if (isJsFile(parts.base)) {
|
|
697
|
-
const fullPath = path5.join(dirPath, file);
|
|
698
|
-
const moduleId = fullPath.slice(rootDir.length);
|
|
699
|
-
if (!excludeElement(moduleId)) {
|
|
700
|
-
elements.push(fullPath);
|
|
701
|
-
elementMap[parts.name] = moduleId;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
}
|
|
752
|
+
const elementsMap = await getElements(rootConfig);
|
|
753
|
+
const elements = Object.values(elementsMap).map(
|
|
754
|
+
(mod) => path6.resolve(rootDir, mod.src)
|
|
755
|
+
);
|
|
707
756
|
const bundleScripts = [];
|
|
708
|
-
if (await isDirectory(
|
|
709
|
-
const bundleFiles = await glob3(
|
|
757
|
+
if (await isDirectory(path6.join(rootDir, "bundles"))) {
|
|
758
|
+
const bundleFiles = await glob3("bundles/*", { cwd: rootDir });
|
|
710
759
|
bundleFiles.forEach((file) => {
|
|
711
|
-
const parts =
|
|
760
|
+
const parts = path6.parse(file);
|
|
712
761
|
if (isJsFile(parts.base)) {
|
|
713
|
-
bundleScripts.push(file);
|
|
762
|
+
bundleScripts.push(path6.resolve(rootDir, file));
|
|
714
763
|
}
|
|
715
764
|
});
|
|
716
765
|
}
|
|
717
766
|
const viteConfig = rootConfig.vite || {};
|
|
718
767
|
const vitePlugins = [
|
|
719
|
-
pluginRoot({
|
|
768
|
+
pluginRoot({ rootConfig }),
|
|
720
769
|
...viteConfig.plugins || [],
|
|
721
770
|
...getVitePlugins(rootConfig.plugins || [])
|
|
722
771
|
];
|
|
@@ -736,14 +785,14 @@ async function build(rootProjectDir, options) {
|
|
|
736
785
|
publicDir: false,
|
|
737
786
|
build: {
|
|
738
787
|
rollupOptions: {
|
|
739
|
-
input: [
|
|
788
|
+
input: [path6.resolve(__dirname2, "./render.js")],
|
|
740
789
|
output: {
|
|
741
790
|
format: "esm",
|
|
742
791
|
chunkFileNames: "chunks/[name].[hash].js",
|
|
743
792
|
assetFileNames: "assets/[name].[hash][extname]"
|
|
744
793
|
}
|
|
745
794
|
},
|
|
746
|
-
outDir:
|
|
795
|
+
outDir: path6.join(distDir, "server"),
|
|
747
796
|
ssr: true,
|
|
748
797
|
ssrManifest: false,
|
|
749
798
|
cssCodeSplit: true,
|
|
@@ -761,14 +810,14 @@ async function build(rootProjectDir, options) {
|
|
|
761
810
|
publicDir: false,
|
|
762
811
|
build: {
|
|
763
812
|
rollupOptions: {
|
|
764
|
-
input: [...
|
|
813
|
+
input: [...routeFiles, ...elements, ...bundleScripts],
|
|
765
814
|
output: {
|
|
766
815
|
format: "esm",
|
|
767
816
|
chunkFileNames: "chunks/[name].[hash].js",
|
|
768
817
|
assetFileNames: "assets/[name].[hash][extname]"
|
|
769
818
|
}
|
|
770
819
|
},
|
|
771
|
-
outDir:
|
|
820
|
+
outDir: path6.join(distDir, "client"),
|
|
772
821
|
ssr: false,
|
|
773
822
|
ssrManifest: false,
|
|
774
823
|
manifest: true,
|
|
@@ -780,24 +829,24 @@ async function build(rootProjectDir, options) {
|
|
|
780
829
|
}
|
|
781
830
|
});
|
|
782
831
|
const viteManifest = await loadJson(
|
|
783
|
-
|
|
832
|
+
path6.join(distDir, "client/manifest.json")
|
|
784
833
|
);
|
|
785
|
-
const assetMap = BuildAssetMap.fromViteManifest(viteManifest,
|
|
834
|
+
const assetMap = BuildAssetMap.fromViteManifest(viteManifest, elementsMap);
|
|
786
835
|
writeFile(
|
|
787
|
-
|
|
836
|
+
path6.join(distDir, "client/root-manifest.json"),
|
|
788
837
|
JSON.stringify(assetMap.toJson(), null, 2)
|
|
789
838
|
);
|
|
790
|
-
const buildDir =
|
|
791
|
-
const publicDir =
|
|
792
|
-
if (fsExtra2.existsSync(
|
|
839
|
+
const buildDir = path6.join(distDir, "html");
|
|
840
|
+
const publicDir = path6.join(rootDir, "public");
|
|
841
|
+
if (fsExtra2.existsSync(path6.join(rootDir, "public"))) {
|
|
793
842
|
fsExtra2.copySync(publicDir, buildDir);
|
|
794
843
|
} else {
|
|
795
844
|
makeDir(buildDir);
|
|
796
845
|
}
|
|
797
|
-
copyDir(
|
|
798
|
-
copyDir(
|
|
846
|
+
copyDir(path6.join(distDir, "client/assets"), path6.join(buildDir, "assets"));
|
|
847
|
+
copyDir(path6.join(distDir, "client/chunks"), path6.join(buildDir, "chunks"));
|
|
799
848
|
if (!ssrOnly) {
|
|
800
|
-
const render = await import(
|
|
849
|
+
const render = await import(path6.join(distDir, "server/render.js"));
|
|
801
850
|
const renderer = new render.Renderer(rootConfig, { assetMap });
|
|
802
851
|
const sitemap = await renderer.getSitemap();
|
|
803
852
|
await Promise.all(
|
|
@@ -806,13 +855,13 @@ async function build(rootProjectDir, options) {
|
|
|
806
855
|
const data = await renderer.renderRoute(route, {
|
|
807
856
|
routeParams: params
|
|
808
857
|
});
|
|
809
|
-
let outPath =
|
|
858
|
+
let outPath = path6.join(distDir, `html${urlPath}`, "index.html");
|
|
810
859
|
if (outPath.endsWith("404/index.html")) {
|
|
811
860
|
outPath = outPath.replace("404/index.html", "404.html");
|
|
812
861
|
}
|
|
813
862
|
const html = await htmlMinify(data.html || "");
|
|
814
863
|
await writeFile(outPath, html);
|
|
815
|
-
const relPath = outPath.slice(
|
|
864
|
+
const relPath = outPath.slice(path6.dirname(distDir).length + 1);
|
|
816
865
|
console.log(`saved ${relPath}`);
|
|
817
866
|
})
|
|
818
867
|
);
|
|
@@ -820,15 +869,15 @@ async function build(rootProjectDir, options) {
|
|
|
820
869
|
}
|
|
821
870
|
|
|
822
871
|
// src/cli/commands/preview.ts
|
|
823
|
-
import
|
|
872
|
+
import path7 from "node:path";
|
|
824
873
|
import { default as express2 } from "express";
|
|
825
874
|
import { dim as dim3 } from "kleur/colors";
|
|
826
875
|
import sirv from "sirv";
|
|
827
876
|
import compression from "compression";
|
|
828
877
|
async function preview(rootProjectDir) {
|
|
829
878
|
process.env.NODE_ENV = "development";
|
|
830
|
-
const rootDir =
|
|
831
|
-
const server = await
|
|
879
|
+
const rootDir = path7.resolve(rootProjectDir || process.cwd());
|
|
880
|
+
const server = await createServer3({ rootDir });
|
|
832
881
|
const port = parseInt(process.env.PORT || "4007");
|
|
833
882
|
console.log();
|
|
834
883
|
console.log(`${dim3("\u2503")} project: ${rootDir}`);
|
|
@@ -837,10 +886,10 @@ async function preview(rootProjectDir) {
|
|
|
837
886
|
console.log();
|
|
838
887
|
server.listen(port);
|
|
839
888
|
}
|
|
840
|
-
async function
|
|
889
|
+
async function createServer3(options) {
|
|
841
890
|
const rootDir = options.rootDir;
|
|
842
891
|
const rootConfig = await loadRootConfig(rootDir);
|
|
843
|
-
const distDir =
|
|
892
|
+
const distDir = path7.join(rootDir, "dist");
|
|
844
893
|
const server = express2();
|
|
845
894
|
server.disable("x-powered-by");
|
|
846
895
|
server.use(compression());
|
|
@@ -855,7 +904,7 @@ async function createServer2(options) {
|
|
|
855
904
|
userMiddlewares.forEach((middleware) => {
|
|
856
905
|
server.use(middleware);
|
|
857
906
|
});
|
|
858
|
-
const publicDir =
|
|
907
|
+
const publicDir = path7.join(distDir, "html");
|
|
859
908
|
server.use(sirv(publicDir, { dev: false }));
|
|
860
909
|
server.use(rootPreviewServerMiddleware());
|
|
861
910
|
},
|
|
@@ -866,8 +915,8 @@ async function createServer2(options) {
|
|
|
866
915
|
}
|
|
867
916
|
async function rootPreviewRendererMiddleware(options) {
|
|
868
917
|
const { distDir, rootConfig } = options;
|
|
869
|
-
const render = await import(
|
|
870
|
-
const manifestPath =
|
|
918
|
+
const render = await import(path7.join(distDir, "server/render.js"));
|
|
919
|
+
const manifestPath = path7.join(distDir, "client/root-manifest.json");
|
|
871
920
|
if (!await fileExists(manifestPath)) {
|
|
872
921
|
throw new Error(
|
|
873
922
|
`could not find ${manifestPath}. run \`root build\` before \`root preview\`.`
|
|
@@ -911,15 +960,15 @@ function rootPreviewServerMiddleware() {
|
|
|
911
960
|
}
|
|
912
961
|
|
|
913
962
|
// src/cli/commands/start.ts
|
|
914
|
-
import
|
|
963
|
+
import path8 from "node:path";
|
|
915
964
|
import { default as express3 } from "express";
|
|
916
965
|
import { dim as dim4 } from "kleur/colors";
|
|
917
966
|
import sirv2 from "sirv";
|
|
918
967
|
import compression2 from "compression";
|
|
919
968
|
async function start(rootProjectDir) {
|
|
920
969
|
process.env.NODE_ENV = "production";
|
|
921
|
-
const rootDir =
|
|
922
|
-
const server = await
|
|
970
|
+
const rootDir = path8.resolve(rootProjectDir || process.cwd());
|
|
971
|
+
const server = await createServer4({ rootDir });
|
|
923
972
|
const port = parseInt(process.env.PORT || "4007");
|
|
924
973
|
console.log();
|
|
925
974
|
console.log(`${dim4("\u2503")} project: ${rootDir}`);
|
|
@@ -928,10 +977,10 @@ async function start(rootProjectDir) {
|
|
|
928
977
|
console.log();
|
|
929
978
|
server.listen(port);
|
|
930
979
|
}
|
|
931
|
-
async function
|
|
980
|
+
async function createServer4(options) {
|
|
932
981
|
const rootDir = options.rootDir;
|
|
933
982
|
const rootConfig = await loadRootConfig(rootDir);
|
|
934
|
-
const distDir =
|
|
983
|
+
const distDir = path8.join(rootDir, "dist");
|
|
935
984
|
const server = express3();
|
|
936
985
|
server.disable("x-powered-by");
|
|
937
986
|
server.use(compression2());
|
|
@@ -946,7 +995,7 @@ async function createServer3(options) {
|
|
|
946
995
|
userMiddlewares.forEach((middleware) => {
|
|
947
996
|
server.use(middleware);
|
|
948
997
|
});
|
|
949
|
-
const publicDir =
|
|
998
|
+
const publicDir = path8.join(distDir, "html");
|
|
950
999
|
server.use(sirv2(publicDir, { dev: false }));
|
|
951
1000
|
server.use(rootProdServerMiddleware());
|
|
952
1001
|
},
|
|
@@ -957,8 +1006,8 @@ async function createServer3(options) {
|
|
|
957
1006
|
}
|
|
958
1007
|
async function rootProdRendererMiddleware(options) {
|
|
959
1008
|
const { distDir, rootConfig } = options;
|
|
960
|
-
const render = await import(
|
|
961
|
-
const manifestPath =
|
|
1009
|
+
const render = await import(path8.join(distDir, "server/render.js"));
|
|
1010
|
+
const manifestPath = path8.join(distDir, "client/root-manifest.json");
|
|
962
1011
|
if (!await fileExists(manifestPath)) {
|
|
963
1012
|
throw new Error(
|
|
964
1013
|
`could not find ${manifestPath}. run \`root build\` before \`root preview\`.`
|