@xingwangzhe/stalux 1.16.0 → 1.18.0

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.
Files changed (49) hide show
  1. package/package.json +3 -1
  2. package/src/components/stalux/layout/head.astro +2 -2
  3. package/src/index.ts +56 -1
  4. package/src/internal/font-subset.ts +94 -24
  5. package/src/layouts/Stalux.astro +6 -20
  6. package/src/scripts/background.ts +19 -7
  7. package/src/scripts/webmcp.ts +4 -2
  8. /package/{src/assets → public}/background/pattern-1.min.svg +0 -0
  9. /package/{src/assets → public}/background/pattern-10.min.svg +0 -0
  10. /package/{src/assets → public}/background/pattern-11.min.svg +0 -0
  11. /package/{src/assets → public}/background/pattern-12.min.svg +0 -0
  12. /package/{src/assets → public}/background/pattern-13.min.svg +0 -0
  13. /package/{src/assets → public}/background/pattern-14.min.svg +0 -0
  14. /package/{src/assets → public}/background/pattern-15.min.svg +0 -0
  15. /package/{src/assets → public}/background/pattern-16.min.svg +0 -0
  16. /package/{src/assets → public}/background/pattern-17.min.svg +0 -0
  17. /package/{src/assets → public}/background/pattern-18.min.svg +0 -0
  18. /package/{src/assets → public}/background/pattern-19.min.svg +0 -0
  19. /package/{src/assets → public}/background/pattern-2.min.svg +0 -0
  20. /package/{src/assets → public}/background/pattern-20.min.svg +0 -0
  21. /package/{src/assets → public}/background/pattern-21.min.svg +0 -0
  22. /package/{src/assets → public}/background/pattern-22.min.svg +0 -0
  23. /package/{src/assets → public}/background/pattern-23.min.svg +0 -0
  24. /package/{src/assets → public}/background/pattern-24.min.svg +0 -0
  25. /package/{src/assets → public}/background/pattern-25.min.svg +0 -0
  26. /package/{src/assets → public}/background/pattern-26.min.svg +0 -0
  27. /package/{src/assets → public}/background/pattern-27.min.svg +0 -0
  28. /package/{src/assets → public}/background/pattern-28.min.svg +0 -0
  29. /package/{src/assets → public}/background/pattern-29.min.svg +0 -0
  30. /package/{src/assets → public}/background/pattern-3.min.svg +0 -0
  31. /package/{src/assets → public}/background/pattern-30.min.svg +0 -0
  32. /package/{src/assets → public}/background/pattern-31.min.svg +0 -0
  33. /package/{src/assets → public}/background/pattern-32.min.svg +0 -0
  34. /package/{src/assets → public}/background/pattern-33.min.svg +0 -0
  35. /package/{src/assets → public}/background/pattern-34.min.svg +0 -0
  36. /package/{src/assets → public}/background/pattern-35.min.svg +0 -0
  37. /package/{src/assets → public}/background/pattern-36.min.svg +0 -0
  38. /package/{src/assets → public}/background/pattern-37.min.svg +0 -0
  39. /package/{src/assets → public}/background/pattern-38.min.svg +0 -0
  40. /package/{src/assets → public}/background/pattern-39.min.svg +0 -0
  41. /package/{src/assets → public}/background/pattern-4.min.svg +0 -0
  42. /package/{src/assets → public}/background/pattern-40.min.svg +0 -0
  43. /package/{src/assets → public}/background/pattern-41.min.svg +0 -0
  44. /package/{src/assets → public}/background/pattern-42.min.svg +0 -0
  45. /package/{src/assets → public}/background/pattern-5.min.svg +0 -0
  46. /package/{src/assets → public}/background/pattern-6.min.svg +0 -0
  47. /package/{src/assets → public}/background/pattern-7.min.svg +0 -0
  48. /package/{src/assets → public}/background/pattern-8.min.svg +0 -0
  49. /package/{src/assets → public}/background/pattern-9.min.svg +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingwangzhe/stalux",
3
- "version": "1.16.0",
3
+ "version": "1.18.0",
4
4
  "description": "A powerful, modern Astro blog theme — use as template or install as plugin",
5
5
  "keywords": [
6
6
  "astro",
@@ -38,6 +38,7 @@
38
38
  "src/plugins/**/*.ts",
39
39
  "src/internal/**/*.ts",
40
40
  "src/scripts/**/*.ts",
41
+ "public/background/**",
41
42
  "src/assets/**/*",
42
43
  "scripts/cli.mjs",
43
44
  "!src/content.config.ts"
@@ -89,6 +90,7 @@
89
90
  "astro-seo": "^1.1.0",
90
91
  "astro-typewriter": "^1.2.0",
91
92
  "badge-maker": "^6.0.0",
93
+ "js-yaml": "^5.2.3",
92
94
  "pagefind": "^1.5.2",
93
95
  "photoswipe": "^5.4.4",
94
96
  "satteri": "^0.9.5",
@@ -2,7 +2,6 @@
2
2
  import { SEO } from "astro-seo";
3
3
  import { ClientRouter } from "astro:transitions";
4
4
  import { getCollection } from "astro:content";
5
- import { Font } from "astro:assets";
6
5
  import VerCount from "@components/stalux/common/verCount.astro";
7
6
  import GoogleAnalytics from "@components/stalux/analytics/google.astro";
8
7
  import BingClarity from "@components/stalux/analytics/clarity.astro";
@@ -175,7 +174,8 @@ const ldJson = JSON.stringify(schema, null, 2).replace(/<\//g, "<\\/");
175
174
  }}
176
175
  />
177
176
  <style set:html={`:root{--font-body:"LXGW WenKai Subset","Noto Sans SC","Noto Sans CJK SC",system-ui,-apple-system,sans-serif}`} />
178
- <Font cssVariable="--font-code" />
177
+ <!-- 代码字体:静态 code.css(font-subset.ts 生成,确定性,不经过 Astro <Font> 管线) -->
178
+ <link rel="stylesheet" href="/fonts/code.css" />
179
179
  {
180
180
  (headConfig?.anyhead) && (
181
181
  <Fragment set:html={headConfig.anyhead} />
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  * 基于 Astro 7.1.3 Integration API(astro:config:setup / injectRoute / injectScript)
9
9
  */
10
10
 
11
- import { existsSync, readFileSync, readdirSync } from "node:fs";
11
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
12
12
  import path from "node:path";
13
13
  import { resolve } from "node:path";
14
14
  import { fileURLToPath } from "node:url";
@@ -93,6 +93,45 @@ function getRoutes(baseDir: URL): RouteEntry[] {
93
93
  ];
94
94
  }
95
95
 
96
+ // ---------------------------------------------------------------------------
97
+ // 背景 SVG 判别式同步
98
+ // ---------------------------------------------------------------------------
99
+
100
+ /**
101
+ * 把主题包的 public/background/*.svg 同步到用户项目 public/background/。
102
+ *
103
+ * 判别式(非无条件覆盖):
104
+ * - 同名文件:内容一致则跳过(幂等);内容不同才覆盖(主题更新了 SVG)
105
+ * - 用户新增的文件:保留,不删除(避免误删用户自定义背景)
106
+ * - 不做整目录 force 复制
107
+ */
108
+ function syncBackgroundSvgs(srcDir: string): { copied: number; skipped: number } {
109
+ const srcBg = path.join(srcDir, "../public/background");
110
+ if (!existsSync(srcBg)) return { copied: 0, skipped: 0 };
111
+
112
+ const destBg = path.resolve(process.cwd(), "public", "background");
113
+ mkdirSync(destBg, { recursive: true });
114
+
115
+ let copied = 0;
116
+ let skipped = 0;
117
+ for (const name of readdirSync(srcBg)) {
118
+ if (!name.endsWith(".svg")) continue;
119
+ const srcFile = path.join(srcBg, name);
120
+ const destFile = path.join(destBg, name);
121
+ if (existsSync(destFile)) {
122
+ const srcBuf = readFileSync(srcFile);
123
+ const destBuf = readFileSync(destFile);
124
+ if (srcBuf.equals(destBuf)) {
125
+ skipped++; // 内容一致,跳过
126
+ continue;
127
+ }
128
+ }
129
+ copyFileSync(srcFile, destFile);
130
+ copied++;
131
+ }
132
+ return { copied, skipped };
133
+ }
134
+
96
135
  // ---------------------------------------------------------------------------
97
136
  // Vite 别名配置(供页面使用 @components etc.)
98
137
  // ---------------------------------------------------------------------------
@@ -172,6 +211,22 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration {
172
211
  // 2. 注入全局 CSS
173
212
  injectScript("page-ssr", `import "${srcDir}/styles/base/init.css";`);
174
213
 
214
+ // 2.5 同步背景 SVG 到用户项目 public/background/
215
+ // 纯客户端引入:SVG 以静态 URL(/background/pattern-*.svg)被 background.ts 硬编码引用,
216
+ // 不经过 Astro 资源管线(import.meta.glob 会打上每次构建随机的 __ASTRO_ASSET_IMAGE__ 占位符,
217
+ // 破坏增量构建依赖图 hash)。Astro 不会合并集成的 public 目录,这里显式同步一次。
218
+ // 判别式:内容一致跳过(幂等),内容不同才覆盖,用户新增文件保留。
219
+ try {
220
+ const { copied, skipped } = syncBackgroundSvgs(srcDir);
221
+ if (copied > 0) {
222
+ logger.info(`Stalux: 同步背景 SVG ${copied} 个(跳过 ${skipped} 个不变)`);
223
+ } else if (skipped > 0) {
224
+ logger.debug(`Stalux: 背景 SVG 无变化(跳过 ${skipped} 个)`);
225
+ }
226
+ } catch (error) {
227
+ logger.warn(`Stalux: 同步背景 SVG 失败: ${String(error)}`);
228
+ }
229
+
175
230
  // 3. 注入所有页面路由(仅插件模式,源码模式下使用文件路由)
176
231
  if (isPluginMode) {
177
232
  const routes = getRoutes(new URL(".", import.meta.url));
@@ -27,6 +27,10 @@ import { resolve, basename, extname, join, dirname } from "node:path";
27
27
  import { fileURLToPath } from "node:url";
28
28
 
29
29
  import type { AstroIntegrationLogger } from "astro";
30
+ // 与 Astro 内容集合同款的 YAML 解析器(astro 依赖 js-yaml 解析 frontmatter,
31
+ // 见 node_modules/astro/dist/content/loaders/file.js)。用它解析 frontmatter
32
+ // 得到的结果与 getCollection 的 data 一致:引号剥离、类型还原、结构完整。
33
+ import { load as parseYaml } from "js-yaml";
30
34
  import subsetFont from "subset-font";
31
35
 
32
36
  // ---------------------------------------------------------------------------
@@ -39,6 +43,9 @@ const CONTENT_ROOT = "stalux";
39
43
  /** Full font input path (relative to project root or package) */
40
44
  const FONT_INPUT = "src/assets/fonts/LXGWWenKai-Regular.ttf";
41
45
 
46
+ /** Code font input path (Google Sans Code, deterministic static @font-face) */
47
+ const CODE_FONT_INPUT = "src/assets/fonts/GoogleSansCode.woff2";
48
+
42
49
  /** Output directory for generated subsets */
43
50
  const FONT_OUT_DIR = "public/fonts";
44
51
 
@@ -97,6 +104,43 @@ interface ContentFile {
97
104
  abbrlink?: string;
98
105
  }
99
106
 
107
+ /**
108
+ * 用 js-yaml(内容集合同款解析器)解析 frontmatter。
109
+ *
110
+ * 结果与 getCollection("posts") 的 data 一致:
111
+ * - abbrlink:number 转 string、引号剥离(YAML 解析天然处理)
112
+ * - tags/categories:单行字符串 "Bing" 按 schema preprocess 转 ["Bing"]
113
+ */
114
+ function parsePostFrontmatter(content: string): {
115
+ abbrlink?: string;
116
+ tags: string[];
117
+ categories: string[];
118
+ } {
119
+ const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
120
+ if (!m) return { abbrlink: undefined, tags: [], categories: [] };
121
+
122
+ let data: Record<string, unknown>;
123
+ try {
124
+ data = (parseYaml(m[1]) as Record<string, unknown>) ?? {};
125
+ } catch {
126
+ return { abbrlink: undefined, tags: [], categories: [] };
127
+ }
128
+
129
+ // 复刻 posts schema 的 preprocess:单行字符串转数组
130
+ const toList = (v: unknown): string[] => {
131
+ if (Array.isArray(v)) return v.map((x) => String(x));
132
+ if (typeof v === "string") return v.trim() ? [v.trim()] : [];
133
+ return [];
134
+ };
135
+
136
+ const abbr = data.abbrlink;
137
+ return {
138
+ abbrlink: abbr === undefined || abbr === null ? undefined : String(abbr),
139
+ tags: toList(data.tags),
140
+ categories: toList(data.categories),
141
+ };
142
+ }
143
+
100
144
  function scanContent(projectRoot: string): ContentFile[] {
101
145
  const files: ContentFile[] = [];
102
146
  const contentRoot = resolve(projectRoot, CONTENT_ROOT);
@@ -114,11 +158,11 @@ function scanContent(projectRoot: string): ContentFile[] {
114
158
  if (!f.endsWith(".md") && !f.endsWith(".mdx")) continue;
115
159
  const content = readFileSync(join(postsDir, f), "utf-8");
116
160
  const isTemplate = f.startsWith("_");
161
+ const fm = parsePostFrontmatter(content);
117
162
 
118
163
  // Per-post routes: skip template (_) files
119
164
  if (!isTemplate) {
120
- const abbrlinkMatch = content.match(/^abbrlink:\s*(.+)$/m);
121
- const abbrlink = abbrlinkMatch?.[1]?.trim() ?? basename(f, extname(f));
165
+ const abbrlink = fm.abbrlink ?? basename(f, extname(f));
122
166
  files.push({
123
167
  route: `posts/${abbrlink}`,
124
168
  filePath: join(postsDir, f),
@@ -128,11 +172,13 @@ function scanContent(projectRoot: string): ContentFile[] {
128
172
 
129
173
  // Virtual routes (archives/tags/categories): include ALL posts including templates
130
174
  // because content collections load them and they render on those pages
131
- const abbrlinkMatch = content.match(/^abbrlink:\s*(.+)$/m);
132
- const abbrlink = abbrlinkMatch?.[1]?.trim() ?? basename(f, extname(f));
133
- const tags = extractYamlList(content, "tags");
134
- const categories = extractYamlList(content, "categories");
135
- allPostContents.push({ abbrlink, tags, categories, content });
175
+ const abbrlink = fm.abbrlink ?? basename(f, extname(f));
176
+ allPostContents.push({
177
+ abbrlink,
178
+ tags: fm.tags,
179
+ categories: fm.categories,
180
+ content,
181
+ });
136
182
  }
137
183
  }
138
184
 
@@ -248,23 +294,6 @@ function scanContent(projectRoot: string): ContentFile[] {
248
294
  return files;
249
295
  }
250
296
 
251
- /** Extract a YAML list value from frontmatter by key */
252
- function extractYamlList(content: string, key: string): string[] {
253
- const lines: string[] = [];
254
- const regex = new RegExp(`^${key}:$`, "m");
255
- const match = content.match(regex);
256
- if (!match) return [];
257
- const startIdx = match.index! + match[0].length;
258
- const afterKey = content.slice(startIdx);
259
- const listMatch = afterKey.match(/^\n((?:\s+-\s+.+\n?)*)/);
260
- if (!listMatch) return [];
261
- for (const line of listMatch[1].split("\n")) {
262
- const item = line.match(/^\s+-\s+(.+)/)?.[1];
263
- if (item) lines.push(item.trim());
264
- }
265
- return lines;
266
- }
267
-
268
297
  // Cache for virtual route char sets
269
298
  const wordCharsCache = new Map<string, string>();
270
299
  const virtualRouteCache = new Map<string, string>();
@@ -404,6 +433,47 @@ export async function runFontSubsetting(
404
433
  }
405
434
  }
406
435
 
436
+ // 1.5 代码字体(Google Sans Code)— 确定性静态 @font-face,不经过 Astro 字体管线
437
+ // Astro 的 <Font> 组件会在 prerender 阶段启动一个随机端口的 HTTP server,
438
+ // 端口号会嵌进 font-file-url-resolver 虚拟模块的编译产物,破坏增量构建依赖图 hash。
439
+ // 这里直接复制 woff2 到 public/fonts/ 并生成静态 code.css,完全绕开该管线。
440
+ try {
441
+ const codeFontPaths = [
442
+ resolve(projectRoot, CODE_FONT_INPUT),
443
+ resolve(projectRoot, "node_modules", "@xingwangzhe", "stalux", CODE_FONT_INPUT),
444
+ resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", CODE_FONT_INPUT),
445
+ ];
446
+ const codeFontPath = codeFontPaths.find((p) => existsSync(p));
447
+ if (codeFontPath) {
448
+ const codeFontBuffer = readFileSync(codeFontPath);
449
+ const codeFontOut = join(outDir, "GoogleSansCode.woff2");
450
+ if (!existsSync(codeFontOut) || !readFileSync(codeFontOut).equals(codeFontBuffer)) {
451
+ writeFileSync(codeFontOut, codeFontBuffer);
452
+ logger.info(
453
+ ` code font: ${codeFontBuffer.length / 1024} KB → GoogleSansCode.woff2`,
454
+ );
455
+ }
456
+ // 静态 CSS:@font-face + --font-code 变量(确定性内容,无 hash)
457
+ const codeCSS = `/* Auto-generated code font (deterministic, no Astro font pipeline) */
458
+ :root {
459
+ --font-code: "Google Sans Code", "JetBrains Mono", "Fira Code", "Consolas", "Courier New", monospace;
460
+ }
461
+ @font-face {
462
+ font-family: "Google Sans Code";
463
+ src: url("/fonts/GoogleSansCode.woff2") format("woff2");
464
+ font-display: swap;
465
+ }
466
+ `;
467
+ writeFileSync(join(outDir, "code.css"), codeCSS);
468
+ } else {
469
+ logger.warn(
470
+ `Code font not found at ${CODE_FONT_INPUT}, --font-code falls back to system monospace`,
471
+ );
472
+ }
473
+ } catch (error) {
474
+ logger.warn(`Code font setup failed: ${String(error)}`);
475
+ }
476
+
407
477
  // 2. Scan content files
408
478
  const contentFiles = scanContent(projectRoot);
409
479
  logger.info(`Font subsetting: found ${contentFiles.length} content files`);
@@ -34,22 +34,11 @@ const fontSubsetUrl = props.fontSubsetId
34
34
  ? `/fonts/subset-${props.fontSubsetId}.css`
35
35
  : undefined;
36
36
 
37
- const bgModules = import.meta.glob<{ default: { src: string } }>(
38
- "../assets/background/pattern-*.min.svg",
39
- { eager: true },
40
- );
41
- const bgUrls = Object.values(bgModules)
42
- .map((m) => m.default.src)
43
- .sort((a, b) => {
44
- const na = parseInt(a.match(/pattern-(\d+)/)?.[1] || "0", 10);
45
- const nb = parseInt(b.match(/pattern-(\d+)/)?.[1] || "0", 10);
46
- return na - nb;
47
- });
48
- // 背景索引固定为 0(确定性):保证 define:vars 内联脚本在构建间稳定,
49
- // 使 Astro 增量构建的依赖图 hash 可复用。真正的随机背景由客户端
50
- // background.ts 在运行时为每个访问者挑选。
51
- const randomIndex = 0;
52
- const initialBg = bgUrls[randomIndex] || "";
37
+ // 背景 SVG 以静态 URL 硬编码在 background.ts(纯客户端),
38
+ // 集成在 config:setup 时把 SVG 复制到用户项目 public/background/。
39
+ // 这里只取第一张作为 SSR 初始背景,客户端运行时随机替换。
40
+ // 不经过 import.meta.glob 资源管线,保证增量构建依赖图 hash 稳定。
41
+ const initialBg = "/background/pattern-1.min.svg";
53
42
  const llmPromoteRaw = promoteConfig?.llm_promote ?? "";
54
43
  const llmPromote = llmPromoteRaw
55
44
  ?.replace(/\{author\}/g, authorConfig.name)
@@ -75,9 +64,6 @@ const llmPromote = llmPromoteRaw
75
64
  <!-- Per-route font subset (generated at build time) -->
76
65
  <link rel="stylesheet" href="/fonts/common.css" />
77
66
  {fontSubsetUrl && <link rel="stylesheet" href={fontSubsetUrl} />}
78
- <script define:vars={{ bgUrls }}>
79
- window.__STALUX_BG_URLS__ = bgUrls;
80
- </script>
81
67
  <script>
82
68
  import "../scripts/background.ts";
83
69
  </script>
@@ -108,7 +94,7 @@ const llmPromote = llmPromoteRaw
108
94
  <body
109
95
  class="stalux-root dark"
110
96
  data-stalux-bg-layer="a"
111
- data-stalux-bg-index={randomIndex}
97
+ data-stalux-bg-index="0"
112
98
  >
113
99
  {llmPromote && <div style="display:none">{llmPromote}</div>}
114
100
  <div
@@ -1,8 +1,12 @@
1
1
  /**
2
- * 双层随机背景 — 客户端运行时随机选图,不做 VT crossfade
3
- * (去掉 view-transition-name 避免 SVG 全屏瓦片在过渡期间双倍 CPU 光栅化)
4
- * 背景随机从服务端移到客户端,保证构建产物确定性,
5
- * 使 Astro 增量构建的依赖图 hash 可跨构建复用。
2
+ * 双层随机背景 — 纯客户端实现
3
+ *
4
+ * SVG 以静态 URL(/background/pattern-N.min.svg)硬编码,由集成在
5
+ * config:setup 时复制到用户项目 public/background/。不经过 Astro 资源
6
+ * 管线(import.meta.glob 会给 SVG 打上每次构建随机的 __ASTRO_ASSET_IMAGE__
7
+ * 占位符,破坏增量构建依赖图 hash),也不做任何构建期扫描。
8
+ *
9
+ * 背景随机在客户端运行时执行,构建产物完全确定。
6
10
  */
7
11
  declare global {
8
12
  interface Window {
@@ -10,7 +14,16 @@ declare global {
10
14
  }
11
15
  }
12
16
 
13
- const backgroundImages: string[] = window.__STALUX_BG_URLS__ || [];
17
+ /** 背景 SVG 静态 URL 列表(与主题包 public/background/ 的 1-42 号一一对应) */
18
+ const BACKGROUND_URLS: string[] = Array.from(
19
+ { length: 42 },
20
+ (_, i) => `/background/pattern-${i + 1}.min.svg`,
21
+ );
22
+
23
+ /** 优先使用注入列表(若用户自定义),否则用内置硬编码列表 */
24
+ const backgroundImages: string[] = window.__STALUX_BG_URLS__?.length
25
+ ? window.__STALUX_BG_URLS__
26
+ : BACKGROUND_URLS;
14
27
 
15
28
  function getLayerEl(layer: "a" | "b"): HTMLElement | null {
16
29
  return document.querySelector(`.bg-layer.bg-${layer}`);
@@ -22,8 +35,7 @@ function setLayerOpacity(layer: "a" | "b", opacity: number): void {
22
35
  }
23
36
 
24
37
  function initBackground(): void {
25
- // 背景随机在客户端运行时执行,保证构建产物确定性
26
- // (SSR 固定索引 → 内联脚本稳定 → Astro 增量构建依赖图可复用)
38
+ // 客户端运行时随机选图,构建产物保持确定性
27
39
  const index = Math.floor(Math.random() * backgroundImages.length);
28
40
  const layer: "a" | "b" = "a";
29
41
  if (backgroundImages.length > 0) {
@@ -93,8 +93,10 @@ async function loadPagefind(): Promise<{ search: (q: string) => Promise<unknown>
93
93
  if (!pagefindLoadPromise) {
94
94
  pagefindLoadPromise = (async () => {
95
95
  try {
96
- // @vite-ignore: pagefind 索引是构建期产物,不参与依赖打包
97
- const mod = await import(/* @vite-ignore */ "/pagefind/pagefind.js");
96
+ // 用完整 URL 构造,让 Vite 在 dev(vite:import-analysis)与 build 都不解析该路径;
97
+ // pagefind 索引是构建期产物,运行时从站点根加载。
98
+ const url = new URL("/pagefind/pagefind.js", location.origin).href;
99
+ const mod = await import(/* @vite-ignore */ url);
98
100
  return mod.default ?? mod;
99
101
  } catch {
100
102
  return null;