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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/render.d.ts CHANGED
@@ -1,58 +1,6 @@
1
- import { ComponentType } from 'preact';
2
- import { b as GetStaticPaths, G as GetStaticProps, R as RootConfig } from './types-d6c0705e.js';
1
+ export { l as Renderer } from './config-0a9ae264.js';
2
+ import 'express';
3
3
  import 'vite';
4
-
5
- interface RouteModule {
6
- default?: ComponentType<unknown>;
7
- getStaticPaths?: GetStaticPaths;
8
- getStaticProps?: GetStaticProps;
9
- }
10
- interface Route {
11
- modulePath: string;
12
- module: RouteModule;
13
- locale: string;
14
- }
15
-
16
- interface Asset {
17
- moduleId: string;
18
- assetUrl: string;
19
- getCssDeps(): Promise<string[]>;
20
- getJsDeps(): Promise<string[]>;
21
- }
22
- interface AssetMap {
23
- get: (moduleId: string) => Promise<Asset | null>;
24
- }
25
-
26
- interface RenderOptions {
27
- assetMap: AssetMap;
28
- }
29
- declare class Renderer {
30
- private routes;
31
- constructor(config: RootConfig);
32
- render(url: string, options: RenderOptions): Promise<{
33
- html: string;
34
- notFound?: boolean;
35
- }>;
36
- renderRoute(route: Route, options: {
37
- routeParams: Record<string, string>;
38
- assetMap: AssetMap;
39
- }): Promise<{
40
- html: string;
41
- notFound?: boolean;
42
- }>;
43
- getSitemap(): Promise<Record<string, {
44
- route: Route;
45
- params: Record<string, string>;
46
- }>>;
47
- private renderHtml;
48
- render404(): Promise<{
49
- html: string;
50
- notFound: boolean;
51
- }>;
52
- renderError(error: unknown): Promise<{
53
- html: string;
54
- }>;
55
- private getScriptDeps;
56
- }
57
-
58
- export { Renderer };
4
+ import 'preact';
5
+ import 'html-minifier-terser';
6
+ import 'js-beautify';
package/dist/render.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  ErrorPage,
3
3
  HEAD_CONTEXT,
4
+ HTML_CONTEXT,
4
5
  I18N_CONTEXT,
6
+ REQUEST_CONTEXT,
5
7
  SCRIPT_CONTEXT,
6
8
  getTranslations
7
- } from "./chunk-CDPH3RKE.js";
9
+ } from "./chunk-BCRXWKTP.js";
8
10
 
9
11
  // src/render/render.tsx
10
- import path2 from "node:path";
11
- import { h } from "preact";
12
12
  import renderToString from "preact-render-to-string";
13
13
 
14
14
  // src/render/router.ts
@@ -19,13 +19,13 @@ var RouteTrie = class {
19
19
  constructor() {
20
20
  this.children = {};
21
21
  }
22
- add(path3, route) {
23
- path3 = this.normalizePath(path3);
24
- if (path3 === "") {
22
+ add(path2, route) {
23
+ path2 = this.normalizePath(path2);
24
+ if (path2 === "") {
25
25
  this.route = route;
26
26
  return;
27
27
  }
28
- const [head, tail] = this.splitPath(path3);
28
+ const [head, tail] = this.splitPath(path2);
29
29
  if (head.startsWith("[...") && head.endsWith("]")) {
30
30
  const paramName = head.slice(4, -1);
31
31
  this.wildcardChild = new WildcardChild(paramName, route);
@@ -47,9 +47,9 @@ var RouteTrie = class {
47
47
  }
48
48
  nextNode.add(tail, route);
49
49
  }
50
- get(path3) {
50
+ get(path2) {
51
51
  const params = {};
52
- const route = this.getRoute(path3, params);
52
+ const route = this.getRoute(path2, params);
53
53
  return [route, params];
54
54
  }
55
55
  walk(cb) {
@@ -114,15 +114,15 @@ var RouteTrie = class {
114
114
  }
115
115
  return void 0;
116
116
  }
117
- normalizePath(path3) {
118
- return path3.replace(/^\/+/g, "");
117
+ normalizePath(path2) {
118
+ return path2.replace(/^\/+/g, "");
119
119
  }
120
- splitPath(path3) {
121
- const i = path3.indexOf("/");
120
+ splitPath(path2) {
121
+ const i = path2.indexOf("/");
122
122
  if (i === -1) {
123
- return [path3, ""];
123
+ return [path2, ""];
124
124
  }
125
- return [path3.slice(0, i), path3.slice(i + 1)];
125
+ return [path2.slice(0, i), path2.slice(i + 1)];
126
126
  }
127
127
  };
128
128
  var ParamChild = class {
@@ -144,12 +144,16 @@ function getRoutes(config) {
144
144
  const locales = ((_a = config.i18n) == null ? void 0 : _a.locales) || [];
145
145
  const i18nUrlFormat = ((_b = config.i18n) == null ? void 0 : _b.urlFormat) || "/{locale}/{path}";
146
146
  const defaultLocale = ((_c = config.i18n) == null ? void 0 : _c.defaultLocale) || "en";
147
- const routes = import.meta.glob(["/routes/*.ts", "/routes/**/*.ts", "/routes/*.tsx", "/routes/**/*.tsx"], {
148
- eager: true
149
- });
147
+ const routes = import.meta.glob(
148
+ ["/routes/*.ts", "/routes/**/*.ts", "/routes/*.tsx", "/routes/**/*.tsx"],
149
+ {
150
+ eager: true
151
+ }
152
+ );
150
153
  const trie = new RouteTrie();
151
- Object.keys(routes).forEach((filePath) => {
152
- let routePath = filePath.replace(/^\/routes/, "");
154
+ Object.keys(routes).forEach((modulePath) => {
155
+ const src = modulePath.slice(1);
156
+ let routePath = modulePath.replace(/^\/routes/, "");
153
157
  const parts = path.parse(routePath);
154
158
  if (parts.name.startsWith("_")) {
155
159
  return;
@@ -159,18 +163,23 @@ function getRoutes(config) {
159
163
  } else {
160
164
  routePath = path.join(parts.dir, parts.name);
161
165
  }
166
+ const localeRoutePath = i18nUrlFormat.replace("{locale}", "[locale]").replace("{path}", routePath.replace(/^\/*/, ""));
162
167
  trie.add(routePath, {
163
- modulePath: filePath,
164
- module: routes[filePath],
165
- locale: defaultLocale
168
+ src,
169
+ module: routes[modulePath],
170
+ locale: defaultLocale,
171
+ routePath,
172
+ localeRoutePath
166
173
  });
167
174
  locales.forEach((locale) => {
168
- const localeRoutePath = i18nUrlFormat.replace("{locale}", locale).replace("{path}", routePath.replace(/^\/*/, ""));
169
- if (localeRoutePath !== routePath) {
170
- trie.add(localeRoutePath, {
171
- modulePath: filePath,
172
- module: routes[filePath],
173
- locale
175
+ const localePath = localeRoutePath.replace("[locale]", locale);
176
+ if (localePath !== routePath) {
177
+ trie.add(localePath, {
178
+ src,
179
+ module: routes[modulePath],
180
+ locale,
181
+ routePath,
182
+ localeRoutePath
174
183
  });
175
184
  }
176
185
  });
@@ -178,60 +187,181 @@ function getRoutes(config) {
178
187
  return trie;
179
188
  }
180
189
  async function getAllPathsForRoute(urlPathFormat, route) {
181
- const urlPaths = [];
182
190
  const routeModule = route.module;
191
+ if (!routeModule.default) {
192
+ return [];
193
+ }
194
+ const urlPaths = [];
183
195
  if (routeModule.getStaticPaths) {
184
196
  const staticPaths = await routeModule.getStaticPaths();
185
- staticPaths.paths.forEach((pathParams) => {
186
- urlPaths.push({
187
- urlPath: replaceParams(urlPathFormat, pathParams.params),
188
- params: pathParams.params || {}
189
- });
190
- });
197
+ if (staticPaths.paths) {
198
+ staticPaths.paths.forEach(
199
+ (pathParams) => {
200
+ const urlPath = replaceParams(urlPathFormat, pathParams.params || {});
201
+ if (pathContainsPlaceholders(urlPath)) {
202
+ console.warn(
203
+ `path contains placeholders: ${urlPathFormat}, double check getStaticPaths() and ensure all params are returned. more info: https://rootjs.dev/guide/routes#getStaticPaths`
204
+ );
205
+ } else {
206
+ urlPaths.push({
207
+ urlPath: replaceParams(urlPathFormat, pathParams.params),
208
+ params: pathParams.params || {}
209
+ });
210
+ }
211
+ }
212
+ );
213
+ }
214
+ } else if (pathContainsPlaceholders(urlPathFormat)) {
215
+ console.warn(
216
+ `path contains placeholders: ${urlPathFormat}, did you forget to define getStaticPaths()? more info: https://rootjs.dev/guide/routes#getStaticPaths`
217
+ );
191
218
  } else {
192
219
  urlPaths.push({ urlPath: urlPathFormat, params: {} });
193
220
  }
194
221
  return urlPaths;
195
222
  }
196
223
  function replaceParams(urlPathFormat, params) {
197
- const urlPath = urlPathFormat.replaceAll(/\[(\.\.\.)?([\w\-_]*)\]/g, (match, _wildcard, key) => {
198
- const val = params[key];
199
- if (!val) {
200
- throw new Error(`unreplaced param ${match} in url: ${urlPathFormat}`);
224
+ const urlPath = urlPathFormat.replaceAll(
225
+ /\[(\.\.\.)?([\w\-_]*)\]/g,
226
+ (match, _wildcard, key) => {
227
+ const val = params[key];
228
+ if (!val) {
229
+ throw new Error(`unreplaced param ${match} in url: ${urlPathFormat}`);
230
+ }
231
+ return val;
201
232
  }
202
- return val;
203
- });
233
+ );
204
234
  return urlPath;
205
235
  }
236
+ function pathContainsPlaceholders(urlPath) {
237
+ const segments = urlPath.split("/");
238
+ return segments.some((segment) => {
239
+ return segment.startsWith("[") && segment.endsWith("]");
240
+ });
241
+ }
242
+
243
+ // src/render/render.tsx
244
+ import { elementsMap } from "virtual:root-elements";
245
+
246
+ // src/core/components/dev-not-found-page.tsx
247
+ import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
248
+ var STYLES = `
249
+ body {
250
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
251
+ }
252
+
253
+ h1 {
254
+ margin-bottom: 40px;
255
+ }
256
+
257
+ .route-list {
258
+ display: grid;
259
+ grid-template-columns: auto 1fr;
260
+ align-items: center;
261
+ column-gap: 24px;
262
+ row-gap: 8px;
263
+ padding: 0 40px;
264
+ }
265
+
266
+ .route-module {
267
+ text-align: right;
268
+ }
269
+
270
+ hr {
271
+ margin: 60px 0;
272
+ }
273
+ `;
274
+ function DevNotFoundPage(props) {
275
+ const routesList = [];
276
+ Object.keys(props.sitemap).forEach((urlPath) => {
277
+ const route = props.sitemap[urlPath].route;
278
+ routesList.push(Object.assign({}, route, { urlPath }));
279
+ });
280
+ return /* @__PURE__ */ jsxs(Fragment, {
281
+ children: [
282
+ /* @__PURE__ */ jsx("style", {
283
+ dangerouslySetInnerHTML: { __html: STYLES }
284
+ }),
285
+ /* @__PURE__ */ jsxs("div", {
286
+ children: [
287
+ /* @__PURE__ */ jsxs("h1", {
288
+ children: [
289
+ /* @__PURE__ */ jsx("strong", {
290
+ children: "404:"
291
+ }),
292
+ " Not Found"
293
+ ]
294
+ }),
295
+ routesList.length > 0 ? /* @__PURE__ */ jsxs(Fragment, {
296
+ children: [
297
+ /* @__PURE__ */ jsx("h2", {
298
+ children: "Routes found in the project:"
299
+ }),
300
+ /* @__PURE__ */ jsx("div", {
301
+ class: "route-list",
302
+ children: routesList.map((route) => /* @__PURE__ */ jsxs(Fragment, {
303
+ children: [
304
+ /* @__PURE__ */ jsx("div", {
305
+ class: "route-module",
306
+ children: route.modulePath
307
+ }),
308
+ /* @__PURE__ */ jsx("a", {
309
+ class: "route-url",
310
+ href: route.urlPath,
311
+ children: route.urlPath
312
+ })
313
+ ]
314
+ }))
315
+ })
316
+ ]
317
+ }) : /* @__PURE__ */ jsxs(Fragment, {
318
+ children: [
319
+ /* @__PURE__ */ jsx("h2", {
320
+ children: "No routes found in the project"
321
+ }),
322
+ /* @__PURE__ */ jsxs("p", {
323
+ children: [
324
+ "Add your first route at ",
325
+ /* @__PURE__ */ jsx("code", {
326
+ children: "/routes/index.tsx"
327
+ })
328
+ ]
329
+ })
330
+ ]
331
+ }),
332
+ /* @__PURE__ */ jsx("hr", {}),
333
+ /* @__PURE__ */ jsx("p", {
334
+ children: "Note: This page could use some love! If you're interested in helping to style this page, please reach out to the root.js developers!"
335
+ })
336
+ ]
337
+ })
338
+ ]
339
+ });
340
+ }
206
341
 
207
342
  // src/render/render.tsx
208
- var ELEMENTS_MAP = {};
209
- var ELEMENTS_MODULES = import.meta.glob([
210
- "/elements/**/*.ts",
211
- "/elements/**/*.tsx"
212
- ]);
213
- Object.keys(ELEMENTS_MODULES).forEach((elementPath) => {
214
- const parts = path2.parse(elementPath);
215
- ELEMENTS_MAP[parts.name] = elementPath;
216
- });
343
+ import { jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
217
344
  var Renderer = class {
218
- constructor(config) {
219
- this.routes = getRoutes(config);
345
+ constructor(rootConfig, options) {
346
+ this.rootConfig = rootConfig;
347
+ this.routes = getRoutes(this.rootConfig);
348
+ this.assetMap = options.assetMap;
220
349
  }
221
- async render(url, options) {
222
- const assetMap = options.assetMap;
350
+ async render(url) {
223
351
  const [route, routeParams] = this.routes.get(url);
224
352
  if (route && route.module && route.module.default) {
225
- return await this.renderRoute(route, { routeParams, assetMap });
353
+ return await this.renderRoute(route, { routeParams });
226
354
  }
227
- return this.render404();
355
+ return { notFound: true };
228
356
  }
229
357
  async renderRoute(route, options) {
230
358
  const routeParams = options.routeParams;
231
- const assetMap = options.assetMap;
359
+ const assetMap = this.assetMap;
232
360
  const Component = route.module.default;
233
361
  if (!Component) {
234
- throw new Error("unable to render route. the route should have a default export that renders a jsx component.");
362
+ throw new Error(
363
+ "unable to render route. the route should have a default export that renders a jsx component."
364
+ );
235
365
  }
236
366
  let props = {};
237
367
  if (route.module.getStaticProps) {
@@ -247,47 +377,71 @@ var Renderer = class {
247
377
  }
248
378
  const locale = route.locale;
249
379
  const translations = getTranslations(locale);
380
+ const ctx = {
381
+ route,
382
+ props,
383
+ routeParams,
384
+ locale,
385
+ translations
386
+ };
250
387
  const headComponents = [];
251
388
  const userScripts = [];
252
- const vdom = /* @__PURE__ */ h(I18N_CONTEXT.Provider, {
253
- value: { locale, translations }
254
- }, /* @__PURE__ */ h(SCRIPT_CONTEXT.Provider, {
255
- value: userScripts
256
- }, /* @__PURE__ */ h(HEAD_CONTEXT.Provider, {
257
- value: headComponents
258
- }, /* @__PURE__ */ h(Component, {
259
- ...props
260
- }))));
261
- const mainHtml = renderToString(vdom, {}, { pretty: true });
262
- const pageAsset = await assetMap.get(route.modulePath);
263
- const cssDeps = await (pageAsset == null ? void 0 : pageAsset.getCssDeps());
264
- if (cssDeps) {
265
- cssDeps.forEach((cssUrl) => {
266
- headComponents.push(/* @__PURE__ */ h("link", {
267
- rel: "stylesheet",
268
- href: cssUrl
269
- }));
270
- });
389
+ const htmlContext = { attrs: {} };
390
+ const vdom = /* @__PURE__ */ jsx2(REQUEST_CONTEXT.Provider, {
391
+ value: ctx,
392
+ children: /* @__PURE__ */ jsx2(I18N_CONTEXT.Provider, {
393
+ value: { locale, translations },
394
+ children: /* @__PURE__ */ jsx2(HTML_CONTEXT.Provider, {
395
+ value: htmlContext,
396
+ children: /* @__PURE__ */ jsx2(HEAD_CONTEXT.Provider, {
397
+ value: headComponents,
398
+ children: /* @__PURE__ */ jsx2(SCRIPT_CONTEXT.Provider, {
399
+ value: userScripts,
400
+ children: /* @__PURE__ */ jsx2(Component, {
401
+ ...props
402
+ })
403
+ })
404
+ })
405
+ })
406
+ })
407
+ });
408
+ const mainHtml = renderToString(vdom);
409
+ const jsDeps = /* @__PURE__ */ new Set();
410
+ const cssDeps = /* @__PURE__ */ new Set();
411
+ const pageAsset = await assetMap.get(route.src);
412
+ if (pageAsset) {
413
+ const pageCssDeps = await pageAsset.getCssDeps();
414
+ pageCssDeps.forEach((dep) => cssDeps.add(dep));
271
415
  }
272
- const scriptDeps = await this.getScriptDeps(mainHtml, { assetMap });
273
- scriptDeps.forEach((jsUrls) => {
274
- headComponents.push(/* @__PURE__ */ h("script", {
275
- type: "module",
276
- src: jsUrls
416
+ await this.collectElementDeps(mainHtml, jsDeps, cssDeps);
417
+ await Promise.all(
418
+ userScripts.map(async (scriptDep) => {
419
+ const scriptAsset = await assetMap.get(scriptDep.src.slice(1));
420
+ if (scriptAsset) {
421
+ jsDeps.add(scriptAsset.assetUrl);
422
+ const scriptJsDeps = await scriptAsset.getJsDeps();
423
+ scriptJsDeps.forEach((dep) => jsDeps.add(dep));
424
+ }
425
+ })
426
+ );
427
+ cssDeps.forEach((cssUrl) => {
428
+ headComponents.push(/* @__PURE__ */ jsx2("link", {
429
+ rel: "stylesheet",
430
+ href: cssUrl
277
431
  }));
278
432
  });
279
- await Promise.all(userScripts.map(async (scriptDep) => {
280
- const scriptAsset = await assetMap.get(scriptDep.src);
281
- if (!scriptAsset && import.meta.env.PROD) {
282
- console.log(`could not find precompiled asset: ${scriptDep.src}`);
283
- }
284
- const scriptUrl = scriptAsset ? scriptAsset.assetUrl : scriptDep.src;
285
- headComponents.push(/* @__PURE__ */ h("script", {
433
+ jsDeps.forEach((jsUrls) => {
434
+ headComponents.push(/* @__PURE__ */ jsx2("script", {
286
435
  type: "module",
287
- src: scriptUrl
436
+ src: jsUrls
288
437
  }));
289
- }));
290
- const html = await this.renderHtml({ mainHtml, locale, headComponents });
438
+ });
439
+ const htmlLang = htmlContext.attrs.lang || locale;
440
+ const html = await this.renderHtml({
441
+ mainHtml,
442
+ locale: htmlLang,
443
+ headComponents
444
+ });
291
445
  return { html };
292
446
  }
293
447
  async getSitemap() {
@@ -304,15 +458,24 @@ var Renderer = class {
304
458
  return sitemap;
305
459
  }
306
460
  async renderHtml(options) {
307
- const page = /* @__PURE__ */ h("html", {
308
- lang: options.locale
309
- }, /* @__PURE__ */ h("head", null, /* @__PURE__ */ h("meta", {
310
- charSet: "utf-8"
311
- }), options.headComponents), /* @__PURE__ */ h("body", {
312
- dangerouslySetInnerHTML: { __html: options.mainHtml }
313
- }));
461
+ const page = /* @__PURE__ */ jsxs2("html", {
462
+ lang: options.locale,
463
+ children: [
464
+ /* @__PURE__ */ jsxs2("head", {
465
+ children: [
466
+ /* @__PURE__ */ jsx2("meta", {
467
+ charSet: "utf-8"
468
+ }),
469
+ options.headComponents
470
+ ]
471
+ }),
472
+ /* @__PURE__ */ jsx2("body", {
473
+ dangerouslySetInnerHTML: { __html: options.mainHtml }
474
+ })
475
+ ]
476
+ });
314
477
  const html = `<!doctype html>
315
- ${renderToString(page, {}, { pretty: true })}
478
+ ${renderToString(page)}
316
479
  `;
317
480
  return html;
318
481
  }
@@ -323,30 +486,47 @@ ${renderToString(page, {}, { pretty: true })}
323
486
  };
324
487
  }
325
488
  async renderError(error) {
326
- const mainHtml = renderToString(/* @__PURE__ */ h(ErrorPage, {
489
+ const mainHtml = renderToString(/* @__PURE__ */ jsx2(ErrorPage, {
327
490
  error
328
491
  }));
329
492
  const html = await this.renderHtml({ mainHtml, locale: "en" });
330
493
  return { html };
331
494
  }
332
- async getScriptDeps(html, options) {
333
- const assetMap = options.assetMap;
334
- const deps = /* @__PURE__ */ new Set();
495
+ async renderDevServer404() {
496
+ const sitemap = await this.getSitemap();
497
+ const mainHtml = renderToString(/* @__PURE__ */ jsx2(DevNotFoundPage, {
498
+ sitemap
499
+ }));
500
+ const html = await this.renderHtml({
501
+ mainHtml,
502
+ locale: "en",
503
+ headComponents: [/* @__PURE__ */ jsx2("title", {
504
+ children: "404: Not Found"
505
+ })]
506
+ });
507
+ return { html };
508
+ }
509
+ async collectElementDeps(html, jsDeps, cssDeps) {
510
+ const assetMap = this.assetMap;
335
511
  const re = /<(\w[\w-]+\w)/g;
336
512
  const matches = Array.from(html.matchAll(re));
337
- await Promise.all(matches.map(async (match) => {
338
- const tagName = match[1];
339
- if (tagName && tagName.includes("-") && tagName in ELEMENTS_MAP) {
340
- const modulePath = ELEMENTS_MAP[tagName];
341
- const asset = await assetMap.get(modulePath);
342
- if (!asset) {
343
- return;
513
+ await Promise.all(
514
+ matches.map(async (match) => {
515
+ const tagName = match[1];
516
+ if (tagName && tagName.includes("-") && tagName in elementsMap) {
517
+ const elementModule = elementsMap[tagName];
518
+ const asset = await assetMap.get(elementModule.src);
519
+ if (!asset) {
520
+ return;
521
+ }
522
+ const assetJsDeps = await asset.getJsDeps();
523
+ assetJsDeps.forEach((dep) => jsDeps.add(dep));
524
+ const assetCssDeps = await asset.getCssDeps();
525
+ assetCssDeps.forEach((dep) => cssDeps.add(dep));
344
526
  }
345
- const assetJsDeps = await asset.getJsDeps();
346
- assetJsDeps.forEach((dep) => deps.add(dep));
347
- }
348
- }));
349
- return Array.from(deps);
527
+ })
528
+ );
529
+ return { jsDeps, cssDeps };
350
530
  }
351
531
  };
352
532
  export {