@blinkk/root 1.0.0-alpha.2 → 1.0.0-alpha.20

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