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

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,55 @@ 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
+ try {
456
+ await createServer({ rootDir });
457
+ } catch (err) {
458
+ console.error("an error occurred");
459
+ }
263
460
  }
264
461
 
265
462
  // src/cli/commands/build.ts
266
- import path6 from "node:path";
463
+ import path5 from "node:path";
267
464
  import { fileURLToPath as fileURLToPath2 } from "node:url";
268
465
  import fsExtra2 from "fs-extra";
269
- import glob2 from "tiny-glob";
466
+ import glob3 from "tiny-glob";
270
467
  import { build as viteBuild } from "vite";
271
468
 
272
469
  // src/render/asset-map/build-asset-map.ts
273
- import path4 from "path";
274
470
  var BuildAssetMap = class {
275
- constructor(manifest) {
276
- this.manifest = manifest;
471
+ constructor() {
277
472
  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
473
  }
283
474
  async get(moduleId) {
284
475
  return this.moduleIdToAsset.get(moduleId) || null;
285
476
  }
477
+ add(asset) {
478
+ this.moduleIdToAsset.set(asset.moduleId, asset);
479
+ }
286
480
  toJson() {
287
481
  const result = {};
288
482
  for (const moduleId of this.moduleIdToAsset.keys()) {
@@ -290,15 +484,46 @@ var BuildAssetMap = class {
290
484
  }
291
485
  return result;
292
486
  }
487
+ static fromViteManifest(viteManifest, elementMap) {
488
+ const assetMap = new BuildAssetMap();
489
+ const elementModuleIds = /* @__PURE__ */ new Set();
490
+ Object.values(elementMap).forEach(
491
+ (moduleId) => elementModuleIds.add(moduleId)
492
+ );
493
+ Object.keys(viteManifest).forEach((manifestKey) => {
494
+ const moduleId = `/${manifestKey}`;
495
+ const manifestChunk = viteManifest[manifestKey];
496
+ const isElement = elementModuleIds.has(moduleId);
497
+ const assetData = {
498
+ moduleId,
499
+ assetUrl: `/${manifestChunk.file}`,
500
+ importedModules: (manifestChunk.imports || []).map(
501
+ (relPath) => `/${relPath}`
502
+ ),
503
+ importedCss: (manifestChunk.css || []).map((relPath) => `/${relPath}`),
504
+ isElement
505
+ };
506
+ assetMap.add(new BuildAsset(assetMap, assetData));
507
+ });
508
+ return assetMap;
509
+ }
510
+ static fromRootManifest(rootManifest) {
511
+ const assetMap = new BuildAssetMap();
512
+ Object.keys(rootManifest).forEach((moduleId) => {
513
+ const assetData = rootManifest[moduleId];
514
+ assetMap.add(new BuildAsset(assetMap, assetData));
515
+ });
516
+ return assetMap;
517
+ }
293
518
  };
294
519
  var BuildAsset = class {
295
- constructor(assetMap, moduleId, manifestData) {
520
+ constructor(assetMap, assetData) {
296
521
  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}`);
522
+ this.moduleId = assetData.moduleId;
523
+ this.assetUrl = assetData.assetUrl;
524
+ this.importedModules = assetData.importedModules;
525
+ this.importedCss = assetData.importedCss;
526
+ this.isElement = assetData.isElement;
302
527
  }
303
528
  async getCssDeps() {
304
529
  const visited = /* @__PURE__ */ new Set();
@@ -323,14 +548,15 @@ var BuildAsset = class {
323
548
  return;
324
549
  }
325
550
  visited.add(asset.moduleId);
326
- const parts = path4.parse(asset.assetUrl);
327
- if ([".ts", ".tsx"].includes(parts.ext) && asset.moduleId.includes("/elements/")) {
551
+ if (asset.isElement) {
328
552
  urls.add(asset.assetUrl);
329
553
  }
330
- await Promise.all(asset.importedModules.map(async (moduleId) => {
331
- const importedAsset = await this.assetMap.get(moduleId);
332
- this.collectJs(importedAsset, urls, visited);
333
- }));
554
+ await Promise.all(
555
+ asset.importedModules.map(async (moduleId) => {
556
+ const importedAsset = await this.assetMap.get(moduleId);
557
+ this.collectJs(importedAsset, urls, visited);
558
+ })
559
+ );
334
560
  }
335
561
  async collectCss(asset, urls, visited) {
336
562
  if (!asset) {
@@ -346,102 +572,90 @@ var BuildAsset = class {
346
572
  if (asset.importedCss) {
347
573
  asset.importedCss.forEach((cssUrl) => urls.add(cssUrl));
348
574
  }
349
- await Promise.all(asset.importedModules.map(async (moduleId) => {
350
- const importedAsset = await this.assetMap.get(moduleId);
351
- this.collectCss(importedAsset, urls, visited);
352
- }));
575
+ await Promise.all(
576
+ asset.importedModules.map(async (moduleId) => {
577
+ const importedAsset = await this.assetMap.get(moduleId);
578
+ this.collectCss(importedAsset, urls, visited);
579
+ })
580
+ );
353
581
  }
354
582
  toJson() {
355
583
  return {
356
584
  moduleId: this.moduleId,
357
585
  assetUrl: this.assetUrl,
358
586
  importedModules: this.importedModules,
359
- importedCss: this.importedCss
587
+ importedCss: this.importedCss,
588
+ isElement: this.isElement
360
589
  };
361
590
  }
362
591
  };
363
592
 
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
593
  // 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
- }
594
+ import { dim as dim2 } from "kleur/colors";
595
+ var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
596
+ async function build(rootProjectDir, options) {
597
+ var _a, _b;
598
+ const rootDir = path5.resolve(rootProjectDir || process.cwd());
415
599
  const rootConfig = await loadRootConfig(rootDir);
416
- const distDir = path6.join(rootDir, "dist");
600
+ const distDir = path5.join(rootDir, "dist");
601
+ const ssrOnly = (options == null ? void 0 : options.ssrOnly) || false;
602
+ const mode = (options == null ? void 0 : options.mode) || "production";
603
+ console.log();
604
+ console.log(`${dim2("\u2503")} project: ${rootDir}`);
605
+ console.log(`${dim2("\u2503")} output: ${distDir}/html`);
606
+ console.log(`${dim2("\u2503")} mode: ${mode}`);
607
+ console.log();
417
608
  await rmDir(distDir);
418
609
  await makeDir(distDir);
419
610
  const pages = [];
420
- if (await isDirectory(path6.join(rootDir, "routes"))) {
421
- const pageFiles = await glob2(path6.join(rootDir, "routes/**/*"));
611
+ if (await isDirectory(path5.join(rootDir, "routes"))) {
612
+ const pageFiles = await glob3(path5.join(rootDir, "routes/**/*"));
422
613
  pageFiles.forEach((file) => {
423
- const parts = path6.parse(file);
424
- if (!parts.name.startsWith("_") && isJsFile2(parts.base)) {
614
+ const parts = path5.parse(file);
615
+ if (!parts.name.startsWith("_") && isJsFile(parts.base)) {
425
616
  pages.push(file);
426
617
  }
427
618
  });
428
619
  }
620
+ const elementsDirs = [path5.join(rootDir, "elements")];
621
+ const elementsInclude = ((_a = rootConfig.elements) == null ? void 0 : _a.include) || [];
622
+ const excludePatterns = ((_b = rootConfig.elements) == null ? void 0 : _b.exclude) || [];
623
+ const excludeElement = (moduleId) => {
624
+ return excludePatterns.some((pattern) => Boolean(moduleId.match(pattern)));
625
+ };
626
+ for (const dirPath of elementsInclude) {
627
+ const elementsDir = path5.resolve(rootDir, dirPath);
628
+ if (!elementsDir.startsWith(rootDir)) {
629
+ throw new Error(
630
+ `the elements dir (${dirPath}) should be relative to the project's root dir (${rootDir})`
631
+ );
632
+ }
633
+ elementsDirs.push(elementsDir);
634
+ }
429
635
  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
- });
636
+ const elementMap = {};
637
+ for (const dirPath of elementsDirs) {
638
+ if (await isDirectory(dirPath)) {
639
+ const elementFiles = await glob3("**/*", { cwd: dirPath });
640
+ elementFiles.forEach((file) => {
641
+ const parts = path5.parse(file);
642
+ if (isJsFile(parts.base)) {
643
+ const fullPath = path5.join(dirPath, file);
644
+ const moduleId = fullPath.slice(rootDir.length);
645
+ if (!excludeElement(moduleId)) {
646
+ elements.push(fullPath);
647
+ elementMap[parts.name] = moduleId;
648
+ }
649
+ }
650
+ });
651
+ }
438
652
  }
439
653
  const bundleScripts = [];
440
- if (await isDirectory(path6.join(rootDir, "bundles"))) {
441
- const bundleFiles = await glob2(path6.join(rootDir, "bundles/*"));
654
+ if (await isDirectory(path5.join(rootDir, "bundles"))) {
655
+ const bundleFiles = await glob3(path5.join(rootDir, "bundles/*"));
442
656
  bundleFiles.forEach((file) => {
443
- const parts = path6.parse(file);
444
- if (isJsFile2(parts.base)) {
657
+ const parts = path5.parse(file);
658
+ if (isJsFile(parts.base)) {
445
659
  bundleScripts.push(file);
446
660
  }
447
661
  });
@@ -450,27 +664,27 @@ async function build(rootDir) {
450
664
  const baseConfig = {
451
665
  ...viteConfig,
452
666
  root: rootDir,
667
+ mode,
453
668
  esbuild: {
454
- jsxFactory: "h",
455
- jsxFragment: "Fragment",
456
- jsxInject: 'import {h, Fragment} from "preact";'
669
+ jsx: "automatic",
670
+ jsxImportSource: "preact",
671
+ treeShaking: true
457
672
  },
458
- plugins: [...viteConfig.plugins || [], pluginRoot()]
673
+ plugins: [...viteConfig.plugins || [], pluginRoot({ rootDir, rootConfig })]
459
674
  };
460
675
  await viteBuild({
461
676
  ...baseConfig,
462
- mode: "production",
463
677
  publicDir: false,
464
678
  build: {
465
679
  rollupOptions: {
466
- input: [path6.resolve(__dirname3, "./render.js")],
680
+ input: [path5.resolve(__dirname2, "./render.js")],
467
681
  output: {
468
682
  format: "esm",
469
683
  chunkFileNames: "chunks/[name].[hash].js",
470
684
  assetFileNames: "assets/[name].[hash][extname]"
471
685
  }
472
686
  },
473
- outDir: path6.join(distDir, "server"),
687
+ outDir: path5.join(distDir, "server"),
474
688
  ssr: true,
475
689
  ssrManifest: false,
476
690
  cssCodeSplit: true,
@@ -478,11 +692,13 @@ async function build(rootDir) {
478
692
  minify: false,
479
693
  polyfillModulePreload: false,
480
694
  reportCompressedSize: false
695
+ },
696
+ ssr: {
697
+ noExternal: ["@blinkk/root"]
481
698
  }
482
699
  });
483
700
  await viteBuild({
484
701
  ...baseConfig,
485
- mode: "production",
486
702
  publicDir: false,
487
703
  build: {
488
704
  rollupOptions: {
@@ -493,7 +709,7 @@ async function build(rootDir) {
493
709
  assetFileNames: "assets/[name].[hash][extname]"
494
710
  }
495
711
  },
496
- outDir: path6.join(distDir, "client"),
712
+ outDir: path5.join(distDir, "client"),
497
713
  ssr: false,
498
714
  ssrManifest: false,
499
715
  manifest: true,
@@ -504,45 +720,174 @@ async function build(rootDir) {
504
720
  reportCompressedSize: false
505
721
  }
506
722
  });
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"))) {
723
+ const viteManifest = await loadJson(
724
+ path5.join(distDir, "client/manifest.json")
725
+ );
726
+ const assetMap = BuildAssetMap.fromViteManifest(viteManifest, elementMap);
727
+ writeFile(
728
+ path5.join(distDir, "client/root-manifest.json"),
729
+ JSON.stringify(assetMap.toJson(), null, 2)
730
+ );
731
+ const buildDir = path5.join(distDir, "html");
732
+ const publicDir = path5.join(rootDir, "public");
733
+ if (fsExtra2.existsSync(path5.join(rootDir, "public"))) {
513
734
  fsExtra2.copySync(publicDir, buildDir);
514
735
  } else {
515
736
  makeDir(buildDir);
516
737
  }
517
- copyDir(path6.join(distDir, "client/assets"), path6.join(buildDir, "assets"));
518
- copyDir(path6.join(distDir, "client/chunks"), path6.join(buildDir, "chunks"));
738
+ copyDir(path5.join(distDir, "client/assets"), path5.join(buildDir, "assets"));
739
+ copyDir(path5.join(distDir, "client/chunks"), path5.join(buildDir, "chunks"));
740
+ if (!ssrOnly) {
741
+ const render = await import(path5.join(distDir, "server/render.js"));
742
+ const renderer = new render.Renderer(rootConfig);
743
+ const sitemap = await renderer.getSitemap();
744
+ await Promise.all(
745
+ Object.keys(sitemap).map(async (urlPath) => {
746
+ const { route, params } = sitemap[urlPath];
747
+ const data = await renderer.renderRoute(route, {
748
+ assetMap,
749
+ routeParams: params
750
+ });
751
+ let outPath = path5.join(distDir, `html${urlPath}`, "index.html");
752
+ if (outPath.endsWith("404/index.html")) {
753
+ outPath = outPath.replace("404/index.html", "404.html");
754
+ }
755
+ const html = await htmlMinify(data.html || "");
756
+ await writeFile(outPath, html);
757
+ const relPath = outPath.slice(path5.dirname(distDir).length + 1);
758
+ console.log(`saved ${relPath}`);
759
+ })
760
+ );
761
+ }
762
+ }
763
+
764
+ // src/cli/commands/preview.ts
765
+ import path6 from "node:path";
766
+ import { default as express2 } from "express";
767
+ import { dim as dim3 } from "kleur/colors";
768
+ async function preview(rootProjectDir) {
769
+ var _a;
770
+ process.env.NODE_ENV = "development";
771
+ const rootDir = path6.resolve(rootProjectDir || process.cwd());
772
+ const rootConfig = await loadRootConfig(rootDir);
773
+ const distDir = path6.join(rootDir, "dist");
519
774
  const render = await import(path6.join(distDir, "server/render.js"));
520
775
  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
- }));
776
+ const manifestPath = path6.join(distDir, "client/root-manifest.json");
777
+ if (!await fileExists(manifestPath)) {
778
+ throw new Error(
779
+ `could not find ${manifestPath}. run \`root build\` before \`root preview\`.`
780
+ );
781
+ }
782
+ const rootManifest = await loadJson(manifestPath);
783
+ const assetMap = BuildAssetMap.fromRootManifest(rootManifest);
784
+ const app = express2();
785
+ app.disable("x-powered-by");
786
+ const userMiddlewares = ((_a = rootConfig.server) == null ? void 0 : _a.middlewares) || [];
787
+ userMiddlewares.forEach((middleware) => {
788
+ app.use(middleware);
789
+ });
790
+ const publicDir = path6.join(distDir, "html");
791
+ app.use(express2.static(publicDir));
792
+ app.use(async (req, res, next) => {
793
+ try {
794
+ const url = req.originalUrl;
795
+ const data = await renderer.render(url, {
796
+ assetMap
797
+ });
798
+ if (data.notFound || !data.html) {
799
+ next();
800
+ return;
801
+ }
802
+ let html = data.html || "";
803
+ if (rootConfig.minifyHtml !== false) {
804
+ html = await htmlMinify(html);
805
+ }
806
+ res.status(200).set({ "Content-Type": "text/html" }).end(html);
807
+ } catch (e) {
808
+ try {
809
+ const { html } = await renderer.renderError(e);
810
+ res.status(500).set({ "Content-Type": "text/html" }).end(html);
811
+ } catch (e2) {
812
+ console.error("failed to render custom error");
813
+ console.error(e2);
814
+ next(e);
815
+ }
816
+ }
817
+ });
818
+ const port = parseInt(process.env.PORT || "4007");
819
+ console.log();
820
+ console.log(`${dim3("\u2503")} project: ${rootDir}`);
821
+ console.log(`${dim3("\u2503")} server: http://localhost:${port}`);
822
+ console.log();
823
+ app.listen(port);
537
824
  }
538
825
 
539
826
  // src/cli/commands/start.ts
540
- async function start(rootDir) {
541
- console.log("SSR production server is coming soon");
827
+ import path7 from "node:path";
828
+ import { default as express3 } from "express";
829
+ import { dim as dim4 } from "kleur/colors";
830
+ async function start(rootProjectDir) {
831
+ var _a;
832
+ process.env.NODE_ENV = "production";
833
+ const rootDir = path7.resolve(rootProjectDir || process.cwd());
834
+ const rootConfig = await loadRootConfig(rootDir);
835
+ const distDir = path7.join(rootDir, "dist");
836
+ const render = await import(path7.join(distDir, "server/render.js"));
837
+ const renderer = new render.Renderer(rootConfig);
838
+ const manifestPath = path7.join(distDir, "client/root-manifest.json");
839
+ if (!await fileExists(manifestPath)) {
840
+ throw new Error(
841
+ `could not find ${manifestPath}. run \`root build\` before \`root start\`.`
842
+ );
843
+ }
844
+ const rootManifest = await loadJson(manifestPath);
845
+ const assetMap = BuildAssetMap.fromRootManifest(rootManifest);
846
+ const app = express3();
847
+ app.disable("x-powered-by");
848
+ const userMiddlewares = ((_a = rootConfig.server) == null ? void 0 : _a.middlewares) || [];
849
+ userMiddlewares.forEach((middleware) => {
850
+ app.use(middleware);
851
+ });
852
+ const publicDir = path7.join(distDir, "html");
853
+ app.use(express3.static(publicDir));
854
+ app.use(async (req, res, next) => {
855
+ try {
856
+ const url = req.originalUrl;
857
+ const data = await renderer.render(url, {
858
+ assetMap
859
+ });
860
+ if (data.notFound || !data.html) {
861
+ next();
862
+ return;
863
+ }
864
+ let html = data.html || "";
865
+ if (rootConfig.minifyHtml !== false) {
866
+ html = await htmlMinify(html);
867
+ }
868
+ res.status(200).set({ "Content-Type": "text/html" }).end(html);
869
+ } catch (e) {
870
+ try {
871
+ const { html } = await renderer.renderError(e);
872
+ res.status(500).set({ "Content-Type": "text/html" }).end(html);
873
+ } catch (e2) {
874
+ console.error("failed to render custom error");
875
+ console.error(e2);
876
+ next(e);
877
+ }
878
+ }
879
+ });
880
+ const port = parseInt(process.env.PORT || "4007");
881
+ console.log();
882
+ console.log(`${dim4("\u2503")} project: ${rootDir}`);
883
+ console.log(`${dim4("\u2503")} server: http://localhost:${port}`);
884
+ console.log();
885
+ app.listen(port);
542
886
  }
543
887
  export {
544
888
  build,
545
889
  dev,
890
+ preview,
546
891
  start
547
892
  };
548
893
  //# sourceMappingURL=cli.js.map