深入浅出 sideEffects

2018-08-11 admin

原文地址

前言

最近在给团队对 webpack 中的 sideEffects 字段用途进行微分享,于是乎,我最后就整理成一篇文章,希望帮助更多的人理解 sideEffects 的作用。

sideEffects 是什么呢?我用一句话来概括就是:让 webpack 去除 tree shaking 带来副作用的代码。

听起来有点绕口,让我们划出重点:tree shaking/用法/副作用

tree shaking

tree shaking 是一种代码优化技术,它能够将无用的代码进行去除,下面举个简单的例子:

// a.js
export const a = 'a';
export const b = 'b';    // 不导出,删除
export const c = 'c';    // 导出不引用,删除
// index.js 
import { a, c } from './a.js'; 
console.log(a);
if(false) {             // 不会执行的代码,删除
  console.log('去除我');  
}

可以从上面简单的例子看出:在 webpack 编译阶段 tree shaking 会将未被使用的代码删除,简单总结一下:

  1. tree shaking 是基于 ES6 模块机制,意味着如果你引用不同的文件就需要遵循 ES6 的模块规范。
  2. webpack 在编译阶段会去除那些只读不写或者是不会被执行的代码。

使用方法

想使用 sideEffects,你的 webpack 的版本号要大于等于 4。那具体应该怎么用呢,如果你在写一个第三方的 npm 模块,sideEffects 支持下面两种写法:

// package.json
{
    "sideEffects": false
}
// antd package.json
{
  "sideEffects": [
    "dist/*",
    "es/**/style/*",
    "lib/**/style/*"
  ]
}

如果你想要对你的业务代码生效,那你可以再 module.rules 里面添加,比如:

module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
        },
        sideEffects: false || []
      }
    ]
  },
}

sideEffects 支持两种写法,一种是 false,一种是数组

  • false 为了告诉 webpack 我这个 npm 包里的所有文件代码都是没有副作用的
  • 数组则表示告诉 webpack 我这个 npm 包里指定文件代码是没有副作用的

webpack 在编译就会去读取这个 sideEffects 字段,如果有的话,它就会将所有引用这个包的副作用代码或者自身具有副作用的业务代码给去除掉。

副作用:

说了这么多,那什么是有副作用的代码呢?简单说来就是 JS 引用类型属性读/写所带来的副作用,看完下面这个简单的例子你就明白了。

var x = {};
Object.defineProperty(x, "a", {
    get: function(val) {
        window.x = 'a';
        return val;
    }
});
function getA ( x ) {
    return x.a
}
getA(x);
console.log(window.x);     // a

x 在获取 a 属性的同时在 window 对象上挂载了 x 字段,跟属性 setter 同理,JS 引用属性的 getter 和 setter 其实是不透明的,webpack 身为保守派,对于这类的代码就会选择保留。

这也是为什么 webpack 对于 ES6 class 类型处理糟糕的原因,在 babel 转义 class 类的时候会有很多 setter 的操作:

class Person {
  constructor ({ name, age, sex }) {
    this.className = 'Person'
    this.name = name
    this.age = age
    this.sex = sex
  }
  getName () {
    return this.name
  }
}
// 分割线

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var _createClass = function() {
  function defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || !1, descriptor.configurable = !0,
      "value" in descriptor && (descriptor.writable = !0), Object.defineProperty(target, descriptor.key, descriptor);
    }
  }
  return function(Constructor, protoProps, staticProps) {
    return protoProps && defineProperties(Constructor.prototype, protoProps), staticProps && defineProperties(Constructor, staticProps),
    Constructor;
  };
}()

var Person = function () {
  function Person(_ref) {
    var name = _ref.name, age = _ref.age, sex = _ref.sex;
    _classCallCheck(this, Person);

    this.className = 'Person';
    this.name = name;
    this.age = age;
    this.sex = sex;
  }

  _createClass(Person, [{
    key: 'getName',
    value: function getName() {
      return this.name;
    }
  }]);
  return Person;
}();

在一个简单的 class 中,会有一个立即执行函数,里面对对象属性进行 set 操作。

副作用带来的影响

举个很常见的例子,一般我们在项目里写代码的时候会区分开发环境和生产环境,比如开发阶段会引入一些调试工具包,如:

import DevTools from 'mobx-react-devtools';
class MyApp extends React.Component {
  render() {
    return (
      <div>
        ...
        { process.env.NODE_ENV === 'production' ? null : <DevTools /> }
      </div>
    );
  }
}

乍一看,还以为如果生产环境下 mobx-react-devtools 就完全不会被引入了,但其实如果没有 sideEffects 的话 mobx-react-devtools 并没有被完全移除,里面的副作用代码仍然是会被引入的。

使用场景:

什么时候应该加上?我也不确定自己写的 npm 模块到底有没有副作用?

我们基本能确保这个包是否会对包以外的对象产生影响,比如是否修改了 window 上的属性,是否复写了原生对象 Array, Object 方法。如果我们能保证这一点,其实我们就能知道整个包是否能设置 sideEffects: false

总结

webpack 的 tree shaking 依赖于 babel编译 + UglifyJS 压缩,这个过程是没有完善的程序流分析,UglifyJS 没有完善的程序流分析。它可以简单的判断变量后续是否被引用、修改,但是不能判断一个变量完整的修改过程,不知道它是否已经指向了外部变量,所以很多有可能会产生副作用的代码,都只能保守的不删除。

tree shaking 处理代码的类型:

  1. 自身业务代码:先 es6 模块进行 tree shaking 打包,然后在压缩代码
  2. npm 包模块:一般会提供两种版本:提供 es6 模块机制的 babel 编译后的文件(ps: 工程化项目的 babel 编译配置,为了提高编译速度,其实是会忽略掉 node_modules 内的文件编译)

在 webpack4 发布之前,tree shaking 在处理第三方 npm 模块时候一般是这么做的:

// antd package.json
{
  "name": "my-package",
  "main": "dist/my-package.umd.js",
  "module": "dist/my-package.esm.js"
}

// webpack.config.jss
module.exports = {
  resolve: {
    mainFields: ['browser', 'module', 'main'], // 设置主入口
  },
};

这样 webpack 在引用 antd 组件的时候就会优先去加载 es 的入口文件,

// antd 入口
export Button from './es/button';
export Message from './es/message';
export Row from './es/row';

// index.js
import { Row } from 'antd';

但是这么做的话虽然 webpack 可以找到 Row 对应的入口模块,然后不打包其它组件(Button,Message)等,其它组件虽然没被打包,但是它们产生的副作用的代码却被保留下来了,所以有个 hack 的方法就是通过引入 babel-plugin-import 将模块路径进行替换。

import { Button, Message } form 'antd';
// 转换
import Button from 'antd/lib/button';
import Message from 'antd/lib/button';

这样其它没被引用的组件,因为不经过 antd 主入口组件映射,其它组件带来的副作用代码就没被引入了。当然你引入的组件文件里面可能存在一些副作用代码,那就会被保留了。

现在有了 webpack4 事情就变得很简单了,直接在第三方模块里面加上 sideEffects: false,webpack 在读取到 es 入口的时候,没被引用到的文件就完完全全不会被引入了,不用使用 babel-plugin-import 进行 hack 了。

参考链接

原文链接:https://zhuanlan.zhihu.com/p/41795312

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-37733.html

文章标题:深入浅出 sideEffects

相关文章
深入浅出学习 react-redux
【深圳】DJI 大疆创新 互联网团队 前端及大量职位招聘中 (内推 15-50k + 丰厚年终 + 股份)简历请发至 evan.zhou#dji.com, 合适的话当天回复,当天内推。 更多福利 上篇文章 结合源码学习 Redux 介绍...
2018-07-20
ES6深入浅出 Generator
一步,一步前進の一步 ES6深入浅出之Generator生成器。本人对生成器的印象是语法难以理解,又没有什么实际的应用场景。为啥要学习一下呢?可能未来某些高级的业务会用到,还有万一面试官问的话,我得能侃几句,显得我牛😏。 定义 Gen...
2018-08-16
ES6深入浅出 模块系统
一步,一步前進の一步。 模块化主要是帮助我们更好的组织代码,模块允许我们将相关的变量和函数放在一个模块中。在 ES6 模块化之前,JS 语言并没有模块的概念,只有函数作用域和全局作用域非常容易发生命名冲突。之前的 RequireJS、S...
2018-08-12
深入浅出代码优化﹣if/else
前言 對於代碼裡面的 if else,我們可以使用邏輯判斷式,或更好的三元判斷式來優化代碼。除了可以降低維護項目的成本之外,還可以提升代碼可讀性。就讓我們從最簡單的 if else 例子開始吧。 最簡單的 if...else... 或者 s...
2018-10-22
【进阶2-1期】深入浅出图解作用域链和闭包
(关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周开始前端进阶的第二期,本周的主题是作用域闭包,今天是第6天。 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进...
2018-11-29
深入浅出,JS原型链的工作原理
前言:原型链,即原型链条。它是由原型、原型的原型、原型的原型的原型…这一规则组合成的,经常被应用于继承。 原型的作用 在JS中,每个对象都有自己的原型。当我们访问对象的属性和方法时,JS会先访问对象本身的方法和属性。如果对象本身不包含这些...
2018-12-04
深入浅出说强制类型转换
原文链接, 阅读时长: 10’ 引子 强制类型转换是JavaScript开发人员最头疼的问题之一, 它常被诟病为语言设计上的一个缺陷, 太危险, 应该束之高阁. 作为开发人员, 往往会遇到或写过涉及到类型转换的代码, 只是我们从来没有意...
2018-12-11
回到顶部