作为一个喜欢魔改网站主题的人,在将博客迁移到静态站后,首先需要解决的便是浏览器的缓存问题。浏览器在遇到对静态资源的请求时,通常会采取两种缓存策略,分别是强制缓存协商缓存

  • 强制缓存:在一定时间(由服务器给出的请求头标识)内不再向服务器发出请求,而是直接使用缓存的内容。
  • 协商缓存:与服务器协商是否需要重新请求。

显然,由于缓存策略的存在,当服务端的静态文件发生变更后,客户端经常不会立刻加载最新版本的文件。对于这一点,之前我在动态博客中的解决方案是手动为静态文件加一个版本号,例如theme.css?v=1.2.1。不过在Hexo中所有的html页面都是自动编译生成的,而且会产生一大堆页面,因此并不可能一个一个地手动为静态文件添加版本。

经过一番搜索,我在一条GitHub issue下面找到了一个看上去不错的方案:

directly use webpack or gulp (commend to install at root folder). first, hexo generate the publish files, then webpack or gulp revision the files.

不过我先前几乎没有这些前端工作流的经验,因此花了半天时间才大概搞明白这句话的含义并让这个方案work。

安装依赖

Gulp是一个强大的前端自动化构建工具,其有很多插件,能够满足许多需求,为文件名添加hash只是其中一个插件的功能,还有一些插件可以用来优化文件大小,这里就顺带一并装上。

先全局安装gulp,并安装一些用于优化静态文件的插件:

npm install gulp --global
npm install gulp gulp-clean-css gulp-uglify-es gulp-html-minifier-terser

然后安装一个可以为静态文件打版本tag的插件:

npm install gulp-rev-all --save

这里的问题是,默认的gulp-rev-all版本是4,并不支持通过旧规范的require语句导入,因此我手动降了一下版本,将package.json里的大版本号改到了3,这里我使用的版本信息如下:

...
  "dependencies": {
    "gulp": "^5.0.0",
    "gulp-clean-css": "^4.3.0",
    "gulp-html-minifier-terser": "^7.1.0",
    "gulp-rev-all": "^3.0.0",
    "gulp-uglify-es": "^3.0.0",
    ...
  }
...

做了版本号调整后,再重新安装,这样便能通过旧的语法进行导入了。

编写gulpfile.js文件

在博客项目的根目录下创建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');
const RevAll = require('gulp-rev-all');


gulp.task('minify-css', () => {
    return gulp.src('./public/**/*.css')           // 处理public目录下所有的css文件,下同
        .pipe(cleancss({ compatibility: 'ie8' }))  // 兼容到IE8
        .pipe(gulp.dest('./public'));
});

gulp.task('minify-js', () => {
    return gulp.src('./public/**/*.js')
        .pipe(uglify())
        .pipe(gulp.dest('./public'));
});

gulp.task('minify-html', () => {
    return gulp.src('./public/**/*.html')
        .pipe(htmlmin({
            removeComments: true,                 // 移除注释
            removeEmptyAttributes: true,          // 移除值为空的参数
            removeRedundantAttributes: true,      // 移除值跟默认值匹配的属性
            collapseBooleanAttributes: true,      // 省略布尔属性的值
            collapseWhitespace: true,             // 移除空格和空行
            minifyCSS: true,                      // 压缩HTML中的CSS
            minifyJS: true,                       // 压缩HTML中的JS
            minifyURLs: true                      // 压缩HTML中的链接
        }))
        .pipe(gulp.dest('./public'))
});

gulp.task('rev', () => {
    return gulp.src('public/**/*.{css,js,html}')
        .pipe(RevAll.revision({
          dontRenameFile: ['.html'],
          dontUpdateReference: ['.html']
        }))
        .pipe(gulp.dest('./public'))
        .pipe(RevAll.manifestFile())
        .pipe(gulp.dest('./public'))
})

gulp.task('default', gulp.parallel(
    'minify-css', 'minify-js', 'minify-html', 'rev'
));

该文件首先定义了四个任务,前三个任务分别优化css、js、html文件的大小,第四个任务用来为css、js的文件名添加hash值标签,默认生成8位的hash值,同时处理所有html,将其中对本地css、js文件的引用处修改为带hash标签的。

最后定义了一个默认任务,用以在执行gulp命令而不带任何参数时自动执行前面四个任务。

配置npm脚本

在package.json中添加scripts字段,配置脚本:

...
  "scripts": {
    "b": "npx hexo clean && npx hexo g && gulp",
    ...
  }
...

这样只要在项目的根目录下执行npm run b,就可以直接完成编译+优化+添加版本一条龙。


最近发现有个奇怪的bug,我只要用了gulp-html-minifier-terser,就会有一个html文件被”优化”成了空文件,研究了下发现原来不应该将最后的rev任务和前面三个并行起来,改成下面这样就好了:

gulp.task('default', gulp.series(
    gulp.parallel('minify-css', 'minify-js', 'minify-html'),
    'rev'
));