前端静态项目自动化部署开发心得分享:css、js、html压缩,文件添加hash,自动打包等

3,376 阅读8分钟

缘起

最近接到一个新项目,游戏内嵌页面,纯HTML。想着用 vue 全家桶搞吧,快。PM说要兼容IE8+,我太难了!好吧,只要用我的杀手锏 div + css + jquery 了。做的差不多了,老大说游戏那边内嵌的浏览器内核是 chrome。Emmmmm... 就这样吧。

待本地和后端的同学联调好后,那就打包给测试吧。然后手动把 src 下的目录压缩成zip给测试同学,测试同学负责上传到内网环境,巴拉巴拉五分钟后部署好了开始测试,这时候却发现一个 api 调用地址不对。

然后重新改罢改罢,手动打包成 zip ,qq发送给测试同学,部署,测试。

重新部署后,测试说没变化。虽从 Chrome 开发者工具中查看代码确实没生效。看了下 Network 面板,css 和 js 文件是从内存里加载的,有缓存。

"稍等",我跟测试同学说,"url 后面我加个版本号,防止浏览器缓存"。

然后,你就看到了像下面这段代码:

<html>
  <head>
    ...
    <link rel="stylesheet" type="text/css" href="../css/global.css?v=1.0.1" />
  </head>
  <body>
    ...
    <script src="../js/stolen-back.js?v=1.0.1" ></script>
  </body>
</html>

通过在 css 和 js 引用路径后面加个 ?v=1.0.1 解决了浏览器的缓存问题。但每次修改完文件都需要手动改一下版本号,一个页面尚可,好几个页面呢,这谁顶得住啊。当时我就在想,如果能自动往 css、js 路径后面加个 hash 那该多好啊,那就彻底解放双手啦!例如,像下面这样的代码:

<link rel="stylesheet" type="text/css" href="../css/global-8883e385f7.css" />

PM着急上线,先这样吧。

最后所有的页面都测试通过了,就该部署到线上了。打个包给测试小姐姐吧,发现整个 zip 包 1.56MB,有点大啊。于是想把 css 和 js 压缩一个 mini 版的。页面重新引用压缩后的再给测试去部署。就像下面这样的代码:

<html>
  <head>
    ...
    <link rel="stylesheet" type="text/css" href="../css/global.min.css?v=1.0.1" />
    <link rel="stylesheet" type="text/css" href="../css/stolen-back.min.css?v=1.0.1" />
  </head>
  <body>
    ...
    <script src="../js/stolen-back.min.js?v=1.0.1" ></script>
  </body>
</html>

由于我用的是 HBuilder X 编辑器开发的,所以右键自带 css 和 js 压缩功能还是挺方便的。所以就一个一个 css、js 文件进行手动压缩。Emmmmm... 说实话,我挺不喜欢做重复劳动的。要是能一个命令行就能把所有的 css 和 js 压缩合并那该多好哇。比如我执行一次下面的命令:

npm run uglify # or gulp uglify

所有压缩好的文件都输出到 dist 目录下面,是不是挺爽的。解放双手,再也不用手动压缩。另外,生产环境,其实我还想把所有的 html 压缩一下,可 HBuilder X 没有这个功能。尴尬!要是能一个命令行把所有的html压缩输出到 dist 多好哇,例如,执行这个的命令:

npm run htmlMini # or gulp htmlMini

以上,如果所有的步骤都处理好了,还有一个繁琐的问题,就是打包成 zip。常规做法是:找到项目目录 -> 右键添加到压缩 -> 重命名 -> 确定。 要是能有个命令行一键打包多好,像下面的命令行:

npm run zip # or gulp zip

想了两下,相比 webpack 构建工具,我最终选择了 gulp。

为什么用 gulp ?

  • 对比 webpack 轻量啊,上手快,插件多。
  • 解决手动添加版本号的问题。自动在 url 后面添加 hash 值并替换页面 url 路径,防止浏览器缓存。
  • 解决手动压缩css、js的问题。自动压缩 css、js,减少线上打包体积。
  • 解决 html 无法压缩的问题。自动压缩 html,减少线上打包体积。
  • 解决手动压缩zip的问题。自动压缩zip。

构建流程梳理

首先说下我项目的目录结构哈:

其实我想要的构建流程是这样婶儿的。如果是开发环境,就执行下面的命令行:

npm run build:dev
  1. assets/css/js/views 目录及其下的文件全部输出到 dist 对应目录下。
  2. dist/cssdist/js 目录下的文件添加 hash 值。
  3. 自动替换 dist/views 页面上对应的 url 为添加 hash 后的 url。
  4. 自动打包 zip。

如果是生产环境就执行下面的命令行:

npm run build:prod
  1. assets/css/js/views 目录及其下的文件全部输出到 dist 对应目录下。
  2. dist/cssdist/js 目录下的文件添加 hash 值。
  3. 将步骤2中的文件全部压缩。
  4. 自动替换 dist/views 页面上对应的 url 为添加 hash 后的 url。
  5. 将步骤4中的 html 页面全部压缩。
  6. 自动打包 zip。

基于以上流程,我画了一张流程图,长这样:

红框中是 gulp 各个插件介入的环节。

怎么用 gulp?

关于安装gulp,官网有个快速入门,移步这里!建议把 gulp-cligulp 安装到本地:

npm i gulp-cli gulp -D

这样就方便和你一起开发的同学了。下面让我们一步一步来实现吧!

1. 将 src 目录下的文件拷贝到 dist 目录下

之所以先拷贝到 dist 目录下,是因为我们后续所有操作基于 dist 进行的。src 目录下的源码不要受到干扰。

gulpfile.js:

const { series, src, dest } = require('gulp');
const clean = require('gulp-clean'); // 文件清理插件。需要先 `npm i gulp-clean -D` 一下

 /**
   * 删除 `dist` 目录避免产生重复文件
   */
  function clear() {
    return src('dist', { allowEmpty: true }).pipe(clean());
  }

  /**
   * 拷贝静态资源文件
   */
  function assets() {
    return src('src/assets/**/*.*').pipe(dest('dist/assets/'));
  }
  
  /**
   * 拷贝 css 文件
   */
  function css() {
    return src(['src/css/**/*.css']).pipe(dest('dist/css'));
  }
  
  /**
   * 拷贝 js 文件
   */
  function js() {
    return src(['src/js/**/*.js']).pipe(dest('dist/js'));
  }
  
  /**
   * 拷贝 html 文件
   */
  function html() {
    return src('src/views/**/*.html').pipe(dest('dist/views/'));
  }
  
  // series(a,b,c) 把所有的任务都按顺序依次执行
  exports.build = series(
    clear,
    assets,
    css,
    js,
    html
  );

2. css/js 文件添加 hash 值

需要用到 gulp-rev 插件。照例,先安装一下:

npm i gulp-rev -D

在 gulpfile.js 中添加下面的代码:

// 省略一些代码
const rev = require('gulp-rev');

// 省略一些代码

/**
 *  Css 文件后面加 hash 值
 */
function cssRevHash() {
  return src('dist/css/**/*.css')
    .pipe(rev())
    .pipe(dest('dist/css/'))
    .pipe(rev.manifest())
    .pipe(dest('rev/css'));
}

/**
 * Js 文件后面加 hash 值
 */
function jsRevHash() {
  return src('dist/js/**/*.js')
    .pipe(rev())
    .pipe(dest('dist/js/'))
    .pipe(rev.manifest())
    .pipe(dest('rev/js'));
}

exports.build = series(
    ...
    cssRevHash,
    jsRevHash
  );

gulp-rev 插件的作用是将所有目标目录下的 css 和 js 都添加 hash 值,并在 rev 目录下保存一份源文件路径和 hash 文件路径的 json 映射文件

3. 自动替换 html 中的源文件

gulp-rev 插件只负责把所有的 css 和 js 加上 hash 值。如果我们想替换文件中的源文件怎么办呢?gulp-rev-collector 插件就是干这个的。安装一下:

npm i gulp-rev-collector -D

在 gulpfile.js 文件中添加如下代码:

  // 省略一些代码
  const revCollector = require('gulp-rev-collector');
  
  // 省略一些代码
  
  /**
   * 将 rev 目录下的 hash 文件替换掉 html 中对应的源路径
   */
  function htmlRevInject() {
    return src([`rev/${revDir}/**/*.json`, 'dist/views/**/*.html'])
      .pipe(revCollector({ replaceReved: true }))
      .pipe(dest('dist/views/'));
  }
  exports.build = series(
    ...
    htmlRevInject
    );

4. 压缩文件 zip

其实就是我们最后要打包给测试部署的zip包了。每次手动比较麻烦,所以找了个 gulp-zip 插件。先安装一下:

npm i gulp-zip -D

在 gulpfile.js 文件中添加如下代码:

// 省略一些代码
const zip = require('gulp-zip');
const pkg = require('./package.json');
const zipName = pkg.name + '.zip'; // zip压缩包名

// 省略一些代码

/**
 * 打包
 */
function zipiupiu() {
  return src('dist/**/*').pipe(zip(zipName)).pipe(dest('dist'));
}

exports.build = series(
  ...
  zipiupiu
);

5. process.env.NODE_ENV 变量值动态设置

我希望变量 NODE_ENV 可以动态改变,那样子我们就可以根据是开发还是生产进行下一步(css、js、html代码压缩)的处理了。有个 cross-env npm包帮我们解决这个问题,来安装一下:

npm i cross-env -D

然后在 package.json 文件的 scripts 中添加如下两个命令行:

{
  ...
  "scripts": {
    "build:dev": "cross-env NODE_ENV=development gulp build",
    "build:prod": "cross-env NODE_ENV=production gulp build"
  },
  ...
}

6. css、js压缩

我希望如果是生产环境,在 css、js 添加 hash 阶段就被压缩掉。否则就不压缩处理了。需要两个插件,分别是:

先安装一下:

npm i gulp-clean-css gulp-uglify -D

接着让我们分别对第二步中的 cssRevHashjsRevHash 方法稍作修改:

gulpfile.js:

  // 省略一些代码
  const cleanCSS = require('gulp-clean-css');
  const uglify = require('gulp-uglify');
  
  // 省略一些代码
  
  /**
   *  Css 文件后面加 hash 值
   */
  function cssRevHash() {
    if (process.env.NODE_ENV === 'production') {
      return src('dist/css/**/*.css')
        .pipe(rev())
        // 压缩。兼容ie8
        .pipe(cleanCSS({ compatibility: 'ie8' }))
        .pipe(dest('dist/css/'))
        .pipe(rev.manifest())
        .pipe(dest(`rev/${revDir}/css`));
    } else {
      return src('dist/css/**/*.css')
        .pipe(rev())
        .pipe(dest('dist/css/'))
        .pipe(rev.manifest())
        .pipe(dest(`rev/${revDir}/css`));
    }
  }
  
  /**
   * Js 文件后面加 hash 值
   */
  function jsRevHash() {
    if (process.env.NODE_ENV === 'production') {
      return src('dist/js/**/*.js')
        .pipe(rev())
        .pipe(uglify())
        .pipe(dest('dist/js/'))
        .pipe(rev.manifest())
        .pipe(dest(`rev/${revDir}/js`));
    } else {
      return src('dist/js/**/*.js')
        .pipe(rev())
        .pipe(dest('dist/js/'))
        .pipe(rev.manifest())
        .pipe(dest(`rev/${revDir}/js`));
    }
  }

上面代码中有个变量 revDir,在 gulpfile.js 头部声明的,长这样:

// 源文件映射文件目录。根据当前环境存放到不同的目录
// 方便我们知道我们是通过哪个构建命令创建的,一目了然
const revDir = process.env.NODE_ENV === 'production' ? 'prod' : 'dev',

7. html页面压缩

生产环境,html页面压缩一下也是有必要的,因为我们的 html 页面可能包含一些不太规范的写法,例如:

  • 空行啊,属性间存在多个空格啊,空内容啊,属性值为空啊
  • 页面内嵌 css、js 啊
  • ...

以上我们都想把它们压缩整齐划一。减少了代码体积不说,还可以一定程度上混淆下代码。所以我用了 gulp-html-minifier2 插件来做这件事情。

先安装一下:

npm i gulp-html-minifier2 -D

在 gulpfile.js 中增加如下代码:

  // 省略一些代码
  const htmlmin = require('gulp-html-minifier2');
  
  // 省略一些代码
  
  /**
   * 压缩 html 页面
   */
  function htmlMinify() {
    return src('dist/views/**/*.html')
      .pipe(htmlmin({
        collapseWhitespace: true, // 合并空格
        minifyCSS: true, // css压缩
        minifyJS: true, // js压缩
        removeComments: true // 删除注释
      }))
      .pipe(dest('dist/views/'));
  }

8. 最后根据不同环境执行不同的构建任务

就像我上面说的,开发环境是不需要 css、js、html压缩的,所以这些都是放在生产环境中需要做的事情。看下面的代码:

  // 生产环境下执行
  if (process.env.NODE_ENV === 'production') {
    exports.build = series(
      clear,
      assets,
      css,
      js,
      html,
      cssRevHash,
      jsRevHash,
      htmlRevInject,
      htmlMinify, // html压缩
      zipiupiu
    );
  }
  // 开发环境下执行
  else {
    exports.build = series(
      clear,
      assets,
      css,
      js,
      html,
      cssRevHash,
      jsRevHash,
      htmlRevInject,
      zipiupiu
    );
  }

完整的示例代码已经上传到github,感兴趣的同学可以看下。啰嗦了这么多,希望看完后对你有所帮助。 2020 爱你爱你,让我们一起进步!

参考