什么是tree-shaking?
前端中的tree-shaking可以理解为通过工具"摇"我们的js文件,将其中用不到的代码"摇"掉,是一个性能优化的范畴。具体来说,在webpack项目中,
有一个入口文件,相当于一棵树的主干,入口文件有很多依赖的模块,相当于树枝。实际情况中,虽然依赖啦某个模块,但其实只使用其中的某些功能。通过
tree-shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。
tree-shaking原理
tree-shaking原理,简单来说
- tree shaking的本质是消除无用的Javascript代码
- 因为ES6模块的出现,ES6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析
怎么使用tree-shaking?
-
在webpackDemo/demo下新建一个文件tree-shaking,文件目录如下
-
index.js内容如下
import {add} from "./math.js"
console.log(mathFn)
function component(){
var element = document.createElement("pre");
element.innerHTML = [
"hello webpack",
"5 cubed is equal to " + add(5,6)
].join("
");
return element;
}
document.body.appendChild(component())
- math.js内容如下
export function add(a,b){
console.log("add");
return a+b;
}
export function minus(a,b){
console.log("minus")
return a-b
}
export function multiply(a,b){
console.log("mutiply");
return a*b;
}
export function divide(a,b){
console.log("divide");
return a/b;
}
- tree-shaking.js内容如下
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const webpack = require("webpack");
module.exports = {
mode:"development",
entry:path.resolve(__dirname,"src/index.js"),
module:{
rules:[
{
test:/.m?js$/,
exclude:/(node_modules|bower_components)/,
use:{
loader:"babel-loader",
options:{
presets:["@babel/preset-env"],
plugins:[
"@babel/plugin-transform-runtime"
]
}
}
}
]
},
output:{
filename:"bundle.js",
path:path.resolve(__dirname,"dist")
},
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackPlugin(),
// new webpack.NamedModulesPlugin(),
// new webpack.HotModuleReplacementPlugin()
],
devtool:"inline-source-map",
// devServer:{
// contentBase:"./dist",
// hot:true,
// inline:true
// }
}
- package.json 内容如下
{
"name": "webpackDevServer",
"sideEffects": false,
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"dev": "webpack-dev-server --config demo/webpack-dev-server/webpack-dev-server.js",
"server": "node demo/webpack-dev-middleware/server.js",
"hmr": "webpack-dev-server --config demo/HMR/webpack.hmr.js",
"treeShaking:dev": "webpack-dev-server --config demo/tree-shaking/tree-shaking.js",
"treeShaking:build": "webpack --config demo/tree-shaking/tree-shaking.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "7.6.4",
"@babel/plugin-transform-runtime": "^7.6.2",
"@babel/preset-env": "^7.6.3",
"babel-loader": "8.0.6",
"clean-webpack-plugin": "3.0.0",
"css-loader": "3.2.0",
"express": "4.17.1",
"html-webpack-plugin": "3.2.0",
"style-loader": "1.0.0",
"webpack": "4.41.2",
"webpack-cli": "3.3.9",
"webpack-dev-middleware": "3.7.2",
"webpack-dev-server": "3.8.2"
}
}
- 执行 npm run treeShaking:build
执行结果如下
执行后我们可以看到bundle.js文件,可以发现math.js中所有的代码都被打包了,而我们只用了文件中的add函数,我们没用到的函数不想打包到文件,这时我们可以用到tree-shaking
- 启用webpack
当前项目中使用的是webpack4,讲mode设置为production即可开启tree shaking
关于side effects(副作用)
副作用是指除了函数返回值以外,任何在函数调用之外观察到的应用程序状态改变。副作用包括:
- 改变了任何外部变量或对象属性(例如,全局变量,或者一个在父级函数作用域链上的变量)
- 写日志
- 在屏幕输出
- 写文件
- 发网络请求
- 触发任何外部进程
- 调用另一个有副作用的函数
tree shaking不能自动的识别那些代码属于side effects,因此手动指定这些代码非常重要,如果不指定可能会出现一些意想不到的问题
在webpack中,是通过packge.json的sideEffects属性来实现的
{
"name":"tree-shaking",
"sideEffects":false
}
如果所有代码都包含副作用,我们可以简单地将属性标记为false,并告知webpack,他可以安全地删除未用到的export导出
如果你的代码确实有一些副作用,那么可以改为提供一个数组
{
"name": "tree-shaking",
"sideEffects": [
"./src/common/polyfill.js"
]
}
总结
- tree shaking 不支持动态导入(如CommonJS的require()语法),只支持纯静态的导入(ES6的import/export)
- webpack中可以在项目package.json中添加一个"sideEffects"属性,手动指定有副作用的脚本
将mode修改,在package.json里设置sideEffects:false结果如下
我们可以看出使用了tree shaking后,代码体积变小,并且我们引用的math.js函数add被转换,其他未引用的代码并没有出现