缘起
最近接到一个新项目,游戏内嵌页面,纯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
- 将
assets/css/js/views
目录及其下的文件全部输出到dist
对应目录下。 - 将
dist/css
、dist/js
目录下的文件添加 hash 值。 - 自动替换
dist/views
页面上对应的 url 为添加 hash 后的 url。 - 自动打包 zip。
如果是生产环境就执行下面的命令行:
npm run build:prod
- 将
assets/css/js/views
目录及其下的文件全部输出到dist
对应目录下。 - 将
dist/css
、dist/js
目录下的文件添加 hash 值。 - 将步骤2中的文件全部压缩。
- 自动替换
dist/views
页面上对应的 url 为添加 hash 后的 url。 - 将步骤4中的 html 页面全部压缩。
- 自动打包 zip。
基于以上流程,我画了一张流程图,长这样:
红框中是 gulp 各个插件介入的环节。
怎么用 gulp?
关于安装gulp,官网有个快速入门,移步这里!建议把 gulp-cli
,gulp
安装到本地:
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 阶段就被压缩掉。否则就不压缩处理了。需要两个插件,分别是:
- gulp-clean-css:css文件的压缩处理插件
- gulp-uglify:js文件的压缩处理插件
先安装一下:
npm i gulp-clean-css gulp-uglify -D
接着让我们分别对第二步中的 cssRevHash
、jsRevHash
方法稍作修改:
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 爱你爱你,让我们一起进步!