"sideEffects": false没有使打包后的bundle减少

webpack指南之Tree shaking

根据上面链接中示例写的demo,运行npm run build:
场景1:当math.js没有副作用,不管配没配置sideEffects:false 最终的bundle中都没有square函数,并且bundle的size没变
场景2:当math.js有副作用,不管配没配置sideEffects:false 最终的bundle中都没有square函数,并且bundle的size没变
我在网上看了一篇文章,说sideEffects的作用是:如果我们引入的 包/模块 被标记为 sideEffects: false 了,那么不管它是否真的有副作用,只要它没有被引用到,整个 模块/包 都会被完整的移除。

那么:
在场景1和场景2中,为啥没配置sideEffects:false,没有引用的代码也会被移除呢?

// src/index.js
import { cube } from './math.js';

function component() {
  var element = document.createElement('pre');
  
  element.innerHTML = [
    'Hello webpack!',
    '5 cubed is equal to ' + cube(5)
  ].join('\n\n');

  return element;
}

document.body.appendChild(component());

// src/math.js - 没有副作用
export function square(x) {
  console.log('square')
  return x * x;
}
export function cube(x) {
  console.log('cube')
  return x * x * x;
}

// src/math.js - 有副作用
export function square(x) {
  console.log('square')
  return x * x;
}
console.log(square(12)); // 产生副作用
export function cube(x) {
  console.log('cube')
  return x * x * x;
}

// package.json
{
  "name": "demo16",
  "sideEffects": false,
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode=production",
    "watch": "webpack --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "lodash": "^4.17.11"
  },
  "devDependencies": {
    "clean-webpack-plugin": "^2.0.1",
    "html-webpack-plugin": "^3.2.0",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.3.0",
    "webpack-dev-server": "^3.2.1"
  }
}

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
  entry: {
    app: './src/index.js'
  },
  devtool: 'inline-source-map',
  devServer: {
    contentBase: './dist',
    hot: true
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Tree Shaking'
    })
  ],
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

阅读 6.4k
1 个回答

"sideEffects" vs sideEffects

看到这个标题,别以为我写错了。
前者是 package.json 中的 "sideEffects",它的可选值以及含义如下。其表示的是:当在开发第三方库的时候,可以通过该字段指定第三方库是不是有副作用。当其他使用 webpack 打包的应用引用这个库的时候,如果它有在 webpack.optimization 中配置 sideEffects: true (后者)的话,webpack 就会去识别第三方库 package.json 中的 "sideEffects" 以剔除无用的模块,用来做 tree-shaking。注意两个地方的 sideEffects 的可选值和含义是不同的。

// package.json 
{
  "sideEffects": false / [], // 整个库是没有副作用的 / 指定文件是有副作用的
}
// webpack.config.js
{
  optimization: {
    sideEffects: true / false, // 是否识别第三方库 package.json 中的 sideEffects 以剔除无用的模块。生产模式下默认开启,其他模式不开启。
  }
}

官方文档给人一种之所以能在打包应用时删掉无用代码是因为 package.json 中设置了 "sideEffects": false 的误解,实际上之所以能删掉无用代码完全是因为在生产环境下使用了压缩插件 terser-webpack-plugin (>= v4.26.0) / uglifyjs-webpack-plugin (< v4.26.0)。两者的区别可参考 terser-webpack-plugin / uglifyjs-webpack-plugin。简而言之,这两个插件都使用了 UglifyJS。在讲这个压缩工具之前,我们先看看 webpack.optimization 中 providedExportsusedExports 的配置。

providedExports & usedExports

  1. providedExports:确定每个模块的导出,用于其他优化或代码生成。默认任何模式下都开启。生成图中的exports provided: square, cube 注释。
  2. usedExports:确定每个模块下被使用的导出。生产模式下默认开启,其他模式下不开启。生成
    图中的exports used: cube 和 unused harmony export square 注释。

clipboard.png

UglifyJS

我的理解是,在生产环境下,没有被使用的 exports 会有 unused harmony export square 注释,UglifyJS 会根据这个注释把无用代码删掉。问题中的两个例子,square 函数都没有被 import,所以都会被标记成 unused,然后被删掉

tree-shaking 还是挺复杂的,我在网上找了一些讲解的文章,供参考。
tree shaking 性能优化实践 - 原理篇
tree shaking 性能优化实践 - 实践篇
你的 tree shaking 并没有什么卵用
webpack 打包文件分析

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进