前言

啊,这是去年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 启用哪些插件进行内置优化。

有三个可选值:developmentproductionnone

development:启用NamedChunksPluginNamedModulesPlugin

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,例如:像SetMapPromise新的内置函数或Array.fromArray.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之前的版本可以在.babelrcenvpreset的配置项加上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)