背景
前端项目从之前的jQuery + ES5
到现在的ES6
一直没有做到一个很平滑地过渡,导致基础设施分为了两块。一块是使用webpack
来管理的ES6
代码,另一块是使用gulp
管理的ES5
老代码。对于ES6
这块,我们经历了从webpack3
到webpack4
的大版本变化,构建速度也是得到了很大的提升。但是gulp
管理的ES5
代码一直是我们的一个痛点。所以打算把整个前端的基础设施梳理一下,都更新到最新的稳定版。
这次更新主要涉及到了gulp4
的一个任务流语法的重写以及一些其他的调整,整个过程还是蛮顺利的,差不多一天的时间搞定。
升级gulp到v4.x
gulp4 新特性
从 gulp changelog文档可以看到 4.0 的主要变化是任务系统的一个改变,之前的 orchestrator 系统被替换为 bach。bach
项目的简介是 Compose your async functions with elegance(优雅地组合异步函数),那我们先来看一下它是如何优雅地组合异步函数。
parallel 和 series
在 4.0 中,我们可以通过series()
和parallel()
这两个强大的函数来组合任务。series()
用于让任务按顺序执行,parallel()
用于让任务并发执行。这两个函数都可以接收任意数目的任务函数或已经组合好的操作。
series()
对于处理顺序的任务流确实优雅了很多。在 3.x 中,如果我们需要按顺序执行任务,需要手动地去维护一个队列。
var gulp = require('gulp');
// 返回一个 callback,因此系统可以知道它什么时候完成
gulp.task('one', function(cb) {
// 做一些事 -- 异步的或者其他的
cb(err); // 如果 err 不是 null 或 undefined,则会停止执行,且注意,这样代表执行失败了
});
// 定义一个所依赖的 task 必须在这个 task 执行之前完成
gulp.task('two', ['one'], function() {
// 'one' 完成后
});
gulp.task('default', ['one', 'two']);
在 4.0 中,我们只要按顺序把任务当成参数传入series()
函数即可。
const task1 = ()=>{
return new Promise((resolve, reject)=>{
setTimeout(()=>{
console.log("2s后执行任务1")
resolve()
}, 2000)
})
}
const task2 = ()=>{
return new Promise((resolve, reject)=>{
setTimeout(()=>{
console.log("1s后执行任务2")
resolve()
}, 1000)
})
}
exports.test = parallel(task1, task2);
对于这两个任务,如果我们并行地去执行,应该是先输出task2的信息,再输出task1的信息。我们改成series()
后,可以看到先执行task1再执行task2。
exports.test = series(task1, task2);
任务(task)
在 3.x 中,我们需要通过gulp.task
函数来创建一个任务。在 4.0 中,我们只需要定义一个异步的函数即可,该函数是一个可以接收 callback 作为参数的函数,或者是一个返回 stream、promise、event emitter、child process 或 observable 类型值的函数。
比如我们使用src
函数,它返回的是一个流,所以我们定义的fontCss
函数就是一个任务。
const fontCss = ()=>{
return src(['src/css/font-awesome.min.css', 'src/css/iconfont.css'])
.pipe(concat('font.css'))
.pipe(autoprefixer())
.pipe(cleanCSS({compatibility: 'ie8'}))
.pipe(rename({suffix: '.min'}))
.pipe(dest('dist/css'))
}
或者我们接收一个 callback,还有前面写的返回一个 Promise,都是可以的。
const clean = (cb) => {
// body omitted
cb();
}
公开任务和私有任务
在 3.x 中,每个任务都是可以直接被执行的。4.0 中为了方便管理,有了公开任务和私有任务之分。私有任务可以被当成参数传入到series()
和parallel()
函数里面。公开任务需要通过exports
导出。
const { series } = require('gulp');
// `clean` 函数并未被导出(export),因此被认为是私有任务(private task)。
// 它仍然可以被用在 `series()` 组合中。
function clean(cb) {
// body omitted
cb();
}
// `build` 函数被导出(export)了,因此它是一个公开任务(public task),并且可以被 `gulp` 命令直接调用。
// 它也仍然可以被用在 `series()` 组合中。
function build(cb) {
// body omitted
cb();
}
exports.build = build;
exports.default = series(clean, build);
如何升级
gulp
的升级相比webpack
来说容易多了,可能也是因为项目的gulpfile.js
配置比较简单吧。只需要卸载老的版本,再安装一下新的版本,把gulpfile.js
语法替换一下就可以了。
//4.0 中直接导出模块函数
const {src, dest, parallel} = require('gulp');
//任务函数的修改,不需要使用 task 函数
const vendorJs = ()=>{
return src(['src/js/jquery.min.js', 'src/js/bootstrap.min.js', 'src/js/iconfont.js'])
.pipe(uglify())
.pipe(concat('vendor_core.js'))
.pipe(rename({suffix: '.min'}))
.pipe(dest('dist/js'))
}
//使用组合函数并发构建,发挥最大的性能
const build = parallel(cssCompress, jsCompress, jsonCompress)
//使用 exports 导出公开任务
exports.build = build
升级babel到7.x
babel7
相对于babel6
也是做了很大的改动,升级起来并不是很容易,虽然官网还专门写了一篇升级的指南:升级到 Babel 7
升级依赖
babel
的依赖跟gulp
比起来就复杂的多了。首先是它使用了@babel
的monorepo
管理方式,需要把之前的一些依赖都重新安装一遍。
"devDependencies": {
"@babel/cli": "^7.10.5",
"@babel/core": "^7.11.4",
"@babel/plugin-proposal-object-rest-spread": "^7.11.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-jsx": "^7.10.4",
"@babel/plugin-transform-modules-commonjs": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.11.0",
"@babel/polyfill": "^7.10.4",
"@babel/preset-env": "^7.11.0",
"@babel/runtime": "^7.11.2",
...
},
配置文件
之前 babel 的配置是写在 webpack 的 module 配置项里的,使用 babel-loader
{
test: /\.m?js$/,
include: /src/,
exclude:
/(node_modules|bower_components)/,
use:
{
loader: 'babel-loader',
options:
{
cacheDirectory: true,
presets: ['env'],
plugins:
['transform-runtime', 'transform-remove-strict-mode',
'transform-vue-jsx',
'transform-object-rest-spread', ["component", {
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}]
]
}
}
},
这次直接把它独立成一个.babelrc
文件
{
"presets": [
[
"@babel/preset-env",
{
"loose": true
}
],
[
"@vue/babel-preset-jsx"
]
],
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-runtime",
[
"@babel/plugin-transform-modules-commonjs",
{
"strictMode": false
}
],
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-syntax-jsx"
]
}
webpack 里面就不做配置了
{
test: /\.m?js$/,
include: /src/,
exclude:
/(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {cacheDirectory: true}
}
},
babel-polyfill
因为 babel 改为了 monorepo 的方式,所以之前项目中使用的
import "babel-polyfill"
需要改为
import "@babel/polyfill";
babel-plugin-component
elementui 的 babel-plugin-component一直没有做 babel7 的适配,导致通过这种方式引入的组件会报错。有一个 issue 提到了这个问题,目前并没有什么好的解决方案,只有全局引入 elementui,废弃掉这种方式了。
其他升级项
使用 dart-sass 替代 node-sass
node-sass
一直有一个问题,就是下载它的包经常失败。对于做持续部署来说,经常会在它上面花费很多时间。使用dart-sass
替代node-sass
后会导致一个问题,v-loader 的深度选择器:/deep/
会出错,>>>
也无效,目前替换为v-deep
使用 yarn 来替换 npm
通过yarn
来安装依赖比npm
快多了,这是一个最直观的感受。它的命令跟npm
也有一些区别。
//安装依赖
npm install
yarn
//安装包
npm install react --save
yarn add react
...
//运行任务
npm run build
yarn build