@blinkk/root 1.0.0-alpha.3 → 1.0.0-alpha.30

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 CHANGED
@@ -1,12 +1,22 @@
1
+ import {
2
+ configureServerPlugins,
3
+ getVitePlugins
4
+ } from "./chunk-ZB7R6ZOY.js";
5
+
1
6
  // src/cli/commands/dev.ts
2
- import path4 from "node:path";
7
+ import path5 from "node:path";
3
8
  import { fileURLToPath } from "node:url";
4
9
  import { default as express } from "express";
5
10
  import { createServer as createViteServer } from "vite";
6
11
 
7
12
  // src/render/vite-plugin-root.ts
8
- import path2 from "path";
13
+ import path3 from "node:path";
14
+
15
+ // src/core/elements.ts
16
+ import fs2 from "node:fs";
17
+ import path2 from "node:path";
9
18
  import glob from "tiny-glob";
19
+ import { searchForWorkspaceRoot } from "vite";
10
20
 
11
21
  // src/core/fsutils.ts
12
22
  import { promises as fs } from "node:fs";
@@ -27,12 +37,6 @@ async function makeDir(dirpath) {
27
37
  await fs.mkdir(dirpath, { recursive: true });
28
38
  }
29
39
  }
30
- async function copyDir(srcdir, dstdir) {
31
- if (!fsExtra.existsSync(srcdir)) {
32
- return;
33
- }
34
- fsExtra.copySync(srcdir, dstdir, { recursive: true, overwrite: true });
35
- }
36
40
  async function rmDir(dirpath) {
37
41
  await fs.rm(dirpath, { recursive: true, force: true });
38
42
  }
@@ -50,42 +54,114 @@ async function isDirectory(dirpath) {
50
54
  throw err;
51
55
  });
52
56
  }
57
+ function fileExists(filepath) {
58
+ return fs.access(filepath).then(() => true).catch(() => false);
59
+ }
60
+ async function directoryContains(dirpath, subpath) {
61
+ const outer = await fs.realpath(dirpath);
62
+ const inner = await fs.realpath(subpath);
63
+ const rel = path.relative(outer, inner);
64
+ return !rel.startsWith("..");
65
+ }
66
+
67
+ // src/core/elements.ts
68
+ async function getElements(rootConfig) {
69
+ var _a, _b;
70
+ const rootDir = rootConfig.rootDir;
71
+ const workspaceRoot = searchForWorkspaceRoot(rootDir);
72
+ const elementsDirs = [path2.join(rootDir, "elements")];
73
+ const elementsInclude = ((_a = rootConfig.elements) == null ? void 0 : _a.include) || [];
74
+ const excludePatterns = ((_b = rootConfig.elements) == null ? void 0 : _b.exclude) || [];
75
+ const excludeElement = (moduleId) => {
76
+ return excludePatterns.some((pattern) => Boolean(moduleId.match(pattern)));
77
+ };
78
+ for (const dirPath of elementsInclude) {
79
+ const elementsDir = path2.resolve(rootDir, dirPath);
80
+ if (!directoryContains(rootDir, elementsDir)) {
81
+ throw new Error(
82
+ `the elements dir (${dirPath}) should be within the project's workspace (${workspaceRoot})`
83
+ );
84
+ }
85
+ elementsDirs.push(elementsDir);
86
+ }
87
+ const elementMap = {};
88
+ for (const dirPath of elementsDirs) {
89
+ if (await isDirectory(dirPath)) {
90
+ const elementFiles = await glob("**/*", { cwd: dirPath });
91
+ elementFiles.forEach((file) => {
92
+ const parts = path2.parse(file);
93
+ if (isJsFile(parts.base)) {
94
+ const filePath = path2.join(dirPath, file);
95
+ const src = path2.relative(rootDir, filePath);
96
+ const realPath = fs2.realpathSync(filePath);
97
+ if (!excludeElement(src)) {
98
+ elementMap[parts.name] = {
99
+ src,
100
+ filePath,
101
+ realPath
102
+ };
103
+ }
104
+ }
105
+ });
106
+ }
107
+ }
108
+ return elementMap;
109
+ }
53
110
 
54
111
  // src/render/vite-plugin-root.ts
55
112
  var JSX_ELEMENTS_REGEX = /jsxs?\("(\w[\w-]+\w)"/g;
56
113
  var HTML_ELEMENTS_REGEX = /<(\w[\w-]+\w)/g;
57
114
  function pluginRoot(options) {
58
- const rootDir = (options == null ? void 0 : options.rootDir) || process.cwd();
59
- let elementMap;
115
+ const elementsVirtualId = "virtual:root-elements";
116
+ const resolvedElementsVirtualId = "\0" + elementsVirtualId;
117
+ const rootConfig = options.rootConfig;
118
+ let tagNameToElement;
119
+ const customElementFiles = /* @__PURE__ */ new Set();
60
120
  async function updateElementMap() {
61
- elementMap = {};
62
- const files = await glob("./elements/**/*", { cwd: rootDir });
63
- files.forEach((file) => {
64
- const parts = path2.parse(file);
65
- if (isJsFile(parts.base)) {
66
- elementMap[parts.name] = `/${file}`;
67
- }
121
+ tagNameToElement = await getElements(rootConfig);
122
+ customElementFiles.clear();
123
+ Object.values(tagNameToElement).forEach((elementModule) => {
124
+ customElementFiles.add(elementModule.filePath);
125
+ customElementFiles.add(elementModule.realPath);
68
126
  });
69
- console.log(JSON.stringify(elementMap));
70
127
  }
71
128
  async function getElementImport(tagname) {
72
- if (!elementMap) {
129
+ if (!tagNameToElement) {
73
130
  await updateElementMap();
74
131
  }
75
- if (tagname in elementMap) {
76
- return elementMap[tagname];
132
+ if (tagname in tagNameToElement) {
133
+ const elementModule = tagNameToElement[tagname];
134
+ if (elementModule.filePath === elementModule.realPath) {
135
+ return elementModule.src;
136
+ }
77
137
  }
78
138
  return null;
79
139
  }
140
+ function isCustomElement(id) {
141
+ if (!isJsFile(id)) {
142
+ return false;
143
+ }
144
+ return customElementFiles.has(id);
145
+ }
80
146
  return {
81
147
  name: "vite-plugin-root",
82
- async transform(src, id) {
83
- if (!id.startsWith(rootDir)) {
84
- return null;
148
+ resolveId(id) {
149
+ if (id === elementsVirtualId) {
150
+ return resolvedElementsVirtualId;
85
151
  }
86
- const moduleId = id.slice(rootDir.length);
87
- if (moduleId.startsWith("/elements/") && isJsFile(id)) {
88
- const idParts = path2.parse(id);
152
+ return null;
153
+ },
154
+ async load(id) {
155
+ if (id === resolvedElementsVirtualId) {
156
+ await updateElementMap();
157
+ return `export const elementsMap = ${JSON.stringify(tagNameToElement)}`;
158
+ }
159
+ return null;
160
+ },
161
+ async transform(src, id) {
162
+ if (isCustomElement(id)) {
163
+ await updateElementMap();
164
+ const idParts = path3.parse(id);
89
165
  const deps = /* @__PURE__ */ new Set();
90
166
  const tagnames = [
91
167
  ...src.matchAll(JSX_ELEMENTS_REGEX),
@@ -102,46 +178,79 @@ function pluginRoot(options) {
102
178
  deps.add(tagname);
103
179
  }
104
180
  const importUrls = [];
105
- await Promise.all(Array.from(deps).map(async (tagname) => {
106
- const importUrl = await getElementImport(tagname);
107
- if (importUrl) {
108
- importUrls.push(importUrl);
109
- }
110
- }));
181
+ await Promise.all(
182
+ Array.from(deps).map(async (tagname) => {
183
+ const importUrl = await getElementImport(tagname);
184
+ if (importUrl) {
185
+ importUrls.push(importUrl);
186
+ }
187
+ })
188
+ );
111
189
  if (importUrls.length > 0) {
112
- const importLines = importUrls.map((url) => `import '${url}';`).join("\n");
190
+ const importLines = importUrls.map((url) => `import '/${url}';`).join("\n");
113
191
  const code = `${src}
114
192
 
115
193
  // autogenerated by root:
116
194
  ${importLines}
117
195
  `;
118
- console.log(code);
119
196
  return { code };
120
197
  }
121
198
  return null;
122
199
  }
200
+ return null;
123
201
  }
124
202
  };
125
203
  }
126
204
 
127
205
  // src/render/asset-map/dev-asset-map.ts
128
- import path3 from "node:path";
206
+ import path4 from "node:path";
207
+ import { searchForWorkspaceRoot as searchForWorkspaceRoot2 } from "vite";
129
208
  var DevServerAssetMap = class {
130
- constructor(moduleGraph) {
209
+ constructor(rootConfig, moduleGraph) {
210
+ this.rootConfig = rootConfig;
131
211
  this.moduleGraph = moduleGraph;
132
212
  }
133
- async get(moduleId) {
134
- const viteModule = await this.moduleGraph.getModuleByUrl(moduleId);
135
- if (!viteModule || !viteModule.id) {
136
- return null;
213
+ async get(src) {
214
+ const file = path4.resolve(this.rootConfig.rootDir, src);
215
+ const viteModules = this.moduleGraph.getModulesByFile(file);
216
+ if (viteModules && viteModules.size > 0) {
217
+ const [viteModule] = viteModules;
218
+ return new DevServerAsset(src, {
219
+ assetMap: this,
220
+ viteModule
221
+ });
222
+ }
223
+ if (file.startsWith(this.rootConfig.rootDir)) {
224
+ const assetUrl = file.slice(this.rootConfig.rootDir.length);
225
+ return {
226
+ src,
227
+ assetUrl,
228
+ getCssDeps: async () => [],
229
+ getJsDeps: async () => [assetUrl]
230
+ };
137
231
  }
138
- return new DevServerAsset(this, viteModule);
232
+ const workspaceRoot = searchForWorkspaceRoot2(this.rootConfig.rootDir);
233
+ if (await directoryContains(workspaceRoot, file)) {
234
+ const assetUrl = `/@fs/${file}`;
235
+ return {
236
+ src,
237
+ assetUrl,
238
+ getCssDeps: async () => [],
239
+ getJsDeps: async () => [assetUrl]
240
+ };
241
+ }
242
+ console.log(`could not find asset in asset map: ${src}`);
243
+ return null;
244
+ }
245
+ filePathToSrc(file) {
246
+ return path4.relative(this.rootConfig.rootDir, file);
139
247
  }
140
248
  };
141
249
  var DevServerAsset = class {
142
- constructor(assetMap, viteModule) {
143
- this.assetMap = assetMap;
144
- this.viteModule = viteModule;
250
+ constructor(src, options) {
251
+ this.src = src;
252
+ this.assetMap = options.assetMap;
253
+ this.viteModule = options.viteModule;
145
254
  this.moduleId = this.viteModule.id;
146
255
  this.assetUrl = this.viteModule.url;
147
256
  }
@@ -171,13 +280,17 @@ var DevServerAsset = class {
171
280
  return;
172
281
  }
173
282
  visited.add(asset.moduleId);
174
- const parts = path3.parse(asset.assetUrl);
283
+ const parts = path4.parse(asset.assetUrl);
175
284
  if ([".js", ".jsx", ".ts", ".tsx"].includes(parts.ext) && asset.moduleId.includes("/elements/")) {
176
285
  urls.add(asset.assetUrl);
177
286
  }
178
287
  asset.getImportedModules().forEach((viteModule) => {
179
- if (viteModule.id) {
180
- const importedAsset = new DevServerAsset(this.assetMap, viteModule);
288
+ if (viteModule.file) {
289
+ const src = this.assetMap.filePathToSrc(viteModule.file);
290
+ const importedAsset = new DevServerAsset(src, {
291
+ assetMap: this.assetMap,
292
+ viteModule
293
+ });
181
294
  this.collectJs(importedAsset, urls, visited);
182
295
  }
183
296
  });
@@ -193,15 +306,19 @@ var DevServerAsset = class {
193
306
  return;
194
307
  }
195
308
  visited.add(asset.assetUrl);
196
- if (asset.moduleId.endsWith(".scss")) {
197
- const parts = path3.parse(asset.assetUrl);
309
+ if (asset.src.endsWith(".scss")) {
310
+ const parts = path4.parse(asset.src);
198
311
  if (!parts.name.startsWith("_")) {
199
312
  urls.add(asset.assetUrl);
200
313
  }
201
314
  }
202
315
  asset.getImportedModules().forEach((viteModule) => {
203
- if (viteModule.id) {
204
- const importedAsset = new DevServerAsset(this.assetMap, viteModule);
316
+ if (viteModule.file) {
317
+ const src = this.assetMap.filePathToSrc(viteModule.file);
318
+ const importedAsset = new DevServerAsset(src, {
319
+ assetMap: this.assetMap,
320
+ viteModule
321
+ });
205
322
  this.collectCss(importedAsset, urls, visited);
206
323
  }
207
324
  });
@@ -221,66 +338,163 @@ async function loadRootConfig(rootDir) {
221
338
  const configBundle = await bundleRequire({
222
339
  filepath: configPath
223
340
  });
224
- return configBundle.mod.default || {};
341
+ return Object.assign({}, configBundle.mod.default || {}, { rootDir });
225
342
  }
226
- return {};
343
+ return { rootDir };
227
344
  }
228
345
 
229
346
  // src/render/html-minify.ts
230
347
  import { minify } from "html-minifier-terser";
231
- async function htmlMinify(html) {
232
- const min = await minify(html, {
348
+ async function htmlMinify(html, options) {
349
+ const minifyOptions = options || {
233
350
  collapseWhitespace: true,
234
351
  removeComments: true,
235
352
  preserveLineBreaks: true
236
- });
237
- return min.trimStart();
353
+ };
354
+ try {
355
+ const min = await minify(html, minifyOptions);
356
+ return min.trimStart();
357
+ } catch (e) {
358
+ console.error("failed to minify html:", e);
359
+ return html;
360
+ }
238
361
  }
239
362
 
240
363
  // src/cli/commands/dev.ts
241
364
  import glob2 from "tiny-glob";
242
- var __dirname2 = path4.dirname(fileURLToPath(import.meta.url));
243
- async function createServer(options) {
244
- const app = express();
245
- app.use(express.static("public"));
246
- const middlewares = await getMiddlewares({ rootDir: options == null ? void 0 : options.rootDir });
247
- middlewares.forEach((middleware) => app.use(middleware));
248
- const port = parseInt(process.env.PORT || "4007");
249
- console.log("\u{1F333} Root.js");
365
+ import { dim } from "kleur/colors";
366
+
367
+ // src/core/middleware.ts
368
+ function rootProjectMiddleware(options) {
369
+ return (req, _, next) => {
370
+ req.rootConfig = Object.assign({}, options.rootConfig, {
371
+ rootDir: options.rootDir
372
+ });
373
+ next();
374
+ };
375
+ }
376
+
377
+ // src/cli/ports.ts
378
+ import { createServer } from "node:net";
379
+ function isPortOpen(port) {
380
+ return new Promise((resolve, reject) => {
381
+ const server = createServer();
382
+ server.on("error", (err) => {
383
+ if (err.code === "EADDRINUSE") {
384
+ resolve(false);
385
+ return;
386
+ }
387
+ reject(err);
388
+ });
389
+ server.on("close", () => {
390
+ resolve(true);
391
+ });
392
+ server.listen(port, () => {
393
+ server.close((err) => {
394
+ if (err) {
395
+ console.log(`error closing server: ${err}`);
396
+ reject(err);
397
+ }
398
+ });
399
+ });
400
+ });
401
+ }
402
+ async function findOpenPort(min, max) {
403
+ let port = min;
404
+ while (port <= max) {
405
+ const isOpen = await isPortOpen(port);
406
+ if (isOpen) {
407
+ return port;
408
+ }
409
+ port += 1;
410
+ }
411
+ throw new Error(`no ports open between ${min} and ${max}`);
412
+ }
413
+
414
+ // src/render/html-pretty.ts
415
+ import { default as beautify } from "js-beautify";
416
+ async function htmlPretty(html, options) {
417
+ const prettyOptions = options || {
418
+ indent_size: 0,
419
+ end_with_newline: true,
420
+ extra_liners: []
421
+ };
422
+ try {
423
+ const output = beautify.html(html, prettyOptions);
424
+ return output.trimStart();
425
+ } catch (e) {
426
+ console.error("failed to pretty html:", e);
427
+ return html;
428
+ }
429
+ }
430
+
431
+ // src/cli/commands/dev.ts
432
+ var __dirname = path5.dirname(fileURLToPath(import.meta.url));
433
+ async function dev(rootProjectDir) {
434
+ process.env.NODE_ENV = "development";
435
+ const rootDir = path5.resolve(rootProjectDir || process.cwd());
436
+ const defaultPort = parseInt(process.env.PORT || "4007");
437
+ const port = await findOpenPort(defaultPort, defaultPort + 10);
438
+ console.log();
439
+ console.log(`${dim("\u2503")} project: ${rootDir}`);
440
+ console.log(`${dim("\u2503")} server: http://localhost:${port}`);
441
+ console.log(`${dim("\u2503")} mode: development`);
250
442
  console.log();
251
- console.log(`Started server: http://localhost:${port}`);
252
- app.listen(port);
443
+ const server = await createServer2({ rootDir, port });
444
+ server.listen(port);
253
445
  }
254
- async function getMiddlewares(options) {
255
- const rootDir = (options == null ? void 0 : options.rootDir) || process.cwd();
446
+ async function createServer2(options) {
447
+ const rootDir = path5.resolve((options == null ? void 0 : options.rootDir) || process.cwd());
256
448
  const rootConfig = await loadRootConfig(rootDir);
449
+ const port = options == null ? void 0 : options.port;
450
+ const server = express();
451
+ server.disable("x-powered-by");
452
+ server.use(rootProjectMiddleware({ rootDir, rootConfig }));
453
+ server.use(await viteServerMiddleware({ rootDir, rootConfig, port }));
454
+ server.use(rootDevRendererMiddleware());
455
+ const plugins = rootConfig.plugins || [];
456
+ await configureServerPlugins(
457
+ server,
458
+ async () => {
459
+ var _a;
460
+ const userMiddlewares = ((_a = rootConfig.server) == null ? void 0 : _a.middlewares) || [];
461
+ for (const middleware of userMiddlewares) {
462
+ server.use(middleware);
463
+ }
464
+ server.use(rootDevServerMiddleware());
465
+ server.use(rootDevServer404Middleware());
466
+ },
467
+ plugins,
468
+ { type: "dev" }
469
+ );
470
+ return server;
471
+ }
472
+ async function viteServerMiddleware(options) {
473
+ var _a, _b;
474
+ const rootDir = options.rootDir;
475
+ const rootConfig = options.rootConfig;
257
476
  const viteConfig = rootConfig.vite || {};
258
- const renderModulePath = path4.resolve(__dirname2, "./render.js");
259
- const pages = [];
260
- if (await isDirectory(path4.join(rootDir, "routes"))) {
261
- const pageFiles = await glob2(path4.join(rootDir, "routes/**/*"));
477
+ let hmrOptions = (_a = viteConfig.server) == null ? void 0 : _a.hmr;
478
+ if (typeof hmrOptions === "undefined" && options.port) {
479
+ hmrOptions = { port: options.port + 10 };
480
+ }
481
+ const routeFiles = [];
482
+ if (await isDirectory(path5.join(rootDir, "routes"))) {
483
+ const pageFiles = await glob2("routes/**/*", { cwd: rootDir });
262
484
  pageFiles.forEach((file) => {
263
- const parts = path4.parse(file);
485
+ const parts = path5.parse(file);
264
486
  if (!parts.name.startsWith("_") && isJsFile(parts.base)) {
265
- pages.push(file);
266
- }
267
- });
268
- }
269
- const elements = [];
270
- if (await isDirectory(path4.join(rootDir, "elements"))) {
271
- const elementFiles = await glob2(path4.join(rootDir, "elements/**/*"));
272
- elementFiles.forEach((file) => {
273
- const parts = path4.parse(file);
274
- if (isJsFile(parts.base)) {
275
- elements.push(file);
487
+ routeFiles.push(file);
276
488
  }
277
489
  });
278
490
  }
491
+ const elementsMap = await getElements(rootConfig);
492
+ const elements = Object.values(elementsMap).map((mod) => mod.src);
279
493
  const bundleScripts = [];
280
- if (await isDirectory(path4.join(rootDir, "bundles"))) {
281
- const bundleFiles = await glob2(path4.join(rootDir, "bundles/*"));
494
+ if (await isDirectory(path5.join(rootDir, "bundles"))) {
495
+ const bundleFiles = await glob2("bundles/*", { cwd: rootDir });
282
496
  bundleFiles.forEach((file) => {
283
- const parts = path4.parse(file);
497
+ const parts = path5.parse(file);
284
498
  if (isJsFile(parts.base)) {
285
499
  bundleScripts.push(file);
286
500
  }
@@ -288,36 +502,96 @@ async function getMiddlewares(options) {
288
502
  }
289
503
  const viteServer = await createViteServer({
290
504
  ...viteConfig,
291
- server: { middlewareMode: true },
505
+ mode: "development",
506
+ root: rootDir,
507
+ publicDir: path5.join(rootDir, "public"),
508
+ server: {
509
+ ...viteConfig.server || {},
510
+ middlewareMode: true,
511
+ hmr: hmrOptions
512
+ },
292
513
  appType: "custom",
293
514
  optimizeDeps: {
294
- include: [...pages, ...elements, ...bundleScripts]
515
+ ...viteConfig.optimizeDeps || {},
516
+ include: [
517
+ ...routeFiles,
518
+ ...elements,
519
+ ...bundleScripts,
520
+ ...((_b = viteConfig.optimizeDeps) == null ? void 0 : _b.include) || []
521
+ ]
295
522
  },
296
523
  ssr: {
524
+ ...viteConfig.ssr || {},
297
525
  noExternal: ["@blinkk/root"]
298
526
  },
299
527
  esbuild: {
528
+ ...viteConfig.esbuild || {},
300
529
  jsx: "automatic",
301
530
  jsxImportSource: "preact"
302
531
  },
303
- plugins: [...viteConfig.plugins || [], pluginRoot({ rootDir, rootConfig })]
532
+ plugins: [
533
+ await pluginRoot({ rootConfig }),
534
+ ...viteConfig.plugins || [],
535
+ ...getVitePlugins(rootConfig.plugins || [])
536
+ ]
304
537
  });
305
- const rootMiddleware = async (req, res, next) => {
306
- const url = req.originalUrl;
307
- const render = await viteServer.ssrLoadModule(renderModulePath);
308
- const renderer = new render.Renderer(rootConfig);
538
+ return async (req, res, next) => {
309
539
  try {
310
- const assetMap = new DevServerAssetMap(viteServer.moduleGraph);
311
- const data = await renderer.render(url, {
312
- assetMap
313
- });
314
- const html = await htmlMinify(await viteServer.transformIndexHtml(url, data.html || ""));
540
+ req.viteServer = viteServer;
541
+ viteServer.middlewares(req, res, next);
542
+ } catch (e) {
543
+ next(e);
544
+ }
545
+ };
546
+ }
547
+ function rootDevRendererMiddleware() {
548
+ const renderModulePath = path5.resolve(__dirname, "./render.js");
549
+ return async (req, _, next) => {
550
+ const rootConfig = req.rootConfig;
551
+ const viteServer = req.viteServer;
552
+ try {
553
+ const render = await viteServer.ssrLoadModule(renderModulePath);
554
+ const assetMap = new DevServerAssetMap(
555
+ rootConfig,
556
+ viteServer.moduleGraph
557
+ );
558
+ req.renderer = new render.Renderer(rootConfig, { assetMap });
559
+ next();
560
+ } catch (e) {
561
+ next(e);
562
+ }
563
+ };
564
+ }
565
+ function rootDevServerMiddleware() {
566
+ return async (req, res, next) => {
567
+ const url = req.path;
568
+ const renderer = req.renderer;
569
+ const viteServer = req.viteServer;
570
+ const rootConfig = req.rootConfig;
571
+ try {
572
+ const data = await renderer.render(url);
573
+ if (data.notFound || !data.html) {
574
+ next();
575
+ return;
576
+ }
577
+ let html = await viteServer.transformIndexHtml(url, data.html || "");
578
+ if (rootConfig.prettyHtml !== false) {
579
+ html = await htmlPretty(html, rootConfig.prettyHtmlOptions);
580
+ }
581
+ if (rootConfig.minifyHtml !== false) {
582
+ html = await htmlMinify(html, rootConfig.minifyHtmlOptions);
583
+ }
315
584
  res.status(200).set({ "Content-Type": "text/html" }).end(html);
316
585
  } catch (e) {
317
586
  viteServer.ssrFixStacktrace(e);
587
+ console.error(e);
318
588
  try {
319
- const { html } = await renderer.renderError(e);
320
- res.status(500).set({ "Content-Type": "text/html" }).end(html);
589
+ if (renderer) {
590
+ const { html } = await renderer.renderError(e);
591
+ res.status(500).set({ "Content-Type": "text/html" }).end(html);
592
+ } else {
593
+ next(e);
594
+ }
321
595
  } catch (e2) {
322
596
  console.error("failed to render custom error");
323
597
  console.error(e2);
@@ -325,49 +599,112 @@ async function getMiddlewares(options) {
325
599
  }
326
600
  }
327
601
  };
328
- return [viteServer.middlewares, rootMiddleware];
329
602
  }
330
- function dev(rootDir) {
331
- createServer({ rootDir });
603
+ function rootDevServer404Middleware() {
604
+ return async (req, res) => {
605
+ const url = req.path;
606
+ const ext = path5.extname(url);
607
+ const renderer = req.renderer;
608
+ if (!ext) {
609
+ const data = await renderer.renderDevServer404();
610
+ const html = data.html || "";
611
+ res.status(404).set({ "Content-Type": "text/html" }).end(html);
612
+ return;
613
+ }
614
+ res.status(404).set({ "Content-Type": "text/plain" }).end("404");
615
+ };
332
616
  }
333
617
 
334
618
  // src/cli/commands/build.ts
335
- import path6 from "node:path";
619
+ import path7 from "node:path";
336
620
  import { fileURLToPath as fileURLToPath2 } from "node:url";
337
621
  import fsExtra2 from "fs-extra";
338
622
  import glob3 from "tiny-glob";
339
623
  import { build as viteBuild } from "vite";
340
624
 
341
625
  // src/render/asset-map/build-asset-map.ts
342
- import path5 from "path";
626
+ import fs3 from "node:fs";
627
+ import path6 from "node:path";
343
628
  var BuildAssetMap = class {
344
- constructor(manifest) {
345
- this.manifest = manifest;
346
- this.moduleIdToAsset = /* @__PURE__ */ new Map();
347
- Object.keys(this.manifest).forEach((manifestKey) => {
348
- const moduleId = `/${manifestKey}`;
349
- this.moduleIdToAsset.set(moduleId, new BuildAsset(this, moduleId, this.manifest[manifestKey]));
350
- });
629
+ constructor(rootConfig) {
630
+ this.rootConfig = rootConfig;
631
+ this.srcToAsset = /* @__PURE__ */ new Map();
351
632
  }
352
- async get(moduleId) {
353
- return this.moduleIdToAsset.get(moduleId) || null;
633
+ async get(src) {
634
+ const asset = this.srcToAsset.get(src);
635
+ if (asset) {
636
+ return asset;
637
+ }
638
+ const realSrc = realPathRelativeTo(this.rootConfig.rootDir, src);
639
+ if (realSrc !== src) {
640
+ const asset2 = this.srcToAsset.get(realSrc);
641
+ if (asset2) {
642
+ return asset2;
643
+ }
644
+ }
645
+ console.log(`could not find build asset: ${src}`);
646
+ return null;
647
+ }
648
+ add(asset) {
649
+ this.srcToAsset.set(asset.src, asset);
354
650
  }
355
651
  toJson() {
356
652
  const result = {};
357
- for (const moduleId of this.moduleIdToAsset.keys()) {
358
- result[moduleId] = this.moduleIdToAsset.get(moduleId).toJson();
653
+ for (const src of this.srcToAsset.keys()) {
654
+ result[src] = this.srcToAsset.get(src).toJson();
359
655
  }
360
656
  return result;
361
657
  }
658
+ static fromViteManifest(rootConfig, viteManifest, elementMap) {
659
+ const assetMap = new BuildAssetMap(rootConfig);
660
+ const elementFiles = /* @__PURE__ */ new Set();
661
+ Object.values(elementMap).forEach((elementModule) => {
662
+ elementFiles.add(elementModule.src);
663
+ const realSrc = realPathRelativeTo(rootConfig.rootDir, elementModule.src);
664
+ if (realSrc !== elementModule.src) {
665
+ elementFiles.add(realSrc);
666
+ }
667
+ });
668
+ Object.keys(viteManifest).forEach((manifestKey) => {
669
+ const src = manifestKey;
670
+ const manifestChunk = viteManifest[manifestKey];
671
+ const isElement = elementFiles.has(src);
672
+ const assetData = {
673
+ src,
674
+ assetUrl: `/${manifestChunk.file}`,
675
+ importedModules: manifestChunk.imports || [],
676
+ importedCss: (manifestChunk.css || []).map((relPath) => `/${relPath}`),
677
+ isElement
678
+ };
679
+ assetMap.add(new BuildAsset(assetMap, assetData));
680
+ });
681
+ return assetMap;
682
+ }
683
+ static fromRootManifest(rootConfig, rootManifest) {
684
+ const assetMap = new BuildAssetMap(rootConfig);
685
+ Object.keys(rootManifest).forEach((moduleId) => {
686
+ const assetData = rootManifest[moduleId];
687
+ assetMap.add(new BuildAsset(assetMap, assetData));
688
+ });
689
+ return assetMap;
690
+ }
362
691
  };
692
+ function realPathRelativeTo(rootDir, src) {
693
+ const fullPath = path6.resolve(rootDir, src);
694
+ if (!fs3.existsSync(fullPath)) {
695
+ return src;
696
+ }
697
+ const realpath = fs3.realpathSync(path6.resolve(rootDir, src));
698
+ return path6.relative(rootDir, realpath);
699
+ }
363
700
  var BuildAsset = class {
364
- constructor(assetMap, moduleId, manifestData) {
701
+ constructor(assetMap, assetData) {
365
702
  this.assetMap = assetMap;
366
- this.moduleId = moduleId;
367
- this.manifestData = manifestData;
368
- this.assetUrl = `/${manifestData.file}`;
369
- this.importedModules = (this.manifestData.imports || []).map((relPath) => `/${relPath}`);
370
- this.importedCss = (this.manifestData.css || []).map((relPath) => `/${relPath}`);
703
+ this.src = assetData.src;
704
+ this.assetUrl = assetData.assetUrl;
705
+ this.importedModules = assetData.importedModules;
706
+ this.importedCss = assetData.importedCss;
707
+ this.isElement = assetData.isElement;
371
708
  }
372
709
  async getCssDeps() {
373
710
  const visited = /* @__PURE__ */ new Set();
@@ -385,21 +722,22 @@ var BuildAsset = class {
385
722
  if (!asset) {
386
723
  return;
387
724
  }
388
- if (!asset.moduleId) {
725
+ if (!asset.src) {
389
726
  return;
390
727
  }
391
- if (visited.has(asset.moduleId)) {
728
+ if (visited.has(asset.src)) {
392
729
  return;
393
730
  }
394
- visited.add(asset.moduleId);
395
- const parts = path5.parse(asset.assetUrl);
396
- if ([".js", ".jsx", ".ts", ".tsx"].includes(parts.ext) && asset.moduleId.includes("/elements/")) {
731
+ visited.add(asset.src);
732
+ if (asset.isElement) {
397
733
  urls.add(asset.assetUrl);
398
734
  }
399
- await Promise.all(asset.importedModules.map(async (moduleId) => {
400
- const importedAsset = await this.assetMap.get(moduleId);
401
- this.collectJs(importedAsset, urls, visited);
402
- }));
735
+ await Promise.all(
736
+ asset.importedModules.map(async (src) => {
737
+ const importedAsset = await this.assetMap.get(src);
738
+ this.collectJs(importedAsset, urls, visited);
739
+ })
740
+ );
403
741
  }
404
742
  async collectCss(asset, urls, visited) {
405
743
  if (!asset) {
@@ -408,94 +746,103 @@ var BuildAsset = class {
408
746
  if (!asset.assetUrl) {
409
747
  return;
410
748
  }
411
- if (visited.has(asset.moduleId)) {
749
+ if (visited.has(asset.src)) {
412
750
  return;
413
751
  }
414
- visited.add(asset.moduleId);
752
+ visited.add(asset.src);
415
753
  if (asset.importedCss) {
416
754
  asset.importedCss.forEach((cssUrl) => urls.add(cssUrl));
417
755
  }
418
- await Promise.all(asset.importedModules.map(async (moduleId) => {
419
- const importedAsset = await this.assetMap.get(moduleId);
420
- this.collectCss(importedAsset, urls, visited);
421
- }));
756
+ await Promise.all(
757
+ asset.importedModules.map(async (moduleId) => {
758
+ const importedAsset = await this.assetMap.get(moduleId);
759
+ this.collectCss(importedAsset, urls, visited);
760
+ })
761
+ );
422
762
  }
423
763
  toJson() {
424
764
  return {
425
- moduleId: this.moduleId,
765
+ src: this.src,
426
766
  assetUrl: this.assetUrl,
427
767
  importedModules: this.importedModules,
428
- importedCss: this.importedCss
768
+ importedCss: this.importedCss,
769
+ isElement: this.isElement
429
770
  };
430
771
  }
431
772
  };
432
773
 
433
774
  // src/cli/commands/build.ts
434
- var __dirname3 = path6.dirname(fileURLToPath2(import.meta.url));
435
- async function build(rootDir) {
436
- console.log("\u{1F333} Root.js");
437
- if (!rootDir) {
438
- rootDir = process.cwd();
439
- }
775
+ import { dim as dim2, cyan } from "kleur/colors";
776
+ var __dirname2 = path7.dirname(fileURLToPath2(import.meta.url));
777
+ async function build(rootProjectDir, options) {
778
+ var _a, _b, _c, _d;
779
+ const rootDir = path7.resolve(rootProjectDir || process.cwd());
440
780
  const rootConfig = await loadRootConfig(rootDir);
441
- const distDir = path6.join(rootDir, "dist");
781
+ const distDir = path7.join(rootDir, "dist");
782
+ const ssrOnly = (options == null ? void 0 : options.ssrOnly) || false;
783
+ const mode = (options == null ? void 0 : options.mode) || "production";
784
+ console.log();
785
+ console.log(`${dim2("\u2503")} project: ${rootDir}`);
786
+ console.log(`${dim2("\u2503")} output: ${distDir}/html`);
787
+ console.log(`${dim2("\u2503")} mode: ${mode}`);
788
+ console.log();
442
789
  await rmDir(distDir);
443
790
  await makeDir(distDir);
444
- const pages = [];
445
- if (await isDirectory(path6.join(rootDir, "routes"))) {
446
- const pageFiles = await glob3(path6.join(rootDir, "routes/**/*"));
791
+ const routeFiles = [];
792
+ if (await isDirectory(path7.join(rootDir, "routes"))) {
793
+ const pageFiles = await glob3("routes/**/*", { cwd: rootDir });
447
794
  pageFiles.forEach((file) => {
448
- const parts = path6.parse(file);
795
+ const parts = path7.parse(file);
449
796
  if (!parts.name.startsWith("_") && isJsFile(parts.base)) {
450
- pages.push(file);
451
- }
452
- });
453
- }
454
- const elements = [];
455
- if (await isDirectory(path6.join(rootDir, "elements"))) {
456
- const elementFiles = await glob3(path6.join(rootDir, "elements/**/*"));
457
- elementFiles.forEach((file) => {
458
- const parts = path6.parse(file);
459
- if (isJsFile(parts.base)) {
460
- elements.push(file);
797
+ routeFiles.push(path7.resolve(rootDir, file));
461
798
  }
462
799
  });
463
800
  }
801
+ const elementsMap = await getElements(rootConfig);
802
+ const elements = Object.values(elementsMap).map(
803
+ (mod) => path7.resolve(rootDir, mod.src)
804
+ );
464
805
  const bundleScripts = [];
465
- if (await isDirectory(path6.join(rootDir, "bundles"))) {
466
- const bundleFiles = await glob3(path6.join(rootDir, "bundles/*"));
806
+ if (await isDirectory(path7.join(rootDir, "bundles"))) {
807
+ const bundleFiles = await glob3("bundles/*", { cwd: rootDir });
467
808
  bundleFiles.forEach((file) => {
468
- const parts = path6.parse(file);
809
+ const parts = path7.parse(file);
469
810
  if (isJsFile(parts.base)) {
470
- bundleScripts.push(file);
811
+ bundleScripts.push(path7.resolve(rootDir, file));
471
812
  }
472
813
  });
473
814
  }
474
815
  const viteConfig = rootConfig.vite || {};
816
+ const vitePlugins = [
817
+ pluginRoot({ rootConfig }),
818
+ ...viteConfig.plugins || [],
819
+ ...getVitePlugins(rootConfig.plugins || [])
820
+ ];
475
821
  const baseConfig = {
476
822
  ...viteConfig,
477
823
  root: rootDir,
824
+ mode,
478
825
  esbuild: {
479
826
  jsx: "automatic",
480
827
  jsxImportSource: "preact",
481
828
  treeShaking: true
482
829
  },
483
- plugins: [...viteConfig.plugins || [], pluginRoot({ rootDir, rootConfig })]
830
+ plugins: vitePlugins
484
831
  };
485
832
  await viteBuild({
486
833
  ...baseConfig,
487
- mode: "production",
488
834
  publicDir: false,
489
835
  build: {
490
836
  rollupOptions: {
491
- input: [path6.resolve(__dirname3, "./render.js")],
837
+ ...(_a = viteConfig == null ? void 0 : viteConfig.build) == null ? void 0 : _a.rollupOptions,
838
+ input: [path7.resolve(__dirname2, "./render.js")],
492
839
  output: {
493
840
  format: "esm",
494
- chunkFileNames: "chunks/[name].[hash].js",
841
+ chunkFileNames: "chunks/[name].[hash].min.js",
495
842
  assetFileNames: "assets/[name].[hash][extname]"
496
843
  }
497
844
  },
498
- outDir: path6.join(distDir, "server"),
845
+ outDir: path7.join(distDir, "server"),
499
846
  ssr: true,
500
847
  ssrManifest: false,
501
848
  cssCodeSplit: true,
@@ -510,18 +857,20 @@ async function build(rootDir) {
510
857
  });
511
858
  await viteBuild({
512
859
  ...baseConfig,
513
- mode: "production",
514
860
  publicDir: false,
515
861
  build: {
516
862
  rollupOptions: {
517
- input: [...pages, ...elements, ...bundleScripts],
863
+ ...(_b = viteConfig == null ? void 0 : viteConfig.build) == null ? void 0 : _b.rollupOptions,
864
+ input: [...routeFiles, ...elements, ...bundleScripts],
518
865
  output: {
519
866
  format: "esm",
520
- chunkFileNames: "chunks/[name].[hash].js",
521
- assetFileNames: "assets/[name].[hash][extname]"
867
+ entryFileNames: "assets/[name].[hash].min.js",
868
+ chunkFileNames: "chunks/[name].[hash].min.js",
869
+ assetFileNames: "assets/[name].[hash][extname]",
870
+ ...(_d = (_c = viteConfig == null ? void 0 : viteConfig.build) == null ? void 0 : _c.rollupOptions) == null ? void 0 : _d.output
522
871
  }
523
872
  },
524
- outDir: path6.join(distDir, "client"),
873
+ outDir: path7.join(distDir, "client"),
525
874
  ssr: false,
526
875
  ssrManifest: false,
527
876
  manifest: true,
@@ -532,45 +881,282 @@ async function build(rootDir) {
532
881
  reportCompressedSize: false
533
882
  }
534
883
  });
535
- const manifest = await loadJson(path6.join(distDir, "client/manifest.json"));
536
- const assetMap = new BuildAssetMap(manifest);
537
- writeFile(path6.join(distDir, "client/root-manifest.json"), JSON.stringify(assetMap.toJson(), null, 2));
538
- const buildDir = path6.join(distDir, "html");
539
- const publicDir = path6.join(rootDir, "public");
540
- if (fsExtra2.existsSync(path6.join(rootDir, "public"))) {
884
+ const viteManifest = await loadJson(
885
+ path7.join(distDir, "client/manifest.json")
886
+ );
887
+ const assetMap = BuildAssetMap.fromViteManifest(
888
+ rootConfig,
889
+ viteManifest,
890
+ elementsMap
891
+ );
892
+ const rootManifest = assetMap.toJson();
893
+ writeFile(
894
+ path7.join(distDir, "client/root-manifest.json"),
895
+ JSON.stringify(rootManifest, null, 2)
896
+ );
897
+ const buildDir = path7.join(distDir, "html");
898
+ const publicDir = path7.join(rootDir, "public");
899
+ if (fsExtra2.existsSync(path7.join(rootDir, "public"))) {
541
900
  fsExtra2.copySync(publicDir, buildDir);
542
901
  } else {
543
902
  makeDir(buildDir);
544
903
  }
545
- copyDir(path6.join(distDir, "client/assets"), path6.join(buildDir, "assets"));
546
- copyDir(path6.join(distDir, "client/chunks"), path6.join(buildDir, "chunks"));
547
- const render = await import(path6.join(distDir, "server/render.js"));
548
- const renderer = new render.Renderer(rootConfig);
549
- const sitemap = await renderer.getSitemap();
550
- await Promise.all(Object.keys(sitemap).map(async (urlPath) => {
551
- const { route, params } = sitemap[urlPath];
552
- const data = await renderer.renderRoute(route, {
553
- assetMap,
554
- routeParams: params
555
- });
556
- let outPath = path6.join(distDir, `html${urlPath}`, "index.html");
557
- if (outPath.endsWith("404/index.html")) {
558
- outPath = outPath.replace("404/index.html", "404.html");
904
+ console.log("\njs/css output:");
905
+ makeDir(path7.join(buildDir, "assets"));
906
+ makeDir(path7.join(buildDir, "chunks"));
907
+ Object.keys(rootManifest).forEach((src) => {
908
+ if (isRouteFile(src)) {
909
+ return;
910
+ }
911
+ const assetData = rootManifest[src];
912
+ const assetRelPath = assetData.assetUrl.slice(1);
913
+ const assetFrom = path7.join(distDir, "client", assetRelPath);
914
+ const assetTo = path7.join(buildDir, assetRelPath);
915
+ fsExtra2.copySync(assetFrom, assetTo);
916
+ printFileOutput(fileSize(assetTo), "dist/html/", assetRelPath);
917
+ });
918
+ if (!ssrOnly) {
919
+ const render = await import(path7.join(distDir, "server/render.js"));
920
+ const renderer = new render.Renderer(rootConfig, { assetMap });
921
+ const sitemap = await renderer.getSitemap();
922
+ console.log("\nhtml output:");
923
+ await Promise.all(
924
+ Object.keys(sitemap).map(async (urlPath) => {
925
+ const { route, params } = sitemap[urlPath];
926
+ const data = await renderer.renderRoute(route, {
927
+ routeParams: params
928
+ });
929
+ let outFilePath = path7.join(urlPath.slice(1), "index.html");
930
+ if (outFilePath.endsWith("404/index.html")) {
931
+ outFilePath = outFilePath.replace("404/index.html", "404.html");
932
+ }
933
+ const outPath = path7.join(buildDir, outFilePath);
934
+ let html = data.html || "";
935
+ if (rootConfig.prettyHtml !== false) {
936
+ html = await htmlPretty(html, rootConfig.prettyHtmlOptions);
937
+ }
938
+ if (rootConfig.minifyHtml !== false) {
939
+ html = await htmlMinify(html, rootConfig.minifyHtmlOptions);
940
+ }
941
+ await writeFile(outPath, html);
942
+ printFileOutput(fileSize(outPath), "dist/html/", outFilePath);
943
+ })
944
+ );
945
+ }
946
+ }
947
+ function isRouteFile(filepath) {
948
+ return filepath.startsWith("routes") && isJsFile(filepath);
949
+ }
950
+ function fileSize(filepath) {
951
+ const stats = fsExtra2.statSync(filepath);
952
+ const bytes = stats.size;
953
+ const k = 1024;
954
+ if (bytes < k) {
955
+ return (bytes / k).toFixed(2) + " kB";
956
+ }
957
+ const units = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
958
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
959
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + units[i];
960
+ }
961
+ function printFileOutput(fileSize2, outputDir, outputFile) {
962
+ const indent = " ".repeat(2);
963
+ const paddedSize = fileSize2.padStart(9, " ");
964
+ console.log(
965
+ `${indent}${dim2(paddedSize)} ${dim2(outputDir)}${cyan(outputFile)}`
966
+ );
967
+ }
968
+
969
+ // src/cli/commands/preview.ts
970
+ import path8 from "node:path";
971
+ import { default as express2 } from "express";
972
+ import { dim as dim3 } from "kleur/colors";
973
+ import sirv from "sirv";
974
+ import compression from "compression";
975
+ async function preview(rootProjectDir) {
976
+ process.env.NODE_ENV = "development";
977
+ const rootDir = path8.resolve(rootProjectDir || process.cwd());
978
+ const server = await createServer3({ rootDir });
979
+ const port = parseInt(process.env.PORT || "4007");
980
+ console.log();
981
+ console.log(`${dim3("\u2503")} project: ${rootDir}`);
982
+ console.log(`${dim3("\u2503")} server: http://localhost:${port}`);
983
+ console.log(`${dim3("\u2503")} mode: staging`);
984
+ console.log();
985
+ server.listen(port);
986
+ }
987
+ async function createServer3(options) {
988
+ const rootDir = options.rootDir;
989
+ const rootConfig = await loadRootConfig(rootDir);
990
+ const distDir = path8.join(rootDir, "dist");
991
+ const server = express2();
992
+ server.disable("x-powered-by");
993
+ server.use(compression());
994
+ server.use(rootProjectMiddleware({ rootDir, rootConfig }));
995
+ server.use(await rootPreviewRendererMiddleware({ rootConfig, distDir }));
996
+ const plugins = rootConfig.plugins || [];
997
+ configureServerPlugins(
998
+ server,
999
+ async () => {
1000
+ var _a;
1001
+ const userMiddlewares = ((_a = rootConfig.server) == null ? void 0 : _a.middlewares) || [];
1002
+ userMiddlewares.forEach((middleware) => {
1003
+ server.use(middleware);
1004
+ });
1005
+ const publicDir = path8.join(distDir, "html");
1006
+ server.use(sirv(publicDir, { dev: false }));
1007
+ server.use(rootPreviewServerMiddleware());
1008
+ },
1009
+ plugins,
1010
+ { type: "preview" }
1011
+ );
1012
+ return server;
1013
+ }
1014
+ async function rootPreviewRendererMiddleware(options) {
1015
+ const { distDir, rootConfig } = options;
1016
+ const render = await import(path8.join(distDir, "server/render.js"));
1017
+ const manifestPath = path8.join(distDir, "client/root-manifest.json");
1018
+ if (!await fileExists(manifestPath)) {
1019
+ throw new Error(
1020
+ `could not find ${manifestPath}. run \`root build\` before \`root preview\`.`
1021
+ );
1022
+ }
1023
+ const rootManifest = await loadJson(manifestPath);
1024
+ const assetMap = BuildAssetMap.fromRootManifest(rootConfig, rootManifest);
1025
+ const renderer = new render.Renderer(rootConfig, { assetMap });
1026
+ return async (req, _, next) => {
1027
+ req.renderer = renderer;
1028
+ next();
1029
+ };
1030
+ }
1031
+ function rootPreviewServerMiddleware() {
1032
+ return async (req, res, next) => {
1033
+ const rootConfig = req.rootConfig;
1034
+ const renderer = req.renderer;
1035
+ try {
1036
+ const url = req.path;
1037
+ const data = await renderer.render(url);
1038
+ if (data.notFound || !data.html) {
1039
+ next();
1040
+ return;
1041
+ }
1042
+ let html = data.html || "";
1043
+ if (rootConfig.prettyHtml !== false) {
1044
+ html = await htmlPretty(html, rootConfig.prettyHtmlOptions);
1045
+ }
1046
+ if (rootConfig.minifyHtml !== false) {
1047
+ html = await htmlMinify(html, rootConfig.minifyHtmlOptions);
1048
+ }
1049
+ res.status(200).set({ "Content-Type": "text/html" }).end(html);
1050
+ } catch (e) {
1051
+ try {
1052
+ const { html } = await renderer.renderError(e);
1053
+ res.status(500).set({ "Content-Type": "text/html" }).end(html);
1054
+ } catch (e2) {
1055
+ console.error("failed to render custom error");
1056
+ console.error(e2);
1057
+ next(e);
1058
+ }
559
1059
  }
560
- const html = await htmlMinify(data.html || "");
561
- await writeFile(outPath, html);
562
- const relPath = outPath.slice(path6.dirname(distDir).length + 1);
563
- console.log(`saved ${relPath}`);
564
- }));
1060
+ };
565
1061
  }
566
1062
 
567
1063
  // src/cli/commands/start.ts
568
- async function start() {
569
- console.log("SSR production server is coming soon");
1064
+ import path9 from "node:path";
1065
+ import { default as express3 } from "express";
1066
+ import { dim as dim4 } from "kleur/colors";
1067
+ import sirv2 from "sirv";
1068
+ import compression2 from "compression";
1069
+ async function start(rootProjectDir) {
1070
+ process.env.NODE_ENV = "production";
1071
+ const rootDir = path9.resolve(rootProjectDir || process.cwd());
1072
+ const server = await createServer4({ rootDir });
1073
+ const port = parseInt(process.env.PORT || "4007");
1074
+ console.log();
1075
+ console.log(`${dim4("\u2503")} project: ${rootDir}`);
1076
+ console.log(`${dim4("\u2503")} server: http://localhost:${port}`);
1077
+ console.log(`${dim4("\u2503")} mode: production`);
1078
+ console.log();
1079
+ server.listen(port);
1080
+ }
1081
+ async function createServer4(options) {
1082
+ const rootDir = options.rootDir;
1083
+ const rootConfig = await loadRootConfig(rootDir);
1084
+ const distDir = path9.join(rootDir, "dist");
1085
+ const server = express3();
1086
+ server.disable("x-powered-by");
1087
+ server.use(compression2());
1088
+ server.use(rootProjectMiddleware({ rootDir, rootConfig }));
1089
+ server.use(await rootProdRendererMiddleware({ rootConfig, distDir }));
1090
+ const plugins = rootConfig.plugins || [];
1091
+ configureServerPlugins(
1092
+ server,
1093
+ async () => {
1094
+ var _a;
1095
+ const userMiddlewares = ((_a = rootConfig.server) == null ? void 0 : _a.middlewares) || [];
1096
+ userMiddlewares.forEach((middleware) => {
1097
+ server.use(middleware);
1098
+ });
1099
+ const publicDir = path9.join(distDir, "html");
1100
+ server.use(sirv2(publicDir, { dev: false }));
1101
+ server.use(rootProdServerMiddleware());
1102
+ },
1103
+ plugins,
1104
+ { type: "prod" }
1105
+ );
1106
+ return server;
1107
+ }
1108
+ async function rootProdRendererMiddleware(options) {
1109
+ const { distDir, rootConfig } = options;
1110
+ const render = await import(path9.join(distDir, "server/render.js"));
1111
+ const manifestPath = path9.join(distDir, "client/root-manifest.json");
1112
+ if (!await fileExists(manifestPath)) {
1113
+ throw new Error(
1114
+ `could not find ${manifestPath}. run \`root build\` before \`root preview\`.`
1115
+ );
1116
+ }
1117
+ const rootManifest = await loadJson(manifestPath);
1118
+ const assetMap = BuildAssetMap.fromRootManifest(rootConfig, rootManifest);
1119
+ const renderer = new render.Renderer(rootConfig, { assetMap });
1120
+ return async (req, _, next) => {
1121
+ req.renderer = renderer;
1122
+ next();
1123
+ };
1124
+ }
1125
+ function rootProdServerMiddleware() {
1126
+ return async (req, res, next) => {
1127
+ const rootConfig = req.rootConfig;
1128
+ const renderer = req.renderer;
1129
+ try {
1130
+ const url = req.path;
1131
+ const data = await renderer.render(url);
1132
+ if (data.notFound || !data.html) {
1133
+ next();
1134
+ return;
1135
+ }
1136
+ let html = data.html || "";
1137
+ if (rootConfig.prettyHtml !== false) {
1138
+ html = await htmlPretty(html, rootConfig.prettyHtmlOptions);
1139
+ }
1140
+ if (rootConfig.minifyHtml !== false) {
1141
+ html = await htmlMinify(html, rootConfig.minifyHtmlOptions);
1142
+ }
1143
+ res.status(200).set({ "Content-Type": "text/html" }).end(html);
1144
+ } catch (e) {
1145
+ try {
1146
+ const { html } = await renderer.renderError(e);
1147
+ res.status(500).set({ "Content-Type": "text/html" }).end(html);
1148
+ } catch (e2) {
1149
+ console.error("failed to render custom error");
1150
+ console.error(e2);
1151
+ next(e);
1152
+ }
1153
+ }
1154
+ };
570
1155
  }
571
1156
  export {
572
1157
  build,
573
1158
  dev,
1159
+ preview,
574
1160
  start
575
1161
  };
576
1162
  //# sourceMappingURL=cli.js.map