tree shaking(摇树优化)
Posted 还一激灵
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tree shaking(摇树优化)相关的知识,希望对你有一定的参考价值。
tree shaking(摇树优化)
前言
随着js的不断发展,性能优化成了主流的方向,但是如何性能优化又成了现在程序员的一大苦恼,而我作为一名前端小白,也就深陷其中,最近学习到了tree shaking,在这里分享一下
一、tree shaking是什么?
在前端的性能优化中,es6 推出了tree shaking机制,tree shaking就是当我们在项目中引入其他模块时,他会自动将我们用不到的代码,或者永远不会执行的代码摇掉,在Uglify阶段查出,不打包到bundle中。
只支持ES6 Module代码。在production 环境默认开启。
二、哪些情况下可以使用tree-shaking呢?
1.首先,要明确一点:Tree Shaking 只支持 ESM 的引入方式,不支持 Common JS 的引入方式。
- ESM: export + import
- Common JS: module.exports + require
提示:如果想要做到tree shaking,在引入模块时就应该避免将全部引入,应该引入局部才可以触发tree shaking机制
代码如下(示例):
// Import everything (not tree-shaking)
import lodash from 'lodash';
// Import named export (can be tree-shaking)
import debounce from 'lodash';
// Import the item directly (can be tree-shaking)
import debounce from 'lodash/lib/debounce';
三、项目中如何配置tree-shaking?
1、开发环境配置tree shaking
// webpack.config.js
module.exports =
// ...
mode: 'development',
optimization:
usedExports: true,
;
- 生产环境下的配置
// webpack.config.js 生产环境下只需要把mode配置成‘production’即可
module.exports =
// ...
mode: 'production',
;
- sideEffects: false
根据环境的不同进行配置以后,还需要在 package.json 中,添加字段:**sideEffects: false,**告诉 Webpack 哪些代码可以处理
"name": "webpack-demo-1",
"sideEffects": false,
// ...
// 列子:
// All files have side effects, and none can be tree-shaken
"sideEffects": true
// No files have side effects, all can be tree-shaken
"sideEffects": false
// Only these files have side effects, all other files can be tree-shaken, but these must be kept
"sideEffects": [
"./src/file1.js",
"./src/file2.js"
]
五、sideEffects 对全局 CSS 的影响
- 对于那些直接引入到 js 文件的文件,例如全局的 css,它们并不会被转换成一个 CSS 模块。
/* reset.css */
*
margin: 0;
padding: 0;
box-sizing: border-box;
html,
body
background-color: #eaeaea;
// main.js
import "./styles/reset.css"
- 这样的代码,在打包后,打开页面,你就会发现样式并没有应用上,原因在于:上面我们将 sideEffects 设置为 false后,所有的文件都会被 Tree Shaking,通过 import 这样的形式引入的 CSS 就会被当作无用代码处理掉。
- 为了解决这个问题,可以在 loader 的规则配置中,添加 sideEffects: true,告诉 Webpack 这些文件不要Tree Shaking。
// webpack.config.js
module.exports =
// ...
module:
rules: [
test: /\\.css$/i,
use: ["style-loader", "css-loader"],
sideEffects: true
]
,
;
总结
- tree shaking就是类似一棵树有长熟的苹果,将已经成熟的苹果摇掉减轻树的负担,这就实现了这个机制
- 在es6中的import和export才可以触发这个机制
- 项目中对tree-shaking的配置
- tree-shaking对项目中的影响
为啥 webpack 在使用“import * as _”时不会对 lodash 进行摇树?
【中文标题】为啥 webpack 在使用“import * as _”时不会对 lodash 进行摇树?【英文标题】:Why webpack doesn't tree-shake the lodash when using "import * as _"?为什么 webpack 在使用“import * as _”时不会对 lodash 进行摇树? 【发布时间】:2020-03-03 13:55:32 【问题描述】:我正在学习如何使用使用 Lodash 的 webpack 4/React 应用程序进行 tree-shaking。
起初,我的 Lodash 用法如下所示:
import * as _ from "lodash";
_.random(...
我很快通过 BundleAnalyzerPlugin 了解到,整个 Lodash 都包含在开发和产品构建中 (527MB)。
googling around 之后我意识到我需要使用特定的语法:
import random from "lodash/random";
random(...
现在,只有 random
及其依赖项正确包含在包中,但我还是有点困惑。
如果我需要在import
语句中明确指定函数,那么摇树实际上扮演什么角色?
在比较开发和生产模式构建时,BundleAnalyzerPlugin 没有显示有效负载大小的差异(两者都是正确的小尺寸,但我认为摇树只发生在生产构建中?)。
我的印象是 TreeShaking 会执行某种静态代码分析,以确定代码的哪些部分实际被使用(可能基于功能?)并剪掉未使用的部分。
为什么我们不能总是在 import
中使用 *
并依靠 TreeShaking 来确定捆绑包中实际包含的内容?
如果有帮助,这是我的webpack.config.js
:
const path = require("path");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
module.exports =
entry:
app: ["babel-polyfill", "./src/index.js"]
,
plugins: [
new BundleAnalyzerPlugin(
analyzerMode: "static",
openAnalyzer: false
)
],
devtool: "source-map",
output:
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
chunkFilename: "[name].bundle.js",
publicPath: ""
,
module:
rules: [
test: /\.js$/,
loader: "babel-loader",
include: /src/,
options:
babelrc: false,
presets: [
[
"env",
targets:
browsers: ["last 2 Chrome versions"]
],
"@babel/preset-env",
"@babel/preset-react"
],
plugins: ["syntax-dynamic-import"]
,
test: /\.(ts|tsx)$/,
use: [
loader: require.resolve("ts-loader"),
options:
compiler: require.resolve("typescript")
]
]
,
resolve:
symlinks: false,
extensions: [".js", ".ts", ".tsx"],
alias:
react: path.resolve("./node_modules/react")
;
我正在使用 webpack --mode=development
和 webpack --mode=production
调用 webpack。
【问题讨论】:
它really shouldnot matter。你到底在用_
对象做什么,而不仅仅是调用它的方法?
您的意思是import random from "lodash/random";
? import * as _ from "lodash";
应该与 import random from "lodash";
完全相同。
@Bergi,是的,对不起,我的意思是“lodash/random”,我在代码中输入正确,只是在问题中输入错误。
@Bergi,感谢您的链接,但是当我使用“import * as _”语法时,捆绑包拥有整个 lodash 库,所以看起来它确实很重要......
我认为这是因为 lodash 不可摇树(糟糕的设计)。 import random from "lodash";
也不应该工作,对吧?
【参考方案1】:
所有两个现有的答案都是错误的,webpack do treeshake import *,但是这只发生在你使用 esmodule 时,而 lodash 不是。正确的解决方法是使用lodash-es
编辑:这个答案只适用于 webpack4,而 webpack 5 支持 commonjs 的有限的树抖动子集,但我自己没有测试过
【讨论】:
lodash-es
具有maintenance issues 并且任何使用它的尝试都需要在您选择的捆绑器中将lodash
别名为lodash-es
,否则您将获得lodash-es
用于您的项目和lodash
对于一些选择使用 lodash/*
导入的依赖项。此外,即使是不正确的 lodash
导入,也可以使用 transform-imports
Babel 插件修复。
好像已经修复了
在package.json
中使用sideEffects:false
和mocha.config.js
中的mode:production
,当安装lodash-es
并卸载lodash
时,它仍然不是摇树。然而,使用 `import join from 'lodash/join' 会将包大小从 77k 发送到
@CraigHicks 你应该从lodash-es
导入而不是lodash
@Austaras - 你是对的!我省略了额外的步骤npm i -D @types/lodash-es
。【参考方案2】:
实际上,它与 Webpack 的 tree-shake 能力无关。基于关于 tree-shaking 的 Webpack 文档
新的 webpack 4 版本扩展了此功能,通过“sideEffects”package.json 属性向编译器提供提示,以指示项目中的哪些文件是“纯”的,因此如果未使用则可以安全地修剪
p>
当您根据链接的文档在package.json
上设置"sideEffects: false
时:
上面提到的所有代码都不包含副作用,因此我们可以简单地将属性标记为 false 以通知 webpack 它可以安全地修剪未使用的导出。
如果你有一些你知道它们是纯的文件或包,请将它们添加到sideEffects
,以便在未使用时将其删除。 tree-shaking
还有其他一些解决方案,我建议阅读 Webpack 文档上的整个 article。
其中一种手动方式是使用直接导入,如下所示:
import get from 'lodash/get';
Webpack 理解 add 只是从整个 lodash 包中获取。另一种方法是破坏需要一些 Webpack optimization 进行摇树的导入,所以你应该像下面这样导入:
import get from 'lodash';
另外,另一个棘手的方法是安装一个特定的包,我的意思是:
yarn add lodash.get
或
npm install --save lodash.get
然后导入只需写:
import get from 'lodash.get';
当然,这不是摇摇欲坠,这是一种紧密的心态发展,但它会让你只添加你想要的东西。
但是
你不要做任何上述解决方案,你只需通过编写 import * as _ from "lodash";
添加整个包,然后使用 _.random
或任何函数并期望 Webpack 理解你想要摇树正在发生吗?
当然,Webpack 运行良好。你应该使用一些配置和编码风格来查看摇树的发生。
【讨论】:
“如果你有一些你知道它们是纯的文件或包,请将它们添加到 sideEffects 以在未使用时对其进行修剪。” ---- 您的书面信息颠倒了意义。如果你知道它们不是纯的,你应该将它们添加到 sideEffects 中。这就是您的参考资料所说的。 @CraigHicks,好的,这是什么意思?或者我该怎么办? 其实这不是你的错——顶部的 webpack 描述具有误导性。继续往下看这里的例子 (webpack.js.org/guides/tree-shaking/…),你会看到sideEffects:false
意味着没有文件有副作用,有副作用的文件应该单独列出,例如sideEffects:['./fileWithSideEffects.js','./anotherFileWithSideEffects.js']
。我认为默认值是sideEffects:false
,因为摇树在没有设置sideEffects
的情况下工作。 (我确实使用了lodash-es
和@types/lodash-es
)。
@CraigHicks,感谢您的通知。我会投赞成票来奖励你的努力和解释。谢谢。【参考方案3】:
如果您已经在使用 Babel,正确摇树 lodash 的最简单方法是使用 official babel-plugin-lodash
by the lodash team。
这使用 Babel 将您的 lodash 导入重写为更可摇树的形式。这样做只用了不到 5 分钟的时间,就将我团队的包大小减少了约 32kB(压缩)。
【讨论】:
以上是关于tree shaking(摇树优化)的主要内容,如果未能解决你的问题,请参考以下文章
The way of Webpack learning (V.) -- css和js的tree shaking
为啥 webpack 在使用“import * as _”时不会对 lodash 进行摇树?
webpack开发与生产环境 性能优化配置 - HMR - 缓存 -tree shaking - 代码分割 - 懒加载 - 预加载 - PWA - 多进程打包 - externals - dll(代码