@blinkk/root 1.0.0-alpha.1 → 1.0.0-alpha.11

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,74 +1,165 @@
1
1
  // src/cli/commands/dev.ts
2
- import path3 from "node:path";
2
+ import path4 from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { default as express } from "express";
5
5
  import { createServer as createViteServer } from "vite";
6
6
 
7
7
  // src/render/vite-plugin-root.ts
8
- import path from "path";
8
+ import path2 from "path";
9
9
  import glob from "tiny-glob";
10
- var ELEMENTS_REGEX = /h\("(\w[\w-]+\w)"/g;
10
+
11
+ // src/core/fsutils.ts
12
+ import { promises as fs } from "node:fs";
13
+ import path from "node:path";
14
+ import fsExtra from "fs-extra";
15
+ function isJsFile(filename) {
16
+ return !!filename.match(/\.(j|t)sx?$/);
17
+ }
18
+ async function writeFile(filepath, content) {
19
+ const dirPath = path.dirname(filepath);
20
+ await makeDir(dirPath);
21
+ await fs.writeFile(filepath, content);
22
+ }
23
+ async function makeDir(dirpath) {
24
+ try {
25
+ await fs.access(dirpath);
26
+ } catch (e) {
27
+ await fs.mkdir(dirpath, { recursive: true });
28
+ }
29
+ }
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
+ async function rmDir(dirpath) {
37
+ await fs.rm(dirpath, { recursive: true, force: true });
38
+ }
39
+ async function loadJson(filepath) {
40
+ const content = await fs.readFile(filepath, "utf-8");
41
+ return JSON.parse(content);
42
+ }
43
+ async function isDirectory(dirpath) {
44
+ return fs.stat(dirpath).then((fsStat) => {
45
+ return fsStat.isDirectory();
46
+ }).catch((err) => {
47
+ if (err.code === "ENOENT") {
48
+ return false;
49
+ }
50
+ throw err;
51
+ });
52
+ }
53
+ function fileExists(filepath) {
54
+ return fs.access(filepath).then(() => true).catch(() => false);
55
+ }
56
+
57
+ // src/render/vite-plugin-root.ts
58
+ var JSX_ELEMENTS_REGEX = /jsxs?\("(\w[\w-]+\w)"/g;
59
+ var HTML_ELEMENTS_REGEX = /<(\w[\w-]+\w)/g;
11
60
  function pluginRoot(options) {
61
+ var _a, _b;
62
+ const elementsVirtualId = "virtual:root-elements";
63
+ const resolvedElementsVirtualId = "\0" + elementsVirtualId;
12
64
  const rootDir = (options == null ? void 0 : options.rootDir) || process.cwd();
13
- let config;
14
- let server;
65
+ const rootConfig = (options == null ? void 0 : options.rootConfig) || {};
66
+ const elementsDirs = [path2.join(rootDir, "elements")];
67
+ const includeDirs = ((_a = rootConfig.elements) == null ? void 0 : _a.include) || [];
68
+ includeDirs.forEach((dirPath) => {
69
+ const elementsDir = path2.resolve(rootDir, dirPath);
70
+ if (!elementsDir.startsWith(rootDir)) {
71
+ throw new Error(
72
+ `the elements dir (${dirPath}) should be relative to the project's root dir (${rootDir})`
73
+ );
74
+ }
75
+ elementsDirs.push(elementsDir);
76
+ });
77
+ const excludePatterns = ((_b = rootConfig.elements) == null ? void 0 : _b.exclude) || [];
78
+ const excludeElement = (moduleId) => {
79
+ return excludePatterns.some((pattern) => Boolean(moduleId.match(pattern)));
80
+ };
15
81
  let elementMap;
16
82
  async function updateElementMap() {
17
83
  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
- }
24
- });
84
+ await Promise.all(
85
+ elementsDirs.map(async (dirPath) => {
86
+ const dirExists = await isDirectory(dirPath);
87
+ if (!dirExists) {
88
+ return;
89
+ }
90
+ const files = await glob("**/*", { cwd: dirPath });
91
+ files.forEach((file) => {
92
+ const parts = path2.parse(file);
93
+ if (isJsFile(parts.base)) {
94
+ const fullPath = path2.join(dirPath, file);
95
+ const moduleId = fullPath.slice(rootDir.length);
96
+ if (!excludeElement(moduleId)) {
97
+ elementMap[parts.name] = moduleId;
98
+ }
99
+ }
100
+ });
101
+ })
102
+ );
25
103
  }
26
- async function getElementImport(tagName) {
104
+ async function getElementImport(tagname) {
27
105
  if (!elementMap) {
28
106
  await updateElementMap();
29
107
  }
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
- }
38
- }
108
+ if (tagname in elementMap) {
109
+ return elementMap[tagname];
39
110
  }
40
111
  return null;
41
112
  }
113
+ function isCustomElement(id) {
114
+ if (!isJsFile(id)) {
115
+ return false;
116
+ }
117
+ return elementsDirs.some((elementsDir) => {
118
+ return id.startsWith(elementsDir);
119
+ });
120
+ }
42
121
  return {
43
122
  name: "vite-plugin-root",
44
- configResolved(resolvedConfig) {
45
- config = resolvedConfig;
123
+ resolveId(id) {
124
+ if (id === elementsVirtualId) {
125
+ return resolvedElementsVirtualId;
126
+ }
127
+ return null;
46
128
  },
47
- configureServer(_server) {
48
- server = _server;
129
+ async load(id) {
130
+ if (id === resolvedElementsVirtualId) {
131
+ await updateElementMap();
132
+ return `export const elementsMap = ${JSON.stringify(elementMap)}`;
133
+ }
134
+ return null;
49
135
  },
50
136
  async transform(src, id) {
51
- if (id.includes("/elements/") && isJsFile(id)) {
52
- const idParts = path.parse(id);
137
+ if (isCustomElement(id)) {
138
+ const idParts = path2.parse(id);
53
139
  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("-")) {
140
+ const tagnames = [
141
+ ...src.matchAll(JSX_ELEMENTS_REGEX),
142
+ ...src.matchAll(HTML_ELEMENTS_REGEX)
143
+ ];
144
+ for (const match of tagnames) {
145
+ const tagname = match[1];
146
+ if (!tagname.includes("-")) {
58
147
  continue;
59
148
  }
60
- if (tagName === idParts.name) {
149
+ if (tagname === idParts.name) {
61
150
  continue;
62
151
  }
63
- deps.add(tagName);
152
+ deps.add(tagname);
64
153
  }
65
154
  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
- }));
155
+ await Promise.all(
156
+ Array.from(deps).map(async (tagname) => {
157
+ const importUrl = await getElementImport(tagname);
158
+ if (importUrl) {
159
+ importUrls.push(importUrl);
160
+ }
161
+ })
162
+ );
72
163
  if (importUrls.length > 0) {
73
164
  const importLines = importUrls.map((url) => `import '${url}';`).join("\n");
74
165
  const code = `${src}
@@ -80,16 +171,13 @@ ${importLines}
80
171
  }
81
172
  return null;
82
173
  }
174
+ return null;
83
175
  }
84
176
  };
85
177
  }
86
- function isJsFile(file) {
87
- return !!file.match(/\.(j|t)sx?$/);
88
- }
89
- var vite_plugin_root_default = pluginRoot;
90
178
 
91
179
  // src/render/asset-map/dev-asset-map.ts
92
- import path2 from "node:path";
180
+ import path3 from "node:path";
93
181
  var DevServerAssetMap = class {
94
182
  constructor(moduleGraph) {
95
183
  this.moduleGraph = moduleGraph;
@@ -97,7 +185,12 @@ var DevServerAssetMap = class {
97
185
  async get(moduleId) {
98
186
  const viteModule = await this.moduleGraph.getModuleByUrl(moduleId);
99
187
  if (!viteModule || !viteModule.id) {
100
- return null;
188
+ return {
189
+ moduleId,
190
+ assetUrl: moduleId,
191
+ getCssDeps: async () => [],
192
+ getJsDeps: async () => [moduleId]
193
+ };
101
194
  }
102
195
  return new DevServerAsset(this, viteModule);
103
196
  }
@@ -135,7 +228,7 @@ var DevServerAsset = class {
135
228
  return;
136
229
  }
137
230
  visited.add(asset.moduleId);
138
- const parts = path2.parse(asset.assetUrl);
231
+ const parts = path3.parse(asset.assetUrl);
139
232
  if ([".js", ".jsx", ".ts", ".tsx"].includes(parts.ext) && asset.moduleId.includes("/elements/")) {
140
233
  urls.add(asset.assetUrl);
141
234
  }
@@ -158,7 +251,7 @@ var DevServerAsset = class {
158
251
  }
159
252
  visited.add(asset.assetUrl);
160
253
  if (asset.moduleId.endsWith(".scss")) {
161
- const parts = path2.parse(asset.assetUrl);
254
+ const parts = path3.parse(asset.assetUrl);
162
255
  if (!parts.name.startsWith("_")) {
163
256
  urls.add(asset.assetUrl);
164
257
  }
@@ -193,62 +286,141 @@ async function loadRootConfig(rootDir) {
193
286
  // src/render/html-minify.ts
194
287
  import { minify } from "html-minifier-terser";
195
288
  async function htmlMinify(html) {
196
- const min = await minify(html, {
197
- collapseWhitespace: true,
198
- removeComments: true,
199
- preserveLineBreaks: true
200
- });
201
- return min.trimStart();
289
+ try {
290
+ const min = await minify(html, {
291
+ collapseWhitespace: true,
292
+ removeComments: true,
293
+ preserveLineBreaks: true
294
+ });
295
+ return min.trimStart();
296
+ } catch (e) {
297
+ console.error("failed to minify html:", e);
298
+ return html;
299
+ }
202
300
  }
203
301
 
204
302
  // src/cli/commands/dev.ts
205
- var __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
303
+ import glob2 from "tiny-glob";
304
+ import { dim } from "kleur/colors";
305
+ var __dirname = path4.dirname(fileURLToPath(import.meta.url));
206
306
  async function createServer(options) {
307
+ const rootDir = (options == null ? void 0 : options.rootDir) || process.cwd();
207
308
  const app = express();
309
+ app.disable("x-powered-by");
208
310
  app.use(express.static("public"));
209
- const middlewares = await getMiddlewares({ rootDir: options == null ? void 0 : options.rootDir });
311
+ const middlewares = await getMiddlewares({ rootDir });
210
312
  middlewares.forEach((middleware) => app.use(middleware));
211
313
  const port = parseInt(process.env.PORT || "4007");
212
- const version = process.env.npm_package_version || "dev";
213
- console.log(`\u{1F333} Root.js v${version}`);
214
314
  console.log();
215
- console.log(`Started server: http://localhost:${port}`);
315
+ console.log(`${dim("\u2503")} project: ${rootDir}`);
316
+ console.log(`${dim("\u2503")} server: http://localhost:${port}`);
317
+ console.log();
216
318
  app.listen(port);
217
319
  }
218
320
  async function getMiddlewares(options) {
219
- const rootDir = (options == null ? void 0 : options.rootDir) || process.cwd();
321
+ var _a, _b, _c;
322
+ const rootDir = path4.resolve((options == null ? void 0 : options.rootDir) || process.cwd());
220
323
  const rootConfig = await loadRootConfig(rootDir);
221
324
  const viteConfig = rootConfig.vite || {};
222
- const renderModulePath = path3.resolve(__dirname2, "./render.js");
325
+ const renderModulePath = path4.resolve(__dirname, "./render.js");
326
+ const pages = [];
327
+ if (await isDirectory(path4.join(rootDir, "routes"))) {
328
+ const pageFiles = await glob2("routes/**/*", { cwd: rootDir });
329
+ pageFiles.forEach((file) => {
330
+ const parts = path4.parse(file);
331
+ if (!parts.name.startsWith("_") && isJsFile(parts.base)) {
332
+ pages.push(file);
333
+ }
334
+ });
335
+ }
336
+ const elementsDirs = [path4.join(rootDir, "elements")];
337
+ const elementsInclude = ((_a = rootConfig.elements) == null ? void 0 : _a.include) || [];
338
+ const excludePatterns = ((_b = rootConfig.elements) == null ? void 0 : _b.exclude) || [];
339
+ const excludeElement = (moduleId) => {
340
+ return excludePatterns.some((pattern) => Boolean(moduleId.match(pattern)));
341
+ };
342
+ for (const dirPath of elementsInclude) {
343
+ const elementsDir = path4.resolve(rootDir, dirPath);
344
+ if (!elementsDir.startsWith(rootDir)) {
345
+ throw new Error(
346
+ `the elements dir (${dirPath}) should be relative to the project's root dir (${rootDir})`
347
+ );
348
+ }
349
+ elementsDirs.push(elementsDir);
350
+ }
351
+ const elements = [];
352
+ for (const dirPath of elementsDirs) {
353
+ if (await isDirectory(dirPath)) {
354
+ const elementFiles = await glob2("**/*", { cwd: dirPath });
355
+ elementFiles.forEach((file) => {
356
+ const parts = path4.parse(file);
357
+ if (isJsFile(parts.base)) {
358
+ const fullPath = path4.join(dirPath, file);
359
+ const moduleId = fullPath.slice(rootDir.length);
360
+ if (!excludeElement(moduleId)) {
361
+ elements.push(moduleId.slice(1));
362
+ }
363
+ }
364
+ });
365
+ }
366
+ }
367
+ const bundleScripts = [];
368
+ if (await isDirectory(path4.join(rootDir, "bundles"))) {
369
+ const bundleFiles = await glob2(path4.join(rootDir, "bundles/*"));
370
+ bundleFiles.forEach((file) => {
371
+ const parts = path4.parse(file);
372
+ if (isJsFile(parts.base)) {
373
+ bundleScripts.push(file);
374
+ }
375
+ });
376
+ }
223
377
  const viteServer = await createViteServer({
224
378
  ...viteConfig,
379
+ mode: "development",
380
+ root: rootDir,
225
381
  server: { middlewareMode: true },
226
382
  appType: "custom",
227
383
  optimizeDeps: {
228
- include: [renderModulePath]
384
+ include: [...pages, ...elements, ...bundleScripts]
385
+ },
386
+ ssr: {
387
+ noExternal: ["@blinkk/root"]
229
388
  },
230
389
  esbuild: {
231
390
  jsx: "automatic",
232
391
  jsxImportSource: "preact"
233
392
  },
234
- plugins: [...viteConfig.plugins || [], vite_plugin_root_default()]
393
+ plugins: [...viteConfig.plugins || [], pluginRoot({ rootDir, rootConfig })]
235
394
  });
236
395
  const rootMiddleware = async (req, res, next) => {
237
396
  const url = req.originalUrl;
238
- const render = await viteServer.ssrLoadModule(renderModulePath);
239
- const renderer = new render.Renderer(rootConfig);
397
+ let renderer = null;
240
398
  try {
399
+ const render = await viteServer.ssrLoadModule(renderModulePath);
400
+ renderer = new render.Renderer(rootConfig);
241
401
  const assetMap = new DevServerAssetMap(viteServer.moduleGraph);
242
402
  const data = await renderer.render(url, {
243
403
  assetMap
244
404
  });
245
- const html = await htmlMinify(await viteServer.transformIndexHtml(url, data.html || ""));
405
+ if (data.notFound || !data.html) {
406
+ next();
407
+ return;
408
+ }
409
+ let html = await viteServer.transformIndexHtml(url, data.html || "");
410
+ if (rootConfig.minifyHtml !== false) {
411
+ html = await htmlMinify(html);
412
+ }
246
413
  res.status(200).set({ "Content-Type": "text/html" }).end(html);
247
414
  } catch (e) {
248
415
  viteServer.ssrFixStacktrace(e);
416
+ console.error(e);
249
417
  try {
250
- const { html } = await renderer.renderError(e);
251
- res.status(500).set({ "Content-Type": "text/html" }).end(html);
418
+ if (renderer) {
419
+ const { html } = await renderer.renderError(e);
420
+ res.status(500).set({ "Content-Type": "text/html" }).end(html);
421
+ } else {
422
+ next(e);
423
+ }
252
424
  } catch (e2) {
253
425
  console.error("failed to render custom error");
254
426
  console.error(e2);
@@ -256,33 +428,51 @@ async function getMiddlewares(options) {
256
428
  }
257
429
  }
258
430
  };
259
- return [viteServer.middlewares, rootMiddleware];
431
+ const notFoundMiddleware = async (req, res) => {
432
+ const url = req.originalUrl;
433
+ const ext = path4.extname(url);
434
+ if (!ext) {
435
+ const render = await viteServer.ssrLoadModule(renderModulePath);
436
+ const renderer = new render.Renderer(rootConfig);
437
+ const data = await renderer.renderDevNotFound();
438
+ const html = data.html || "";
439
+ res.status(404).set({ "Content-Type": "text/html" }).end(html);
440
+ return;
441
+ }
442
+ res.status(404).set({ "Content-Type": "text/plain" }).end("404");
443
+ };
444
+ const userMiddlewares = ((_c = rootConfig.server) == null ? void 0 : _c.middlewares) || [];
445
+ return [
446
+ ...userMiddlewares,
447
+ viteServer.middlewares,
448
+ rootMiddleware,
449
+ notFoundMiddleware
450
+ ];
260
451
  }
261
- function dev(rootDir) {
262
- createServer({ rootDir });
452
+ async function dev(rootProjectDir) {
453
+ process.env.NODE_ENV = "development";
454
+ const rootDir = path4.resolve(rootProjectDir || process.cwd());
455
+ await createServer({ rootDir });
263
456
  }
264
457
 
265
458
  // src/cli/commands/build.ts
266
- import path6 from "node:path";
459
+ import path5 from "node:path";
267
460
  import { fileURLToPath as fileURLToPath2 } from "node:url";
268
461
  import fsExtra2 from "fs-extra";
269
- import glob2 from "tiny-glob";
462
+ import glob3 from "tiny-glob";
270
463
  import { build as viteBuild } from "vite";
271
464
 
272
465
  // src/render/asset-map/build-asset-map.ts
273
- import path4 from "path";
274
466
  var BuildAssetMap = class {
275
- constructor(manifest) {
276
- this.manifest = manifest;
467
+ constructor() {
277
468
  this.moduleIdToAsset = /* @__PURE__ */ new Map();
278
- Object.keys(this.manifest).forEach((manifestKey) => {
279
- const moduleId = `/${manifestKey}`;
280
- this.moduleIdToAsset.set(moduleId, new BuildAsset(this, moduleId, this.manifest[manifestKey]));
281
- });
282
469
  }
283
470
  async get(moduleId) {
284
471
  return this.moduleIdToAsset.get(moduleId) || null;
285
472
  }
473
+ add(asset) {
474
+ this.moduleIdToAsset.set(asset.moduleId, asset);
475
+ }
286
476
  toJson() {
287
477
  const result = {};
288
478
  for (const moduleId of this.moduleIdToAsset.keys()) {
@@ -290,15 +480,46 @@ var BuildAssetMap = class {
290
480
  }
291
481
  return result;
292
482
  }
483
+ static fromViteManifest(viteManifest, elementMap) {
484
+ const assetMap = new BuildAssetMap();
485
+ const elementModuleIds = /* @__PURE__ */ new Set();
486
+ Object.values(elementMap).forEach(
487
+ (moduleId) => elementModuleIds.add(moduleId)
488
+ );
489
+ Object.keys(viteManifest).forEach((manifestKey) => {
490
+ const moduleId = `/${manifestKey}`;
491
+ const manifestChunk = viteManifest[manifestKey];
492
+ const isElement = elementModuleIds.has(moduleId);
493
+ const assetData = {
494
+ moduleId,
495
+ assetUrl: `/${manifestChunk.file}`,
496
+ importedModules: (manifestChunk.imports || []).map(
497
+ (relPath) => `/${relPath}`
498
+ ),
499
+ importedCss: (manifestChunk.css || []).map((relPath) => `/${relPath}`),
500
+ isElement
501
+ };
502
+ assetMap.add(new BuildAsset(assetMap, assetData));
503
+ });
504
+ return assetMap;
505
+ }
506
+ static fromRootManifest(rootManifest) {
507
+ const assetMap = new BuildAssetMap();
508
+ Object.keys(rootManifest).forEach((moduleId) => {
509
+ const assetData = rootManifest[moduleId];
510
+ assetMap.add(new BuildAsset(assetMap, assetData));
511
+ });
512
+ return assetMap;
513
+ }
293
514
  };
294
515
  var BuildAsset = class {
295
- constructor(assetMap, moduleId, manifestData) {
516
+ constructor(assetMap, assetData) {
296
517
  this.assetMap = assetMap;
297
- this.moduleId = moduleId;
298
- this.manifestData = manifestData;
299
- this.assetUrl = `/${manifestData.file}`;
300
- this.importedModules = (this.manifestData.imports || []).map((relPath) => `/${relPath}`);
301
- this.importedCss = (this.manifestData.css || []).map((relPath) => `/${relPath}`);
518
+ this.moduleId = assetData.moduleId;
519
+ this.assetUrl = assetData.assetUrl;
520
+ this.importedModules = assetData.importedModules;
521
+ this.importedCss = assetData.importedCss;
522
+ this.isElement = assetData.isElement;
302
523
  }
303
524
  async getCssDeps() {
304
525
  const visited = /* @__PURE__ */ new Set();
@@ -323,14 +544,15 @@ var BuildAsset = class {
323
544
  return;
324
545
  }
325
546
  visited.add(asset.moduleId);
326
- const parts = path4.parse(asset.assetUrl);
327
- if ([".ts", ".tsx"].includes(parts.ext) && asset.moduleId.includes("/elements/")) {
547
+ if (asset.isElement) {
328
548
  urls.add(asset.assetUrl);
329
549
  }
330
- await Promise.all(asset.importedModules.map(async (moduleId) => {
331
- const importedAsset = await this.assetMap.get(moduleId);
332
- this.collectJs(importedAsset, urls, visited);
333
- }));
550
+ await Promise.all(
551
+ asset.importedModules.map(async (moduleId) => {
552
+ const importedAsset = await this.assetMap.get(moduleId);
553
+ this.collectJs(importedAsset, urls, visited);
554
+ })
555
+ );
334
556
  }
335
557
  async collectCss(asset, urls, visited) {
336
558
  if (!asset) {
@@ -346,102 +568,90 @@ var BuildAsset = class {
346
568
  if (asset.importedCss) {
347
569
  asset.importedCss.forEach((cssUrl) => urls.add(cssUrl));
348
570
  }
349
- await Promise.all(asset.importedModules.map(async (moduleId) => {
350
- const importedAsset = await this.assetMap.get(moduleId);
351
- this.collectCss(importedAsset, urls, visited);
352
- }));
571
+ await Promise.all(
572
+ asset.importedModules.map(async (moduleId) => {
573
+ const importedAsset = await this.assetMap.get(moduleId);
574
+ this.collectCss(importedAsset, urls, visited);
575
+ })
576
+ );
353
577
  }
354
578
  toJson() {
355
579
  return {
356
580
  moduleId: this.moduleId,
357
581
  assetUrl: this.assetUrl,
358
582
  importedModules: this.importedModules,
359
- importedCss: this.importedCss
583
+ importedCss: this.importedCss,
584
+ isElement: this.isElement
360
585
  };
361
586
  }
362
587
  };
363
588
 
364
- // src/core/fsutils.ts
365
- import { promises as fs } from "node:fs";
366
- import path5 from "node:path";
367
- import fsExtra from "fs-extra";
368
- function isJsFile2(file) {
369
- return !!file.match(/\.(j|t)sx?$/);
370
- }
371
- async function writeFile(filePath, content) {
372
- const dirPath = path5.dirname(filePath);
373
- await makeDir(dirPath);
374
- await fs.writeFile(filePath, content);
375
- }
376
- async function makeDir(dirPath) {
377
- try {
378
- await fs.access(dirPath);
379
- } catch (e) {
380
- await fs.mkdir(dirPath, { recursive: true });
381
- }
382
- }
383
- async function copyDir(srcDir, dstDir) {
384
- if (!fsExtra.existsSync(srcDir)) {
385
- return;
386
- }
387
- fsExtra.copySync(srcDir, dstDir, { recursive: true, overwrite: true });
388
- }
389
- async function rmDir(dirPath) {
390
- await fs.rm(dirPath, { recursive: true, force: true });
391
- }
392
- async function loadJson(filePath) {
393
- const content = await fs.readFile(filePath, "utf-8");
394
- return JSON.parse(content);
395
- }
396
- async function isDirectory(dirPath) {
397
- return fs.stat(dirPath).then((fsStat) => {
398
- return fsStat.isDirectory();
399
- }).catch((err) => {
400
- if (err.code === "ENOENT") {
401
- return false;
402
- }
403
- throw err;
404
- });
405
- }
406
-
407
589
  // src/cli/commands/build.ts
408
- var __dirname3 = path6.dirname(fileURLToPath2(import.meta.url));
409
- async function build(rootDir) {
410
- const version = process.env.npm_package_version || "dev";
411
- console.log(`\u{1F333} Root.js v${version}`);
412
- if (!rootDir) {
413
- rootDir = process.cwd();
414
- }
590
+ import { dim as dim2 } from "kleur/colors";
591
+ var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
592
+ async function build(rootProjectDir, options) {
593
+ var _a, _b;
594
+ const rootDir = path5.resolve(rootProjectDir || process.cwd());
415
595
  const rootConfig = await loadRootConfig(rootDir);
416
- const distDir = path6.join(rootDir, "dist");
596
+ const distDir = path5.join(rootDir, "dist");
597
+ const ssrOnly = (options == null ? void 0 : options.ssrOnly) || false;
598
+ const mode = (options == null ? void 0 : options.mode) || "production";
599
+ console.log();
600
+ console.log(`${dim2("\u2503")} project: ${rootDir}`);
601
+ console.log(`${dim2("\u2503")} output: ${distDir}/html`);
602
+ console.log(`${dim2("\u2503")} mode: ${mode}`);
603
+ console.log();
417
604
  await rmDir(distDir);
418
605
  await makeDir(distDir);
419
606
  const pages = [];
420
- if (await isDirectory(path6.join(rootDir, "routes"))) {
421
- const pageFiles = await glob2(path6.join(rootDir, "routes/**/*"));
607
+ if (await isDirectory(path5.join(rootDir, "routes"))) {
608
+ const pageFiles = await glob3(path5.join(rootDir, "routes/**/*"));
422
609
  pageFiles.forEach((file) => {
423
- const parts = path6.parse(file);
424
- if (!parts.name.startsWith("_") && isJsFile2(parts.base)) {
610
+ const parts = path5.parse(file);
611
+ if (!parts.name.startsWith("_") && isJsFile(parts.base)) {
425
612
  pages.push(file);
426
613
  }
427
614
  });
428
615
  }
616
+ const elementsDirs = [path5.join(rootDir, "elements")];
617
+ const elementsInclude = ((_a = rootConfig.elements) == null ? void 0 : _a.include) || [];
618
+ const excludePatterns = ((_b = rootConfig.elements) == null ? void 0 : _b.exclude) || [];
619
+ const excludeElement = (moduleId) => {
620
+ return excludePatterns.some((pattern) => Boolean(moduleId.match(pattern)));
621
+ };
622
+ for (const dirPath of elementsInclude) {
623
+ const elementsDir = path5.resolve(rootDir, dirPath);
624
+ if (!elementsDir.startsWith(rootDir)) {
625
+ throw new Error(
626
+ `the elements dir (${dirPath}) should be relative to the project's root dir (${rootDir})`
627
+ );
628
+ }
629
+ elementsDirs.push(elementsDir);
630
+ }
429
631
  const elements = [];
430
- if (await isDirectory(path6.join(rootDir, "elements"))) {
431
- const elementFiles = await glob2(path6.join(rootDir, "elements/**/*"));
432
- elementFiles.forEach((file) => {
433
- const parts = path6.parse(file);
434
- if (isJsFile2(parts.base)) {
435
- elements.push(file);
436
- }
437
- });
632
+ const elementMap = {};
633
+ for (const dirPath of elementsDirs) {
634
+ if (await isDirectory(dirPath)) {
635
+ const elementFiles = await glob3("**/*", { cwd: dirPath });
636
+ elementFiles.forEach((file) => {
637
+ const parts = path5.parse(file);
638
+ if (isJsFile(parts.base)) {
639
+ const fullPath = path5.join(dirPath, file);
640
+ const moduleId = fullPath.slice(rootDir.length);
641
+ if (!excludeElement(moduleId)) {
642
+ elements.push(fullPath);
643
+ elementMap[parts.name] = moduleId;
644
+ }
645
+ }
646
+ });
647
+ }
438
648
  }
439
649
  const bundleScripts = [];
440
- if (await isDirectory(path6.join(rootDir, "bundles"))) {
441
- const bundleFiles = await glob2(path6.join(rootDir, "bundles/*"));
650
+ if (await isDirectory(path5.join(rootDir, "bundles"))) {
651
+ const bundleFiles = await glob3(path5.join(rootDir, "bundles/*"));
442
652
  bundleFiles.forEach((file) => {
443
- const parts = path6.parse(file);
444
- if (isJsFile2(parts.base)) {
653
+ const parts = path5.parse(file);
654
+ if (isJsFile(parts.base)) {
445
655
  bundleScripts.push(file);
446
656
  }
447
657
  });
@@ -450,27 +660,27 @@ async function build(rootDir) {
450
660
  const baseConfig = {
451
661
  ...viteConfig,
452
662
  root: rootDir,
663
+ mode,
453
664
  esbuild: {
454
- jsxFactory: "h",
455
- jsxFragment: "Fragment",
456
- jsxInject: 'import {h, Fragment} from "preact";'
665
+ jsx: "automatic",
666
+ jsxImportSource: "preact",
667
+ treeShaking: true
457
668
  },
458
- plugins: [...viteConfig.plugins || [], pluginRoot()]
669
+ plugins: [...viteConfig.plugins || [], pluginRoot({ rootDir, rootConfig })]
459
670
  };
460
671
  await viteBuild({
461
672
  ...baseConfig,
462
- mode: "production",
463
673
  publicDir: false,
464
674
  build: {
465
675
  rollupOptions: {
466
- input: [path6.resolve(__dirname3, "./render.js")],
676
+ input: [path5.resolve(__dirname2, "./render.js")],
467
677
  output: {
468
678
  format: "esm",
469
679
  chunkFileNames: "chunks/[name].[hash].js",
470
680
  assetFileNames: "assets/[name].[hash][extname]"
471
681
  }
472
682
  },
473
- outDir: path6.join(distDir, "server"),
683
+ outDir: path5.join(distDir, "server"),
474
684
  ssr: true,
475
685
  ssrManifest: false,
476
686
  cssCodeSplit: true,
@@ -478,11 +688,13 @@ async function build(rootDir) {
478
688
  minify: false,
479
689
  polyfillModulePreload: false,
480
690
  reportCompressedSize: false
691
+ },
692
+ ssr: {
693
+ noExternal: ["@blinkk/root"]
481
694
  }
482
695
  });
483
696
  await viteBuild({
484
697
  ...baseConfig,
485
- mode: "production",
486
698
  publicDir: false,
487
699
  build: {
488
700
  rollupOptions: {
@@ -493,7 +705,7 @@ async function build(rootDir) {
493
705
  assetFileNames: "assets/[name].[hash][extname]"
494
706
  }
495
707
  },
496
- outDir: path6.join(distDir, "client"),
708
+ outDir: path5.join(distDir, "client"),
497
709
  ssr: false,
498
710
  ssrManifest: false,
499
711
  manifest: true,
@@ -504,45 +716,174 @@ async function build(rootDir) {
504
716
  reportCompressedSize: false
505
717
  }
506
718
  });
507
- const manifest = await loadJson(path6.join(distDir, "client/manifest.json"));
508
- const assetMap = new BuildAssetMap(manifest);
509
- writeFile(path6.join(distDir, "client/root-manifest.json"), JSON.stringify(assetMap.toJson(), null, 2));
510
- const buildDir = path6.join(distDir, "html");
511
- const publicDir = path6.join(rootDir, "public");
512
- if (fsExtra2.existsSync(path6.join(rootDir, "public"))) {
719
+ const viteManifest = await loadJson(
720
+ path5.join(distDir, "client/manifest.json")
721
+ );
722
+ const assetMap = BuildAssetMap.fromViteManifest(viteManifest, elementMap);
723
+ writeFile(
724
+ path5.join(distDir, "client/root-manifest.json"),
725
+ JSON.stringify(assetMap.toJson(), null, 2)
726
+ );
727
+ const buildDir = path5.join(distDir, "html");
728
+ const publicDir = path5.join(rootDir, "public");
729
+ if (fsExtra2.existsSync(path5.join(rootDir, "public"))) {
513
730
  fsExtra2.copySync(publicDir, buildDir);
514
731
  } else {
515
732
  makeDir(buildDir);
516
733
  }
517
- copyDir(path6.join(distDir, "client/assets"), path6.join(buildDir, "assets"));
518
- copyDir(path6.join(distDir, "client/chunks"), path6.join(buildDir, "chunks"));
734
+ copyDir(path5.join(distDir, "client/assets"), path5.join(buildDir, "assets"));
735
+ copyDir(path5.join(distDir, "client/chunks"), path5.join(buildDir, "chunks"));
736
+ if (!ssrOnly) {
737
+ const render = await import(path5.join(distDir, "server/render.js"));
738
+ const renderer = new render.Renderer(rootConfig);
739
+ const sitemap = await renderer.getSitemap();
740
+ await Promise.all(
741
+ Object.keys(sitemap).map(async (urlPath) => {
742
+ const { route, params } = sitemap[urlPath];
743
+ const data = await renderer.renderRoute(route, {
744
+ assetMap,
745
+ routeParams: params
746
+ });
747
+ let outPath = path5.join(distDir, `html${urlPath}`, "index.html");
748
+ if (outPath.endsWith("404/index.html")) {
749
+ outPath = outPath.replace("404/index.html", "404.html");
750
+ }
751
+ const html = await htmlMinify(data.html || "");
752
+ await writeFile(outPath, html);
753
+ const relPath = outPath.slice(path5.dirname(distDir).length + 1);
754
+ console.log(`saved ${relPath}`);
755
+ })
756
+ );
757
+ }
758
+ }
759
+
760
+ // src/cli/commands/preview.ts
761
+ import path6 from "node:path";
762
+ import { default as express2 } from "express";
763
+ import { dim as dim3 } from "kleur/colors";
764
+ async function preview(rootProjectDir) {
765
+ var _a;
766
+ process.env.NODE_ENV = "development";
767
+ const rootDir = path6.resolve(rootProjectDir || process.cwd());
768
+ const rootConfig = await loadRootConfig(rootDir);
769
+ const distDir = path6.join(rootDir, "dist");
519
770
  const render = await import(path6.join(distDir, "server/render.js"));
520
771
  const renderer = new render.Renderer(rootConfig);
521
- const sitemap = await renderer.getSitemap();
522
- await Promise.all(Object.keys(sitemap).map(async (urlPath) => {
523
- const { route, params } = sitemap[urlPath];
524
- const data = await renderer.renderRoute(route, {
525
- assetMap,
526
- routeParams: params
527
- });
528
- let outPath = path6.join(distDir, `html${urlPath}`, "index.html");
529
- if (outPath.endsWith("404/index.html")) {
530
- outPath = outPath.replace("404/index.html", "404.html");
531
- }
532
- const html = await htmlMinify(data.html || "");
533
- await writeFile(outPath, html);
534
- const relPath = outPath.slice(path6.dirname(distDir).length + 1);
535
- console.log(`saved ${relPath}`);
536
- }));
772
+ const manifestPath = path6.join(distDir, "client/root-manifest.json");
773
+ if (!await fileExists(manifestPath)) {
774
+ throw new Error(
775
+ `could not find ${manifestPath}. run \`root build\` before \`root preview\`.`
776
+ );
777
+ }
778
+ const rootManifest = await loadJson(manifestPath);
779
+ const assetMap = BuildAssetMap.fromRootManifest(rootManifest);
780
+ const app = express2();
781
+ app.disable("x-powered-by");
782
+ const userMiddlewares = ((_a = rootConfig.server) == null ? void 0 : _a.middlewares) || [];
783
+ userMiddlewares.forEach((middleware) => {
784
+ app.use(middleware);
785
+ });
786
+ const publicDir = path6.join(distDir, "html");
787
+ app.use(express2.static(publicDir));
788
+ app.use(async (req, res, next) => {
789
+ try {
790
+ const url = req.originalUrl;
791
+ const data = await renderer.render(url, {
792
+ assetMap
793
+ });
794
+ if (data.notFound || !data.html) {
795
+ next();
796
+ return;
797
+ }
798
+ let html = data.html || "";
799
+ if (rootConfig.minifyHtml !== false) {
800
+ html = await htmlMinify(html);
801
+ }
802
+ res.status(200).set({ "Content-Type": "text/html" }).end(html);
803
+ } catch (e) {
804
+ try {
805
+ const { html } = await renderer.renderError(e);
806
+ res.status(500).set({ "Content-Type": "text/html" }).end(html);
807
+ } catch (e2) {
808
+ console.error("failed to render custom error");
809
+ console.error(e2);
810
+ next(e);
811
+ }
812
+ }
813
+ });
814
+ const port = parseInt(process.env.PORT || "4007");
815
+ console.log();
816
+ console.log(`${dim3("\u2503")} project: ${rootDir}`);
817
+ console.log(`${dim3("\u2503")} server: http://localhost:${port}`);
818
+ console.log();
819
+ app.listen(port);
537
820
  }
538
821
 
539
822
  // src/cli/commands/start.ts
540
- async function start(rootDir) {
541
- console.log("SSR production server is coming soon");
823
+ import path7 from "node:path";
824
+ import { default as express3 } from "express";
825
+ import { dim as dim4 } from "kleur/colors";
826
+ async function start(rootProjectDir) {
827
+ var _a;
828
+ process.env.NODE_ENV = "production";
829
+ const rootDir = path7.resolve(rootProjectDir || process.cwd());
830
+ const rootConfig = await loadRootConfig(rootDir);
831
+ const distDir = path7.join(rootDir, "dist");
832
+ const render = await import(path7.join(distDir, "server/render.js"));
833
+ const renderer = new render.Renderer(rootConfig);
834
+ const manifestPath = path7.join(distDir, "client/root-manifest.json");
835
+ if (!await fileExists(manifestPath)) {
836
+ throw new Error(
837
+ `could not find ${manifestPath}. run \`root build\` before \`root start\`.`
838
+ );
839
+ }
840
+ const rootManifest = await loadJson(manifestPath);
841
+ const assetMap = BuildAssetMap.fromRootManifest(rootManifest);
842
+ const app = express3();
843
+ app.disable("x-powered-by");
844
+ const userMiddlewares = ((_a = rootConfig.server) == null ? void 0 : _a.middlewares) || [];
845
+ userMiddlewares.forEach((middleware) => {
846
+ app.use(middleware);
847
+ });
848
+ const publicDir = path7.join(distDir, "html");
849
+ app.use(express3.static(publicDir));
850
+ app.use(async (req, res, next) => {
851
+ try {
852
+ const url = req.originalUrl;
853
+ const data = await renderer.render(url, {
854
+ assetMap
855
+ });
856
+ if (data.notFound || !data.html) {
857
+ next();
858
+ return;
859
+ }
860
+ let html = data.html || "";
861
+ if (rootConfig.minifyHtml !== false) {
862
+ html = await htmlMinify(html);
863
+ }
864
+ res.status(200).set({ "Content-Type": "text/html" }).end(html);
865
+ } catch (e) {
866
+ try {
867
+ const { html } = await renderer.renderError(e);
868
+ res.status(500).set({ "Content-Type": "text/html" }).end(html);
869
+ } catch (e2) {
870
+ console.error("failed to render custom error");
871
+ console.error(e2);
872
+ next(e);
873
+ }
874
+ }
875
+ });
876
+ const port = parseInt(process.env.PORT || "4007");
877
+ console.log();
878
+ console.log(`${dim4("\u2503")} project: ${rootDir}`);
879
+ console.log(`${dim4("\u2503")} server: http://localhost:${port}`);
880
+ console.log();
881
+ app.listen(port);
542
882
  }
543
883
  export {
544
884
  build,
545
885
  dev,
886
+ preview,
546
887
  start
547
888
  };
548
889
  //# sourceMappingURL=cli.js.map