Webpack 中的 sideEffects 到底該怎麼用?

原文連接vue

webpack v4 開始新增了一個 sideEffects 特性,經過給 package.json 加入 sideEffects: false 聲明該包模塊是否包含 sideEffects(反作用),從而能夠爲 tree-shaking 提供更大的優化空間。react

先看張圖感覺一下:webpack

注:v4 beta 版時叫 pure module, 後來改爲了 sideEffects git

基於咱們對 fp 中的 side effect 的理解,咱們能夠認爲,只要咱們肯定當前包裏的模塊不包含反作用,而後將發佈到 npm 裏的包標註爲 sideEffects: false ,咱們就能爲使用方提供更好的打包體驗。原理是 webpack 能將標記爲 side-effects-free 的包由 import {a} from xx 轉換爲 import {a} from 'xx/a',從而自動修剪掉沒必要要的 import,做用同 babel-plugin-importgithub

因而很愉快的我給個人幾個庫都加上了這個配置(肯定都不含反作用)。web

直到我幾個月前看到 @Sean Larkin 給 vue 提交了這樣一個 pr:chore(package.json): Add sideEffects: false field in package.json, 當時我就有點疑惑,依我對 vue 的瞭解,代碼裏的反作用挺多啊,好比不少函數都有對 Vue.prototype 的引用甚至修改,應該不能設置 sideEffects: false 纔對啊。然而事實是我被打臉了,由於尤大很快的合併了這個 pr。。這直接致使我不敢給 mobx 加上這個配置,由於已經徹底不明白 webpack 的這個 sideEffects 指的是什麼了。。npm

直到前兩天有人給 mobx-utils 提了 issue 說能夠加上這個配置幫助 tree shaking,疑惑中我想起了 vue 的那個 pr 又翻出來看了一遍,發如今 pr 下已經有人跟我提了同樣的疑問:json

Hy Sean!

Could you please specify what you mean by "vue's original source files"?安全

I looked at the index.js file in the src/core folder and to my knowledge there are plenty sideeffects that would be prune away by tree shaking. (e.g Object.defineProperty)babel

I hope you can help me understand how this works.

Sean 原來的 pr 裏是這樣寫的:

This PR adds the "sideEffects": false property in vue's package.json file. This allow's webpack (for those who want to opt-in to requiring vue's original source files (instead of the flattened esm bundles) and want to remove flow type through a babel-transform, then this will allow webpack to aggressively ignore and treeshake unused exports throughout the module system.

Sean 的意思是當你按需引入 vue 的源碼文件而不是打包的 bundle 時,webpack 能幫助你作更好的 tree shaking。好比你這樣引用 vue 中的模塊:import Vue from 'vue/src/core'

而後 Sean 就說此反作用非彼反作用(fp 中的),而後給了一個他在 stackoverflow 上的回答來解釋 sideEffects,中心思想是:

whenever a module  reexports all exports (regardless if used or unused) need to be evaluated and executed in the case that one of those exports created a side-effect with another.

每當一個模塊重導出了全部導出(不管是否會被用) 須要被計算和執行時,其中一個導出就對其餘的導出產生了反作用。

老實講仍是沒懂。。有興趣的看原答案:what-does-webpack-4-expect-from-a-package-with-sideeffects-false

翻完 官方文檔官方 example,只是瞭解到有了 sideEffects 後 bundle 的變化,依然沒法解釋 webpack sideEffects 跟 fp 中的 sideEffect 有什麼區別,進而也沒法解釋爲何 vue 明明不少反作用依然能配置 sideEffects: false ?

毛主席教導咱們:自力更生,豐衣足食。

Tree Shaking 與反作用

Tree Shaking 的背景就不介紹了想必不少人都瞭解,webpack 的 tree shaking 的做用是能夠將未被使用的 exported member 標記爲 unused 同時在將其 re-export 的模塊中再也不 export。提及來很拗口,看代碼:

// a.js
export function a() {}
// b.js
export function b(){}
// package/index.js
import a from './a'
import b from './b'
export { a, b }
// app.js
import {a} from 'package'
console.log(a)

當咱們已 app.js 爲 entry 時,通過搖樹後的代碼會變成這樣:

// a.js
export function a() {}
// b.js 再也不導出 function b(){}
function b() {}
// package/index.js 再也不導出 b 模塊
import a from './a'
import b from './b'
export { a }
// app.js
import {a} from 'package'
console.log(a)

配合 webpack 的 scope hoisting 和 uglify 以後,b 模塊的痕跡會被徹底抹殺掉。

可是若是 b 模塊中添加了一些反作用,好比一個簡單的 log:

// b.js
export function b(v) { reutrn v }
console.log(b(1))

webpack 以後會發現 b 模塊內容變成了:

// b.js
console.log(function (v){return v}(1))

雖然 b 模塊的導出是被忽略了,可是反作用代碼被保留下來了。因爲目前 transformer 轉換後可能引入的各類奇怪操做引起的反作用(參考:你的Tree-Shaking並沒什麼卵用),不少時候咱們會發現就算有了 tree shaking 咱們的 bundle size 仍是沒有明顯的減少。而一般咱們指望的是 b 模塊既然不被使用了,其中全部的代碼應該不被引入纔對。

這個時候 sideEffects 的做用就顯現出來了:若是咱們引入的 包/模塊 被標記爲 sideEffects: false 了,那麼無論它是否真的有反作用,只要它沒有被引用到,整個 模塊/包 都會被完整的移除。以 mobx-react-devtool 爲例,咱們一般這樣去用:

import DevTools from 'mobx-react-devtools';

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

這是一個很常見的按需導入場景,然而在沒有 sideEffects: false 配置時,即使 NODE_ENV 設爲 production ,打包後的代碼裏依然會包含 mobx-react-devtools 包,雖然咱們沒使用過其導出成員,可是 mobx-react-devtools 仍是會被 import,由於裏面「可能」會有反作用。但當咱們加上 sideEffects false 以後,tree shaking 就能安全的把它從 bundle 裏完整的移除掉了。

sideEffects 的使用場景

上面也說到,一般咱們發佈到 npm 上的包很難保證其是否包含反作用(多是代碼的鍋多是 transformer 的鍋),可是咱們基本能確保這個包是否會對包之外的對象產生影響,好比是否修改了 window 上的屬性,是否複寫了原生對象方法等。若是咱們能保證這一點,其實咱們就能知道整個包是否能設置 sideEffects: false了,至因而不是真的有反作用則並不重要,這對於 webpack 而言都是能夠接受的。這也就能解釋爲何能給 vue 這個自己充滿反作用的包加上 sideEffects: false 了。

因此其實 webpack 裏的 sideEffects: false 的意思並非我這個模塊真的沒有反作用,而只是爲了在搖樹時告訴 webpack:我這個包在設計的時候就是指望沒有反作用的,即便他打完包後是有反作用的,webpack 同窗你搖樹時放心的當成無反作用包搖就好啦!

也就是說,只要你的包不是用來作 polyfill 或 shim 之類的事情,就儘管放心的給他加上 sideEffects: false 吧!

相關文章
相關標籤/搜索