@xingwangzhe/stalux 1.35.1 → 1.35.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/README.md +4 -0
- package/package.json +23 -23
- package/src/components/stalux/archives/archivesList.astro +58 -50
- package/src/components/stalux/categories/categoriesList.astro +4 -4
- package/src/components/stalux/layout/footer.astro +8 -4
- package/src/components/stalux/layout/head.astro +3 -3
- package/src/components/stalux/layout/navs.astro +15 -15
- package/src/components/stalux/links/linkList.astro +4 -4
- package/src/components/stalux/links/socialMedia.astro +35 -35
- package/src/components/stalux/posts/PostTaxonomy.astro +14 -10
- package/src/components/stalux/posts/postCC.astro +7 -7
- package/src/components/stalux/posts/postCard.astro +47 -41
- package/src/components/stalux/posts/postContent.astro +49 -49
- package/src/components/stalux/posts/postList.astro +4 -4
- package/src/components/stalux/posts/postToc.astro +16 -16
- package/src/components/stalux/tags/tagsCloud.astro +20 -20
- package/src/components/stalux/words/wordCard.astro +28 -28
- package/src/components/stalux/words/wordList.astro +17 -17
- package/src/index.ts +98 -20
- package/src/internal/font-slices.ts +56 -6
- package/src/internal/html-images.ts +23 -2
- package/src/internal/image-dimensions.ts +60 -33
- package/src/internal/page-output-cache.ts +78 -0
- package/src/layouts/PostLayout.astro +9 -7
- package/src/layouts/Stalux.astro +4 -4
- package/src/pages/about.astro +6 -6
- package/src/pages/categories/[category].astro +4 -4
- package/src/pages/tags/[tag].astro +5 -1
- package/src/plugins/feature-flags.ts +5 -17
|
@@ -47,61 +47,61 @@ const mdSourceUrl = exportMd ? `${Astro.url.pathname.replace(/\/$/, "")}.md` : "
|
|
|
47
47
|
</h1>
|
|
48
48
|
<div class="stalux-post-metadata">
|
|
49
49
|
{props.date && (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
)}
|
|
50
|
+
<time
|
|
51
|
+
class="stalux-post-meta-item"
|
|
52
|
+
datetime={timezone ? toMachineDateTime(props.date, timezone) : props.date}
|
|
53
|
+
>
|
|
54
|
+
{t("post.publishTime")}
|
|
55
|
+
{props.date}
|
|
56
|
+
</time>
|
|
57
|
+
)}
|
|
58
58
|
|
|
59
59
|
{props.updated && (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)}
|
|
60
|
+
<time
|
|
61
|
+
class="stalux-post-meta-item"
|
|
62
|
+
datetime={timezone ? toMachineDateTime(props.updated, timezone) : props.updated}
|
|
63
|
+
>
|
|
64
|
+
{t("post.updateTime")}
|
|
65
|
+
{props.updated}
|
|
66
|
+
</time>
|
|
67
|
+
)}
|
|
68
68
|
</div>
|
|
69
69
|
{(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
<div class="stalux-post-page-stats">
|
|
71
|
+
{(props.readingMinutes ?? 0) > 0 && (
|
|
72
|
+
<span class="stalux-post-reading-time">
|
|
73
|
+
{t("post.readingTime")}
|
|
74
|
+
{props.readingMinutes && props.readingMinutes > 0
|
|
75
|
+
? props.readingMinutes < 1
|
|
76
|
+
? t("common.lessThanOneMinute")
|
|
77
|
+
: t("common.minute", { n: props.readingMinutes })
|
|
78
|
+
: ""}
|
|
79
|
+
</span>
|
|
80
|
+
)}
|
|
81
|
+
{(props.wordCount ?? 0) > 0 && (
|
|
82
|
+
<span class="wordCount">
|
|
83
|
+
{t("post.wordCount")}
|
|
84
|
+
{props.wordCount}
|
|
85
|
+
</span>
|
|
86
|
+
)}
|
|
87
|
+
<span id="vercount_container_page_pv">
|
|
88
|
+
{t("post.viewCount")}
|
|
89
|
+
<span id="vercount_value_page_pv">Loading...</span>
|
|
90
|
+
</span>
|
|
91
|
+
</div>
|
|
80
92
|
)}
|
|
81
|
-
{(props.wordCount ?? 0) > 0 && (
|
|
82
|
-
<span class="wordCount">
|
|
83
|
-
{t("post.wordCount")}
|
|
84
|
-
{props.wordCount}
|
|
85
|
-
</span>
|
|
86
|
-
)}
|
|
87
|
-
<span id="vercount_container_page_pv">
|
|
88
|
-
{t("post.viewCount")}
|
|
89
|
-
<span id="vercount_value_page_pv">Loading...</span>
|
|
90
|
-
</span>
|
|
91
|
-
</div>
|
|
92
|
-
)}
|
|
93
93
|
{exportMd && (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
)}
|
|
94
|
+
<div class="stalux-post-source-link-row">
|
|
95
|
+
<a
|
|
96
|
+
class="stalux-post-source-link"
|
|
97
|
+
href={mdSourceUrl}
|
|
98
|
+
target="_blank"
|
|
99
|
+
rel="noopener noreferrer"
|
|
100
|
+
>
|
|
101
|
+
{t("post.viewSource")}
|
|
102
|
+
</a>
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
105
|
</header>
|
|
106
106
|
|
|
107
107
|
<ArticleSummary description={props.description} label={t("post.summary")} />
|
|
@@ -14,9 +14,9 @@ const posts = props.posts ?? (await getCollection("posts", ({ data }) => !data.d
|
|
|
14
14
|
<div data-stalux="postlist" class="stalux-post-list-container">
|
|
15
15
|
<ul class="post-list">
|
|
16
16
|
{posts.map((post, index) => (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
))}
|
|
17
|
+
<li>
|
|
18
|
+
<PostCard post={post} priority={index === 0} />
|
|
19
|
+
</li>
|
|
20
|
+
))}
|
|
21
21
|
</ul>
|
|
22
22
|
</div>
|
|
@@ -36,22 +36,22 @@ const tocItems = headings
|
|
|
36
36
|
>
|
|
37
37
|
<h3 class="stalux-toc-title">{title}</h3>
|
|
38
38
|
{tocItems.length > 0 ? (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
) : (
|
|
53
|
-
|
|
54
|
-
)}
|
|
39
|
+
<ul class="stalux-toc-list" transition:name="toc-content">
|
|
40
|
+
{tocItems.map((heading) => (
|
|
41
|
+
<li>
|
|
42
|
+
<a
|
|
43
|
+
class={`stalux-toc-item ${({ 1: "stalux-toc-depth-1", 2: "stalux-toc-depth-2", 3: "stalux-toc-depth-3", 4: "stalux-toc-depth-4", 5: "stalux-toc-depth-5", 6: "stalux-toc-depth-6" } as Record<number, string>)[heading.depth] || ""} a-none`}
|
|
44
|
+
href={`#${heading.slug}`}
|
|
45
|
+
data-toc-link
|
|
46
|
+
>
|
|
47
|
+
{heading.text}
|
|
48
|
+
</a>
|
|
49
|
+
</li>
|
|
50
|
+
))}
|
|
51
|
+
</ul>
|
|
52
|
+
) : (
|
|
53
|
+
<div class="stalux-toc-empty">{t("taxonomy.noToc")}</div>
|
|
54
|
+
)}
|
|
55
55
|
</nav>
|
|
56
56
|
|
|
57
57
|
<script src="../../../scripts/toc-scrollspy.ts"></script>
|
|
@@ -24,26 +24,26 @@ const tagLinks = tagList.map((t) => ({
|
|
|
24
24
|
<!-- 移动端:SSR 卡片 -->
|
|
25
25
|
<div class="stalux-cloud-cards" id="tags-cards">
|
|
26
26
|
{tagList.length > 0 ? (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
) : (
|
|
45
|
-
|
|
46
|
-
)}
|
|
27
|
+
tagGroups.map((group) => (
|
|
28
|
+
<div class="stalux-cloud-tag-group">
|
|
29
|
+
{group.map(({ name, count }) => (
|
|
30
|
+
<a
|
|
31
|
+
href={`/tags/${name}/`}
|
|
32
|
+
class="stalux-cloud-tag-card"
|
|
33
|
+
data-stalux-card
|
|
34
|
+
data-stalux-tag-card
|
|
35
|
+
data-count={count}
|
|
36
|
+
data-astro-prefetch
|
|
37
|
+
>
|
|
38
|
+
<span class="stalux-cloud-tag-name">{name}</span>
|
|
39
|
+
<span class="stalux-cloud-tag-count">{count}</span>
|
|
40
|
+
</a>
|
|
41
|
+
))}
|
|
42
|
+
</div>
|
|
43
|
+
))
|
|
44
|
+
) : (
|
|
45
|
+
<div class="stalux-cloud-no-tags">{t("tags.noTags")}</div>
|
|
46
|
+
)}
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
49
|
<!-- 桌面端:Canvas 标签云 -->
|
|
@@ -23,32 +23,32 @@ const machineDate = date && timezone ? toMachineDateTime(date, timezone) : date;
|
|
|
23
23
|
<Content />
|
|
24
24
|
</div>
|
|
25
25
|
{(date || source || sourceDate) && (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)}
|
|
26
|
+
<footer class="stalux-word-footer">
|
|
27
|
+
{date ? (
|
|
28
|
+
<time class="stalux-word-date" datetime={machineDate}>
|
|
29
|
+
{date}
|
|
30
|
+
</time>
|
|
31
|
+
) : (
|
|
32
|
+
<span />
|
|
33
|
+
)}
|
|
34
|
+
{(source || sourceDate) && (
|
|
35
|
+
<div class="stalux-word-source-meta">
|
|
36
|
+
{sourceDate && <span class="stalux-word-source-date">{sourceDate}</span>}
|
|
37
|
+
{source &&
|
|
38
|
+
(link ? (
|
|
39
|
+
<a
|
|
40
|
+
href={link}
|
|
41
|
+
target="_blank"
|
|
42
|
+
rel="noopener noreferrer"
|
|
43
|
+
class="stalux-word-source-link"
|
|
44
|
+
>
|
|
45
|
+
— {source}
|
|
46
|
+
</a>
|
|
47
|
+
) : (
|
|
48
|
+
<span class="stalux-word-source-text">— {source}</span>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
)}
|
|
52
|
+
</footer>
|
|
53
|
+
)}
|
|
54
54
|
</article>
|
|
@@ -21,24 +21,24 @@ const sorted = [...entries].sort((a, b) => {
|
|
|
21
21
|
|
|
22
22
|
<ul class="stalux-word-list-list">
|
|
23
23
|
{sorted.map((entry) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
const hash = entry.data.abbrlink ? `#word-${entry.data.abbrlink}` : undefined;
|
|
25
|
+
return (
|
|
26
|
+
<li class="stalux-word-list-item">
|
|
27
|
+
{hash ? (
|
|
28
|
+
<a
|
|
29
|
+
href={hash}
|
|
30
|
+
class="a-none"
|
|
31
|
+
data-word-hash={hash}
|
|
32
|
+
aria-label={`Jump to ${entry.data.abbrlink}`}
|
|
33
|
+
>
|
|
34
|
+
<WordCard entry={entry} timezone={timezone} />
|
|
35
|
+
</a>
|
|
36
|
+
) : (
|
|
34
37
|
<WordCard entry={entry} timezone={timezone} />
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
</li>
|
|
40
|
-
);
|
|
41
|
-
})}
|
|
38
|
+
)}
|
|
39
|
+
</li>
|
|
40
|
+
);
|
|
41
|
+
})}
|
|
42
42
|
</ul>
|
|
43
43
|
|
|
44
44
|
<script src="../../../scripts/word-list.ts"></script>
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
readdirSync,
|
|
20
20
|
readFileSync,
|
|
21
21
|
} from "node:fs";
|
|
22
|
+
import { createRequire } from "node:module";
|
|
22
23
|
import path from "node:path";
|
|
23
24
|
import { fileURLToPath } from "node:url";
|
|
24
25
|
import partytown from "@astrojs/partytown";
|
|
@@ -30,6 +31,7 @@ import { fontProviders } from "astro/config";
|
|
|
30
31
|
// pagefind 是 ESM-only 包,需要在模块顶层导入
|
|
31
32
|
// 因为 astro:build:done 钩子中 Vite module runner 已关闭,无法动态 import
|
|
32
33
|
import { createIndex as pagefindCreateIndex } from "pagefind";
|
|
34
|
+
import { parse } from "parse5";
|
|
33
35
|
import { parse as parseYaml } from "yaml";
|
|
34
36
|
import type { StaluxOptions } from "./config";
|
|
35
37
|
import { expressiveCode } from "./expressive-code";
|
|
@@ -38,10 +40,12 @@ import {
|
|
|
38
40
|
clearPageFontSubsets,
|
|
39
41
|
readLinkedStylesheetText,
|
|
40
42
|
resolveFontInputs,
|
|
41
|
-
|
|
43
|
+
writePageFontSubsetAsync,
|
|
42
44
|
} from "./internal/font-slices";
|
|
43
|
-
import {
|
|
45
|
+
import { findHtmlImages } from "./internal/html-images";
|
|
46
|
+
import { addLocalImageDimensions, type ImageDimensions } from "./internal/image-dimensions";
|
|
44
47
|
import { createInjectedRoutes } from "./internal/injected-routes";
|
|
48
|
+
import { createPageOutputCache } from "./internal/page-output-cache";
|
|
45
49
|
import { createRuntimeCacheKey } from "./internal/runtime-cache-key";
|
|
46
50
|
import {
|
|
47
51
|
appendUniquePlugin,
|
|
@@ -49,11 +53,7 @@ import {
|
|
|
49
53
|
prepareSatteriProcessor,
|
|
50
54
|
} from "./internal/satteri-config";
|
|
51
55
|
import { createViteAliases } from "./internal/vite-aliases";
|
|
52
|
-
import {
|
|
53
|
-
applyHtmlImageLoadingPolicy,
|
|
54
|
-
featureFlagsHast,
|
|
55
|
-
featureFlagsMdast,
|
|
56
|
-
} from "./plugins/feature-flags";
|
|
56
|
+
import { featureFlagsHast, featureFlagsMdast } from "./plugins/feature-flags";
|
|
57
57
|
import { temml } from "./plugins/satteri-temml";
|
|
58
58
|
import { describeError } from "./utils/diagnostics";
|
|
59
59
|
|
|
@@ -386,53 +386,127 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
386
386
|
// Astro's Markdown image transform drops arbitrary lazy-loading hints
|
|
387
387
|
// added during HAST processing. Reapply the policy to emitted HTML.
|
|
388
388
|
const htmlFiles = await fs.readdir(outDir, { recursive: true });
|
|
389
|
+
const sourceDir = fileURLToPath(new URL(".", import.meta.url));
|
|
390
|
+
const resolveModule = createRequire(import.meta.url).resolve;
|
|
391
|
+
const parserEntry = resolveModule("parse5");
|
|
392
|
+
const nativeEntry = resolveModule("@xingwangzhe/cjk-font-split-native");
|
|
393
|
+
const outputCache = await createPageOutputCache(
|
|
394
|
+
path.resolve("node_modules/.astro/stalux-page-output"),
|
|
395
|
+
[
|
|
396
|
+
...[
|
|
397
|
+
"index.ts",
|
|
398
|
+
"internal/font-slices.ts",
|
|
399
|
+
"internal/image-dimensions.ts",
|
|
400
|
+
"internal/html-images.ts",
|
|
401
|
+
"internal/page-output-cache.ts",
|
|
402
|
+
"internal/style-names.generated.ts",
|
|
403
|
+
"plugins/feature-flags.ts",
|
|
404
|
+
"../package.json",
|
|
405
|
+
].map((entry) => path.join(sourceDir, entry)),
|
|
406
|
+
path.join(path.dirname(nativeEntry), "package.json"),
|
|
407
|
+
path.resolve(path.dirname(parserEntry), "../package.json"),
|
|
408
|
+
],
|
|
409
|
+
pageFontData?.fontBuffer,
|
|
410
|
+
htmlFiles
|
|
411
|
+
.filter((entry) => /\.(?:css|png|jpe?g|gif|webp)$/iu.test(entry))
|
|
412
|
+
.map((entry) => path.join(outDir, entry)),
|
|
413
|
+
);
|
|
414
|
+
let cacheHits = 0;
|
|
389
415
|
let optimizedImagePages = 0;
|
|
390
416
|
let pageFontPages = 0;
|
|
391
417
|
if (pageFontData) await fs.mkdir(pageFontData.outputDir, { recursive: true });
|
|
392
418
|
if (pageFontData) await fs.mkdir(pageFontData.cacheDir, { recursive: true });
|
|
393
419
|
const referencedPageFonts = new Set<string>();
|
|
394
420
|
const linkedCssTextCache = new Map<string, string>();
|
|
395
|
-
|
|
396
|
-
|
|
421
|
+
const dimensionsCache = new Map<string, Promise<ImageDimensions | undefined>>();
|
|
422
|
+
const pageEntries = htmlFiles.filter((entry) => entry.endsWith(".html"));
|
|
423
|
+
const processPage = async (entry: string) => {
|
|
397
424
|
const output = path.join(outDir, entry);
|
|
398
425
|
const before = await fs.readFile(output, "utf8");
|
|
399
426
|
const withoutPageFont = before.replace(
|
|
400
427
|
/<style data-stalux-page-font>[\s\S]*?<\/style>/gi,
|
|
401
428
|
"",
|
|
402
429
|
);
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
430
|
+
const cached = await outputCache.get(entry, withoutPageFont);
|
|
431
|
+
if (cached) {
|
|
432
|
+
if (cached.font && pageFontData) {
|
|
433
|
+
if (!referencedPageFonts.has(cached.font.filename)) {
|
|
434
|
+
copyFileSync(
|
|
435
|
+
cached.font.sourcePath,
|
|
436
|
+
path.join(pageFontData.outputDir, cached.font.filename),
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
referencedPageFonts.add(cached.font.filename);
|
|
440
|
+
pageFontPages++;
|
|
441
|
+
}
|
|
442
|
+
if (cached.html !== before) {
|
|
443
|
+
await fs.writeFile(output, cached.html);
|
|
444
|
+
optimizedImagePages++;
|
|
445
|
+
}
|
|
446
|
+
cacheHits++;
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
let cachedFont: { filename: string; sourcePath: string } | undefined;
|
|
450
|
+
const dependencies = new Set<string>();
|
|
451
|
+
const document = parse(withoutPageFont, { sourceCodeLocationInfo: true });
|
|
452
|
+
const images = findHtmlImages(withoutPageFont, document);
|
|
453
|
+
let after = await addLocalImageDimensions(withoutPageFont, outDir, {
|
|
454
|
+
images,
|
|
455
|
+
cache: dimensionsCache,
|
|
456
|
+
loadingPolicy: true,
|
|
457
|
+
dependencies,
|
|
458
|
+
});
|
|
408
459
|
if (pageFontData) {
|
|
409
460
|
const linkedStylesheetText = readLinkedStylesheetText(
|
|
410
461
|
after,
|
|
411
462
|
output,
|
|
412
463
|
outDir,
|
|
413
464
|
linkedCssTextCache,
|
|
465
|
+
document,
|
|
466
|
+
dependencies,
|
|
414
467
|
);
|
|
415
|
-
const pageFont =
|
|
468
|
+
const pageFont = await writePageFontSubsetAsync(
|
|
416
469
|
after,
|
|
417
470
|
pageFontData.fontBuffer,
|
|
418
471
|
pageFontData.cacheDir,
|
|
419
472
|
"/_astro/fonts/",
|
|
420
473
|
linkedStylesheetText,
|
|
474
|
+
"",
|
|
475
|
+
document,
|
|
421
476
|
);
|
|
422
477
|
if (pageFont) {
|
|
423
478
|
after = pageFont.html;
|
|
424
479
|
pageFontPages++;
|
|
480
|
+
cachedFont = {
|
|
481
|
+
filename: pageFont.filename,
|
|
482
|
+
sourcePath: pageFont.sourcePath,
|
|
483
|
+
};
|
|
484
|
+
if (!referencedPageFonts.has(pageFont.filename)) {
|
|
485
|
+
copyFileSync(
|
|
486
|
+
pageFont.sourcePath,
|
|
487
|
+
path.join(pageFontData.outputDir, pageFont.filename),
|
|
488
|
+
);
|
|
489
|
+
}
|
|
425
490
|
referencedPageFonts.add(pageFont.filename);
|
|
426
|
-
copyFileSync(
|
|
427
|
-
pageFont.sourcePath,
|
|
428
|
-
path.join(pageFontData.outputDir, pageFont.filename),
|
|
429
|
-
);
|
|
430
491
|
}
|
|
431
492
|
}
|
|
432
493
|
if (after !== before) {
|
|
433
494
|
await fs.writeFile(output, after);
|
|
434
495
|
optimizedImagePages++;
|
|
435
496
|
}
|
|
497
|
+
await outputCache.set(
|
|
498
|
+
entry,
|
|
499
|
+
withoutPageFont,
|
|
500
|
+
{
|
|
501
|
+
html: after,
|
|
502
|
+
font: cachedFont,
|
|
503
|
+
},
|
|
504
|
+
[...dependencies],
|
|
505
|
+
);
|
|
506
|
+
};
|
|
507
|
+
// Bound I/O concurrency without retaining the entire site's HTML in memory.
|
|
508
|
+
for (let offset = 0; offset < pageEntries.length; offset += 4) {
|
|
509
|
+
await Promise.all(pageEntries.slice(offset, offset + 4).map(processPage));
|
|
436
510
|
}
|
|
437
511
|
if (pageFontData) {
|
|
438
512
|
clearPageFontSubsets(pageFontData.outputDir, referencedPageFonts);
|
|
@@ -441,11 +515,15 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
441
515
|
logger.info(
|
|
442
516
|
`generated page-specific body font subsets for ${pageFontPages} HTML pages`,
|
|
443
517
|
);
|
|
518
|
+
logger.info(
|
|
519
|
+
`HTML postprocessing ${(performance.now() - started).toFixed(1)}ms; cache ${cacheHits}/${htmlFiles.filter((entry) => entry.endsWith(".html")).length}`,
|
|
520
|
+
);
|
|
444
521
|
|
|
445
522
|
// 后处理:Pagefind 搜索索引。启用时任何失败都必须让构建失败,
|
|
446
523
|
// 避免发布一个页面正常但搜索已损坏的产物。
|
|
447
524
|
if (opt.pagefind) {
|
|
448
525
|
logger.info("Running Pagefind indexer...");
|
|
526
|
+
const pagefindStarted = performance.now();
|
|
449
527
|
|
|
450
528
|
try {
|
|
451
529
|
const { index, errors } = await pagefindCreateIndex();
|
|
@@ -466,7 +544,7 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
466
544
|
});
|
|
467
545
|
|
|
468
546
|
logger.info(
|
|
469
|
-
`indexed ${page_count} pages → ${outputPath} (${(performance.now() -
|
|
547
|
+
`indexed ${page_count} pages → ${outputPath} (${(performance.now() - pagefindStarted).toFixed(1)}ms)`,
|
|
470
548
|
);
|
|
471
549
|
} catch (error) {
|
|
472
550
|
logger.error(`Pagefind indexing failed: ${describeError(error)}`);
|
|
@@ -18,7 +18,7 @@ import { styleNames } from "./style-names.generated.ts";
|
|
|
18
18
|
import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
19
19
|
import { dirname, join, resolve, sep } from "node:path";
|
|
20
20
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
21
|
-
import {
|
|
21
|
+
import { FontSubsetter } from "@xingwangzhe/cjk-font-split-native";
|
|
22
22
|
import type { AstroIntegrationLogger } from "astro";
|
|
23
23
|
import { type DefaultTreeAdapterMap, parse } from "parse5";
|
|
24
24
|
|
|
@@ -46,6 +46,8 @@ const PAGE_SLICE_OUT_DIR = "node_modules/.astro/stalux-page-fonts";
|
|
|
46
46
|
*/
|
|
47
47
|
export const RESERVED_GLYPHS = "0123456789";
|
|
48
48
|
|
|
49
|
+
const preparedFonts = new WeakMap<Buffer, FontSubsetter>();
|
|
50
|
+
|
|
49
51
|
// ---------------------------------------------------------------------------
|
|
50
52
|
// Types
|
|
51
53
|
// ---------------------------------------------------------------------------
|
|
@@ -164,10 +166,12 @@ export function readLinkedStylesheetText(
|
|
|
164
166
|
pagePath: string,
|
|
165
167
|
outputDir: string,
|
|
166
168
|
cache = new Map<string, string>(),
|
|
169
|
+
document = parse(html),
|
|
170
|
+
dependencies?: Set<string>,
|
|
167
171
|
): string {
|
|
168
172
|
const root = resolve(outputDir);
|
|
169
173
|
const links: string[] = [];
|
|
170
|
-
collectStylesheetLinks(
|
|
174
|
+
collectStylesheetLinks(document, links);
|
|
171
175
|
const content: string[] = [];
|
|
172
176
|
for (const href of links) {
|
|
173
177
|
if (/^(?:[a-z][a-z\d+.-]*:|\/\/|data:)/iu.test(href)) continue;
|
|
@@ -180,14 +184,15 @@ export function readLinkedStylesheetText(
|
|
|
180
184
|
continue;
|
|
181
185
|
}
|
|
182
186
|
if (cssPath !== root && !cssPath.startsWith(`${root}${sep}`)) continue;
|
|
187
|
+
dependencies?.add(cssPath);
|
|
183
188
|
if (!cache.has(cssPath)) {
|
|
184
189
|
try {
|
|
185
|
-
cache.set(cssPath, readFileSync(cssPath, "utf8"));
|
|
190
|
+
cache.set(cssPath, cssGeneratedText(readFileSync(cssPath, "utf8")));
|
|
186
191
|
} catch {
|
|
187
192
|
cache.set(cssPath, "");
|
|
188
193
|
}
|
|
189
194
|
}
|
|
190
|
-
content.push(
|
|
195
|
+
content.push(cache.get(cssPath) ?? "");
|
|
191
196
|
}
|
|
192
197
|
return content.join(" ");
|
|
193
198
|
}
|
|
@@ -256,8 +261,40 @@ export function writePageFontSubset(
|
|
|
256
261
|
publicUrlPrefix = "/_astro/fonts/",
|
|
257
262
|
linkedStylesheetText = "",
|
|
258
263
|
extraChars = "",
|
|
264
|
+
document = parse(html),
|
|
259
265
|
): PageFontSubset | undefined {
|
|
260
|
-
const
|
|
266
|
+
const input = preparePageFont(html, fontBuffer, linkedStylesheetText, extraChars, document);
|
|
267
|
+
if (!input) return undefined;
|
|
268
|
+
return renderPageFont(
|
|
269
|
+
html,
|
|
270
|
+
publicUrlPrefix,
|
|
271
|
+
input.chars,
|
|
272
|
+
input.prepared.subset(input.chars.join(""), cacheDir),
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export async function writePageFontSubsetAsync(
|
|
277
|
+
html: string,
|
|
278
|
+
fontBuffer: Buffer,
|
|
279
|
+
cacheDir: string,
|
|
280
|
+
publicUrlPrefix = "/_astro/fonts/",
|
|
281
|
+
linkedStylesheetText = "",
|
|
282
|
+
extraChars = "",
|
|
283
|
+
document = parse(html),
|
|
284
|
+
): Promise<PageFontSubset | undefined> {
|
|
285
|
+
const input = preparePageFont(html, fontBuffer, linkedStylesheetText, extraChars, document);
|
|
286
|
+
if (!input) return undefined;
|
|
287
|
+
const result = await input.prepared.subsetAsync(input.chars.join(""), cacheDir);
|
|
288
|
+
return renderPageFont(html, publicUrlPrefix, input.chars, result);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function preparePageFont(
|
|
292
|
+
html: string,
|
|
293
|
+
fontBuffer: Buffer,
|
|
294
|
+
linkedStylesheetText: string,
|
|
295
|
+
extraChars: string,
|
|
296
|
+
document: DefaultTreeAdapterMap["document"],
|
|
297
|
+
) {
|
|
261
298
|
const body = findBody(document);
|
|
262
299
|
if (!body) return undefined;
|
|
263
300
|
|
|
@@ -274,7 +311,20 @@ export function writePageFontSubset(
|
|
|
274
311
|
);
|
|
275
312
|
if (chars.length === 0) return undefined;
|
|
276
313
|
|
|
277
|
-
|
|
314
|
+
let prepared = preparedFonts.get(fontBuffer);
|
|
315
|
+
if (!prepared) {
|
|
316
|
+
prepared = new FontSubsetter(fontBuffer);
|
|
317
|
+
preparedFonts.set(fontBuffer, prepared);
|
|
318
|
+
}
|
|
319
|
+
return { chars, prepared };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function renderPageFont(
|
|
323
|
+
html: string,
|
|
324
|
+
publicUrlPrefix: string,
|
|
325
|
+
chars: string[],
|
|
326
|
+
result: { hash: string; path: string; cacheHit: boolean },
|
|
327
|
+
): PageFontSubset {
|
|
278
328
|
const filename = `page-${result.hash}.woff2`;
|
|
279
329
|
|
|
280
330
|
const rel = `${publicUrlPrefix}${filename}`;
|