@uniweb/runtime 0.14.1 → 0.14.2

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/ssr.js CHANGED
@@ -1,8 +1,15 @@
1
- import { isRichSchema, deriveCacheKey as deriveCacheKey$1, resolveDefaultLocale as resolveDefaultLocale$1, hasDarkScheme, createUniweb, resolveRequestStyle, resolveServiceUrl, substitutePlaceholders as substitutePlaceholders$1, matchWhere } from "@uniweb/core";
1
+ import { isRichSchema, deriveCacheKey, resolveDefaultLocale, hasDarkScheme, createUniweb, resolveRequestStyle, resolveServiceUrl, substitutePlaceholders, matchWhere } from "@uniweb/core";
2
2
  import React from "react";
3
3
  import { renderToString } from "react-dom/server";
4
4
  import { sectionDomId } from "@uniweb/core/section-id";
5
+ import { routePatternToRegex } from "@uniweb/core/route-match";
6
+ import { DEFAULT_ICON_BASE, iconUrl } from "@uniweb/core/icon-corpus";
5
7
  import { buildTheme, FONT_LINKS_MARKER, buildSectionOverrides } from "@uniweb/theming";
8
+ import "@uniweb/core/services";
9
+ import "@uniweb/core/tracker";
10
+ import { resolveFetchConfigs } from "@uniweb/core/fetch-config";
11
+ import { deriveCacheKey as deriveCacheKey$1 } from "@uniweb/core/datastore";
12
+ import { resolveDefaultLocale as resolveDefaultLocale$1 } from "@uniweb/core/locale-config";
6
13
  function guaranteeItemStructure(item) {
7
14
  return {
8
15
  title: item.title || "",
@@ -240,27 +247,6 @@ function getComponentMeta(componentName) {
240
247
  function getComponentDefaults(componentName) {
241
248
  return globalThis.uniweb?.getComponentDefaults?.(componentName) || {};
242
249
  }
243
- const PARAM_NAME = "[A-Za-z0-9_-]+";
244
- const REGEX_SPECIALS = /[.*+?^${}()|[\]\\]/g;
245
- function normalizeRoute(route) {
246
- if (typeof route !== "string" || route === "") return "/";
247
- return route === "/" ? "/" : route.replace(/\/+$/, "") || "/";
248
- }
249
- function routePatternToRegex(pattern) {
250
- const paramNames = [];
251
- const source = normalizeRoute(pattern).replace(REGEX_SPECIALS, "\\$&").replace(new RegExp(`:(${PARAM_NAME})`, "g"), (_, name) => {
252
- paramNames.push(name);
253
- return "([^/]+)";
254
- });
255
- return { regex: new RegExp(`^${source}$`), paramNames };
256
- }
257
- const DEFAULT_ICON_BASE = "https://uniweb.github.io/icons";
258
- function iconPath(family, name) {
259
- return `${family}/${family}-${name}.svg`;
260
- }
261
- function iconUrl(family, name, base = DEFAULT_ICON_BASE) {
262
- return `${String(base).replace(/\/+$/, "")}/${iconPath(family, name)}`;
263
- }
264
250
  function default404Html(basePath = "") {
265
251
  const homeHref = basePath ? `${basePath}/` : "/";
266
252
  return `<div class="page-not-found" style="min-height:80vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem;text-align:center"><h1 style="font-size:3rem;font-weight:bold;color:#1f2937;margin-bottom:1rem">404</h1><p style="color:#64748b;margin-bottom:2rem">Page not found</p><a href="${homeHref}" style="color:#3b82f6;text-decoration:underline">Go to homepage</a></div>`;
@@ -282,7 +268,7 @@ function wireFoundationCapabilities(uniweb, foundation) {
282
268
  }
283
269
  }
284
270
  function sliceContentForLocale(content, locale) {
285
- const defaultLang = resolveDefaultLocale$1(content?.config);
271
+ const defaultLang = resolveDefaultLocale(content?.config);
286
272
  const locData = content?.locales?.[locale];
287
273
  if (!locale || locale === defaultLang || !locData) return content;
288
274
  return {
@@ -299,7 +285,7 @@ function hydrateDataStore(website, fetchedData) {
299
285
  if (!website?.dataStore || !fetchedData?.length) return;
300
286
  for (const entry of fetchedData) {
301
287
  if (entry.outcome && entry.outcome !== "fetched") continue;
302
- website.dataStore.set(deriveCacheKey$1(entry.config), { data: entry.data });
288
+ website.dataStore.set(deriveCacheKey(entry.config), { data: entry.data });
303
289
  }
304
290
  }
305
291
  function ensureThemeCss(uniweb, foundation) {
@@ -628,7 +614,7 @@ function renderLayout(page, website) {
628
614
  function initPrerenderForLocale(content, foundation, locale, extensionsOrOptions, maybeOptions) {
629
615
  const localeContent = sliceContentForLocale(content, locale);
630
616
  const uniweb = initPrerender(localeContent, foundation, extensionsOrOptions, maybeOptions);
631
- const defaultLang = resolveDefaultLocale$1(content?.config);
617
+ const defaultLang = resolveDefaultLocale(content?.config);
632
618
  if (locale && locale !== defaultLang && uniweb.activeWebsite?.setActiveLocale) {
633
619
  uniweb.activeWebsite.setActiveLocale(locale);
634
620
  }
@@ -852,169 +838,6 @@ function generate404Html({ baseHtml, website, siteContent }) {
852
838
  }
853
839
  return { html, hasNotFoundPage: !!notFoundPage };
854
840
  }
855
- const DATA_DIR = "data";
856
- const DATA_URL_PREFIX = `/${DATA_DIR}/`;
857
- function queryDataUrl(name) {
858
- return `${DATA_URL_PREFIX}${name}.json`;
859
- }
860
- function recordDataUrl(query, slug) {
861
- return `${DATA_URL_PREFIX}${query}/${slug}.json`;
862
- }
863
- function isDataUrl(path) {
864
- return typeof path === "string" && path.startsWith(DATA_URL_PREFIX);
865
- }
866
- const PLACEHOLDER_RE = /\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
867
- function substitutePlaceholders(value, context, options = {}) {
868
- const { encode = true } = options;
869
- if (typeof value === "string") {
870
- return value.replace(PLACEHOLDER_RE, (literal, key) => {
871
- if (!(key in (context || {}))) return literal;
872
- const raw = context[key];
873
- if (raw === void 0 || raw === null) return literal;
874
- return encode ? encodeURIComponent(String(raw)) : String(raw);
875
- });
876
- }
877
- if (Array.isArray(value)) {
878
- return value.map((item) => substitutePlaceholders(item, context, options));
879
- }
880
- if (value && typeof value === "object") {
881
- const result = {};
882
- for (const key of Object.keys(value)) {
883
- result[key] = substitutePlaceholders(value[key], context, options);
884
- }
885
- return result;
886
- }
887
- return value;
888
- }
889
- const PATH_SLOT = "{path}";
890
- const PARAM_SLOT = "{param}";
891
- const warnedPatterns = /* @__PURE__ */ new Set();
892
- function warnOnce(key, message) {
893
- if (warnedPatterns.has(key)) return;
894
- warnedPatterns.add(key);
895
- console.warn(`[query-address] ${message}`);
896
- }
897
- function readPattern(lane, key) {
898
- if (!lane || typeof lane !== "object" || Array.isArray(lane)) return null;
899
- const pattern = lane[key];
900
- return typeof pattern === "string" && pattern.length > 0 ? pattern : null;
901
- }
902
- function resolveQueryAddress(query, lane) {
903
- if (typeof query !== "string" || query.length === 0) return null;
904
- const pattern = readPattern(lane, "list");
905
- if (!pattern) return null;
906
- if (!pattern.includes(PATH_SLOT)) {
907
- warnOnce(
908
- `list:${pattern}`,
909
- `config.records.list carries no ${PATH_SLOT} placeholder, so every query would resolve to the same address. Ignoring it and reading the compiled file instead.`
910
- );
911
- return null;
912
- }
913
- return substitutePlaceholders(pattern, { path: query });
914
- }
915
- function resolveRecordAddressPattern(query, lane) {
916
- if (typeof query !== "string" || query.length === 0) return null;
917
- const pattern = readPattern(lane, "record");
918
- if (!pattern) return null;
919
- if (!pattern.includes(PARAM_SLOT)) {
920
- warnOnce(
921
- `record:${pattern}`,
922
- `config.records.record carries no ${PARAM_SLOT} placeholder, so every record would resolve to the same address. Ignoring it and reading the per-record file instead.`
923
- );
924
- return null;
925
- }
926
- return substitutePlaceholders(pattern, { path: query });
927
- }
928
- function localizeConfig(cfg, locale, defaultLocale) {
929
- if (!cfg.path) return cfg;
930
- if (!locale || locale === defaultLocale) return cfg;
931
- if (!isDataUrl(cfg.path)) return cfg;
932
- return { ...cfg, path: `/${locale}${cfg.path}` };
933
- }
934
- function applyDeferredDetail(cfg, queries, records) {
935
- if (cfg.detail !== void 0) return cfg;
936
- if (cfg.endpoint) {
937
- const recordPattern = resolveRecordAddressPattern(cfg.query, records);
938
- if (recordPattern) return { ...cfg, detail: recordPattern };
939
- }
940
- const queryName = cfg.query || bindingKey(cfg);
941
- if (!queryName || !queries) return cfg;
942
- const collConfig = queries[queryName];
943
- if (!collConfig || typeof collConfig !== "object") return cfg;
944
- const deferred = Array.isArray(collConfig.deferred) ? collConfig.deferred : null;
945
- if (!deferred || deferred.length === 0) return cfg;
946
- const pattern = typeof collConfig.detailUrl === "string" ? collConfig.detailUrl : recordDataUrl(queryName, "{slug}");
947
- return { ...cfg, detail: pattern };
948
- }
949
- function resolveQuerySource(cfg, records) {
950
- if (typeof cfg.query !== "string" || cfg.query.length === 0) return cfg;
951
- const endpoint = resolveQueryAddress(cfg.query, records);
952
- if (endpoint) {
953
- const { path, url, ...rest } = cfg;
954
- return { ...rest, endpoint };
955
- }
956
- return { ...cfg, path: queryDataUrl(cfg.query) };
957
- }
958
- function bindingKey(cfg) {
959
- return cfg?.as;
960
- }
961
- function resolveFetchConfigs(sources, options = {}) {
962
- const {
963
- schemas = [],
964
- locale = null,
965
- defaultLocale = null,
966
- queries = null,
967
- records = null
968
- } = options;
969
- const configs = /* @__PURE__ */ new Map();
970
- const collectAll = schemas.length === 0;
971
- for (const source of sources) {
972
- if (!source) continue;
973
- const configList = Array.isArray(source) ? source : [source];
974
- for (const cfg of configList) {
975
- const key = bindingKey(cfg);
976
- if (!key) continue;
977
- if (configs.has(key)) continue;
978
- if (!collectAll && !schemas.includes(key)) continue;
979
- const sourced = resolveQuerySource(cfg, records);
980
- const localized = localizeConfig(sourced, locale, defaultLocale);
981
- configs.set(key, applyDeferredDetail(localized, queries, records));
982
- }
983
- }
984
- return configs;
985
- }
986
- function deriveCacheKey(request) {
987
- const { path, url, endpoint, transform } = request || {};
988
- const as = request?.as;
989
- const method = request?.method && request.method.toUpperCase() !== "GET" ? request.method.toUpperCase() : void 0;
990
- const body = method === "POST" ? request?.body : void 0;
991
- return JSON.stringify({ path, url, endpoint, as, transform, method, body });
992
- }
993
- function codeOf(entry) {
994
- if (typeof entry === "string" && entry.trim() && entry.trim() !== "*") return entry.trim();
995
- if (entry && typeof entry === "object" && typeof entry.code === "string" && entry.code.trim()) {
996
- return entry.code.trim();
997
- }
998
- return null;
999
- }
1000
- function normalizeLanguageList(value) {
1001
- if (!Array.isArray(value)) return [];
1002
- const seen = /* @__PURE__ */ new Set();
1003
- const codes = [];
1004
- for (const entry of value) {
1005
- const code = codeOf(entry);
1006
- if (!code || seen.has(code)) continue;
1007
- seen.add(code);
1008
- codes.push(code);
1009
- }
1010
- return codes;
1011
- }
1012
- function resolveDefaultLocale(config = {}) {
1013
- if (typeof config?.defaultLanguage === "string" && config.defaultLanguage.trim()) {
1014
- return config.defaultLanguage.trim();
1015
- }
1016
- return normalizeLanguageList(config?.languages)[0] || "en";
1017
- }
1018
841
  const KNOWN_OPERATORS = /* @__PURE__ */ new Set(["where", "limit", "sort"]);
1019
842
  function createDefaultFetcher({ basePath = "", config = {}, dev = false, records = null, fetch: fetchImpl = null } = {}) {
1020
843
  const doFetch = (input, init) => (fetchImpl || globalThis.fetch)(input, init);
@@ -1044,7 +867,7 @@ function createDefaultFetcher({ basePath = "", config = {}, dev = false, records
1044
867
  * the predicate travels in the request.
1045
868
  */
1046
869
  cacheKey(request) {
1047
- const base = deriveCacheKey$1(request);
870
+ const base = deriveCacheKey(request);
1048
871
  const projected = {};
1049
872
  for (const op of supports) {
1050
873
  if (!style.canPush.has(op)) continue;
@@ -1095,7 +918,7 @@ function createDefaultFetcher({ basePath = "", config = {}, dev = false, records
1095
918
  }
1096
919
  if (method === "POST") {
1097
920
  const dc = request.dynamicContext;
1098
- const resolvedBody = rawBody !== void 0 && rawBody !== null && dc && dc.paramName ? substitutePlaceholders$1(rawBody, { [dc.paramName]: dc.paramValue }, { encode: false }) : rawBody;
921
+ const resolvedBody = rawBody !== void 0 && rawBody !== null && dc && dc.paramName ? substitutePlaceholders(rawBody, { [dc.paramName]: dc.paramValue }, { encode: false }) : rawBody;
1099
922
  const finalBody = composePostBody(resolvedBody, encoded.bodyMerge);
1100
923
  if (finalBody !== null) {
1101
924
  if (!hasHeader(headers, "Content-Type")) {
@@ -1293,14 +1116,14 @@ function resolvePageFetchConfigs(content, route, { locale = null } = {}) {
1293
1116
  const parent = page.parent ? pages.find((p) => p.route === page.parent) : null;
1294
1117
  const options = {
1295
1118
  locale,
1296
- defaultLocale: resolveDefaultLocale(content?.config) ?? null,
1119
+ defaultLocale: resolveDefaultLocale$1(content?.config) ?? null,
1297
1120
  queries: content?.config?.queries ?? null,
1298
1121
  records: content?.config?.records ?? null
1299
1122
  };
1300
1123
  const out = /* @__PURE__ */ new Map();
1301
1124
  const add = (sources) => {
1302
1125
  for (const cfg of resolveFetchConfigs(sources, options).values()) {
1303
- const key = deriveCacheKey(cfg);
1126
+ const key = deriveCacheKey$1(cfg);
1304
1127
  if (!out.has(key)) out.set(key, cfg);
1305
1128
  }
1306
1129
  };
@@ -1343,10 +1166,45 @@ async function prefetchPageData({ content, route, locale = null, fetch: fetch2 =
1343
1166
  const configs = resolvePageFetchConfigs(content, route, { locale });
1344
1167
  return executeFetchConfigs(configs, { content, fetch: fetch2, dev, prerender });
1345
1168
  }
1169
+ function createPageRenderer({ website, shell }) {
1170
+ if (!website) throw new Error("createPageRenderer: `website` is required");
1171
+ if (typeof shell !== "string") throw new Error("createPageRenderer: `shell` must be an HTML string");
1172
+ function render(target, { inject = {} } = {}) {
1173
+ const page = typeof target === "string" ? resolvePage(website, target) : target;
1174
+ if (!page) return { outcome: "notFound", html: null, page: null, error: null };
1175
+ let result;
1176
+ try {
1177
+ result = renderPage(page, website);
1178
+ } catch (err) {
1179
+ return { outcome: "failed", html: null, page, error: classifyRenderError(err) };
1180
+ }
1181
+ if (result.error) return { outcome: "failed", html: null, page, error: result.error };
1182
+ const html = injectPageContent(shell, result.renderedContent, page, {
1183
+ ...inject,
1184
+ sectionOverrideCSS: result.sectionOverrideCSS
1185
+ });
1186
+ return { outcome: "rendered", html, page, error: null };
1187
+ }
1188
+ return { website, render };
1189
+ }
1190
+ async function prefetchAndHydrate({ website, content, route, locale = null, fetch: fetch2 = null, dev = false, prerender = "always" }) {
1191
+ if (!website?.dataStore) {
1192
+ throw new Error("prefetchAndHydrate: `website` must be an initialized Website with a dataStore");
1193
+ }
1194
+ if (typeof fetch2 !== "function") {
1195
+ throw new Error(
1196
+ "prefetchAndHydrate: `fetch` must be a function. A transport does not survive a JSON-serialized isolate boundary — pass it as an RPC method argument. To use the ambient fetch deliberately, pass `fetch: globalThis.fetch`."
1197
+ );
1198
+ }
1199
+ const fetched = await prefetchPageData({ content, route, locale, fetch: fetch2, dev, prerender });
1200
+ hydrateDataStore(website, fetched);
1201
+ return fetched;
1202
+ }
1346
1203
  export {
1347
1204
  applyDefaults,
1348
1205
  applySchemas,
1349
1206
  classifyRenderError,
1207
+ createPageRenderer,
1350
1208
  escapeHtml,
1351
1209
  executeFetchConfigs,
1352
1210
  findPageForRoute,
@@ -1359,6 +1217,7 @@ export {
1359
1217
  initPrerender,
1360
1218
  initPrerenderForLocale,
1361
1219
  injectPageContent,
1220
+ prefetchAndHydrate,
1362
1221
  prefetchIcons,
1363
1222
  prefetchPageData,
1364
1223
  prepareProps,