@xingwangzhe/stalux 1.4.0 → 1.4.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingwangzhe/stalux",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "A powerful, modern Astro blog theme \u2014 use as template or install as plugin",
5
5
  "keywords": [
6
6
  "astro",
@@ -73,8 +73,10 @@
73
73
  "prepare": "git config core.hooksPath scripts/git-hooks"
74
74
  },
75
75
  "dependencies": {
76
+ "@astrojs/rss": "^4.0.19",
76
77
  "@astrojs/sitemap": "^3.7.3",
77
78
  "@expressive-code/plugin-line-numbers": "^0.44.1",
79
+ "@lucide/astro": "^1.27.0",
78
80
  "@pagefind/component-ui": "^1.5.2",
79
81
  "@waline/client": "3.15.2",
80
82
  "@xingwangzhe/satteri-mermaid": "0.7.1",
@@ -83,15 +85,13 @@
83
85
  "animejs": "^4.5.0",
84
86
  "astro": "^7.1.3",
85
87
  "astro-expressive-code": "^0.44.1",
86
- "pagefind": "^1.5.2",
87
- "photoswipe": "^5.4.4",
88
- "subset-font": "^2.5.0",
89
- "@astrojs/rss": "^4.0.19",
90
88
  "astro-seo": "^1.1.0",
91
89
  "astro-typewriter": "^1.2.0",
92
- "@lucide/astro": "^1.27.0",
93
90
  "badge-maker": "^6.0.0",
91
+ "pagefind": "^1.5.2",
92
+ "photoswipe": "^5.4.4",
94
93
  "simple-icons": "^16.27.1",
94
+ "subset-font": "^2.5.0",
95
95
  "temml": "^0.13.3"
96
96
  },
97
97
  "devDependencies": {
package/src/index.ts CHANGED
@@ -192,14 +192,14 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration {
192
192
  // 5. 字体裁剪(在 config:setup 阶段执行,保证 dev/build 都能生成)
193
193
  try {
194
194
  const projectRoot = process.cwd();
195
- await runFontSubsetting(projectRoot, logger);
195
+ await runFontSubsetting(projectRoot, logger, true);
196
196
  } catch (error) {
197
197
  logger.debug(`Font subsetting deferred: ${String(error)}`);
198
198
  }
199
199
  },
200
200
 
201
201
  "astro:build:start": async ({ logger }) => {
202
- // 6. 字体裁剪(构建时备用)
202
+ // 6. 字体裁剪(构建时,生成全部路由子集)
203
203
  try {
204
204
  const projectRoot = process.cwd();
205
205
  await runFontSubsetting(projectRoot, logger);
@@ -209,10 +209,10 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration {
209
209
  },
210
210
 
211
211
  "astro:server:setup": async ({ logger }) => {
212
- // 7. 字体裁剪(开发模式备用)
212
+ // 7. 字体裁剪(开发模式,仅 common 子集)
213
213
  try {
214
214
  const projectRoot = process.cwd();
215
- await runFontSubsetting(projectRoot, logger);
215
+ await runFontSubsetting(projectRoot, logger, true);
216
216
  } catch (error) {
217
217
  logger.debug(`Font subsetting deferred in dev: ${String(error)}`);
218
218
  }
@@ -229,34 +229,39 @@ function deduplicate(routes: RouteCharSet[]): Map<string, string[]> {
229
229
 
230
230
  /**
231
231
  * Run font subsetting during build.
232
- * Called from astro:build:start hook.
232
+ * Called from integration hooks.
233
+ *
234
+ * @param projectRoot - Project root directory
235
+ * @param logger - Astro integration logger
236
+ * @param skipPerRoute - Skip per-route subsets (for dev mode, only generate common)
233
237
  */
234
238
  export async function runFontSubsetting(
235
- projectRoot: string,
236
- logger: AstroIntegrationLogger,
239
+ projectRoot: string,
240
+ logger: AstroIntegrationLogger,
241
+ skipPerRoute = false,
237
242
  ): Promise<void> {
238
- logger.info("Font subsetting: scanning content...");
239
-
240
- // 1. Locate font file (check project root first, then stalux package dir)
241
- const fontPaths = [
242
- resolve(projectRoot, FONT_INPUT),
243
- resolve(projectRoot, "node_modules", "@xingwangzhe", "stalux", FONT_INPUT),
244
- resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", FONT_INPUT),
245
- ];
246
-
247
- let fontPath: string | undefined;
248
- for (const p of fontPaths) {
249
- if (existsSync(p)) {
250
- fontPath = p;
251
- break;
243
+ logger.info("Font subsetting: scanning content...");
244
+
245
+ // 1. Locate font file (check project root first, then stalux package dir)
246
+ const fontPaths = [
247
+ resolve(projectRoot, FONT_INPUT),
248
+ resolve(projectRoot, "node_modules", "@xingwangzhe", "stalux", FONT_INPUT),
249
+ resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", FONT_INPUT),
250
+ ];
251
+
252
+ let fontPath: string | undefined;
253
+ for (const p of fontPaths) {
254
+ if (existsSync(p)) {
255
+ fontPath = p;
256
+ break;
257
+ }
252
258
  }
253
- }
254
259
 
255
- if (!fontPath) {
256
- logger.warn(`Font not found at ${FONT_INPUT}, skipping subsetting`);
257
- return;
258
- }
259
- const fontBuffer = readFileSync(fontPath);
260
+ if (!fontPath) {
261
+ logger.warn(`Font not found at ${FONT_INPUT}, skipping subsetting`);
262
+ return;
263
+ }
264
+ const fontBuffer = readFileSync(fontPath);
260
265
 
261
266
  // 2. Scan content files
262
267
  const contentFiles = scanContent(projectRoot);
@@ -309,69 +314,71 @@ export async function runFontSubsetting(
309
314
  `;
310
315
  writeFileSync(join(outDir, "common.css"), commonCSS);
311
316
 
312
- // Generate per-route subsets (deduplicated) and their CSS
313
317
  const groups = deduplicate(routeSets);
314
318
  const routeMap: Record<string, string> = {};
315
319
  const postMap: Record<string, string> = {};
316
320
 
317
321
  let subsetCount = 0;
318
- // Track which subsets we've already generated CSS for
319
322
  const cssGenerated = new Set<string>();
320
323
 
321
- for (const [hash, routes] of groups) {
322
- const filename = `subset-${hash}.woff2`;
323
- const outPath = join(outDir, filename);
324
- if (!existsSync(outPath)) {
325
- // Find the route chars for this hash
326
- const route = routeSets.find((r) => r.hash === hash);
327
- const chars = route?.chars ?? "";
328
- // Only subset characters NOT in common (already covered by common.css)
329
- const uniqueChars = chars
330
- .split("")
331
- .filter((ch) => !commonChars.includes(ch))
332
- .join("");
333
- if (uniqueChars.length > 0) {
334
- const data = await subsetFont(fontBuffer, uniqueChars, { targetFormat: "woff2" });
335
- writeFileSync(outPath, data);
336
- subsetCount++;
337
- logger.info(
338
- ` subset ${subsetCount}: ${(data.length / 1024).toFixed(1)} KB → ${filename}`,
339
- );
324
+ if (!skipPerRoute) {
325
+ for (const [hash, routes] of groups) {
326
+ const filename = `subset-${hash}.woff2`;
327
+ const outPath = join(outDir, filename);
328
+ if (!existsSync(outPath)) {
329
+ // Find the route chars for this hash
330
+ const route = routeSets.find((r) => r.hash === hash);
331
+ const chars = route?.chars ?? "";
332
+ // Only subset characters NOT in common (already covered by common.css)
333
+ const uniqueChars = chars
334
+ .split("")
335
+ .filter((ch) => !commonChars.includes(ch))
336
+ .join("");
337
+ if (uniqueChars.length > 0) {
338
+ const data = await subsetFont(fontBuffer, uniqueChars, {
339
+ targetFormat: "woff2",
340
+ });
341
+ writeFileSync(outPath, data);
342
+ subsetCount++;
343
+ logger.info(
344
+ ` subset ${subsetCount}: ${(data.length / 1024).toFixed(1)} KB → ${filename}`,
345
+ );
346
+ }
340
347
  }
341
- }
342
348
 
343
- // Generate per-route CSS (one per unique hash, references all routes with this hash)
344
- if (!cssGenerated.has(hash)) {
345
- cssGenerated.add(hash);
346
- for (const route of routes) {
347
- const cssContent = `/* Auto-generated font subset for route: ${route} */
349
+ // Generate per-route CSS (one per unique hash, references all routes with this hash)
350
+ if (!cssGenerated.has(hash)) {
351
+ cssGenerated.add(hash);
352
+ for (const route of routes) {
353
+ const cssContent = `/* Auto-generated font subset for route: ${route} */
348
354
  @font-face {
349
355
  font-family: "LXGW WenKai Subset";
350
356
  src: url("/fonts/${filename}") format("woff2");
351
357
  font-display: swap;
352
358
  }
353
359
  `;
354
- // Use first route's name as the CSS filename (they share the same subset)
355
- const cssName = routes.length === 1 ? route.replace("/", "-") : `group-${hash}`;
356
- const cssFilename = `subset-${cssName}.css`;
357
- writeFileSync(join(outDir, cssFilename), cssContent);
358
- routeMap[route] = cssFilename;
359
-
360
- if (route.startsWith("posts/")) {
361
- const abbrlink = route.replace("posts/", "");
362
- postMap[abbrlink] = cssFilename;
360
+ // Use first route's name as the CSS filename (they share the same subset)
361
+ const cssName = routes.length === 1 ? route.replace("/", "-") : `group-${hash}`;
362
+ const cssFilename = `subset-${cssName}.css`;
363
+ writeFileSync(join(outDir, cssFilename), cssContent);
364
+ routeMap[route] = cssFilename;
365
+
366
+ if (route.startsWith("posts/")) {
367
+ const abbrlink = route.replace("posts/", "");
368
+ postMap[abbrlink] = cssFilename;
369
+ }
363
370
  }
364
- }
365
- } else {
366
- // CSS already generated, just map
367
- for (const route of routes) {
368
- const cssFilename =
369
- routes.length === 1
370
- ? `subset-${route.replace("/", "-")}.css`
371
- : `subset-group-${hash}.css`;
372
- routeMap[route] = cssFilename;
373
- if (route.startsWith("posts/")) {
374
- postMap[route.replace("posts/", "")] = cssFilename;
371
+ } else {
372
+ // CSS already generated, just map
373
+ for (const route of routes) {
374
+ const cssFilename =
375
+ routes.length === 1
376
+ ? `subset-${route.replace("/", "-")}.css`
377
+ : `subset-group-${hash}.css`;
378
+ routeMap[route] = cssFilename;
379
+ if (route.startsWith("posts/")) {
380
+ postMap[route.replace("posts/", "")] = cssFilename;
381
+ }
375
382
  }
376
383
  }
377
384
  }
@@ -28,8 +28,8 @@ const authorConfig = getAuthorData(configEntries);
28
28
  const promoteConfig = getPromoteData(configEntries);
29
29
  const htmlLang = siteConfig.lang || "zh-CN";
30
30
 
31
- // Resolve font subset CSS URL
32
- const fontSubsetUrl = props.fontSubsetId
31
+ // Resolve font subset CSS URL (only in build mode, dev mode uses only common subset)
32
+ const fontSubsetUrl = import.meta.env.PROD && props.fontSubsetId
33
33
  ? `/fonts/subset-${props.fontSubsetId}.css`
34
34
  : undefined;
35
35
 
@@ -2,9 +2,9 @@ import { satteri } from "@astrojs/markdown-satteri";
2
2
  import sitemap from "@astrojs/sitemap";
3
3
  import { mermaidHast } from "@xingwangzhe/satteri-mermaid";
4
4
  import { photoswipe } from "@xingwangzhe/satteri-photoswipe";
5
+ import stalux from "@xingwangzhe/stalux";
5
6
  import expressiveCode from "astro-expressive-code";
6
7
  import { defineConfig, fontProviders } from "astro/config";
7
- import stalux from "@xingwangzhe/stalux";
8
8
 
9
9
  // https://astro.build/config
10
10
  export default defineConfig({
@@ -24,12 +24,16 @@ export default defineConfig({
24
24
  {
25
25
  weight: "100 900",
26
26
  style: "normal",
27
- src: ["./node_modules/@xingwangzhe/stalux/src/assets/fonts/GoogleSansCode.woff2"],
27
+ src: [
28
+ "./node_modules/@xingwangzhe/stalux/src/assets/fonts/GoogleSansCode.woff2",
29
+ ],
28
30
  },
29
31
  {
30
32
  weight: "100 900",
31
33
  style: "italic",
32
- src: ["./node_modules/@xingwangzhe/stalux/src/assets/fonts/GoogleSansCode-Italic.woff2"],
34
+ src: [
35
+ "./node_modules/@xingwangzhe/stalux/src/assets/fonts/GoogleSansCode-Italic.woff2",
36
+ ],
33
37
  },
34
38
  ],
35
39
  },