优化多语言站点静态资源
2025-09-06  / site  / optimize

该页面由AI翻译并经过人工校对,你可能想要 查看原文

前言

由于 post_asset_folder 在 _config.yml 文件中的设置如下,因此每篇文章的资源都可以在与文章同名的目录中找到。

1
2
3
4
post_asset_folder: true
marked:
prependRoot: true
postAsset: true

然而,在本网站支持国际化之后,冗余资源会大量积累。不同语言的文章内容应该不同,但资源应该相同。以下是源文件的目录结构。不同语言中带有 (*) 的资源目录是重复的,可以进行优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
source
├── _posts
│ ├── en
│ │ └── 2025
│ │ ├── 07
│ │ │ ├── online-404-error-troubleshooting (*)
│ │ │ └── online-404-error-troubleshooting.md
│ │ └── 08
│ │ ├── exploring-multilingual-site-solutions-in-hexo (*)
│ │ └── exploring-multilingual-site-solutions-in-hexo.md
│ └── zh-CN
│ └── 2025
│ ├── 07
│ │ ├── online-404-error-troubleshooting (*)
│ │ └── online-404-error-troubleshooting.md
│ └── 08
│ ├── exploring-multilingual-site-solutions-in-hexo (*)
│ └── exploring-multilingual-site-solutions-in-hexo.md
├── en
│ ├── 404
│ ├── about
│ ├── albums
│ │ ├── animal
│ │ │ ├── cat (*)
│ │ │ ├── cat.md
│ │ │ ├── dog (*)
│ │ │ ├── dog.md
│ │ │ └── index.md
│ │ └── index.md
│ ├── categories
│ └── tags
└── zh-CN
├── 404
├── about
├── albums
│ ├── animal
│ │ ├── cat (*)
│ │ ├── cat.md
│ │ ├── dog (*)
│ │ ├── dog.md
│ │ └── index.md
│ └── index.md
├── categories
└── tags

优化

设计

首先,为了减少之前对资源引用方式的影响,我需要设计静态资源的组织方式。幸运的是,只需将所有重复的资源目录移动到 /source/images 目录(带有 (*) 的目录)即可,如下所示。之后,如果使用相对路径引用资源,只需进行少量更改即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
.
├── _posts
│ ├── en
│ │ └── 2025
│ │ ├── 07
│ │ │ └── online-404-error-troubleshooting.md
│ │ └── 08
│ │ └── exploring-multilingual-site-solutions-in-hexo.md
│ └── zh-CN
│ └── 2025
│ ├── 07
│ │ └── online-404-error-troubleshooting.md
│ └── 08
│ └── exploring-multilingual-site-solutions-in-hexo.md
├── en
│ ├── 404
│ ├── about
│ ├── albums
│ │ ├── animal
│ │ │ ├── cat.md
│ │ │ ├── dog.md
│ │ │ └── index.md
│ │ └── index.md
│ ├── categories
│ └── tags
├── images
│ ├── 2025
│ │ ├── 07
│ │ │ └── online-404-error-troubleshooting (*)
│ │ └── 08
│ │ └── exploring-multilingual-site-solutions-in-hexo (*)
│ ├── albums
│ │ └── animal
│ │ ├── cat (*)
│ │ └── dog (*)
│ ├── my_avatar.png
│ └── my_favicon.png
└── zh-CN
├── 404
├── about
├── albums
│ ├── animal
│ │ ├── cat.md
│ │ ├── dog.md
│ │ └── index.md
│ └── index.md
├── categories
└── tags

然后,找到一种方法来替换每篇帖子中引用的资源路径。由于资源可以通过相对路径或绝对路径(以 http、https 或 / 开头)引用,如果是绝对路径,则直接使用该路径;否则,在 /images 目录中与帖子同名的目录中查找资源。因此,只需将相对路径转换为 ​​/images/[prefix]/[name]/[resource],其中 name 表示帖子名称,resource 表示资源名称,prefix 表示从 /source/[lang] 到帖子的路径。

例如,对于帖子 /2025/07/my-post.md,无论语言是什么。

  1. 如果资源引用为 ./image.png,则资源路径将转换为 /images/2025/07/my-post/image.png。在本例中,prefix 为 2025/07,name 为 my-post,resource 为 image.png注意 如果您使用相对路径,请确保它始终以 './' 开头。如果您使用 ../another-post/image.png,它将不会被转换为 /images/2025/07/another-post/image.png。如果您确实需要使用属于其他帖子的资源,请尝试使用绝对路径,例如 /images/2025/07/another-post/image.png。
  2. 如果资源被引用为 /images/2025/07/my-post/image.png,由于它是绝对路径,因此资源路径不会被转换。
  3. 如果资源被引用为 https://exmaple.com/image.png,资源路径也不会被转换。

更多示例请参见下文。

实现

在 themes/arch/scripts 目录下创建一个名为 hexo-filter.js 的文件,并输入以下代码。在 after_post_rende 的回调中,可以访问所有数据,包括原始内容、解析后的内容以及页面元信息。获取所有 <img> 的 src 值,如果 src 是相对路径,则应用上述路径转换。就是这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// http, https, / 开头的都是绝对路径
const absPathRegExp = /^(https?:\/\/|\/)/i;

function replaceRelativePath(src, prefix) {
// 绝对路径,不变
if (absPathRegExp.test(src)) {
return src;
}
// 相对路径
// 去除开头的 ./ 或 ../
let cleanSrc = src.replace(/^(\.\/|\.\.\/)+/, "");

// 拼接新的路径
return [prefix, cleanSrc].join("/");
}

/**
* Hexo 过滤器,处理文章和页面中的相对图片路径
* 将相对路径替换为 /images/ 目录下对应的路径,绝对路径(以 http:// 或 https:// 或 / 开头)不做处理, / 表示网站根目录
* 例如
* 1. 对于 post,路径是 /2025/07/my-post.md,图片路径是 ./image.png,则替换为 /images/2025/07/my-post/image.png
* 2. 对于普通 page,路径是 /about/index.md,图片路径是 ./image.png,则替换为 /images/about/image.png
* 3. 对于 albums page,路径是 /albums/index.md,图片路径是 ./animial/dog/1.png,则替换为 /images/albums/animial/dog/1.png
* 4. 对于 album page,路径是 /albums/animal/dog.md,图片路径是 ./1.png,则替换为 /images/albums/animial/dog/1.png
*
*/
hexo.extend.filter.register("after_post_render", function (data) {
const { source, type } = data;

// 不处理 tags 和 categories
if (["tags", "categories"].includes(type)) {
return data;
}

// 从 source 中提取目录名
// 对于 post,格式是 _posts/:lang/:year/:month/:title.md,比如 _posts/en/2025/07/problems-using-hexo-theme.md
// 对于 page,格式是 :lang/xxx/:title.md,比如 en/about/index.md, en/albums/index.md, en/albums/animal/dog.md
const parts = source.split("/");

let dirParts = !type ? parts.slice(2, -1) : parts.slice(1, -1);
// post 和 album 类型,需要加上 title 作为最后一级目录
if (!type || type === "album") {
dirParts = dirParts.concat(parts[parts.length - 1].replace(/\.md$/, ""));
}

const prefix = `/images/${dirParts.join("/")}`;

// data.content 是文章渲染后的 HTML 内容
// 使用正则匹配所有 <img> 标签的 src 属性
data.content = data.content.replace(
/<img\s+([^>]*?)src=["']([^"']+)["']([^>]*?)>/gi,
function (match, pre, src, post) {
// 绝对路径,不变
if (absPathRegExp.test(src)) {
return match;
}
// 返回替换后的 img 标签
return `<img ${pre}src="${replaceRelativePath(src, prefix)}"${post}>`;
}
);

// 处理 albums 类型的 children 字段
if (type === "albums" && Array.isArray(data.children)) {
data.children.forEach((child) => {
if (child.cover) {
child.cover = replaceRelativePath(child.cover, prefix);
}
});
}

// 处理 album 类型的 resources 字段
if (type === "album" && Array.isArray(data.resources)) {
data.resources.forEach((resource) => {
if (resource.src) {
resource.src = replaceRelativePath(resource.src, prefix);
}
});
}

return data;
});

使用方法

如果在文章中使用 <img /> 以相对路径引用资源,则无需执行任何操作。对于相册和相册类型页面,由于存在绝对路径,因此需要进行如下更改。

image.png


请注意,只有使用 <img /> 以相对路径引用资源时,才会转换资源路径。核心逻辑如下。

1
2
3
4
5
6
7
8
9
10
11
data.content = data.content.replace(
/<img\s+([^>]*?)src=["']([^"']+)["']([^>]*?)>/gi,
function (match, pre, src, post) {
// 绝对路径,不变
if (absPathRegExp.test(src)) {
return match;
}
// 返回替换后的 img 标签
return `<img ${pre}src="${replaceRelativePath(src, prefix)}"${post}>`;
}
);

如果您使用 Markdown 语法或 Hexo 标签使用相对路径(如下所示),资源路径将不会被转换,图片将加载失败。

1
2
3
![image.png](image.png)

{% asset_img "image.png" "image.png" %}