webpack二刷之五、生產環境優化(3.sideEffects 副作用)

sideEffects 副作用

webpack4新增的功能。

允許通過配置的方式去標識代碼是否有副作用,從而爲 Tree Shaking 提供更多的壓縮空間。

sideEffects 一般用於開發npm模塊時,標記是否有副作用。

官方文檔中講它和 Tree Shaking 放在一起講,所以容易誤解爲它們是因果關係,實際上二者沒什麼關係。

副作用

副作用:模塊執行時除了導出成員之外所作的事情。

例如一個模塊中定義了其他內容:

export default () => {
  console.log('本模塊不只導出一個默認成員,還做了一些其他事情')
}
// 以下是副作用代碼
console.log('模塊被使用')
window.foo = '往全局添加一個變量'

webpack 的 Tree Shaking 是 通過 usedExports 標記未使用的成員,打包時不生成導出它們的代碼,從而通過minimize 移除它們。

所以 Tree Shaking 通過 usedExports 搖掉的是模塊自己的代碼中不被需要的部分。

但是導入這個模塊的文件中,還保留了import代碼,例如Tree Shaking示例打包後包裹/src/index.js的函數中會保留「導入模塊行爲」的代碼。

// 打包結果中包裹 /src/index.js 的函數
(function(module, __webpack_exports__, __webpack_require__) {

  "use strict";
  __webpack_require__.r(__webpack_exports__);
  // 保留了「導入模塊行爲」的代碼
  /* harmony import */ var _component__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);

  document.body.append(Object(_component__WEBPACK_IMPORTED_MODULE_0__[/* Button */ "a"])());

})

因爲package.json中的sideEffects字段,默認爲true,即默認這個npm包有副作用(其下模塊們出了導出還做了其他事情)

並且webpack的當前配置下,也未指定是否處理無副作用的代碼。

所以這些import代碼被保留下來,自然,打包後,那些被import調用的模塊,不管裏面的成員是否被使用或者導出的內容是否爲空,都會生成一個包裹它們的函數。

而這些函數,作爲打包文件中,執行webpack方法的參數(一個數組)中的元素,自然不會被minimize刪除。


例如將上面的/src/component.js中的成員分別單獨創建一個文件(即「模塊A」們),並在一個index.js(即「模塊B」)文件中全部導入。

使用時直接導入這個index.js

// /src/components/button.js ---------------
export default () => {
  return document.createElement('button')
}

// /src/components/link.js ---------------
export default () => {
  return document.createElement('a')
}

// 添加一個副作用代碼
document.write('This is a side effect code')

// /src/components/heading.js ---------------
export default level => {
  return document.createElement('h' + level)
}

// src/components/index.js ---------------
export { default as Button } from './button'
export { default as Link } from './link'
export { default as Heading } from './heading'

// src/index.js ---------------
import { Button } from './components'

document.body.append(Button())

沿用之前的示例,關閉babel的modules配置,optmization只啓用usedExports。

// webpack.config.js
module.exports = {
  mode: 'none',
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [ '@babel/preset-env' ]
          },
        },
      },
    ],
  },
  optimization: {
    usedExports: true,
    // 壓縮代碼
    // minimize: true,
  },
}

打包查看結果,3個模塊中的成員正常被標記已使用和未使用,但是/src/comonents/index.js中保留了3個模塊的「導入」代碼。

即使link和headingm沒有副作用,導入它們已經沒有了意義。

// 包裹/src/components/index.js的函數
(function(module, __webpack_exports__, __webpack_require__) {

  "use strict";
  // 導入button.js
  /* harmony import */ var _button__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
  // 導出button.js
  /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "a", function() { return _button__WEBPACK_IMPORTED_MODULE_0__["a"]; });

  // 雖然沒有用到link和heading中的成員,但還是保留了「導入模塊行爲」的代碼
  /* harmony import */ var _link__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
  /* harmony import */ var _heading__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);

})
webpack 和 package.json 中的 sideEffects

webpack通過配置optimization.sideEffectstrue,表示打包時跳過那些沒有被使用的且被package.json標記爲無副作用的模塊。

表現爲:

  1. webpack打包結果不會保留這些沒有意義的「導入模塊行爲」的代碼。
  2. webpack打包結果不會保留沒有使用(導入)的模塊。

開啓後,webpack會去package.json中尋找sideEffects字段。

package.json中的sideEffects字段表示,當前項目中的模塊是否有副作用,默認爲true

webpack找到這個字段,且它定義的如果是false(當前項目中的模塊無副作用)。

那麼這些沒有被用到的模塊就不會被打包。

從而實現,一個模塊中的成員全都沒有被外部使用時,即這個模塊沒有被使用,這個模塊就不會被打包進結果。

// package.json
{
  "sideEffects": false // 默認true,表示當前項目中的模塊是否有副作用
}
// webpack.config.js
module.exports = {
	optimization: {
    sideEffects: true // 默認false,表示是否移除無副作用的模塊
    usedExports: true,
  }  
}

打包結果:

// 打包中函數的參數
[
  /* 0 */,
  /* 1 */,
  /* 2 */,
  /* 3 */
  (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4);

    document.body.append(Object(_components__WEBPACK_IMPORTED_MODULE_0__[/* default */ "a"])());

  }),
  /* 4 */
  (function(module, __webpack_exports__, __webpack_require__) {

    "use strict";
    /* harmony default export */ __webpack_exports__["a"] = (function () {
      return document.createElement('button');
    });

  })
]

對比下不移除無副作用代碼的打包結果:

[
  /* 0 */
  (function(module, __webpack_exports__, __webpack_require__) {

    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);

    document.body.append(Object(_components__WEBPACK_IMPORTED_MODULE_0__[/* Button */ "a"])());

  }),
  /* 1 */
  (function(module, __webpack_exports__, __webpack_require__) {

    "use strict";
    /* harmony import */ var _button__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
    /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "a", function() { return _button__WEBPACK_IMPORTED_MODULE_0__["a"]; });

    /* harmony import */ var _link__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
    /* harmony import */ var _heading__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);

  }),
  /* 2 */
  (function(module, __webpack_exports__, __webpack_require__) {

    "use strict";
    /* harmony default export */ __webpack_exports__["a"] = (function () {
      return document.createElement('button');
    });

  }),
  /* 3 */
  (function(module, __webpack_exports__, __webpack_require__) {

    "use strict";
    /* unused harmony default export */ var _unused_webpack_default_export = (function () {
      return document.createElement('a');
    });
		document.write('This is a side effect code');

  }),
  /* 4 */
  (function(module, __webpack_exports__, __webpack_require__) {

    "use strict";
    /* unused harmony default export */ var _unused_webpack_default_export = (function (level) {
      return document.createElement('h' + level);
    });

  })
]

注意:

package.json和webpack配置文件中的sideEffects雖然同名,但表示的意義不同。

  • package.json的sideEffects:標識當前package.json所影響的項目,當中所有的代碼是否有副作用
    • 默認true,表示當前項目中的代碼有副作用
  • webpack配置文件中的sideEffects:開啓功能,是否移除無副作用的代碼
    • 默認false,表示不移除無副作用的模塊
    • 在production模式下自動開啓。

webpack不會識別代碼是否有副作用,只會讀取package.json的sideEffects字段。

二者需要配合使用,才能處理無副作用的模塊。

sideEffects 使用注意

使用webpack sideEffects功能的前提是,確保代碼沒有副作用。

否則webpack打包時就會誤刪那些有副作用的代碼。

例如導入一個模塊,它的內容是立即執行的,而不是通過導出成員等待被調用。

// /src/extend.js
window.onload = () => {
  alert('This is a side effect code')
}

// /src/index.js
import './extend'

當使用sideEffects功能後,這個模塊就不會被打包。

這樣的場景通常是直接加載一個模塊中的內容:

  • 加載立即執行的腳本
  • 加載 css 模塊

解決辦法:

  • 在package.json中關閉標識無副作用
  • 或者具體標識項目中哪些文件有副作用,webpack就不會忽略這些有副作用的模塊
    • 這種方式,未被標識的模塊,就會被當作無副作用處理
// package.json
{
  "sideEffects": [
    "./src/extend.js", // 標識有副作用的文件
    "*.css", // 也可以使用路徑通配符
    "style/" // 注意目錄必須帶後面的/
  ]
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章