Webpack 中的sideEffects 到底該怎麼用?
webpack v4開始新增了一個sideEffects
特性,通過給package.json
加入sideEffects
聲明該包/模塊是否包含sideEffects(副作用),從而可以為tree-shaking提供更大的優化空間。
先看張圖感受一下:

注:v4 beta版時叫pure module
,後來改成了sideEffects
基於我們對side effect的常規理解,我們可以認為,只要我們確定當前包裡的模塊不包含副作用,然後將發佈到npm裡的包標註為sideEffects: false
,我們就能為使用方提供更好的打包體驗。原理是webpack能將標記為side-effects-free的包由import {a} from xx
轉換為import {a} from 'xx/a'
,從而自動修剪掉不必要的import,作用同babel-plugin-import 。
於是很愉快的我給我的幾個庫都加上了這個配置(確定都不含副作用)。
直到我幾個月前看到@Sean Larkin給vue提交了這樣一個pr: chore(package.json): Add sideEffects: false field in package.json ,當時我就有點疑惑,依我對vue的了解,代碼裡的副作用挺多啊,比如很多函數都有對Vue.prototype
的引用甚至修改,應該不能設置sideEffects: false
才對啊。然而事實是我被打臉了,因為尤大很快的合併了這個pr。 。這直接導致我不敢給mobx加上這個配置,因為已經完全不明白webpack的這個sideEffects指的是什麼了。 。
直到前兩天有人給mobx-utils提了issue說可以加上這個配置幫助tree shaking,疑惑中我想起了vue的那個pr又翻出來看了一下,發現在pr下已經有人跟我提了一樣的疑問:
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. (eg Object.defineProperty)
I hope you can help me understand how this works.
Sean 原來的pr 裡是這樣寫的:
This PR adds the
"sideEffects": false
property in vue'spackage.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 上的回答來解釋webpack 裡的這個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' 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
吧!