压缩可以移除模块内部的无效代码 tree shaking 可以移除模块之间的无效代码

背景
某个模块导出的代码不一定被用到
//myMath.jsexport function add(a,b){console.log('add');return a+b;}export function sub(a,b){conosle.log('sub');return a=-b;}
//index.js
import {add} from './myMath'
conosle.log(add(1,2));
tree shaking 用于移除掉不会用到的**
使用
webpack2.0就已经开始支持tree shaking
只要是生产环境,tree shaking 自动开启
原理
webpack会从入口模块出发寻找依赖关系
当解析一个模块的时候,webpack会根据es6的模块导入语句来判断,该模块依赖了另一个模块的哪导出
webpack之所以选择es6模块导入语句,是因为es6模块有一下特点:
- 导入导出语句只能是顶层语句
- import 的模块名只能是字符串常量
- import 绑定的变量是不可变的
这些特征非常有利于分析出稳定的依赖
在具体分析依赖的时候,webpack坚持的原则是: 保证代码正常运行,然后再尽量tree shaking
所以,如果你依赖的是一个导出的对象,由于js语言的动态特性,以及webpack还不够智能,为了保证代码正常运行,它不会移除对象中的任何信息
因此,我们在写代码的时候,尽量:
- 使用
export xxx导出,而不是export default {xxx}导出 - 使用
iimport {xxx} fromz "xxx"或import * as xxx from 'xxx'导入,而不是使用import xxx from 'xxxx'
依赖分析完毕以后,,webpack会根据每个模块每个导出是否被使用,标记其他导出为dead code ,然后交给代码压缩工具处理
代码压缩工具最终移除掉那些dead code 代码
使用第三方库
某些第三方库可能使用的是commonjs 的方式导出的,比如lodash
又或者没有提供普通的ES6导出方式
对于这些库, tree shking 是无法发挥作用的
因此要寻找这些库的es6的版本,好在很多流行但没有使用es6的第三方库,都发布了它的es6版本,比如lodash-es
作用于分析
tree shaking 本身并没有完善的作用域分析,可能导致在一些dead code 函数中的依赖仍然会被视为依赖插件webpack-deep-scope-plugin (这个库是个人开发,可能小的问题比较多,所以要慎重使用 提供了作用域分析,可以解决这些问题
副作用问题
webpack在tree shaking的使用,有一个原则:一定要保证代码的正确运行
在满足该原则的基础上,再来决定如何tree shaking
因此,当webpack无法确定某个模块是否有副作用的时候,往往将其视为有副作用
因此,某些情况可能不是我们想要的
//common.js
var m=Math.random();
//index.js
import "./common.js"
虽然我们根本没有使用common.js的导出,但是webpack 担心common.js有副作用,如果去掉会影响某些功能
如果要解决该问题,就需要标记该文件没有副作用
在package.json中加入sideEffects
{
"sideEffests":false 该模块是没有副作用的
}
有两种配置方式:
- false: 当前工程中,所有的模块都没有副作用,注意:这种写法会影响到某些css文件的导入
- 数组: 设置哪些文件时有副作用的,例如:
["!src/common.js"],表示只要不是src/common.js的文件,都有副作用这种方式我们一般不处理,通常一些第三方库在他们自己的
package.json中标注
css tree shaking
webpack 无法对css 完成tree shaking,因为css 跟 es6没有半毛钱关系
因此对,css的tree shaking需要其他的插件完成
例如: purgecss-webpack-plugin
purgecss-webapck-plugin对css module无能为力
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const DeepScope = require("webpack-deep-scope-plugin").default;
const MiniCss = require("mini-css-extract-plugin");
const Purgecss = require("purgecss-webpack-plugin");
const path = require("path");
const globAll = require("glob-all");
const srcAbs = path.resolve(__dirname, "src"); //得到src的绝对路径
const htmlPath = path.resolve(__dirname, "public/index.html");
const paths = globAll.sync([`${srcAbs}**/*.js`, htmlPath]);
module.exports = {
mode: "production",
module: {
rules: [{ test: /\.css$/, use: [MiniCss.loader, "css-loader"] }]
},
plugins: [
new CleanWebpackPlugin(),
new DeepScope(),
new MiniCss(),
new Purgecss({
paths
})
]
};
