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

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,103 +338,269 @@ 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, _c;
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
  }
287
501
  });
288
502
  }
503
+ const noExternalConfig = (_b = viteConfig.ssr) == null ? void 0 : _b.noExternal;
504
+ const noExternal = [];
505
+ if (noExternalConfig) {
506
+ if (Array.isArray(noExternalConfig)) {
507
+ noExternal.push(...noExternalConfig);
508
+ } else {
509
+ noExternal.push(noExternalConfig);
510
+ }
511
+ }
289
512
  const viteServer = await createViteServer({
290
513
  ...viteConfig,
291
- server: { middlewareMode: true },
514
+ mode: "development",
515
+ root: rootDir,
516
+ publicDir: path5.join(rootDir, "public"),
517
+ server: {
518
+ ...viteConfig.server || {},
519
+ middlewareMode: true,
520
+ hmr: hmrOptions
521
+ },
292
522
  appType: "custom",
293
523
  optimizeDeps: {
294
- include: [...pages, ...elements, ...bundleScripts]
524
+ ...viteConfig.optimizeDeps || {},
525
+ include: [
526
+ ...routeFiles,
527
+ ...elements,
528
+ ...bundleScripts,
529
+ ...((_c = viteConfig.optimizeDeps) == null ? void 0 : _c.include) || []
530
+ ]
295
531
  },
296
532
  ssr: {
297
- noExternal: ["@blinkk/root"]
533
+ ...viteConfig.ssr || {},
534
+ noExternal: ["@blinkk/root", ...noExternal]
298
535
  },
299
536
  esbuild: {
537
+ ...viteConfig.esbuild || {},
300
538
  jsx: "automatic",
301
539
  jsxImportSource: "preact"
302
540
  },
303
- plugins: [...viteConfig.plugins || [], pluginRoot({ rootDir, rootConfig })]
541
+ plugins: [
542
+ await pluginRoot({ rootConfig }),
543
+ ...viteConfig.plugins || [],
544
+ ...getVitePlugins(rootConfig.plugins || [])
545
+ ]
304
546
  });
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);
547
+ return async (req, res, next) => {
309
548
  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 || ""));
549
+ req.viteServer = viteServer;
550
+ viteServer.middlewares(req, res, next);
551
+ } catch (e) {
552
+ next(e);
553
+ }
554
+ };
555
+ }
556
+ function rootDevRendererMiddleware() {
557
+ const renderModulePath = path5.resolve(__dirname, "./render.js");
558
+ return async (req, _, next) => {
559
+ const rootConfig = req.rootConfig;
560
+ const viteServer = req.viteServer;
561
+ try {
562
+ const render = await viteServer.ssrLoadModule(renderModulePath);
563
+ const assetMap = new DevServerAssetMap(
564
+ rootConfig,
565
+ viteServer.moduleGraph
566
+ );
567
+ req.renderer = new render.Renderer(rootConfig, { assetMap });
568
+ next();
569
+ } catch (e) {
570
+ next(e);
571
+ }
572
+ };
573
+ }
574
+ function rootDevServerMiddleware() {
575
+ return async (req, res, next) => {
576
+ const url = req.path;
577
+ const renderer = req.renderer;
578
+ const viteServer = req.viteServer;
579
+ const rootConfig = req.rootConfig;
580
+ try {
581
+ const data = await renderer.render(url);
582
+ if (data.notFound || !data.html) {
583
+ next();
584
+ return;
585
+ }
586
+ let html = await viteServer.transformIndexHtml(url, data.html || "");
587
+ if (rootConfig.prettyHtml !== false) {
588
+ html = await htmlPretty(html, rootConfig.prettyHtmlOptions);
589
+ }
590
+ if (rootConfig.minifyHtml !== false) {
591
+ html = await htmlMinify(html, rootConfig.minifyHtmlOptions);
592
+ }
315
593
  res.status(200).set({ "Content-Type": "text/html" }).end(html);
316
594
  } catch (e) {
317
595
  viteServer.ssrFixStacktrace(e);
596
+ console.error(e);
318
597
  try {
319
- const { html } = await renderer.renderError(e);
320
- res.status(500).set({ "Content-Type": "text/html" }).end(html);
598
+ if (renderer) {
599
+ const { html } = await renderer.renderError(e);
600
+ res.status(500).set({ "Content-Type": "text/html" }).end(html);
601
+ } else {
602
+ next(e);
603
+ }
321
604
  } catch (e2) {
322
605
  console.error("failed to render custom error");
323
606
  console.error(e2);
@@ -325,49 +608,112 @@ async function getMiddlewares(options) {
325
608
  }
326
609
  }
327
610
  };
328
- return [viteServer.middlewares, rootMiddleware];
329
611
  }
330
- function dev(rootDir) {
331
- createServer({ rootDir });
612
+ function rootDevServer404Middleware() {
613
+ return async (req, res) => {
614
+ const url = req.path;
615
+ const ext = path5.extname(url);
616
+ const renderer = req.renderer;
617
+ if (!ext) {
618
+ const data = await renderer.renderDevServer404();
619
+ const html = data.html || "";
620
+ res.status(404).set({ "Content-Type": "text/html" }).end(html);
621
+ return;
622
+ }
623
+ res.status(404).set({ "Content-Type": "text/plain" }).end("404");
624
+ };
332
625
  }
333
626
 
334
627
  // src/cli/commands/build.ts
335
- import path6 from "node:path";
628
+ import path7 from "node:path";
336
629
  import { fileURLToPath as fileURLToPath2 } from "node:url";
337
630
  import fsExtra2 from "fs-extra";
338
631
  import glob3 from "tiny-glob";
339
632
  import { build as viteBuild } from "vite";
340
633
 
341
634
  // src/render/asset-map/build-asset-map.ts
342
- import path5 from "path";
635
+ import fs3 from "node:fs";
636
+ import path6 from "node:path";
343
637
  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
- });
638
+ constructor(rootConfig) {
639
+ this.rootConfig = rootConfig;
640
+ this.srcToAsset = /* @__PURE__ */ new Map();
351
641
  }
352
- async get(moduleId) {
353
- return this.moduleIdToAsset.get(moduleId) || null;
642
+ async get(src) {
643
+ const asset = this.srcToAsset.get(src);
644
+ if (asset) {
645
+ return asset;
646
+ }
647
+ const realSrc = realPathRelativeTo(this.rootConfig.rootDir, src);
648
+ if (realSrc !== src) {
649
+ const asset2 = this.srcToAsset.get(realSrc);
650
+ if (asset2) {
651
+ return asset2;
652
+ }
653
+ }
654
+ console.log(`could not find build asset: ${src}`);
655
+ return null;
656
+ }
657
+ add(asset) {
658
+ this.srcToAsset.set(asset.src, asset);
354
659
  }
355
660
  toJson() {
356
661
  const result = {};
357
- for (const moduleId of this.moduleIdToAsset.keys()) {
358
- result[moduleId] = this.moduleIdToAsset.get(moduleId).toJson();
662
+ for (const src of this.srcToAsset.keys()) {
663
+ result[src] = this.srcToAsset.get(src).toJson();
359
664
  }
360
665
  return result;
361
666
  }
667
+ static fromViteManifest(rootConfig, viteManifest, elementMap) {
668
+ const assetMap = new BuildAssetMap(rootConfig);
669
+ const elementFiles = /* @__PURE__ */ new Set();
670
+ Object.values(elementMap).forEach((elementModule) => {
671
+ elementFiles.add(elementModule.src);
672
+ const realSrc = realPathRelativeTo(rootConfig.rootDir, elementModule.src);
673
+ if (realSrc !== elementModule.src) {
674
+ elementFiles.add(realSrc);
675
+ }
676
+ });
677
+ Object.keys(viteManifest).forEach((manifestKey) => {
678
+ const src = manifestKey;
679
+ const manifestChunk = viteManifest[manifestKey];
680
+ const isElement = elementFiles.has(src);
681
+ const assetData = {
682
+ src,
683
+ assetUrl: `/${manifestChunk.file}`,
684
+ importedModules: manifestChunk.imports || [],
685
+ importedCss: (manifestChunk.css || []).map((relPath) => `/${relPath}`),
686
+ isElement
687
+ };
688
+ assetMap.add(new BuildAsset(assetMap, assetData));
689
+ });
690
+ return assetMap;
691
+ }
692
+ static fromRootManifest(rootConfig, rootManifest) {
693
+ const assetMap = new BuildAssetMap(rootConfig);
694
+ Object.keys(rootManifest).forEach((moduleId) => {
695
+ const assetData = rootManifest[moduleId];
696
+ assetMap.add(new BuildAsset(assetMap, assetData));
697
+ });
698
+ return assetMap;
699
+ }
362
700
  };
701
+ function realPathRelativeTo(rootDir, src) {
702
+ const fullPath = path6.resolve(rootDir, src);
703
+ if (!fs3.existsSync(fullPath)) {
704
+ return src;
705
+ }
706
+ const realpath = fs3.realpathSync(path6.resolve(rootDir, src));
707
+ return path6.relative(rootDir, realpath);
708
+ }
363
709
  var BuildAsset = class {
364
- constructor(assetMap, moduleId, manifestData) {
710
+ constructor(assetMap, assetData) {
365
711
  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}`);
712
+ this.src = assetData.src;
713
+ this.assetUrl = assetData.assetUrl;
714
+ this.importedModules = assetData.importedModules;
715
+ this.importedCss = assetData.importedCss;
716
+ this.isElement = assetData.isElement;
371
717
  }
372
718
  async getCssDeps() {
373
719
  const visited = /* @__PURE__ */ new Set();
@@ -385,21 +731,22 @@ var BuildAsset = class {
385
731
  if (!asset) {
386
732
  return;
387
733
  }
388
- if (!asset.moduleId) {
734
+ if (!asset.src) {
389
735
  return;
390
736
  }
391
- if (visited.has(asset.moduleId)) {
737
+ if (visited.has(asset.src)) {
392
738
  return;
393
739
  }
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/")) {
740
+ visited.add(asset.src);
741
+ if (asset.isElement) {
397
742
  urls.add(asset.assetUrl);
398
743
  }
399
- await Promise.all(asset.importedModules.map(async (moduleId) => {
400
- const importedAsset = await this.assetMap.get(moduleId);
401
- this.collectJs(importedAsset, urls, visited);
402
- }));
744
+ await Promise.all(
745
+ asset.importedModules.map(async (src) => {
746
+ const importedAsset = await this.assetMap.get(src);
747
+ this.collectJs(importedAsset, urls, visited);
748
+ })
749
+ );
403
750
  }
404
751
  async collectCss(asset, urls, visited) {
405
752
  if (!asset) {
@@ -408,94 +755,112 @@ var BuildAsset = class {
408
755
  if (!asset.assetUrl) {
409
756
  return;
410
757
  }
411
- if (visited.has(asset.moduleId)) {
758
+ if (visited.has(asset.src)) {
412
759
  return;
413
760
  }
414
- visited.add(asset.moduleId);
761
+ visited.add(asset.src);
415
762
  if (asset.importedCss) {
416
763
  asset.importedCss.forEach((cssUrl) => urls.add(cssUrl));
417
764
  }
418
- await Promise.all(asset.importedModules.map(async (moduleId) => {
419
- const importedAsset = await this.assetMap.get(moduleId);
420
- this.collectCss(importedAsset, urls, visited);
421
- }));
765
+ await Promise.all(
766
+ asset.importedModules.map(async (moduleId) => {
767
+ const importedAsset = await this.assetMap.get(moduleId);
768
+ this.collectCss(importedAsset, urls, visited);
769
+ })
770
+ );
422
771
  }
423
772
  toJson() {
424
773
  return {
425
- moduleId: this.moduleId,
774
+ src: this.src,
426
775
  assetUrl: this.assetUrl,
427
776
  importedModules: this.importedModules,
428
- importedCss: this.importedCss
777
+ importedCss: this.importedCss,
778
+ isElement: this.isElement
429
779
  };
430
780
  }
431
781
  };
432
782
 
433
783
  // 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
- }
784
+ import { dim as dim2, cyan } from "kleur/colors";
785
+ var __dirname2 = path7.dirname(fileURLToPath2(import.meta.url));
786
+ async function build(rootProjectDir, options) {
787
+ var _a, _b, _c, _d, _e;
788
+ const rootDir = path7.resolve(rootProjectDir || process.cwd());
440
789
  const rootConfig = await loadRootConfig(rootDir);
441
- const distDir = path6.join(rootDir, "dist");
790
+ const distDir = path7.join(rootDir, "dist");
791
+ const ssrOnly = (options == null ? void 0 : options.ssrOnly) || false;
792
+ const mode = (options == null ? void 0 : options.mode) || "production";
793
+ console.log();
794
+ console.log(`${dim2("\u2503")} project: ${rootDir}`);
795
+ console.log(`${dim2("\u2503")} output: ${distDir}/html`);
796
+ console.log(`${dim2("\u2503")} mode: ${mode}`);
797
+ console.log();
442
798
  await rmDir(distDir);
443
799
  await makeDir(distDir);
444
- const pages = [];
445
- if (await isDirectory(path6.join(rootDir, "routes"))) {
446
- const pageFiles = await glob3(path6.join(rootDir, "routes/**/*"));
800
+ const routeFiles = [];
801
+ if (await isDirectory(path7.join(rootDir, "routes"))) {
802
+ const pageFiles = await glob3("routes/**/*", { cwd: rootDir });
447
803
  pageFiles.forEach((file) => {
448
- const parts = path6.parse(file);
804
+ const parts = path7.parse(file);
449
805
  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);
806
+ routeFiles.push(path7.resolve(rootDir, file));
461
807
  }
462
808
  });
463
809
  }
810
+ const elementsMap = await getElements(rootConfig);
811
+ const elements = Object.values(elementsMap).map(
812
+ (mod) => path7.resolve(rootDir, mod.src)
813
+ );
464
814
  const bundleScripts = [];
465
- if (await isDirectory(path6.join(rootDir, "bundles"))) {
466
- const bundleFiles = await glob3(path6.join(rootDir, "bundles/*"));
815
+ if (await isDirectory(path7.join(rootDir, "bundles"))) {
816
+ const bundleFiles = await glob3("bundles/*", { cwd: rootDir });
467
817
  bundleFiles.forEach((file) => {
468
- const parts = path6.parse(file);
818
+ const parts = path7.parse(file);
469
819
  if (isJsFile(parts.base)) {
470
- bundleScripts.push(file);
820
+ bundleScripts.push(path7.resolve(rootDir, file));
471
821
  }
472
822
  });
473
823
  }
474
824
  const viteConfig = rootConfig.vite || {};
825
+ const vitePlugins = [
826
+ pluginRoot({ rootConfig }),
827
+ ...viteConfig.plugins || [],
828
+ ...getVitePlugins(rootConfig.plugins || [])
829
+ ];
475
830
  const baseConfig = {
476
831
  ...viteConfig,
477
832
  root: rootDir,
833
+ mode,
478
834
  esbuild: {
479
835
  jsx: "automatic",
480
836
  jsxImportSource: "preact",
481
837
  treeShaking: true
482
838
  },
483
- plugins: [...viteConfig.plugins || [], pluginRoot({ rootDir, rootConfig })]
839
+ plugins: vitePlugins
484
840
  };
841
+ const noExternalConfig = (_a = viteConfig.ssr) == null ? void 0 : _a.noExternal;
842
+ const noExternal = [];
843
+ if (noExternalConfig) {
844
+ if (Array.isArray(noExternalConfig)) {
845
+ noExternal.push(...noExternalConfig);
846
+ } else {
847
+ noExternal.push(noExternalConfig);
848
+ }
849
+ }
485
850
  await viteBuild({
486
851
  ...baseConfig,
487
- mode: "production",
488
852
  publicDir: false,
489
853
  build: {
490
854
  rollupOptions: {
491
- input: [path6.resolve(__dirname3, "./render.js")],
855
+ ...(_b = viteConfig == null ? void 0 : viteConfig.build) == null ? void 0 : _b.rollupOptions,
856
+ input: [path7.resolve(__dirname2, "./render.js")],
492
857
  output: {
493
858
  format: "esm",
494
- chunkFileNames: "chunks/[name].[hash].js",
859
+ chunkFileNames: "chunks/[name].[hash].min.js",
495
860
  assetFileNames: "assets/[name].[hash][extname]"
496
861
  }
497
862
  },
498
- outDir: path6.join(distDir, "server"),
863
+ outDir: path7.join(distDir, "server"),
499
864
  ssr: true,
500
865
  ssrManifest: false,
501
866
  cssCodeSplit: true,
@@ -505,23 +870,26 @@ async function build(rootDir) {
505
870
  reportCompressedSize: false
506
871
  },
507
872
  ssr: {
508
- noExternal: ["@blinkk/root"]
873
+ ...viteConfig.ssr,
874
+ noExternal: ["@blinkk/root", ...noExternal]
509
875
  }
510
876
  });
511
877
  await viteBuild({
512
878
  ...baseConfig,
513
- mode: "production",
514
879
  publicDir: false,
515
880
  build: {
516
881
  rollupOptions: {
517
- input: [...pages, ...elements, ...bundleScripts],
882
+ ...(_c = viteConfig == null ? void 0 : viteConfig.build) == null ? void 0 : _c.rollupOptions,
883
+ input: [...routeFiles, ...elements, ...bundleScripts],
518
884
  output: {
519
885
  format: "esm",
520
- chunkFileNames: "chunks/[name].[hash].js",
521
- assetFileNames: "assets/[name].[hash][extname]"
886
+ entryFileNames: "assets/[name].[hash].min.js",
887
+ chunkFileNames: "chunks/[name].[hash].min.js",
888
+ assetFileNames: "assets/[name].[hash][extname]",
889
+ ...(_e = (_d = viteConfig == null ? void 0 : viteConfig.build) == null ? void 0 : _d.rollupOptions) == null ? void 0 : _e.output
522
890
  }
523
891
  },
524
- outDir: path6.join(distDir, "client"),
892
+ outDir: path7.join(distDir, "client"),
525
893
  ssr: false,
526
894
  ssrManifest: false,
527
895
  manifest: true,
@@ -532,45 +900,282 @@ async function build(rootDir) {
532
900
  reportCompressedSize: false
533
901
  }
534
902
  });
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"))) {
903
+ const viteManifest = await loadJson(
904
+ path7.join(distDir, "client/manifest.json")
905
+ );
906
+ const assetMap = BuildAssetMap.fromViteManifest(
907
+ rootConfig,
908
+ viteManifest,
909
+ elementsMap
910
+ );
911
+ const rootManifest = assetMap.toJson();
912
+ writeFile(
913
+ path7.join(distDir, "client/root-manifest.json"),
914
+ JSON.stringify(rootManifest, null, 2)
915
+ );
916
+ const buildDir = path7.join(distDir, "html");
917
+ const publicDir = path7.join(rootDir, "public");
918
+ if (fsExtra2.existsSync(path7.join(rootDir, "public"))) {
541
919
  fsExtra2.copySync(publicDir, buildDir);
542
920
  } else {
543
921
  makeDir(buildDir);
544
922
  }
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");
923
+ console.log("\njs/css output:");
924
+ makeDir(path7.join(buildDir, "assets"));
925
+ makeDir(path7.join(buildDir, "chunks"));
926
+ Object.keys(rootManifest).forEach((src) => {
927
+ if (isRouteFile(src)) {
928
+ return;
929
+ }
930
+ const assetData = rootManifest[src];
931
+ const assetRelPath = assetData.assetUrl.slice(1);
932
+ const assetFrom = path7.join(distDir, "client", assetRelPath);
933
+ const assetTo = path7.join(buildDir, assetRelPath);
934
+ fsExtra2.copySync(assetFrom, assetTo);
935
+ printFileOutput(fileSize(assetTo), "dist/html/", assetRelPath);
936
+ });
937
+ if (!ssrOnly) {
938
+ const render = await import(path7.join(distDir, "server/render.js"));
939
+ const renderer = new render.Renderer(rootConfig, { assetMap });
940
+ const sitemap = await renderer.getSitemap();
941
+ console.log("\nhtml output:");
942
+ await Promise.all(
943
+ Object.keys(sitemap).map(async (urlPath) => {
944
+ const { route, params } = sitemap[urlPath];
945
+ const data = await renderer.renderRoute(route, {
946
+ routeParams: params
947
+ });
948
+ let outFilePath = path7.join(urlPath.slice(1), "index.html");
949
+ if (outFilePath.endsWith("404/index.html")) {
950
+ outFilePath = outFilePath.replace("404/index.html", "404.html");
951
+ }
952
+ const outPath = path7.join(buildDir, outFilePath);
953
+ let html = data.html || "";
954
+ if (rootConfig.prettyHtml !== false) {
955
+ html = await htmlPretty(html, rootConfig.prettyHtmlOptions);
956
+ }
957
+ if (rootConfig.minifyHtml !== false) {
958
+ html = await htmlMinify(html, rootConfig.minifyHtmlOptions);
959
+ }
960
+ await writeFile(outPath, html);
961
+ printFileOutput(fileSize(outPath), "dist/html/", outFilePath);
962
+ })
963
+ );
964
+ }
965
+ }
966
+ function isRouteFile(filepath) {
967
+ return filepath.startsWith("routes") && isJsFile(filepath);
968
+ }
969
+ function fileSize(filepath) {
970
+ const stats = fsExtra2.statSync(filepath);
971
+ const bytes = stats.size;
972
+ const k = 1024;
973
+ if (bytes < k) {
974
+ return (bytes / k).toFixed(2) + " kB";
975
+ }
976
+ const units = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
977
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
978
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + units[i];
979
+ }
980
+ function printFileOutput(fileSize2, outputDir, outputFile) {
981
+ const indent = " ".repeat(2);
982
+ const paddedSize = fileSize2.padStart(9, " ");
983
+ console.log(
984
+ `${indent}${dim2(paddedSize)} ${dim2(outputDir)}${cyan(outputFile)}`
985
+ );
986
+ }
987
+
988
+ // src/cli/commands/preview.ts
989
+ import path8 from "node:path";
990
+ import { default as express2 } from "express";
991
+ import { dim as dim3 } from "kleur/colors";
992
+ import sirv from "sirv";
993
+ import compression from "compression";
994
+ async function preview(rootProjectDir) {
995
+ process.env.NODE_ENV = "development";
996
+ const rootDir = path8.resolve(rootProjectDir || process.cwd());
997
+ const server = await createServer3({ rootDir });
998
+ const port = parseInt(process.env.PORT || "4007");
999
+ console.log();
1000
+ console.log(`${dim3("\u2503")} project: ${rootDir}`);
1001
+ console.log(`${dim3("\u2503")} server: http://localhost:${port}`);
1002
+ console.log(`${dim3("\u2503")} mode: staging`);
1003
+ console.log();
1004
+ server.listen(port);
1005
+ }
1006
+ async function createServer3(options) {
1007
+ const rootDir = options.rootDir;
1008
+ const rootConfig = await loadRootConfig(rootDir);
1009
+ const distDir = path8.join(rootDir, "dist");
1010
+ const server = express2();
1011
+ server.disable("x-powered-by");
1012
+ server.use(compression());
1013
+ server.use(rootProjectMiddleware({ rootDir, rootConfig }));
1014
+ server.use(await rootPreviewRendererMiddleware({ rootConfig, distDir }));
1015
+ const plugins = rootConfig.plugins || [];
1016
+ configureServerPlugins(
1017
+ server,
1018
+ async () => {
1019
+ var _a;
1020
+ const userMiddlewares = ((_a = rootConfig.server) == null ? void 0 : _a.middlewares) || [];
1021
+ userMiddlewares.forEach((middleware) => {
1022
+ server.use(middleware);
1023
+ });
1024
+ const publicDir = path8.join(distDir, "html");
1025
+ server.use(sirv(publicDir, { dev: false }));
1026
+ server.use(rootPreviewServerMiddleware());
1027
+ },
1028
+ plugins,
1029
+ { type: "preview" }
1030
+ );
1031
+ return server;
1032
+ }
1033
+ async function rootPreviewRendererMiddleware(options) {
1034
+ const { distDir, rootConfig } = options;
1035
+ const render = await import(path8.join(distDir, "server/render.js"));
1036
+ const manifestPath = path8.join(distDir, "client/root-manifest.json");
1037
+ if (!await fileExists(manifestPath)) {
1038
+ throw new Error(
1039
+ `could not find ${manifestPath}. run \`root build\` before \`root preview\`.`
1040
+ );
1041
+ }
1042
+ const rootManifest = await loadJson(manifestPath);
1043
+ const assetMap = BuildAssetMap.fromRootManifest(rootConfig, rootManifest);
1044
+ const renderer = new render.Renderer(rootConfig, { assetMap });
1045
+ return async (req, _, next) => {
1046
+ req.renderer = renderer;
1047
+ next();
1048
+ };
1049
+ }
1050
+ function rootPreviewServerMiddleware() {
1051
+ return async (req, res, next) => {
1052
+ const rootConfig = req.rootConfig;
1053
+ const renderer = req.renderer;
1054
+ try {
1055
+ const url = req.path;
1056
+ const data = await renderer.render(url);
1057
+ if (data.notFound || !data.html) {
1058
+ next();
1059
+ return;
1060
+ }
1061
+ let html = data.html || "";
1062
+ if (rootConfig.prettyHtml !== false) {
1063
+ html = await htmlPretty(html, rootConfig.prettyHtmlOptions);
1064
+ }
1065
+ if (rootConfig.minifyHtml !== false) {
1066
+ html = await htmlMinify(html, rootConfig.minifyHtmlOptions);
1067
+ }
1068
+ res.status(200).set({ "Content-Type": "text/html" }).end(html);
1069
+ } catch (e) {
1070
+ try {
1071
+ const { html } = await renderer.renderError(e);
1072
+ res.status(500).set({ "Content-Type": "text/html" }).end(html);
1073
+ } catch (e2) {
1074
+ console.error("failed to render custom error");
1075
+ console.error(e2);
1076
+ next(e);
1077
+ }
559
1078
  }
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
- }));
1079
+ };
565
1080
  }
566
1081
 
567
1082
  // src/cli/commands/start.ts
568
- async function start() {
569
- console.log("SSR production server is coming soon");
1083
+ import path9 from "node:path";
1084
+ import { default as express3 } from "express";
1085
+ import { dim as dim4 } from "kleur/colors";
1086
+ import sirv2 from "sirv";
1087
+ import compression2 from "compression";
1088
+ async function start(rootProjectDir) {
1089
+ process.env.NODE_ENV = "production";
1090
+ const rootDir = path9.resolve(rootProjectDir || process.cwd());
1091
+ const server = await createServer4({ rootDir });
1092
+ const port = parseInt(process.env.PORT || "4007");
1093
+ console.log();
1094
+ console.log(`${dim4("\u2503")} project: ${rootDir}`);
1095
+ console.log(`${dim4("\u2503")} server: http://localhost:${port}`);
1096
+ console.log(`${dim4("\u2503")} mode: production`);
1097
+ console.log();
1098
+ server.listen(port);
1099
+ }
1100
+ async function createServer4(options) {
1101
+ const rootDir = options.rootDir;
1102
+ const rootConfig = await loadRootConfig(rootDir);
1103
+ const distDir = path9.join(rootDir, "dist");
1104
+ const server = express3();
1105
+ server.disable("x-powered-by");
1106
+ server.use(compression2());
1107
+ server.use(rootProjectMiddleware({ rootDir, rootConfig }));
1108
+ server.use(await rootProdRendererMiddleware({ rootConfig, distDir }));
1109
+ const plugins = rootConfig.plugins || [];
1110
+ configureServerPlugins(
1111
+ server,
1112
+ async () => {
1113
+ var _a;
1114
+ const userMiddlewares = ((_a = rootConfig.server) == null ? void 0 : _a.middlewares) || [];
1115
+ userMiddlewares.forEach((middleware) => {
1116
+ server.use(middleware);
1117
+ });
1118
+ const publicDir = path9.join(distDir, "html");
1119
+ server.use(sirv2(publicDir, { dev: false }));
1120
+ server.use(rootProdServerMiddleware());
1121
+ },
1122
+ plugins,
1123
+ { type: "prod" }
1124
+ );
1125
+ return server;
1126
+ }
1127
+ async function rootProdRendererMiddleware(options) {
1128
+ const { distDir, rootConfig } = options;
1129
+ const render = await import(path9.join(distDir, "server/render.js"));
1130
+ const manifestPath = path9.join(distDir, "client/root-manifest.json");
1131
+ if (!await fileExists(manifestPath)) {
1132
+ throw new Error(
1133
+ `could not find ${manifestPath}. run \`root build\` before \`root preview\`.`
1134
+ );
1135
+ }
1136
+ const rootManifest = await loadJson(manifestPath);
1137
+ const assetMap = BuildAssetMap.fromRootManifest(rootConfig, rootManifest);
1138
+ const renderer = new render.Renderer(rootConfig, { assetMap });
1139
+ return async (req, _, next) => {
1140
+ req.renderer = renderer;
1141
+ next();
1142
+ };
1143
+ }
1144
+ function rootProdServerMiddleware() {
1145
+ return async (req, res, next) => {
1146
+ const rootConfig = req.rootConfig;
1147
+ const renderer = req.renderer;
1148
+ try {
1149
+ const url = req.path;
1150
+ const data = await renderer.render(url);
1151
+ if (data.notFound || !data.html) {
1152
+ next();
1153
+ return;
1154
+ }
1155
+ let html = data.html || "";
1156
+ if (rootConfig.prettyHtml !== false) {
1157
+ html = await htmlPretty(html, rootConfig.prettyHtmlOptions);
1158
+ }
1159
+ if (rootConfig.minifyHtml !== false) {
1160
+ html = await htmlMinify(html, rootConfig.minifyHtmlOptions);
1161
+ }
1162
+ res.status(200).set({ "Content-Type": "text/html" }).end(html);
1163
+ } catch (e) {
1164
+ try {
1165
+ const { html } = await renderer.renderError(e);
1166
+ res.status(500).set({ "Content-Type": "text/html" }).end(html);
1167
+ } catch (e2) {
1168
+ console.error("failed to render custom error");
1169
+ console.error(e2);
1170
+ next(e);
1171
+ }
1172
+ }
1173
+ };
570
1174
  }
571
1175
  export {
572
1176
  build,
573
1177
  dev,
1178
+ preview,
574
1179
  start
575
1180
  };
576
1181
  //# sourceMappingURL=cli.js.map