縮小前端大小

如何使用 Webpack 調整應用程式的大小

最佳化應用程式的過程中,首要之務是盡量縮小 以下說明如何使用 webpack。

使用正式版模式 (僅限 Webpack 4)

Webpack 4 推出了新的 mode 標記。您可以 這個標記給 'development''production',以提示正在建構的 webpack 鎖定特定環境的應用程式:

// webpack.config.js
module.exports = {
  mode: 'production',
};

建構正式版應用程式時,請務必啟用 production 模式。 這會讓 Webpack 套用最佳化功能,例如壓縮、移除開發專用的程式碼 程式庫或其他方法

延伸閱讀

啟用壓縮功能

壓縮是指透過移除多餘的空格、縮短變數名稱以及 依此類推如下所示:

// Original code
function map(array, iteratee) {
  let index = -1;
  const length = array == null ? 0 : array.length;
  const result = new Array(length);

  while (++index < length) {
    result[index] = iteratee(array[index], index, array);
  }
  return result;
}

↓。

// Minified code
function map(n,r){let t=-1;for(const a=null==n?0:n.length,l=Array(a);++t<a;)l[t]=r(n[t],t,n);return l}

Webpack 支援兩種壓縮程式碼的方式:軟體包層級壓縮載入器相關選項。請同時使用。

套件層級壓縮

套件層級壓縮作業會在編譯後壓縮整個套件。運作方式如下:

  1. 編寫程式碼的方式如下:

    // comments.js
    import './comments.css';
    export function render(data, target) {
      console.log('Rendered!');
    }
    
  2. Webpack 的編譯作業大致如下:

    // bundle.js (part of)
    "use strict";
    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
    /* harmony export (immutable) */ __webpack_exports__["render"] = render;
    /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css__ = __webpack_require__(1);
    /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css_js___default =
    __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__comments_css__);
    
    function render(data, target) {
    console.log('Rendered!');
    }
    
  3. 壓縮器會將壓縮器壓縮成下列約略內容:

    // minified bundle.js (part of)
    "use strict";function t(e,n){console.log("Rendered!")}
    Object.defineProperty(n,"__esModule",{value:!0}),n.render=t;var o=r(1);r.n(o)
    

在 webpack 4 中,系統會自動啟用套件層級的壓縮功能,兩者到 第一個模式。該 API 使用 UglifyJS 壓縮工具 建立完全不同的部署方式(如果需要停用壓縮功能,只要使用開發模式即可 或將 false 傳遞至 optimization.minimize 選項)。

在 webpack 3 中,您需要使用 UglifyJS 外掛程式 這個外掛程式隨附 webpack;如要啟用,請將其新增至 plugins 部分:

// webpack.config.js
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
  ],
};
敬上

載入器相關選項

另一種壓縮程式碼的方法是載入程式專用選項 (什麼是載入器) )。使用載入器選項時 壓縮工具無法壓縮舉例來說,當您使用 css-loader,檔案會編譯為字串:

/* comments.css */
.comment {
  color: black;
}
// minified bundle.js (part of)
exports=module.exports=__webpack_require__(1)(),
exports.push([module.i,".comment {\r\n  color: black;\r\n}",""]);

這個代碼是字串,因此壓縮器無法壓縮。如要壓縮檔案內容,必須 設定載入器來執行這項操作:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          { loader: 'css-loader', options: { minimize: true } },
        ],
      },
    ],
  },
};

延伸閱讀

指定NODE_ENV=production

另一種縮減前端大小的方法,是設定 NODE_ENV 環境變數 改為 production 這個值。

程式庫會讀取 NODE_ENV 變數,偵測應在哪種模式下運作。 或實際工作環境的映像檔部分程式庫的行為會因這個變數而有不同行為。適用對象 例如,如果 NODE_ENV 未設為 production,Vue.js 會執行額外的檢查和列印作業 警告:

// vue/dist/vue.runtime.esm.js
// …
if (process.env.NODE_ENV !== 'production') {
  warn('props must be strings when using array syntax.');
}
// …

React 的運作方式類似,它會載入含有警告的開發版本:

// react/index.js
if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

// react/cjs/react.development.js
// …
warning$3(
    componentClass.getDefaultProps.isReactClassApproved,
    'getDefaultProps is only used on classic React.createClass ' +
    'definitions. Use a static property named `defaultProps` instead.'
);
// …

在實際工作環境中,這類檢查和警告通常不需要進行,但仍會保留在程式碼中。 增加程式庫大小在 webpack 4 中,請將其移除 optimization.nodeEnv: 'production' 選項:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    nodeEnv: 'production',
    minimize: true,
  },
};

在 webpack 3 中,請改用 DefinePlugin

// webpack.config.js (for webpack 3)
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': '"production"'
    }),
    new webpack.optimize.UglifyJsPlugin()
  ]
};

optimization.nodeEnv 選項和 DefinePlugin 的運作方式都相同: 這些物件會以指定值取代所有出現的 process.env.NODE_ENV。使用 設定:

  1. Webpack 會將所有出現的「process.env.NODE_ENV」替換為 "production":

    // vue/dist/vue.runtime.esm.js
    if (typeof val === 'string') {
      name = camelize(val);
      res[name] = { type: null };
    } else if (process.env.NODE_ENV !== 'production') {
      warn('props must be strings when using array syntax.');
    }
    

    ↓。

    // vue/dist/vue.runtime.esm.js
    if (typeof val === 'string') {
      name = camelize(val);
      res[name] = { type: null };
    } else if ("production" !== 'production') {
      warn('props must be strings when using array syntax.');
    }
    
  2. 然後縮小工具會移除所有此類資訊 if 分支版本 – 由於 "production" !== 'production' 一律為 false, 外掛程式瞭解這些分支版本中的程式碼永遠不會執行:

    // vue/dist/vue.runtime.esm.js
    if (typeof val === 'string') {
      name = camelize(val);
      res[name] = { type: null };
    } else if ("production" !== 'production') {
      warn('props must be strings when using array syntax.');
    }
    

    ↓。

    // vue/dist/vue.runtime.esm.js (without minification)
    if (typeof val === 'string') {
      name = camelize(val);
      res[name] = { type: null };
    }
    

延伸閱讀

使用 ES 模組

另一個縮減前端大小的方法是使用 ES 模組

使用 ES 模組時,Webpack 能夠進行樹軸作業。搖晃的樹幹 掃遍整個依附性樹狀結構、檢查使用的依附元件,並移除未使用的依附元件。所以 如果您使用 ES 模組語法,Webpack 就可以刪除未使用的程式碼:

  1. 您可以編寫含有多項匯出內容的檔案,但應用程式只會使用其中一個匯出項目:

    // comments.js
    export const render = () => { return 'Rendered!'; };
    export const commentRestEndpoint = '/rest/comments';
    
    // index.js
    import { render } from './comments.js';
    render();
    
  2. Webpack 瞭解 commentRestEndpoint 並未使用,且不會產生 套件中的獨立匯出點:

    // bundle.js (part that corresponds to comments.js)
    (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    const render = () => { return 'Rendered!'; };
    /* harmony export (immutable) */ __webpack_exports__["a"] = render;
    
    const commentRestEndpoint = '/rest/comments';
    /* unused harmony export commentRestEndpoint */
    })
    
  3. 減號會移除未使用的變數:

    // bundle.js (part that corresponds to comments.js)
    (function(n,e){"use strict";var r=function(){return"Rendered!"};e.b=r})
    

這項功能也適用於以 ES 模組編寫的程式庫。

不過,你不需要使用精確的 webpack 的內建壓縮工具 (UglifyJsPlugin)。 任何支援無效程式碼移除功能的壓縮器 (例如 Babel Minify 外掛程式)。 或 Google Closure Compiler 外掛程式) 才能一探究竟

延伸閱讀

最佳化圖片

圖片來源: 網頁的一半。雖然 就跟 JavaScript 一樣重要 (例如,它們沒有阻擋轉譯),但仍佔了很大一部分 頻寬。使用url-loadersvg-url-loaderimage-webpack-loader,在以下位置最佳化這些廣告活動: webpack。

url-loader 會將小型靜態檔案內嵌到 應用程式。如果沒有設定,這個檔案會接收傳遞的檔案,將其放在已編譯的套件旁,並傳回 該檔案的網址。然而,如果指定 limit 選項,則編碼的檔案不會小於 設為 Base64 資料網址,然後傳回此網址。這個 將圖片內嵌至 JavaScript 程式碼,並儲存 HTTP 要求:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif)$/,
        loader: 'url-loader',
        options: {
          // Inline files smaller than 10 kB (10240 bytes)
          limit: 10 * 1024,
        },
      },
    ],
  }
};
// index.js
import imageUrl from './image.png';
// → If image.png is smaller than 10 kB, `imageUrl` will include
// the encoded image: 'data:image/png;base64,iVBORw0KGg…'
// → If image.png is larger than 10 kB, the loader will create a new file,
// and `imageUrl` will include its url: `/2fcd56a1920be.png`
敬上

svg-url-loader 的運作方式和 url-loader 相同: ,可使用 URL 編碼檔案 編碼,而非 Base64 第一。這個做法適合用於可擴充向量圖形的圖片,因為 SVG 檔案只是純文字, 而且效能更佳

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        loader: "svg-url-loader",
        options: {
          limit: 10 * 1024,
          noquotes: true
        }
      }
    ]
  }
};
敬上

image-webpack-loader 會壓縮掃描的圖片 移除。這個程式庫支援 JPG、PNG、GIF 和 SVG 圖片,因此我們要針對這所有類型使用圖片。

此載入器無法將圖片嵌入應用程式,因此必須與 url-loadersvg-url-loader。為避免將文字複製到兩個規則中 (一個用於 JPG/PNG/GIF 圖片, 一個用於可擴充向量圖形的載入器),使用 enforce: 'pre' 將此載入程式新增為個別規則:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        loader: 'image-webpack-loader',
        // This will apply the loader before the other ones
        enforce: 'pre'
      }
    ]
  }
};

載入器的預設設定已就緒,但如果您想進行設定 詳情請參閱外掛程式選項。目的地: 選擇要指定的選項,請查看 Addy Osmani 製作的優質圖片指南 最佳化

延伸閱讀

最佳化依附元件

平均超過一半的 JavaScript 大小來自依附元件,而一部分大小可能會 不必要的事件。

舉例來說,Lodash (自 4.17.4 版起) 會在套件中加入 72 KB 的壓縮程式碼。但若只使用 例如 20 種方法,那麼大約 65 KB 壓縮的程式碼就沒有任何作用。

另一個例子是 Moment.js2.19.1 版需要 223 KB 的壓縮程式碼,非常龐大 網頁的 JavaScript 平均大小為 10 月 452 KB 2017 年。但該大小的 170 KB 是本地化 檔案。如果 您未搭配多種語言使用 Moment.js,這些檔案就會佔用套裝組合, 發展路徑可能包括 縮小技術提議用途的範圍

這些依附元件都可以輕鬆最佳化。我們已收集多種最佳化做法, GitHub 存放區 – 一探究竟

為 ES 模組啟用模組串連 (也就是範圍提升)

當您建構套件時,Webpack 會將每個模組納入一個函式中:

// index.js
import {render} from './comments.js';
render();

// comments.js
export function render(data, target) {
  console.log('Rendered!');
}

↓。

// bundle.js (part  of)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
  "use strict";
  Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
  var __WEBPACK_IMPORTED_MODULE_0__comments_js__ = __webpack_require__(1);
  Object(__WEBPACK_IMPORTED_MODULE_0__comments_js__["a" /* render */])();
}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
  "use strict";
  __webpack_exports__["a"] = render;
  function render(data, target) {
    console.log('Rendered!');
  }
})

在過去,這是將 CommonJS/AMD 模組彼此隔離的必要步驟。不過, 每個模組的大小和效能負擔

Webpack 2 引入了 ES 模組的支援,與 CommonJS 和 AMD 模組不同 每個物件都能使用函式進行包裝Webpack 3 讓產品整合化, 模組串連。歡迎參考 串連模組的作用:

// index.js
import {render} from './comments.js';
render();

// comments.js
export function render(data, target) {
  console.log('Rendered!');
}

↓。

// Unlike the previous snippet, this bundle has only one module
// which includes the code from both files

// bundle.js (part of; compiled with ModuleConcatenationPlugin)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
  "use strict";
  Object.defineProperty(__webpack_exports__, "__esModule", { value: true });

  // CONCATENATED MODULE: ./comments.js
    function render(data, target) {
    console.log('Rendered!');
  }

  // CONCATENATED MODULE: ./index.js
  render();
})

看到兩者的差異嗎?在純套件中,模組 0 要求模組 1 中的 render。取代為 模組串連,require 僅替換為必要函式,模組 1 則 已移除這個套件包含的模組較少,模組負擔也較少!

如要開啟這項行為,請在 webpack 4 中啟用 optimization.concatenateModules 選項:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    concatenateModules: true
  }
};

在 webpack 3 中使用 ModuleConcatenationPlugin

// webpack.config.js (for webpack 3)
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
};
敬上

延伸閱讀

如果同時有 webpack 和非 Webpack 程式碼,請使用 externals

您可能有一個大型專案,其中某些程式碼是以 Webpack 編譯,但有些程式碼並沒有使用。喜歡 影片代管網站,使用 Webpack 建構播放器小工具,以及相關頁面 可能不是:

影片代管網站的螢幕截圖 (完全隨機的影片代管網站)

如果兩段程式碼有共同的依附元件,就可以共用這些程式碼,避免下載程式碼。 以便重複使用方法是使用Webpack 的「externals」 選項,將模組取代為變數或 然後再匯入其他外部匯入項目

如果 window 中有依附元件

如果您的非 Webpack 程式碼依賴於 window 中做為變數的依附元件,別名 將依附元件名稱對應至變數名稱:

// webpack.config.js
module.exports = {
  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM'
  }
};

利用這項設定,Webpack 不會包含 reactreact-dom 套件。而是 替換成類似下方的內容:

// bundle.js (part of)
(function(module, exports) {
  // A module that exports `window.React`. Without `externals`,
  // this module would include the whole React bundle
  module.exports = React;
}),
(function(module, exports) {
  // A module that exports `window.ReactDOM`. Without `externals`,
  // this module would include the whole ReactDOM bundle
  module.exports = ReactDOM;
})

如果是以 AMD 套件載入依附元件

如果您的非 Webpack 程式碼不會向 window 公開依附元件,問題就比較複雜。 不過,如果非 webpack 程式碼會耗用這些程式碼,仍可避免重複載入相同的程式碼兩次 AMD 套件做為依附元件。

如要這麼做,請將 webpack 程式碼編譯為 AMD 套件,並將別名模組編譯為程式庫網址:

// webpack.config.js
module.exports = {
  output: {
    libraryTarget: 'amd'
  },
  externals: {
    'react': {
      amd: '/libraries/react.min.js'
    },
    'react-dom': {
      amd: '/libraries/react-dom.min.js'
    }
  }
};

Webpack 會將套件包裝成 define(),並依附於下列網址:

// bundle.js (beginning)
define(["/libraries/react.min.js", "/libraries/react-dom.min.js"], function () { … });

如果非 Webpack 程式碼使用相同網址載入依附元件,系統就會載入這些檔案 只會使用一次 - 其他要求將使用載入器快取。

延伸閱讀

總結

  • 如果使用 webpack 4,請啟用正式版模式
  • 使用套件層級壓縮器和載入器選項,盡量減少程式碼
  • NODE_ENV 替換為 production,移除開發專用程式碼
  • 使用 ES 模組啟用樹軸
  • 壓縮圖片
  • 套用特定依附元件的最佳化功能
  • 啟用模組串連功能
  • 如有需要,請使用 externals