@blinkk/root 1.0.0-rc.3 → 1.0.0-rc.30

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,17 @@ 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.endsWith("/index")) {
479
+ urlPath = urlPath.slice(0, -6);
480
+ }
481
+ if (!urlPath.startsWith("/")) {
482
+ urlPath = `/${urlPath}`;
483
+ }
568
484
  return urlPath;
569
485
  }
570
486
  function pathContainsPlaceholders(urlPath) {
@@ -573,19 +489,30 @@ function pathContainsPlaceholders(urlPath) {
573
489
  return segment.startsWith("[") && segment.endsWith("]");
574
490
  });
575
491
  }
492
+ function removeSlashes(str) {
493
+ return str.replace(/^\/*/g, "").replace(/\/*$/g, "");
494
+ }
495
+ function toSquareBrackets(str) {
496
+ if (str.includes("{") || str.includes("}")) {
497
+ const val = str.replaceAll("{", "[").replaceAll("}", "]");
498
+ console.warn(`"${str}" is a deprecated format, please switch to "${val}"`);
499
+ return val;
500
+ }
501
+ return str;
502
+ }
576
503
 
577
504
  // src/render/render.tsx
578
505
  import { jsx as jsx4, jsxs as jsxs4 } from "preact/jsx-runtime";
579
506
  var Renderer = class {
580
507
  constructor(rootConfig, options) {
581
508
  this.rootConfig = rootConfig;
582
- this.routes = getRoutes(this.rootConfig);
583
509
  this.assetMap = options.assetMap;
584
510
  this.elementGraph = options.elementGraph;
511
+ this.router = new Router(rootConfig);
585
512
  }
586
513
  async handle(req, res, next) {
587
- const url = req.path.toLowerCase();
588
- const [route, routeParams] = this.routes.get(url);
514
+ const url = req.path;
515
+ const [route, routeParams] = this.router.get(url);
589
516
  if (!route) {
590
517
  next();
591
518
  return;
@@ -697,7 +624,12 @@ var Renderer = class {
697
624
  const routeAsset = await this.assetMap.get(route.src);
698
625
  if (routeAsset) {
699
626
  const routeCssDeps = await routeAsset.getCssDeps();
700
- routeCssDeps.forEach((dep) => cssDeps.add(dep));
627
+ routeCssDeps.forEach((dep) => {
628
+ if (dep.endsWith("?inline")) {
629
+ return;
630
+ }
631
+ cssDeps.add(dep);
632
+ });
701
633
  }
702
634
  await this.collectElementDeps(mainHtml, jsDeps, cssDeps);
703
635
  await Promise.all(
@@ -780,8 +712,8 @@ var Renderer = class {
780
712
  }
781
713
  async getSitemap() {
782
714
  const sitemap = {};
783
- await this.routes.walk(async (urlPath, route) => {
784
- const routePaths = await getAllPathsForRoute(urlPath, route);
715
+ await this.router.walk(async (urlPath, route) => {
716
+ const routePaths = await this.router.getAllPathsForRoute(urlPath, route);
785
717
  routePaths.forEach((routePath) => {
786
718
  sitemap[routePath.urlPath] = {
787
719
  route,
@@ -808,7 +740,7 @@ ${renderToString(page)}
808
740
  }
809
741
  async render404(options) {
810
742
  const currentPath = (options == null ? void 0 : options.currentPath) || "/404";
811
- const [route, routeParams] = this.routes.get("/404");
743
+ const [route, routeParams] = this.router.get("/404");
812
744
  if (route && route.src === "routes/404.tsx" && route.module.default) {
813
745
  const Component = route.module.default;
814
746
  return this.renderComponent(
@@ -844,7 +776,7 @@ ${renderToString(page)}
844
776
  }
845
777
  async renderError(err, options) {
846
778
  const currentPath = (options == null ? void 0 : options.currentPath) || "/500";
847
- const [route, routeParams] = this.routes.get("/500");
779
+ const [route, routeParams] = this.router.get("/500");
848
780
  if (route && route.src === "routes/500.tsx" && route.module.default) {
849
781
  const Component = route.module.default;
850
782
  return this.renderComponent(
@@ -889,7 +821,7 @@ ${renderToString(page)}
889
821
  return { html };
890
822
  }
891
823
  async renderDevServer500(req, error) {
892
- const [route, routeParams] = this.routes.get(req.path);
824
+ const [route, routeParams] = this.router.get(req.path);
893
825
  const mainHtml = renderToString(
894
826
  /* @__PURE__ */ jsx4(
895
827
  DevErrorPage,
@@ -932,7 +864,12 @@ ${renderToString(page)}
932
864
  const assetJsDeps = await asset.getJsDeps();
933
865
  assetJsDeps.forEach((dep) => jsDeps.add(dep));
934
866
  const assetCssDeps = await asset.getCssDeps();
935
- assetCssDeps.forEach((dep) => cssDeps.add(dep));
867
+ assetCssDeps.forEach((dep) => {
868
+ if (dep.endsWith("?inline")) {
869
+ return;
870
+ }
871
+ cssDeps.add(dep);
872
+ });
936
873
  })
937
874
  );
938
875
  return { jsDeps, cssDeps };