1. TreeShaking
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和export
1.1 问题引入
我们新增一个工具文件math.js,里面编写一个add和minus方法:
//math.js
export const add = (a,b) =>{
console.log(a+b)
}
export const minus = (a,b) => {
console.log(a-b)
}
然后index.js里面导入:
// index.js
import {add} from './math.js'
add(1,2)
接着我们build打包一下,发现打包后端的文件除了add的代码外还包含了没有使用的minus相关代码。
这个其实是没有必要的,会使得js文件变大,理想情况下就是引入什么真正用到了什么,你帮我打包什么。
这也就是webpack提供的tree shaking功能 — 把一个模块里面没用的部分摇掉,只留下部分需要的。
1.2 配置使用
首先需要注意tree Shaking只支持 ES Module这种模块化方式(import,export),不支持CommonJS(require(./))。
这是因为前者底层基于一种静态引入方式,而后者是动态化的,无法在打包阶段支持。
接着让我们关注一下webpack配置文件,当mode是开发模式development时是默认不开启tree shaking的,需要加上:
optimization:{
usedExports:true
}
接着我们还需要去package.json里面再配置一下:
{
...
"sideEffects":false,
...
}
这里关于sideEffects配置项和treeShaking的关系可以参考这篇博文
这里要补充一个概念,当你的import对象并不是要使用的时候,而是做一些额外操作的时候,例如导入polyfill,这时候tree shaking可能直接摇掉了,其实是需要的,所以这里有可能还需要这样配置:
{
...
"sideEffects":["@babel/polly-fill"],
...
}
之前我们通过配置了useBuiltIns字段从而避免了直接import polyfill函数,所以这里改为false就行了。
此外我们还有可能import一些css文件,这时候tree shaking也可能将其干掉,所以一般在这里我们还会写一个css规则:
{
...
"sideEffects":["*.css"],
...
}
重新打包后我们发现bundle里面仍然有minus代码,但是exports used:minus的注释没有了。
这是因为我们目前是在开发环境,需要做一些调试,如果tree shaking干掉了,那么sourcemap就会出错,无法调试了。
我们尝试把mode切换为production,注意production环境下tree shaking会自动添加,原先新增的配置可以不要。
然后重新打包后搜索发现已经找不到minus的相关代码。
2. buildMode
其实这一点之前都带着提到过,就是设置打包模式mode字段,包括development和production
之前我们在开发过程中使用了包括devServer,HMR这种技术很方便,但是一旦源代码开发完成需要上线,就需要将mode修改为production。
development下的sourceMap是很全的,而produciton则是可以生成一个map文件来进行存储。
development下的代码不会压缩,可以完全看到源代码,而produciton则是压缩过的
2.1 为不同模式分别编写配置文件
之前的方式我们如果需要改动配置,必须去webpack.config.js里面手动修改一下,显得比较繁琐(并且不同的mode可能有一些配置还不一样)。
要想解决这个问题我们可以如下解决:
- 首先重命名webpack.config.js -> webpack.dev.js。
- 然后新创建一个webpack.prod.js文件,分别对应dev和prod环境下的配置项,prod配置项文件所需要的变化主要是将之前的devServer、HMR、optiimaztion相关的配置项移除。
- 配置package.json文件,新增对应的script:
...
scripts:{
"dev":"webpack-dev-server --config webpack.dev.js", //开发环境使用devServer
"build":"webpack --config webpack.prod.js" // 线上环境直接打包就可以
}
开发时候直接npm run dev,启动一个devServer配合HMR提高开发效率。
然后开发完成上线时执行npm run build,可以在dist目录下面生成线上所使用的html和打包的js文件
2.2 配置封装
这里我们发现两个配置文件其实有很多代码都是相同的,感觉可以提取封装一下,这里我们创建一个webpack.common.js文件,把共同的代码抽出放进去,例如entry,module,output,这里plugins虽然两者value有一些不一样,但是还是可以把共同的部分提取出来:
提取之后的prod配置文件:
module.exports = {
mode:'production',
devtool:'cheap-module-source-map'
}
提取之后的dev配置文件:
const webpack = require('webapck')
module.exports = {
mode:'development',
devtool:'cheap-module-eval-source-map',
devServer:{
contentBase:'./dist',
open:true,
port:8080,
hot:true
},
plugins:[
new webpack.HotModuleReplacmentPlugin()
],
optimization:{
usedExports:true
}
}
抽取封装的common.js:
// 内部是之前未拆分的部分,就不重复写了
const path = ...
const HtmlWebpackPlugin = ...
...
module.exports = {
entry:...,
module:...,
export:...,
...
}
这里光这样写还不够,记得在dev.js和prod.js同common.js做一个合并,这里就需要借助一个第三方模块:webpack-merge
npm install -D webpack-merge
并地方引入webpack-merge,并引入commonConfig进行merge就可以了
// webpack.dev.js
const merge = require('webpack-merge'),
const commonConfig = require('./webpack.common.js')
const devConfig = {
... // 之前的dev配置对象
}
module.exports = merge(commonConfig,devConfig)
2.3 小提示
有一些框架里面会额外创建一个build文件夹,然后把webpack的3个配置文件都放到build目录里面。
这样的话记得script的路径也要修改