@uniweb/runtime 1.3.2 → 1.3.3
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 +85 -7
- package/dist/ssr.js.map +1 -1
- package/package.json +3 -3
- package/src/load-page-data.js +12 -3
- package/src/page-icons.js +136 -0
- package/src/page-renderer.js +5 -1
- package/src/setup.js +1 -38
- package/src/ssr-renderer.js +8 -0
package/dist/ssr.js
CHANGED
|
@@ -5,17 +5,18 @@ import { sectionDomId } from "@uniweb/core/section-id";
|
|
|
5
5
|
import { applyBasePath } from "@uniweb/core/base-path";
|
|
6
6
|
import { isDynamicRoute, routePatternToRegex } from "@uniweb/core/route-match";
|
|
7
7
|
import { findPageForRoute } from "@uniweb/core/route-match";
|
|
8
|
-
import { DEFAULT_ICON_BASE, iconUrl } from "@uniweb/core/icon-corpus";
|
|
8
|
+
import { DEFAULT_ICON_BASE, iconUrl, ICON_FAMILY_MAP } from "@uniweb/core/icon-corpus";
|
|
9
9
|
import { buildTheme, FONT_LINKS_MARKER, buildSectionOverrides } from "@uniweb/theming";
|
|
10
10
|
import "@uniweb/core/services";
|
|
11
11
|
import "@uniweb/core/tracker";
|
|
12
12
|
import { escapeHtml } from "@uniweb/core/page-shell";
|
|
13
13
|
import { escapeHtml as escapeHtml2, foundationCssUrl, pageShell } from "@uniweb/core/page-shell";
|
|
14
|
+
import { jsonForScript } from "@uniweb/core/data-script";
|
|
15
|
+
import { dataScript, jsonForScript as jsonForScript2 } from "@uniweb/core/data-script";
|
|
14
16
|
import DataStore, { deriveCacheKey as deriveCacheKey$1 } from "@uniweb/core/datastore";
|
|
15
17
|
import { RESOLUTION_STATUS, carryQuery, localeOfPath, localeUrl, resolveRoute, routeIndex } from "@uniweb/core/resolve-route";
|
|
16
18
|
import { resolveFetchConfigs } from "@uniweb/core/fetch-config";
|
|
17
19
|
import { resolveRecordsService } from "@uniweb/core/records-service";
|
|
18
|
-
import { dataScript, jsonForScript } from "@uniweb/core/data-script";
|
|
19
20
|
function guaranteeItemStructure(item) {
|
|
20
21
|
return {
|
|
21
22
|
title: item.title || "",
|
|
@@ -800,6 +801,11 @@ ${themeData.links}
|
|
|
800
801
|
${options.sectionOverrideCSS}
|
|
801
802
|
</style>`;
|
|
802
803
|
result = result.replace("</head>", `${overrideStyle}
|
|
804
|
+
</head>`);
|
|
805
|
+
}
|
|
806
|
+
if (options.icons?.size && !result.includes('id="__ICON_CACHE__"')) {
|
|
807
|
+
const iconScript = `<script id="__ICON_CACHE__" type="application/json">${jsonForScript(Object.fromEntries(options.icons))}<\/script>`;
|
|
808
|
+
result = result.replace("</head>", () => ` ${iconScript}
|
|
803
809
|
</head>`);
|
|
804
810
|
}
|
|
805
811
|
result = result.replace(
|
|
@@ -1191,7 +1197,74 @@ function blocksOf(page) {
|
|
|
1191
1197
|
for (const area of Object.values(page.getLayoutAreas?.() ?? {})) walk(area);
|
|
1192
1198
|
return out;
|
|
1193
1199
|
}
|
|
1194
|
-
|
|
1200
|
+
const ICON_WAIT_MS = 1e3;
|
|
1201
|
+
const MAX_ICON_REQUESTS = 40;
|
|
1202
|
+
function parseIconReference(ref) {
|
|
1203
|
+
if (typeof ref !== "string") return null;
|
|
1204
|
+
const colon = ref.indexOf(":");
|
|
1205
|
+
if (colon > 0) return { library: ref.slice(0, colon), name: ref.slice(colon + 1) };
|
|
1206
|
+
const dash = ref.indexOf("-");
|
|
1207
|
+
if (dash < 2 || dash > 3) return null;
|
|
1208
|
+
const library = ref.slice(0, dash);
|
|
1209
|
+
return ICON_FAMILY_MAP[library] ? { library, name: ref.slice(dash + 1) } : null;
|
|
1210
|
+
}
|
|
1211
|
+
function visit(node, found) {
|
|
1212
|
+
if (!node || typeof node !== "object") return;
|
|
1213
|
+
if (node.type === "image" && node.attrs?.role === "icon") found.push(node.attrs);
|
|
1214
|
+
for (const mark of node.marks || []) {
|
|
1215
|
+
const ref = parseIconReference(mark?.attrs?.icon);
|
|
1216
|
+
if (ref) found.push(ref);
|
|
1217
|
+
}
|
|
1218
|
+
if (Array.isArray(node.content)) for (const child of node.content) visit(child, found);
|
|
1219
|
+
}
|
|
1220
|
+
function pageIcons(blocks) {
|
|
1221
|
+
const named = [];
|
|
1222
|
+
for (const block of blocks) visit(block.rawContent?.doc ?? block.rawContent, named);
|
|
1223
|
+
const icons = /* @__PURE__ */ new Map();
|
|
1224
|
+
for (const { library, name } of named) {
|
|
1225
|
+
const family = ICON_FAMILY_MAP[String(library).toLowerCase()];
|
|
1226
|
+
if (family && name) icons.set(`${library}:${name}`, { family, name });
|
|
1227
|
+
}
|
|
1228
|
+
return icons;
|
|
1229
|
+
}
|
|
1230
|
+
async function loadPageIcons({ blocks, website, fetch: fetch2, cache }) {
|
|
1231
|
+
const config = website.config?.icons;
|
|
1232
|
+
if (config?.cdn === false) return;
|
|
1233
|
+
const base = config?.base || DEFAULT_ICON_BASE;
|
|
1234
|
+
const wanted = [...pageIcons(blocks)].filter(([key]) => !cache.has(key)).slice(0, MAX_ICON_REQUESTS);
|
|
1235
|
+
if (wanted.length === 0) return;
|
|
1236
|
+
const all = Promise.all(wanted.map(async ([key, { family, name }]) => {
|
|
1237
|
+
try {
|
|
1238
|
+
const response = await fetch2(iconUrl(family, name, base), { method: "GET" });
|
|
1239
|
+
if (!response.ok) return;
|
|
1240
|
+
const svg = await response.text();
|
|
1241
|
+
if (/<svg[\s>]/i.test(svg)) cache.set(key, svg);
|
|
1242
|
+
} catch {
|
|
1243
|
+
}
|
|
1244
|
+
}));
|
|
1245
|
+
let timer;
|
|
1246
|
+
const waited = new Promise((resolve) => {
|
|
1247
|
+
timer = setTimeout(resolve, ICON_WAIT_MS);
|
|
1248
|
+
});
|
|
1249
|
+
await Promise.race([all, waited]);
|
|
1250
|
+
clearTimeout(timer);
|
|
1251
|
+
}
|
|
1252
|
+
function whileReadingIcons(uniweb, read, draw) {
|
|
1253
|
+
const cache = uniweb?.iconCache;
|
|
1254
|
+
if (!cache) return draw();
|
|
1255
|
+
const { get } = cache;
|
|
1256
|
+
cache.get = (key) => {
|
|
1257
|
+
const svg = get.call(cache, key);
|
|
1258
|
+
if (svg) read.set(key, svg);
|
|
1259
|
+
return svg;
|
|
1260
|
+
};
|
|
1261
|
+
try {
|
|
1262
|
+
return draw();
|
|
1263
|
+
} finally {
|
|
1264
|
+
delete cache.get;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
async function loadPageData({ website, route, fetch: fetch2, dev = false, locale = null, prerender = "always", cache = null, icons = true }) {
|
|
1195
1268
|
if (prerender !== "author" && prerender !== "always") {
|
|
1196
1269
|
throw new Error(`loadPageData: prerender must be 'author' or 'always', got ${JSON.stringify(prerender)}`);
|
|
1197
1270
|
}
|
|
@@ -1203,6 +1276,7 @@ async function loadPageData({ website, route, fetch: fetch2, dev = false, locale
|
|
|
1203
1276
|
);
|
|
1204
1277
|
}
|
|
1205
1278
|
website.makeCurrent();
|
|
1279
|
+
const iconCache = globalThis.uniweb?.iconCache;
|
|
1206
1280
|
const named = typeof route === "string" ? resolveRequestedPage(website, route) : route;
|
|
1207
1281
|
const resolved = named ?? (typeof route === "string" ? website.getNotFoundPage() : null);
|
|
1208
1282
|
if (!resolved) return [];
|
|
@@ -1241,11 +1315,13 @@ async function loadPageData({ website, route, fetch: fetch2, dev = false, locale
|
|
|
1241
1315
|
}
|
|
1242
1316
|
};
|
|
1243
1317
|
const store = website.entityStore;
|
|
1318
|
+
const blocks = blocksOf(page);
|
|
1244
1319
|
await Promise.all([
|
|
1245
1320
|
// The page's own record: asked whether or not a section declares the route key, because the
|
|
1246
1321
|
// page names itself from it and a URL naming no record is a 404.
|
|
1247
1322
|
store.fetchPageRecord(page, { dispatcher: recording }),
|
|
1248
|
-
...
|
|
1323
|
+
...blocks.map((block) => store.fetch(block, getComponentMeta(block.type), { dispatcher: recording })),
|
|
1324
|
+
icons && iconCache && loadPageIcons({ blocks, website, fetch: fetch2, cache: iconCache })
|
|
1249
1325
|
]);
|
|
1250
1326
|
return [...entries.values()];
|
|
1251
1327
|
}
|
|
@@ -1268,16 +1344,18 @@ function createPageRenderer({ website, shell }) {
|
|
|
1268
1344
|
const missing = !named || page?.notFound === true;
|
|
1269
1345
|
restoreStartPage(website);
|
|
1270
1346
|
if (typeof target === "string") website.setActivePage(target);
|
|
1347
|
+
const icons = /* @__PURE__ */ new Map();
|
|
1271
1348
|
let result;
|
|
1272
1349
|
try {
|
|
1273
|
-
result = renderPage(page, website);
|
|
1350
|
+
result = whileReadingIcons(globalThis.uniweb, icons, () => renderPage(page, website));
|
|
1274
1351
|
} catch (err) {
|
|
1275
1352
|
return { outcome: missing ? "notFound" : "failed", html: null, page, error: classifyRenderError(err) };
|
|
1276
1353
|
}
|
|
1277
1354
|
if (result.error) return { outcome: missing ? "notFound" : "failed", html: null, page, error: result.error };
|
|
1278
1355
|
const html = injectPageContent(shell, result.renderedContent, page, {
|
|
1279
1356
|
...inject,
|
|
1280
|
-
sectionOverrideCSS: result.sectionOverrideCSS
|
|
1357
|
+
sectionOverrideCSS: result.sectionOverrideCSS,
|
|
1358
|
+
icons
|
|
1281
1359
|
});
|
|
1282
1360
|
return { outcome: missing ? "notFound" : "rendered", html, page, error: null };
|
|
1283
1361
|
}
|
|
@@ -1344,7 +1422,7 @@ export {
|
|
|
1344
1422
|
initPrerender,
|
|
1345
1423
|
initPrerenderForLocale,
|
|
1346
1424
|
injectPageContent,
|
|
1347
|
-
jsonForScript,
|
|
1425
|
+
jsonForScript2 as jsonForScript,
|
|
1348
1426
|
loadPageData,
|
|
1349
1427
|
localeOfPath,
|
|
1350
1428
|
localeUrl,
|