高性能 Webpack3 生產(chǎn)環(huán)境配置

原文:
High-performance webpack config for front-end delivery
webpack3 指南

高性能 Webpack 配置

Scope Hoisting (作用域提升)

官方文檔:ModuleConcatenationPlugin

過去 webpack 打包時的一個取舍是將 bundle 中各個模塊單獨打包成閉包委可。這些打包函數(shù)使你的 JavaScript 在瀏覽器中處理的更慢撬码。相比之下粉怕,一些工具像 Closure Compiler 和 RollupJS 可以提升(hoist)或者預(yù)編譯所有模塊到一個閉包中攒钳,提升你的代碼在瀏覽器中的執(zhí)行速度架曹。

在生產(chǎn)環(huán)境中配置:

const webpack = require('webpack');

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

下面放一張用戶使用之后包體的對比,大概減少了50%,對于模塊數(shù)量很多的項目來說提升較大。

Minification and Uglification (壓縮和丑化)

代碼壓縮和“丑化”是生產(chǎn)環(huán)境中必不可少的厌均,然而偶爾的會遺忘,所以在部署到生產(chǎn)環(huán)境之前告唆,首先要做的就是檢查代碼是否經(jīng)過壓縮和“丑化”

錯誤的方式

直接運行 webpack 命令進(jìn)行打包棺弊,查看包體積

正確的方式

只需要在 webpack 命令后面加上 -p 參數(shù)!

通過對比可以發(fā)現(xiàn)擒悬,減少了整整 60% 的體積模她!沒有壓縮前,充斥著空格懂牧、換行侈净、注釋尊勿!

?? -p 參數(shù)不會設(shè)置 node 環(huán)境變量為生產(chǎn)環(huán)境 production,當(dāng)你需要在生產(chǎn)環(huán)境中執(zhí)行時畜侦,可使用該命令行:NODE_ENV=production PLATFORM=web webpack -p

?? 為了快速打包元扔,可以將參數(shù)添加到 package.json 中:

"scripts": {
  "build": "webpack -p"
},

高級壓縮方式

使用 UglifyjsWebpackPlugin 插件
安裝

npm i -D uglifyjs-webpack-plugin

用法

webpack.config.js

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
  plugins: [
    new UglifyJsPlugin()
  ]
}

默認(rèn)的配置已經(jīng)很好的滿足大部分的項目需求,但如何你想更進(jìn)一步壓縮旋膳,減少一部分不必要的代碼澎语,可以使用 Webpack 版本 > 3.0:

plugins:[
  new webpack.optimize.UglifyJsPlugin({/* options here */}),
],

Dynamic Imports for Lazy-loaded Modules (動態(tài)引入和懶加載)

生產(chǎn)環(huán)境下的使用

使用動態(tài)引入的項目,編譯時:

可以看到之前整個的 bundle.js 文件被拆分了多個验懊,在 index.html 中只引入了 index.bundle.js 作為入口擅羞,按需加載其他被拆分的js文件

安裝配置

安裝 Babel

yarn add babel-loader babel-core babel-preset-env

配置 webpack.config.js,允許 Babel 處理你的 js 文件

module: {
  rules: [
    {
      test: /\.js$/,
      use: 'babel-loader',
      exclude: /node_modules/,
    },
  ],
},

完成以上設(shè)置后义图,可以安裝處理動態(tài)引入的插件了

yarn add babel-plugin-syntax-dynamic-import

創(chuàng)建或修改 .babelrc 在項目的根目錄

{
  "presets": ["env"],
  "plugins": ["syntax-dynamic-import", "transform-react-jsx"]
}

改造代碼

將需要被改造成懶加載的模塊做簡單的代替:

import Home from './components/Home';

with

const Home = import('./components/Home');

最終我們的 index.js 入口文件被改造成了這樣减俏,這種 import 的方式只是一種語法糖,在一些框架中如 vue 已經(jīng)得到支持

import React from 'react';
import Async from 'react-code-splitting';

const Nav = () => (<Async load={import('./components/Nav')} />);
const Home = () => (<Async load={import('./views/home')} />);
const Countdown = () => (<Async load={import('./views/countdown')} />);

webpack.config.js

配置文件的出口和出口

  entry: {
    index: './index.js',
  },
  
    output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js',
    publicPath: '/',
  },

Deterministic Hashes for Caching(對緩存使用確定的Hash值)

webpack 在默認(rèn)的情況下不會自動添加 hash碱工,例如 app.8087f8d9fed812132141.js娃承,這意味著,數(shù)據(jù)保存在緩存?zhèn)€中怕篷,當(dāng)用戶刷新時不會得到更新历筝。

webpack 快速添加 hashes:

output: {
  filename: '[name].[hash].js',
},

?? 注意這里有一個陷阱,每一次的構(gòu)建匙头,不管文件有沒有更改都會重新聲場新的 hash 值漫谷,例如在使用 webpack -p 時,即使用戶已經(jīng)已經(jīng)下載相關(guān)文件蹂析,那么講不得不刷新舔示!

?? 使用哈希值構(gòu)建,將會降低編譯的速度电抚,請在生產(chǎn)環(huán)境中使用惕稻!

Deterministic Hashes 配置

??這里我們需要解決的一個問題是,當(dāng)內(nèi)容改變到時候用戶才會刷新緩存蝙叛,如果沒有改變俺祠,則不會去刷新緩存。

插件安裝:

yarn add chunk-manifest-webpack-plugin webpack-chunk-hash

chunk-manifest-webpack-plugin: 允許導(dǎo)出一個json文件借帘,將id映射到其中蜘渣,webpack 會讀取該json,以確定需要刷新緩存的模塊
webpack-chunk-hash: 使用自定義的(md5)代替標(biāo)準(zhǔn)的webpack 生成的 hash

修改 webpack.config.js 肺然,用于生產(chǎn)環(huán)境 :

const webpack = require('webpack');
const ChunkManifestPlugin = require('chunk-manifest-webpack-plugin');
const WebpackChunkHash = require('webpack-chunk-hash');
const HtmlWebpackPlugin = require('html-webpack-plugin');

/* Shared Dev & Production */

const config = {
  /* … our webpack config up until now */

  plugins: [
    // /* other plugins here */
    // 
    // /* Uncomment to enable automatic HTML generation */
    // new HtmlWebpackPlugin({
    //   inlineManifestWebpackName: 'webpackManifest',
    //   template: require('html-webpack-template'),
    // }),
  ],
};

/* Production 指定了生產(chǎn)環(huán)境中使用 */
if (process.env.NODE_ENV === 'production') {
  config.output.filename = '[name].[chunkhash].js';
  config.plugins = [
    ...config.plugins, // ES6 array destructuring, available in Node 5+
    new webpack.HashedModuleIdsPlugin(),
    new WebpackChunkHash(),
    new ChunkManifestPlugin({
      filename: 'chunk-manifest.json',
      manifestVariable: 'webpackManifest',
      inlineManifest: true,
    }),
  ];
}

module.exports = config;

?? Tip
對于上面的例子蔫缸,當(dāng)你使用 yarn add html-webpack-plugin html-webpack-template 模板時,會自動的添加注釋际起,如果沒有使用 webpack 的 HTML 模板拾碌,則需要手動的引入吐葱,引入方式為:

<head>
  <script>
    //<![CDATA[
    window.webpackManifest = { /* contents of chunk-manifest.json */ };
    //]]>
  </script>
</head>

同樣的 manifest.js 也需要引入,當(dāng)你設(shè)置好了這兩個文件校翔,那么一切準(zhǔn)備就緒弟跑。

CommonsChunkPlugin for Vendor Caching

CommonsChunkPluginCommonsChunkPlugin 插件,是一個可選的用于建立一個獨立文件(又稱作 chunk)的功能防症,這個文件包括多個入口 chunk 的公共模塊孟辑。通過將公共模塊拆出來,最終合成的文件能夠在最開始的時候加載一次告希,便存起來到緩存中供后續(xù)使用扑浸。這個帶來速度上的提升烧给,因為瀏覽器會迅速將公共的代碼從緩存中取出來燕偶,而不是每次訪問一個新頁面時,再去加載一個更大的文件础嫡。

一行代碼的簡單配置:

module.exports = {
  entry: {
    app: './app.js',
    vendor: ['react', 'react-dom', 'react-router'],
  },
};

然后運行 webpack -p

但是這種配置有個問題指么,這里的 'react', 'react-dom', 'react-router', 會同時打包到 index.bundle.jsvendor.bundle.js

要解決上述問題榴鼎,我們需要使用 CommonsChunkPlugin 插件伯诬,將文件分離出來,在 webpack.config.js 中的配置

const webpack = require('webpack');

plugins: [
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',
  }),
],

?? 需要注意的是巫财,插件中的name 要是 入口的名字匹配

配置完成之后盗似,重新打包,發(fā)現(xiàn)包體被拆分了平项!

Offline Plugin for webpack(PWA)

使用offline-plugin搭配webpack輕松實現(xiàn)PWA

此插件用于實現(xiàn)PWA赫舒,新技術(shù),是否需要使用和探索闽瓢,這里記錄配置的方法接癌,這并不復(fù)雜

安裝:

yarn add offline-plugin

添加到r= webpack config:

const OfflinePlugin = require('offline-plugin');

module.exports = {
  entry: {
    // Adding to vendor recommended, but optional
    vendor: ['offline-plugin/runtime', /* … */],
  },
  plugins: [
    new OfflinePlugin({
      AppCache: false,
      ServiceWorker: { events: true },
    }),
  ],
};

然后,在 app 中的入口文件扣讼,在開始渲染之前使用:

/* index.js */
if (process.env.NODE_ENV === 'production') {
  const runtime = require('offline-plugin/runtime');

  runtime.install({
    onUpdateReady() {
      runtime.applyUpdate();
    },
    onUpdated() {
      window.location.reload();
    },
  });
}

webpack Bundle Analyzer(包分析工具)

yarn add --dev webpack-bundle-analyzer

添加到開發(fā)環(huán)境

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

config = { /* shared webpack config */ };

if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
  config.plugins = [
    ...config.plugins,
    new BundleAnalyzerPlugin(),
  ];
}

運行并監(jiān)聽 8888 端口

node_module/.bin/webpack --profile --json > stats.json

Multi-entry Automatic CommonsChunk Plugin(多頁面應(yīng)用程序)

應(yīng)用場景不多缺猛,只記錄下來。

webpack.config.js

const config = {
  entry: {
    pageOne: './src/pageOne/index.js',
    pageTwo: './src/pageTwo/index.js',
    pageThree: './src/pageThree/index.js'
  }
};

這是什么椭符?我們告訴 webpack 需要 3 個獨立分離的依賴圖荔燎。

為什么?在多頁應(yīng)用中销钝,(譯注:每當(dāng)頁面跳轉(zhuǎn)時)服務(wù)器將為你獲取一個新的 HTML 文檔有咨。頁面重新加載新文檔,并且資源被重新下載曙搬。然而摔吏,這給了我們特殊的機(jī)會去做很多事:

  • 使用 CommonsChunkPlugin 為每個頁面間的應(yīng)用程序共享代碼創(chuàng)建 bundle鸽嫂。由于入口起點增多,多頁應(yīng)用能夠復(fù)用入口起點之間的大量代碼/模塊征讲,從而可以極大地從這些技術(shù)中受益据某。

We can update CommonsChunk to just figure things out automatically:

/* Dev & Production */
new webpack.optimize.CommonsChunkPlugin({
  name: 'commons',
  minChunks: 2,
}),
/* Production */
new webpack.optimize.CommonsChunkPlugin({
  name: 'manifest',
  minChunks: Infinity,
}),
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市诗箍,隨后出現(xiàn)的幾起案子癣籽,更是在濱河造成了極大的恐慌,老刑警劉巖滤祖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筷狼,死亡現(xiàn)場離奇詭異,居然都是意外死亡匠童,警方通過查閱死者的電腦和手機(jī)埂材,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汤求,“玉大人俏险,你說我怎么就攤上這事⊙镄鳎” “怎么了竖独?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長挤牛。 經(jīng)常有香客問我莹痢,道長,這世上最難降的妖魔是什么墓赴? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任竞膳,我火速辦了婚禮,結(jié)果婚禮上竣蹦,老公的妹妹穿的比我還像新娘顶猜。我一直安慰自己,他們只是感情好痘括,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布长窄。 她就那樣靜靜地躺著,像睡著了一般纲菌。 火紅的嫁衣襯著肌膚如雪挠日。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天翰舌,我揣著相機(jī)與錄音嚣潜,去河邊找鬼。 笑死椅贱,一個胖子當(dāng)著我的面吹牛懂算,可吹牛的內(nèi)容都是我干的只冻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼计技,長吁一口氣:“原來是場噩夢啊……” “哼喜德!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起垮媒,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤舍悯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后睡雇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萌衬,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年它抱,在試婚紗的時候發(fā)現(xiàn)自己被綠了秕豫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡抗愁,死狀恐怖馁蒂,靈堂內(nèi)的尸體忽然破棺而出呵晚,到底是詐尸還是另有隱情蜘腌,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布饵隙,位于F島的核電站撮珠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏金矛。R本人自食惡果不足惜芯急,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望驶俊。 院中可真熱鬧娶耍,春花似錦、人聲如沸饼酿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽故俐。三九已至想鹰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間药版,已是汗流浹背辑舷。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留槽片,地道東北人何缓。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓肢础,卻偏偏與公主長得像,于是被迫代替她去往敵國和親碌廓。 傳聞我的和親對象是個殘疾皇子乔妈,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355