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

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