Blog性能优化记录

Blog性能优化记录

采取高效编码

采用 webp ,webm等格式进行编码,可以使用cwebp命令压缩图片。视频可以通过在线网站进行转换;

推荐的格式

  • 图片 webp,avif
  • 视频/动图 webm
  • 字体 woff2

Best Practice
While MP4 has been around since 1999, WebM is a relatively new file format initially released in 2010. WebM videos are much smaller than MP4 videos, but not all browsers support WebM so it makes sense to generate both.

批量转换图片格式

下载libwebp库,使用cwebp命令;下面为个人自用的批量转换的 Python 脚本

windows 自带截图和 qq 截图并不支持 webp 格式的截图;
最新版的 pixpin,snipaste 等第三方截图软件支持 webp 格式

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
from pathlib import Path
import subprocess
import shutil
import argparse

covert_these_types = (".png", ".jpg", ".jpeg")

def convert_to_webp(input_dir: Path, output_dir: Path):
    """
    将指定目录及其子目录中的文件转换为.webp格式。输出文件保存在指定的输出目录下,并保留原有目录结构。
    """

    for file_path in input_dir.rglob("*"):
        # 检查文件是否为需要转换的类型
        if file_path.suffix.lower() in covert_these_types:
            # 构建输出文件路径,/ 操作符在这里将 output_dir 和 relative_path 组合成一个新的路径对象
            relative_path = file_path.relative_to(input_dir)
            output_path = output_dir / relative_path.with_suffix(".webp")
            output_path.parent.mkdir(parents=True, exist_ok=True)

            # 使用cwebp命令进行转换
            try:
                subprocess.run(
                    ["cwebp", str(file_path), "-o", str(output_path)], check=True
                )
            except subprocess.CalledProcessError as e:
                print(f"Failed to convert {file_path}: {e}")
        else:
            output_path = output_dir / relative_path
            shutil.move(file_path, output_dir)

def main():
    # 创建命令行参数解析器
    parser = argparse.ArgumentParser(
        description="Convert PNG, JPG, and JPEG files to WEBP format."
    )
    parser.add_argument(
        "input_dir", type=str, help="Input directory containing images to convert"
    )
    parser.add_argument(
        "output_dir", type=str, help="Output directory to save converted images"
    )
    args = parser.parse_args()
    input_dir = Path(args.input_dir)
    output_dir = Path(args.output_dir)
    convert_to_webp(input_dir, output_dir)


if __name__ == "__main__":
    main()
1
2
[USAGE]
python ./cwebp.py ../source/img/unused/ ./output

效果测试

下面是本网站图片压缩前后的体积对比:

1
2
3
4
5
6
7
8
9
╭───┬───────────────────┬──────┬───────────╮
│ # │       name        │ type │   size    │
├───┼───────────────────┼──────┼───────────┤
│ 0 │ gallery           │ dir  │  66.2 MiB │
│ 1 │ gallery_origin    │ dir  │ 186.8 MiB │
│ 2 │ thumbnails        │ dir  │  19.6 MiB │
│ 3 │ thumbnails_origin │ dir  │  88.0 MiB │
│ 4 │ unused            │ dir  │  18.4 MiB │
╰───┴───────────────────┴──────┴───────────╯
  • gallery 目录的体积缩小了约 64.57%
    • 原始大小: gallery_origin: 186.8 MiB;图片*54(jpg,png),视频*3(mp4)
    • 压缩后大小: gallery: 66.2 MiB
    • 压缩体积: 186.8 MiB - 66.2 MiB = 120.6 MiB
  • thumbnails 目录的体积缩小了约 77.73%
    • 原始大小: thumbnails_origin: 88.0 MiB;图片×100(jpg,png)
    • 压缩后大小: thumbnails: 19.6 MiB
    • 压缩量: 88.0 MiB - 19.6 MiB = 68.4 MiB

使用 gulp 压缩代码

添加以下依赖,并在scripts.build里添加gulp,这样执行build操作后会对代码进行压缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// File: package.json

"scripts": {
    "build": "hexo generate && gulp",
    "clean": "hexo clean",
    "deploy": "hexo deploy",
    "server": "hexo server"
},
"dependencies": {
    "gulp": "^5.0.0",
    "gulp-clean-css": "^4.3.0",
    "gulp-html-minifier-terser": "^7.1.0",
    "gulp-uglify-es": "^3.0.0",
}

网站根目录添加gulpfile.js文件,

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
// File: Efterklang.github.io/gulpfile.js

const gulp = require("gulp");
const cleancss = require("gulp-clean-css");
const uglify = require("gulp-uglify-es").default;
const htmlmin = require("gulp-html-minifier-terser");

// 压缩public目录下的css文件
// 可接受参数的文档:https://github.com/jakubpawlowicz/clean-css#constructor-options
gulp.task("minify-css", () => {
  return gulp
    .src("./public/**/*.css") // 处理public目录下所有的css文件,下同
    .pipe(cleancss({ compatibility: "ie8" })) // 兼容到IE8
    .pipe(gulp.dest("./public"));
});

// 压缩public目录下的js文件
gulp.task("minify-js", () => {
  return gulp
    .src(["./public/**/*.js", "!./public/**/*.min.js"])
    .pipe(uglify())
    .pipe(gulp.dest("./public"));
});

// 压缩public目录下的html文件
// 可接受参数的文档:https://github.com/terser/html-minifier-terser#options-quick-reference
gulp.task("minify-html", () => {
  return gulp
    .src(["./public/**/*.html", "!./public/about/index.html"])
    .pipe(
      htmlmin({
        removeComments: true, // 移除注释
        removeEmptyAttributes: true, // 移除值为空的参数
        removeRedundantAttributes: true, // 移除值跟默认值匹配的属性
        collapseBooleanAttributes: true, // 省略布尔属性的值
        collapseWhitespace: true, // 移除空格和空行
        minifyCSS: false, // 压缩HTML中的CSS
        minifyJS: true, // 压缩HTML中的JS
        minifyURLs: true, // 压缩HTML中的链接
        ignoreCustomFragments: [
          /<%[\s\S]*?%>/,
          /<\?[\s\S]*?\?>/,
          /\$\$[\s\S]*?\$\$/,
        ],
      })
    )
    .pipe(gulp.dest("./public"));
});

// 默认任务,不带任务名运行gulp时执行的任务
gulp.task("default", gulp.parallel("minify-css", "minify-js", "minify-html"));

可能的问题

压缩时可能会报 parseError,可以在ignoreCustomFragments里添加相应的规则忽略某些语法的解析。如添加/\$\$[\s\S]*?\$\$/],以忽略$$...$$中的代码;不会写 js 正则表达式,可以询问 ai

1
2
3
4
5
6
7
8
9
Error in plugin "gulp-html-minifier-terser"
Message:
    Parse Error: < W_{T} \lt 2^{n-1}; n为分组中的bit数 \\\\
& 若W_{T} = 1,则退化为停止等待协议;若W_{T} \gt 2^{n-1},则会造成接收方无法辨别新旧数据分组的问题\\\\
& 接收窗口尺寸W_{R}必须满足: 1 < W_{R} \leq W_{T}\\\\
& 若W_{R} = 1,则退化为回退 N 帧协议;W_{R} \gt W_{T}没有意义
\end{align*}
$$
</div>

图片懒加载

npm install hexo-native-lazy-load --save

1
2
3
lazy_load:
  enable: true
  onlypost: false

hexo clean,部署后在页面打开 devtools;可以看到 img 已添加loading="lazy"

Instant Page

instant.page uses just-in-time preloading — it preloads a page right before a user clicks on it.

根目录创建scripts/instant-page.js,注入以下代码

1
2
3
4
5
hexo.extend.injector.register(
  "body_end",
  '<script src="//instant.page/5.2.0" type="module" integrity="sha384-jnZyxPjiipYXnSU0ygqeac2q7CVYMbh84q0uHVRRxEtvFPiQYbXWUorga2aqZJ0z"></script>',
  "default"
);

Pjax

目前 Icarus 仅初步支持 Pjax,详见https://github.com/ppoffice/hexo-theme-icarus/pull/1287

OSS + CDN

一种广泛采用的免费方案:github 作为图床,jsdelivr 作为 CDN;优点是免费,但国内访问效果一般。如果预算充足的话可以考虑替换掉
引用图片时:<img src="https://cdn.jsdelivr.net/gh/<user_name>/<repo_name>@<branch_name>/<path_to_your_image>" alt="">

此外 Cloudflare 也免费提供了一定额度的 R2 + CDN 服务,可以考虑使用;需要信用卡,下载 Paypal 绑定国内银联的信用卡即可。这也是本站目前使用的方案

To be continued(?)

Tools

更多优化方案

作者

Jiaxing Gao

发布于

2024-09-27

更新于

2024-11-16

许可协议

评论

}