@blinkk/root 1.0.0-rc.2 → 1.0.0-rc.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
@@ -5,10 +5,11 @@ import {
5
5
  getTranslations
6
6
  } from "./chunk-MJCIAH6K.js";
7
7
  import {
8
+ RouteTrie,
8
9
  htmlMinify,
9
10
  htmlPretty,
10
11
  parseTagNames
11
- } from "./chunk-DFBTOMQF.js";
12
+ } from "./chunk-IUQLRDFW.js";
12
13
 
13
14
  // src/render/render.tsx
14
15
  import renderToString from "preact-render-to-string";
@@ -203,6 +204,46 @@ function parseAcceptLanguage(value) {
203
204
 
204
205
  // src/render/i18n-fallbacks.ts
205
206
  var UNKNOWN_COUNTRY = "zz";
207
+ var ES_419_COUNTRIES = [
208
+ "ar",
209
+ // Argentina
210
+ "bo",
211
+ // Bolivia
212
+ "cl",
213
+ // Chile
214
+ "co",
215
+ // Colombia
216
+ "cr",
217
+ // Costa Rica
218
+ "cu",
219
+ // Cuba
220
+ "do",
221
+ // Dominican Republic
222
+ "ec",
223
+ // Ecuador
224
+ "sv",
225
+ // El Salvador
226
+ "gt",
227
+ // Guatemala
228
+ "hn",
229
+ // Honduras
230
+ "mx",
231
+ // Mexico
232
+ "ni",
233
+ // Nicaragua
234
+ "pa",
235
+ // Panama
236
+ "py",
237
+ // Paraguay
238
+ "pe",
239
+ // Peru
240
+ "pr",
241
+ // Puerto Rico
242
+ "uy",
243
+ // Uruguay
244
+ "ve"
245
+ // Venezuela
246
+ ];
206
247
  function getFallbackLocales(req) {
207
248
  var _a, _b;
208
249
  const hl = getFirstQueryParam(req, "hl");
@@ -226,9 +267,14 @@ function getFallbackLocales(req) {
226
267
  locales.add(`${langCode}_${countryCode}`);
227
268
  });
228
269
  locales.add(`ALL_${countryCode}`);
270
+ const isEs419Country = test419Country(countryCode);
229
271
  langs.forEach((langCode) => {
230
272
  locales.add(`${langCode}_ALL`);
231
273
  locales.add(langCode);
274
+ if (langCode === "es" && isEs419Country) {
275
+ locales.add("es-419_ALL");
276
+ locales.add("es-419");
277
+ }
232
278
  });
233
279
  return Array.from(locales);
234
280
  }
@@ -251,6 +297,9 @@ function getFallbackLanguages(req) {
251
297
  parseAcceptLanguage(acceptLangHeader).forEach((lang) => {
252
298
  if (lang.region) {
253
299
  langs.add(`${lang.code}-${lang.region}`);
300
+ if (lang.code === "es" && test419Country(lang.region)) {
301
+ langs.add("es-419");
302
+ }
254
303
  }
255
304
  langs.add(lang.code);
256
305
  });
@@ -279,275 +328,135 @@ function isWebCrawler(req) {
279
328
  const userAgent = userAgentHeader.toLowerCase();
280
329
  return userAgent.includes("googlebot") || userAgent.includes("bingbot") || userAgent.includes("twitterbot");
281
330
  }
331
+ function test419Country(countryCode) {
332
+ return ES_419_COUNTRIES.includes(countryCode);
333
+ }
282
334
 
283
335
  // src/render/router.ts
284
336
  import path from "node:path";
285
-
286
- // src/render/route-trie.ts
287
- var RouteTrie = class _RouteTrie {
288
- constructor() {
289
- this.children = {};
290
- }
291
- /**
292
- * Adds a route to the trie.
293
- */
294
- add(path2, route) {
295
- path2 = this.normalizePath(path2);
296
- if (path2 === "") {
297
- this.route = route;
298
- return;
299
- }
300
- const [head, tail] = this.splitPath(path2);
301
- if (head.startsWith("[[...") && head.endsWith("]]")) {
302
- const paramName = head.slice(5, -2);
303
- this.optCatchAllNodes = new CatchAllNode(paramName, route);
304
- return;
305
- }
306
- if (head.startsWith("[...") && head.endsWith("]")) {
307
- const paramName = head.slice(4, -1);
308
- this.catchAllNodes = new CatchAllNode(paramName, route);
309
- return;
310
- }
311
- let nextNode;
312
- if (head.startsWith("[") && head.endsWith("]")) {
313
- if (!this.paramNodes) {
314
- this.paramNodes = {};
315
- }
316
- const paramName = head.slice(1, -1);
317
- if (!this.paramNodes[paramName]) {
318
- this.paramNodes[paramName] = new ParamNode(paramName);
319
- }
320
- nextNode = this.paramNodes[paramName].trie;
321
- } else {
322
- nextNode = this.children[head];
323
- if (!nextNode) {
324
- nextNode = new _RouteTrie();
325
- this.children[head] = nextNode;
337
+ var ROUTES_FILES = import.meta.glob(
338
+ ["/routes/*.ts", "/routes/**/*.ts", "/routes/*.tsx", "/routes/**/*.tsx"],
339
+ { eager: true }
340
+ );
341
+ var Router = class {
342
+ constructor(rootConfig) {
343
+ this.rootConfig = rootConfig;
344
+ this.routeTrie = this.initRouteTrie();
345
+ }
346
+ get(url) {
347
+ return this.routeTrie.get(url);
348
+ }
349
+ async walk(cb) {
350
+ await this.routeTrie.walk(cb);
351
+ }
352
+ initRouteTrie() {
353
+ var _a, _b;
354
+ const locales = ((_a = this.rootConfig.i18n) == null ? void 0 : _a.locales) || [];
355
+ const basePath = this.rootConfig.base || "/";
356
+ const defaultLocale = ((_b = this.rootConfig.i18n) == null ? void 0 : _b.defaultLocale) || "en";
357
+ const trie = new RouteTrie();
358
+ Object.keys(ROUTES_FILES).forEach((modulePath) => {
359
+ var _a2;
360
+ const src = modulePath.slice(1);
361
+ let relativeRoutePath = modulePath.replace(/^\/routes/, "");
362
+ const parts = path.parse(relativeRoutePath);
363
+ if (parts.name.startsWith("_")) {
364
+ return;
326
365
  }
327
- }
328
- nextNode.add(tail, route);
329
- }
330
- /**
331
- * Returns a route mapped to the given path and any parameter values from the
332
- * URL.
333
- */
334
- get(path2) {
335
- const params = {};
336
- const route = this.getRoute(path2, params);
337
- return [route, params];
338
- }
339
- /**
340
- * Walks the route trie and calls a callback function for each route.
341
- */
342
- walk(cb) {
343
- const promises = [];
344
- const addPromise = (promise) => {
345
- if (promise) {
346
- promises.push(promise);
366
+ if (parts.name === "index") {
367
+ relativeRoutePath = parts.dir;
368
+ } else {
369
+ relativeRoutePath = path.join(parts.dir, parts.name);
347
370
  }
348
- };
349
- if (this.route) {
350
- addPromise(cb("/", this.route));
351
- }
352
- if (this.paramNodes) {
353
- Object.values(this.paramNodes).forEach((paramChild) => {
354
- const param = `[${paramChild.name}]`;
355
- paramChild.trie.walk((childPath, route) => {
356
- const paramUrlPath = `/${param}${childPath}`;
357
- addPromise(cb(paramUrlPath, route));
371
+ const urlFormat = "/[base]/[path]";
372
+ const i18nUrlFormat = toSquareBrackets(
373
+ ((_a2 = this.rootConfig.i18n) == null ? void 0 : _a2.urlFormat) || "/[locale]/[base]/[path]"
374
+ );
375
+ const placeholders = {
376
+ base: removeSlashes(basePath),
377
+ path: removeSlashes(relativeRoutePath)
378
+ };
379
+ const formatUrl = (format) => {
380
+ var _a3;
381
+ const url = format.replaceAll("[base]", placeholders.base).replaceAll("[path]", placeholders.path);
382
+ return normalizeUrlPath(url, {
383
+ trailingSlash: (_a3 = this.rootConfig.server) == null ? void 0 : _a3.trailingSlash
358
384
  });
385
+ };
386
+ const routePath = formatUrl(urlFormat);
387
+ const localeRoutePath = formatUrl(i18nUrlFormat);
388
+ trie.add(routePath, {
389
+ src,
390
+ module: ROUTES_FILES[modulePath],
391
+ locale: defaultLocale,
392
+ isDefaultLocale: true,
393
+ routePath,
394
+ localeRoutePath
359
395
  });
360
- }
361
- if (this.catchAllNodes) {
362
- const wildcardUrlPath = `/[...${this.catchAllNodes.name}]`;
363
- addPromise(cb(wildcardUrlPath, this.catchAllNodes.route));
364
- }
365
- if (this.optCatchAllNodes) {
366
- const wildcardUrlPath = `/[[...${this.optCatchAllNodes.name}]]`;
367
- addPromise(cb(wildcardUrlPath, this.optCatchAllNodes.route));
368
- }
369
- for (const subpath of Object.keys(this.children)) {
370
- const childTrie = this.children[subpath];
371
- childTrie.walk((childPath, childRoute) => {
372
- addPromise(cb(`/${subpath}${childPath}`, childRoute));
373
- });
374
- }
375
- return Promise.all(promises).then(() => {
376
- });
377
- }
378
- /**
379
- * Removes all routes from the trie.
380
- */
381
- clear() {
382
- this.children = {};
383
- this.paramNodes = void 0;
384
- this.catchAllNodes = void 0;
385
- this.optCatchAllNodes = void 0;
386
- this.route = void 0;
387
- }
388
- getRoute(urlPath, params) {
389
- urlPath = this.normalizePath(urlPath);
390
- if (urlPath === "") {
391
- if (this.route) {
392
- return this.route;
393
- }
394
- if (this.optCatchAllNodes) {
395
- if (urlPath) {
396
- params[this.optCatchAllNodes.name] = urlPath;
397
- }
398
- return this.optCatchAllNodes.route;
399
- }
400
- return void 0;
401
- }
402
- const [head, tail] = this.splitPath(urlPath);
403
- const child = this.children[head];
404
- if (child) {
405
- const route = child.getRoute(tail, params);
406
- if (route) {
407
- return route;
408
- }
409
- }
410
- if (this.paramNodes) {
411
- for (const paramChild of Object.values(this.paramNodes)) {
412
- const route = paramChild.trie.getRoute(tail, params);
413
- if (route) {
414
- params[paramChild.name] = head;
415
- return route;
416
- }
417
- }
418
- }
419
- if (this.catchAllNodes) {
420
- params[this.catchAllNodes.name] = urlPath;
421
- return this.catchAllNodes.route;
422
- }
423
- if (this.optCatchAllNodes) {
424
- params[this.optCatchAllNodes.name] = urlPath;
425
- return this.optCatchAllNodes.route;
426
- }
427
- return void 0;
428
- }
429
- /**
430
- * Normalizes a path for inclusion into the route trie.
431
- */
432
- normalizePath(path2) {
433
- return path2.replace(/^\/+/g, "").replace(/\/+$/g, "");
434
- }
435
- /**
436
- * Splits the parent directory from its children, e.g.:
437
- *
438
- * splitPath("foo/bar/baz") -> ["foo", "bar/baz"]
439
- */
440
- splitPath(path2) {
441
- const i = path2.indexOf("/");
442
- if (i === -1) {
443
- return [path2, ""];
444
- }
445
- return [path2.slice(0, i), path2.slice(i + 1)];
446
- }
447
- };
448
- var ParamNode = class {
449
- constructor(name) {
450
- this.trie = new RouteTrie();
451
- this.name = name;
452
- }
453
- };
454
- var CatchAllNode = class {
455
- constructor(name, route) {
456
- this.name = name;
457
- this.route = route;
458
- }
459
- };
460
-
461
- // src/render/router.ts
462
- function getRoutes(config) {
463
- var _a, _b, _c;
464
- const locales = ((_a = config.i18n) == null ? void 0 : _a.locales) || [];
465
- const i18nUrlFormat = ((_b = config.i18n) == null ? void 0 : _b.urlFormat) || "/{locale}/{path}";
466
- const defaultLocale = ((_c = config.i18n) == null ? void 0 : _c.defaultLocale) || "en";
467
- const routes = import.meta.glob(
468
- ["/routes/*.ts", "/routes/**/*.ts", "/routes/*.tsx", "/routes/**/*.tsx"],
469
- {
470
- eager: true
471
- }
472
- );
473
- const trie = new RouteTrie();
474
- Object.keys(routes).forEach((modulePath) => {
475
- const src = modulePath.slice(1);
476
- let routePath = modulePath.replace(/^\/routes/, "");
477
- const parts = path.parse(routePath);
478
- if (parts.name.startsWith("_")) {
479
- return;
480
- }
481
- if (parts.name === "index") {
482
- routePath = parts.dir;
483
- } else {
484
- routePath = path.join(parts.dir, parts.name);
485
- }
486
- const localeRoutePath = i18nUrlFormat.replace("{locale}", "[locale]").replace("{path}", routePath.replace(/^\/*/, ""));
487
- trie.add(routePath, {
488
- src,
489
- module: routes[modulePath],
490
- locale: defaultLocale,
491
- isDefaultLocale: true,
492
- routePath: normalizeUrlPath(routePath),
493
- localeRoutePath: normalizeUrlPath(localeRoutePath)
494
- });
495
- locales.forEach((locale) => {
496
- const localePath = localeRoutePath.replace("[locale]", locale);
497
- if (localePath !== routePath) {
498
- trie.add(localePath, {
499
- src,
500
- module: routes[modulePath],
501
- locale,
502
- isDefaultLocale: false,
503
- routePath,
504
- localeRoutePath
396
+ if (i18nUrlFormat.includes("[locale]")) {
397
+ locales.forEach((locale) => {
398
+ const localePath = localeRoutePath.replace("[locale]", locale);
399
+ if (localePath !== relativeRoutePath) {
400
+ trie.add(localePath, {
401
+ src,
402
+ module: ROUTES_FILES[modulePath],
403
+ locale,
404
+ isDefaultLocale: false,
405
+ routePath,
406
+ localeRoutePath
407
+ });
408
+ }
505
409
  });
506
410
  }
507
411
  });
508
- });
509
- return trie;
510
- }
511
- async function getAllPathsForRoute(urlPathFormat, route) {
512
- const routeModule = route.module;
513
- if (!routeModule.default) {
514
- return [];
412
+ return trie;
515
413
  }
516
- const urlPaths = [];
517
- if (routeModule.getStaticPaths) {
518
- const staticPaths = await routeModule.getStaticPaths();
519
- if (staticPaths.paths) {
520
- staticPaths.paths.forEach(
521
- (pathParams) => {
522
- const urlPath = replaceParams(urlPathFormat, pathParams.params || {});
523
- if (pathContainsPlaceholders(urlPath)) {
524
- console.warn(
525
- `path contains placeholders: ${urlPathFormat}, double check getStaticPaths() and ensure all params are returned. more info: https://rootjs.dev/guide/routes#getStaticPaths`
414
+ async getAllPathsForRoute(urlPathFormat, route) {
415
+ const routeModule = route.module;
416
+ if (!routeModule.default) {
417
+ return [];
418
+ }
419
+ const urlPaths = [];
420
+ if (routeModule.getStaticPaths) {
421
+ const staticPaths = await routeModule.getStaticPaths({
422
+ rootConfig: this.rootConfig
423
+ });
424
+ if (staticPaths.paths) {
425
+ staticPaths.paths.forEach(
426
+ (pathParams) => {
427
+ const urlPath = replaceParams(
428
+ urlPathFormat,
429
+ pathParams.params || {}
526
430
  );
527
- } else {
528
- urlPaths.push({
529
- urlPath: normalizeUrlPath(urlPath),
530
- params: pathParams.params || {}
531
- });
431
+ if (pathContainsPlaceholders(urlPath)) {
432
+ console.warn(
433
+ `path contains placeholders: ${urlPathFormat}, double check getStaticPaths() and ensure all params are returned. more info: https://rootjs.dev/guide/routes#getStaticPaths`
434
+ );
435
+ } else {
436
+ urlPaths.push({
437
+ urlPath: normalizeUrlPath(urlPath),
438
+ params: pathParams.params || {}
439
+ });
440
+ }
532
441
  }
533
- }
442
+ );
443
+ }
444
+ } else if (routeModule.getStaticProps && !pathContainsPlaceholders(urlPathFormat)) {
445
+ urlPaths.push({ urlPath: normalizeUrlPath(urlPathFormat), params: {} });
446
+ } else if (!routeModule.handle && !pathContainsPlaceholders(urlPathFormat)) {
447
+ urlPaths.push({ urlPath: normalizeUrlPath(urlPathFormat), params: {} });
448
+ } else if (pathContainsPlaceholders(urlPathFormat) && !routeModule.handle && !routeModule.getStaticPaths) {
449
+ console.warn(
450
+ [
451
+ `warning: path contains placeholders: ${urlPathFormat}.`,
452
+ `define either ssg getStaticPaths() or ssr handle() for route: ${route.src}.`,
453
+ "more info: https://rootjs.dev/guide/routes"
454
+ ].join("\n")
534
455
  );
535
456
  }
536
- } else if (routeModule.getStaticProps && !pathContainsPlaceholders(urlPathFormat)) {
537
- urlPaths.push({ urlPath: normalizeUrlPath(urlPathFormat), params: {} });
538
- } else if (!routeModule.handle && !pathContainsPlaceholders(urlPathFormat)) {
539
- urlPaths.push({ urlPath: normalizeUrlPath(urlPathFormat), params: {} });
540
- } else if (pathContainsPlaceholders(urlPathFormat) && !routeModule.handle && !routeModule.getStaticPaths) {
541
- console.warn(
542
- [
543
- `warning: path contains placeholders: ${urlPathFormat}.`,
544
- `define either ssg getStaticPaths() or ssr handle() for route: ${route.src}.`,
545
- "more info: https://rootjs.dev/guide/routes"
546
- ].join("\n")
547
- );
457
+ return urlPaths;
548
458
  }
549
- return urlPaths;
550
- }
459
+ };
551
460
  function replaceParams(urlPathFormat, params) {
552
461
  const urlPath = urlPathFormat.replaceAll(
553
462
  /\[\[?(\.\.\.)?([\w\-_]*)\]?\]/g,
@@ -561,10 +470,14 @@ function replaceParams(urlPathFormat, params) {
561
470
  );
562
471
  return urlPath;
563
472
  }
564
- function normalizeUrlPath(urlPath) {
565
- if (urlPath !== "/" && urlPath.endsWith("/")) {
473
+ function normalizeUrlPath(urlPath, options) {
474
+ urlPath = urlPath.replace(/\/+/g, "/");
475
+ if ((options == null ? void 0 : options.trailingSlash) === false && urlPath !== "/" && urlPath.endsWith("/")) {
566
476
  urlPath = urlPath.replace(/\/*$/g, "");
567
477
  }
478
+ if (!urlPath.startsWith("/")) {
479
+ urlPath = `/${urlPath}`;
480
+ }
568
481
  return urlPath;
569
482
  }
570
483
  function pathContainsPlaceholders(urlPath) {
@@ -573,19 +486,30 @@ function pathContainsPlaceholders(urlPath) {
573
486
  return segment.startsWith("[") && segment.endsWith("]");
574
487
  });
575
488
  }
489
+ function removeSlashes(str) {
490
+ return str.replace(/^\/*/g, "").replace(/\/*$/g, "");
491
+ }
492
+ function toSquareBrackets(str) {
493
+ if (str.includes("{") || str.includes("}")) {
494
+ const val = str.replaceAll("{", "[").replaceAll("}", "]");
495
+ console.warn(`"${str}" is a deprecated format, please switch to "${val}"`);
496
+ return val;
497
+ }
498
+ return str;
499
+ }
576
500
 
577
501
  // src/render/render.tsx
578
502
  import { jsx as jsx4, jsxs as jsxs4 } from "preact/jsx-runtime";
579
503
  var Renderer = class {
580
504
  constructor(rootConfig, options) {
581
505
  this.rootConfig = rootConfig;
582
- this.routes = getRoutes(this.rootConfig);
583
506
  this.assetMap = options.assetMap;
584
507
  this.elementGraph = options.elementGraph;
508
+ this.router = new Router(rootConfig);
585
509
  }
586
510
  async handle(req, res, next) {
587
- const url = req.path.toLowerCase();
588
- const [route, routeParams] = this.routes.get(url);
511
+ const url = req.path;
512
+ const [route, routeParams] = this.router.get(url);
589
513
  if (!route) {
590
514
  next();
591
515
  return;
@@ -697,7 +621,12 @@ var Renderer = class {
697
621
  const routeAsset = await this.assetMap.get(route.src);
698
622
  if (routeAsset) {
699
623
  const routeCssDeps = await routeAsset.getCssDeps();
700
- routeCssDeps.forEach((dep) => cssDeps.add(dep));
624
+ routeCssDeps.forEach((dep) => {
625
+ if (dep.endsWith("?inline")) {
626
+ return;
627
+ }
628
+ cssDeps.add(dep);
629
+ });
701
630
  }
702
631
  await this.collectElementDeps(mainHtml, jsDeps, cssDeps);
703
632
  await Promise.all(
@@ -780,8 +709,8 @@ var Renderer = class {
780
709
  }
781
710
  async getSitemap() {
782
711
  const sitemap = {};
783
- await this.routes.walk(async (urlPath, route) => {
784
- const routePaths = await getAllPathsForRoute(urlPath, route);
712
+ await this.router.walk(async (urlPath, route) => {
713
+ const routePaths = await this.router.getAllPathsForRoute(urlPath, route);
785
714
  routePaths.forEach((routePath) => {
786
715
  sitemap[routePath.urlPath] = {
787
716
  route,
@@ -808,7 +737,7 @@ ${renderToString(page)}
808
737
  }
809
738
  async render404(options) {
810
739
  const currentPath = (options == null ? void 0 : options.currentPath) || "/404";
811
- const [route, routeParams] = this.routes.get("/404");
740
+ const [route, routeParams] = this.router.get("/404");
812
741
  if (route && route.src === "routes/404.tsx" && route.module.default) {
813
742
  const Component = route.module.default;
814
743
  return this.renderComponent(
@@ -844,7 +773,7 @@ ${renderToString(page)}
844
773
  }
845
774
  async renderError(err, options) {
846
775
  const currentPath = (options == null ? void 0 : options.currentPath) || "/500";
847
- const [route, routeParams] = this.routes.get("/500");
776
+ const [route, routeParams] = this.router.get("/500");
848
777
  if (route && route.src === "routes/500.tsx" && route.module.default) {
849
778
  const Component = route.module.default;
850
779
  return this.renderComponent(
@@ -889,7 +818,7 @@ ${renderToString(page)}
889
818
  return { html };
890
819
  }
891
820
  async renderDevServer500(req, error) {
892
- const [route, routeParams] = this.routes.get(req.path);
821
+ const [route, routeParams] = this.router.get(req.path);
893
822
  const mainHtml = renderToString(
894
823
  /* @__PURE__ */ jsx4(
895
824
  DevErrorPage,
@@ -932,7 +861,12 @@ ${renderToString(page)}
932
861
  const assetJsDeps = await asset.getJsDeps();
933
862
  assetJsDeps.forEach((dep) => jsDeps.add(dep));
934
863
  const assetCssDeps = await asset.getCssDeps();
935
- assetCssDeps.forEach((dep) => cssDeps.add(dep));
864
+ assetCssDeps.forEach((dep) => {
865
+ if (dep.endsWith("?inline")) {
866
+ return;
867
+ }
868
+ cssDeps.add(dep);
869
+ });
936
870
  })
937
871
  );
938
872
  return { jsDeps, cssDeps };