前言
啊,这是去年9月份就准备写的一篇博客,居然拖到了现在!!那时候在写vue的开发模板,是根据vue-cli生成的模板来改造的,主要是为了深入了解一下vue + webpack开发所需的配置项,并且尝试一下webpack4,在此记录遇到的问题和webpack4的一些新特性。
webpack-cli
webpack-cli是用于在命令行中运行webpack,webpack4之前,webpack-cli和webpack放在同一个包中,webpack4以及之后的版本,将webpack-cli抽离出来独立一个包。
所以如果需要在命令行中全局运行webpack命令,需要全局安装一下webpack-cli。也可以安装在项目下,然后使用npx webpack
来缩短调用命令(npx命令需要npm@5.2.0及更高版本)
webpack4——mode
webpack4新增了一个mode
属性,告诉 webpack 启用哪些插件进行内置优化。
有三个可选值:development
,production
,none
。
development
:启用NamedChunksPlugin
和 NamedModulesPlugin
。
production
: 启用NoEmitOnErrorsPlugin
, UglifyJsPlugin
等一系列生产环境使用的插件。
none
:不做任何默认优化。
设置mode
会同步设置一个编译时可访问的process.env.NODE_ENV
全局常量(在webpack4之前,这一步由DefinePlugin设置),这样你就可以在src目录的源码中直接访问process.env.NODE_ENV这个变量,这不同于node环境的process.env.NODE_ENV
,node环境中的需要cross-env来设置。
使用webpack4必须设置mode
,如果没有设置,终端会出现报错提示。
webpack4——optimization
runtimeChunk
splitChunks
待补充。。
Babel相关
既然都用了webpack4,Babel也直接用最新的v7版本,配置上没多大区别,只是名字变了。自版本7开始,Babel模块都包含在@babel
下。
这里主要记一下@babel/polyfill
和@babel/plugin-transform-runtime
两者的使用方法和区别。
@babel/polyfill 使用场景
Babel默认只转换ES2015+的语法,而不转换ES2015+的api,例如:像Set
、Map
、Promise
新的内置函数或Array.from
、Array.prototype.includes
这样的方法
例如下面这段index.js
代码:
let fn = () => {
let arr = [1, 2, 3]
if (arr.includes(1)) {
return true
} else {
return false
}
}
使用env
预设时会被转换成如下:
var fn = function fn() {
var arr = [1, 2, 3];
if (arr.includes(1)) {
return true;
} else {
return false;
}
};
可以看到,这里只转换了箭头函数的写法,而Array.prototype.includes方法并未转换。
这时就该babel-polyfill
出场了,用法是直接在你的js文件的头部引入babel-polyfill
:
import 'babel-polyfill'
let fn = () => {
let arr = [1, 2, 3]
if (arr.includes(1)) {
return true
} else {
return false
}
}
babel输出如下:
require('babel-polyfill')
// 或如果设置了 "modules": false
// import 'babel-polyfill'
let fn = () => {
let arr = [1, 2, 3]
if (arr.includes(1)) {
return true
} else {
return false
}
}
这样相当于把所有的垫片都引入进来了,所以打包出来的JS文件体积会比较大。babel v7之前的版本可以在.babelrc
的env
preset的配置项加上useBuiltIns: true
。
.babelrc
文件
{
"presets": [
["env", {
"modules": false,
"useBuiltIns": true
}]
]
}
babel转译后输出类似如下的代码:
import '...其他垫片';
import 'core-js/modules/es7.array.includes';
import '...其他垫片';
var fn = function fn() {
var arr = [1, 2, 3];
if (arr.includes(1)) {
return true;
} else {
return false;
}
};
这样会把所有垫片都内置在你的JS文件中,也相当于引入了所有垫片,但是体积会比之前的小一些,具体原因还没深究(TODO)。
而Babel v7之后只需要设置useBuiltIns: 'usage'
就指引入需要的垫片,不会引入全部,这样打包后的体积就很小了。
index.js
的代码babel转译后输出如下:
import "core-js/modules/es7.array.includes";
var fn = function fn() {
var arr = [1, 2, 3];
if (arr.includes(1)) {
return true;
} else {
return false;
}
};
@babel/plugin-transform-runtime
Babel在转译时为了实现和源码同样的功能,可能需要借助一些helpers(帮助函数),如:
let key = 'name';
let obj = {
[key]: 'myName'
}
转译后:
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var key = 'name';
var obj = _defineProperty({}, key, 'myName');
_defineProperty
就是所谓的帮助函数,类似这样的帮助函数可能会重复出现在其他文件里,导致编译后的体积变大。为了解决这个问题,Babel提供了单独的包babel-runtime
,存放所有这些转译时需要的帮助函数,然后再通过@babel/plugin-transform-runtime
来调用这些工具函数。
在.babelrc
中加上"plugins": ["@babel/plugin-transform-runtime"]
转译后:
import _defineProperty from "@babel/runtime/helpers/defineProperty";
var key = 'name';
var obj = _defineProperty({}, key, 'myName');
这样就不会重复定义_defineProperty
等帮助函数了。
上面说到的@babel/polyfill
为浏览器环境提供垫片,新增的实例方法是通过修改对象原型的方式来实现的,像Promise, Set和Map这些新增的全局对象则会污染全局命名空间。
@babel/plugin-transform-runtime
可以替代一些polyfill的功能,可以通过设置"corejs": 2
来转换内置函数(Promise, Set, Map, Symbol)和静态方法(如:Object.assign, Arrat.from)。
注意:@babel/plugin-transform-runtime
不会转换实例方法(如:Array.prototype.includes)