为啥 webpack 在使用“import * as _”时不会对 lodash 进行摇树?

Posted

技术标签:

【中文标题】为啥 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=developmentwebpack --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:falsemocha.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(压缩)。

【讨论】:

以上是关于为啥 webpack 在使用“import * as _”时不会对 lodash 进行摇树?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Tailwind 中的清除选项不能与 Webpack 一起使用

为啥 webpack 在使用“import * as _”时不会对 lodash 进行摇树?

为啥要使用 WebPack?

当与新的 Vue.js CLI Webpack 模板项目一起使用时,为啥 Webpack-Dev-Server(在 docker 容器内)不检测文件更改?

为啥 server.js 从来没有内置在 Webpack 中?

任务运行程序(Gulp、Grunt 等)和捆绑程序(Webpack、Browserify)。为啥要一起使用?