@xingwangzhe/stalux 1.3.9 → 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 +6 -6
- package/src/index.ts +4 -4
- package/src/internal/font-subset.ts +91 -70
- package/src/layouts/Stalux.astro +2 -2
- package/template/astro.config.mjs +7 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xingwangzhe/stalux",
|
|
3
|
-
"version": "1.
|
|
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
|
}
|
|
@@ -191,6 +191,20 @@ function collectCommonChars(projectRoot: string): string {
|
|
|
191
191
|
|
|
192
192
|
scanDir(srcDir);
|
|
193
193
|
|
|
194
|
+
// Also scan user config YAML files for nav titles, descriptions, etc.
|
|
195
|
+
const configDir = resolve(projectRoot, CONTENT_ROOT, "config");
|
|
196
|
+
if (existsSync(configDir)) {
|
|
197
|
+
for (const f of readdirSync(configDir)) {
|
|
198
|
+
if (!f.endsWith(".yml") && !f.endsWith(".yaml")) continue;
|
|
199
|
+
try {
|
|
200
|
+
const content = readFileSync(join(configDir, f), "utf-8");
|
|
201
|
+
for (const ch of content) chars.add(ch);
|
|
202
|
+
} catch {
|
|
203
|
+
/* skip */
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
194
208
|
return [...chars].sort().join("");
|
|
195
209
|
}
|
|
196
210
|
|
|
@@ -215,34 +229,39 @@ function deduplicate(routes: RouteCharSet[]): Map<string, string[]> {
|
|
|
215
229
|
|
|
216
230
|
/**
|
|
217
231
|
* Run font subsetting during build.
|
|
218
|
-
* Called from
|
|
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)
|
|
219
237
|
*/
|
|
220
238
|
export async function runFontSubsetting(
|
|
221
|
-
|
|
222
|
-
|
|
239
|
+
projectRoot: string,
|
|
240
|
+
logger: AstroIntegrationLogger,
|
|
241
|
+
skipPerRoute = false,
|
|
223
242
|
): Promise<void> {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
+
}
|
|
238
258
|
}
|
|
239
|
-
}
|
|
240
259
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
260
|
+
if (!fontPath) {
|
|
261
|
+
logger.warn(`Font not found at ${FONT_INPUT}, skipping subsetting`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const fontBuffer = readFileSync(fontPath);
|
|
246
265
|
|
|
247
266
|
// 2. Scan content files
|
|
248
267
|
const contentFiles = scanContent(projectRoot);
|
|
@@ -295,69 +314,71 @@ export async function runFontSubsetting(
|
|
|
295
314
|
`;
|
|
296
315
|
writeFileSync(join(outDir, "common.css"), commonCSS);
|
|
297
316
|
|
|
298
|
-
// Generate per-route subsets (deduplicated) and their CSS
|
|
299
317
|
const groups = deduplicate(routeSets);
|
|
300
318
|
const routeMap: Record<string, string> = {};
|
|
301
319
|
const postMap: Record<string, string> = {};
|
|
302
320
|
|
|
303
321
|
let subsetCount = 0;
|
|
304
|
-
// Track which subsets we've already generated CSS for
|
|
305
322
|
const cssGenerated = new Set<string>();
|
|
306
323
|
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
+
}
|
|
326
347
|
}
|
|
327
|
-
}
|
|
328
348
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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} */
|
|
334
354
|
@font-face {
|
|
335
355
|
font-family: "LXGW WenKai Subset";
|
|
336
356
|
src: url("/fonts/${filename}") format("woff2");
|
|
337
357
|
font-display: swap;
|
|
338
358
|
}
|
|
339
359
|
`;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
+
}
|
|
349
370
|
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
+
}
|
|
361
382
|
}
|
|
362
383
|
}
|
|
363
384
|
}
|
package/src/layouts/Stalux.astro
CHANGED
|
@@ -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: [
|
|
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: [
|
|
34
|
+
src: [
|
|
35
|
+
"./node_modules/@xingwangzhe/stalux/src/assets/fonts/GoogleSansCode-Italic.woff2",
|
|
36
|
+
],
|
|
33
37
|
},
|
|
34
38
|
],
|
|
35
39
|
},
|