webpack2 終極優化

webpack是當下最流行的js打包工具,這得益於網頁應用日益複雜和js模塊化的流行。webpack2增長了一些新特性也正式發佈了一段時間,是時候告訴你們如何用webpack2優化你的構建讓它構建出更小的文件尺寸和更好的開發體驗。css

優化輸出

打包結果更小可讓網頁打開速度更快以及簡約寬帶。能夠經過這如下幾點作到html

壓縮css

css-loader 在webpack2裏默認是沒有開啓壓縮的,最後生成的css文件裏有不少空格和tab,經過配置
css-loader?minimize參數能夠開啓壓縮輸出最小的css。css的壓縮實際是是經過cssnano實現的。node

tree-shaking

tree-shaking 是指藉助es6 import export 語法靜態性的特色來刪掉export可是沒有import過的東西。要讓tree-shaking工做須要注意如下幾點:react

  • 配置babel讓它在編譯轉化es6代碼時不把import export轉換爲cmd的module.export,配置以下:
"presets": [
    [
      "es2015",
      {
        "modules": false
      }
    ]
]
  • 大多數分佈到npm的庫裏的代碼都是es5的,可是也有部分庫(redux,react-router等等)開始支持tree-shaking。這些庫發佈到npm裏的代碼即包含es5的又包含全採用了es6 import export 語法的代碼。

拿redux庫來講,npm下載到的目錄結構以下:webpack

├── es
│   └── utils
├── lib
│   └── utils

其中lib目錄裏是編譯出的es5代碼,es目錄裏是編譯出的採用import export 語法的es5代碼,在redux的package.json文件裏有這兩個配置:git

"main": "lib/index.js",
"jsnext:main": "es/index.js",

這是指這個庫的入口文件的位置,因此要讓webpack去讀取es目錄下的代碼須要使用jsnext:main字段配置的入口,要作到這點webpack須要這樣配置:es6

module.exports = {
    resolve: {
            mainFields: ['jsnext:main','main'],
        }
};

這會讓webpack先使用jsnext:main字段,在沒有時使用main字段。這樣就能夠優化支持tree-shaking的庫。github

優化 UglifyJsPlugin

webpack --optimize-minimize 選項會開啓 UglifyJsPlugin來壓縮輸出的js,可是默認的UglifyJsPlugin配置並無把代碼壓縮到最小輸出的js裏仍是有註釋和空格,須要覆蓋默認的配置:web

new UglifyJsPlugin({
    // 最緊湊的輸出
    beautify: false,
    // 刪除全部的註釋
    comments: false,
    compress: {
      // 在UglifyJs刪除沒有用到的代碼時不輸出警告  
      warnings: false,
      // 刪除全部的 `console` 語句
      // 還能夠兼容ie瀏覽器
      drop_console: true,
      // 內嵌定義了可是隻用到一次的變量
      collapse_vars: true,
      // 提取出出現屢次可是沒有定義成變量去引用的靜態值
      reduce_vars: true,
    }
})

定義環境變量 NODE_ENV=production

不少庫裏(好比react)有部分代碼是這樣的:正則表達式

if(process.env.NODE_ENV !== 'production'){
// 不是生產環境才須要用到的代碼,好比控制檯裏看到的警告    
}

在環境變量 NODE_ENV 等於 production 的時候UglifyJs會認爲if語句裏的是死代碼在壓縮代碼時刪掉。

使用 CommonsChunkPlugin 抽取公共代碼

CommonsChunkPlugin能夠提取出多個代碼塊都依賴的模塊造成一個單獨的模塊。要發揮CommonsChunkPlugin的做用還須要瀏覽器緩存機制的配合。在應用有多個頁面的場景下提取出全部頁面公共的代碼減小單個頁面的代碼,在不一樣頁面之間切換時全部頁面公共的代碼以前被加載過而沒必要從新加載。這個方法能夠很是有效的提高應用性能。

在生產環境按照文件內容md5打hash

webpack編譯在生產環境出來的js、css、圖片、字體這些文件應該放到CDN上,再根據文件內容的md5命名文件,利用緩存機制用戶只須要加載一次,第二次加載時就直接訪問緩存。若是你以後有修改就會爲對應的文件生產新的md5值。作到以上你須要這樣配置:

{
  output: {
    publicPath: CND_URL,
    filename: '[name]_[chunkhash].js',
  },
}

知道以上原理後咱們還能夠進一步優化:利用CommonsChunkPlugin提取出使用頁面都依賴的基礎運行環境。好比對於最多見的react體系你能夠抽出基礎庫react react-dom redux react-redux到一個單獨的文件而不是和其它文件放在一塊兒打包爲一個文件,這樣作的好處是隻要你不升級他們的版本這個文件永遠不會被刷新。若是你把這些基礎庫和業務代碼打包在一個文件裏每次改動業務代碼都會致使瀏覽器重複下載這些包含基礎庫的代碼。以上的配置爲:

// vender.js 文件抽離基礎庫到單獨的一個文件裏防止跟隨業務代碼被刷新
// 全部頁面都依賴的第三方庫
// react基礎
import 'react';
import 'react-dom';
import 'react-redux';
// redux基礎
import 'redux';
import 'redux-thunk';
// webpack配置
{
  entry: {
    vendor: './path/to/vendor.js',
  },
}

DedupePlugin 和 OccurrenceOrderPlugin

在webpack1裏常常會使用 DedupePlugin 插件來消除重複的模塊以及使用 OccurrenceOrderPlugin 插件讓被依賴次數更高的模塊靠前分到更小的id 來達到輸出更少的代碼,在webpack2裏這些已經這兩個插件已經被移除了由於這些功能已經被內置了。

除了壓縮文本代碼外還能夠:

以上優化點只須要在構建用於生產環境代碼的時候才使用,在開發環境時最好關閉由於它們很耗時。

優化開發體驗

優化開發體驗主要從更快的構建和更方便的功能入手。

更快的構建

縮小文件搜索範圍

webpack的resolve.modules配置模塊庫(一般是指node_modules)所在的位置,在js裏出現import 'redux'這樣不是相對也不是絕對路徑的寫法時會去node_modules目錄下找。可是默認的配置會採用向上遞歸搜索的方式去尋找node_modules,但一般項目目錄裏只有一個node_modules在項目根目錄,爲了減小搜索咱們直接寫明node_modules的全路徑:

module.exports = {
    resolve: {
        modules: [path.resolve(__dirname, 'node_modules')]
    }
};

除此以外webpack配置loader時也能夠縮小文件搜索範圍。

  • loader的test正則表達式也應該儘量的簡單,好比在你的項目裏只有.js文件時就不要把test寫成/\.jsx?$/
  • loader使用include命中只須要處理的文件,好比babel-loader的這兩個配置:

只對項目目錄下src目錄裏的代碼進行babel編譯

{
    test: /\.js$/,
    loader: 'babel-loader',
    include: path.resolve(__dirname, 'src')
}

項目目錄下的全部js都會進行babel編譯,包括龐大的node_modules下的js

{
    test: /\.js$/,
    loader: 'babel-loader'
}

開啓 babel-loader 緩存

babel編譯過程很耗時,好在babel-loader提供緩存編譯結果選項,在重啓webpack時不須要創新編譯而是複用緩存結果減小編譯流程。babel-loader緩存機制默認是關閉的,打開的配置以下:

module.exports = {
    module: {
         loaders: [{
                test: /\.js$/,
                loader: 'babel-loader?cacheDirectory',
         }]
  }
};

使用 alias

resolve.alias 配置路徑映射。
發佈到npm的庫大多數都包含兩個目錄,一個是放着cmd模塊化的lib目錄,一個是把全部文件合成一個文件的dist目錄,多數的入口文件是指向lib裏面下的。
默認狀況下webpack會去讀lib目錄下的入口文件再去遞歸加載其它依賴的文件這個過程很耗時,alias配置可讓webpack直接使用dist目錄的總體文件減小文件遞歸解析。配置以下:

module.exports = {
  resolve: {
    alias: {
      'moment': 'moment/min/moment.min.js',
      'react': 'react/dist/react.js',
      'react-dom': 'react-dom/dist/react-dom.js'
    }
  }
};

使用 noParse

module.noParse 配置哪些文件能夠脫離webpack的解析。
有些庫是自成一體不依賴其餘庫的沒有使用模塊化的,好比jquey、momentjs、chart.js,要使用它們必須總體所有引入。
webpack是模塊化打包工具徹底沒有必要去解析這些文件的依賴,由於它們都不依賴其它文件體積也很龐大,要忽略它們配置以下:

module.exports = {
  module: {
    noParse: /node_modules\/(jquey|moment|chart\.js)/
  }
};

除此之外還有不少能夠加速的方法:

更方便的功能

模塊熱替換

模塊熱替換是指在開發的過程當中修改代碼後不用刷新頁面直接把變化的模塊替換到老模塊讓頁面呈現出最新的效果。
webpack-dev-server內置模塊熱替換,配置起來也很方便,下面以react應用爲例,步驟以下:

  • 在啓動webpack-dev-server的時候帶上--hot參數開啓模塊熱替換,在開啓--hot後針對css的變化是會自動熱替換的,可是js涉及到複雜的邏輯還須要進一步配置。
  • 配置頁面入口文件
import App from './app';

function run(){
    render(<App/>,document.getElementById('app'));
}
run();

// 只在開發模式下配置模塊熱替換
if (process.env.NODE_ENV !== 'production') {
  module.hot.accept('./app', run);
}

當./app發生變化或者當./app依賴的文件發生變化時會把./app編譯成一個模塊去替換老的,替換完畢後從新執行run函數渲染出最新的效果。

自動生成html

webpack只作了資源打包的工做還缺乏把這些加載到html裏運行的功能,在龐大的app裏手寫html去加載這些資源是很繁瑣易錯的,咱們須要自動正確的加載打包出的資源。
webpack原生不支持這個功能因而我作了一個插件 web-webpack-plugin
具體使用點開連接看詳細文檔,使用大概以下:

demo

webpack配置

module.exports = {
    entry: {
        A: './a',
        B: './b',
    },
    plugins: [
        new WebPlugin({
            // 輸出的html文件名稱,必填,注意不要重名,重名會覆蓋相互文件。
            filename: 'index.html',
            // 該html文件依賴的entry,必須是一個數組。依賴的資源的注入順序按照數組的順序。
            requires: ['A', 'B'],
        }),
    ]
};

將會輸出一個index.html文件,這個文件將會自動引入 entry AB 生成的js文件,

輸出的html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
<script src="A.js"></script>
<script src="B.js"></script>
</body>
</html>

輸出的目錄結構

├── A.js
├── B.js
└── index.html

管理多頁面

雖然webpack適用於單頁應用,但複雜的系統常常是由多個單頁應用組成,每一個頁面一個功能模塊。webpack給出了js打包方案但缺乏管理多個頁面的功能。 web-webpack-pluginAutoWebPlugin會自動的爲你的系統裏每一個單頁應用生成一個html入口頁,這個入口會自動的注入當前單頁應用依賴的資源,使用它你只需以下幾行代碼:

plugins: [
    // ./src/pages/ 表明存放全部頁面的根目錄,這個目錄下的每個目錄被看着是一個單頁應用
    // 會爲裏面的每個目錄生成一個html入口
    new AutoWebPlugin('./src/pages/', {
      //使用單頁應用的html模版文件,這裏你能夠自定義配置
      template: './src/assets/template.html',
    }),
  ],

查看web-webpack-plugin的文檔瞭解更多

分析輸出結果

若是你對當前的配置輸出或者構建速度不滿意,webpack有一個工具叫作webpack analyze 以可視化的方式直觀的分析構建,來進一步優化構建結果和速度。要使用它你須要在執行webpack的時候帶上--json --profile2個參數,這表明讓webpack把構建結果以json輸出並帶上構建性能信息,使用以下:

webpack --json --profile > stats.json

會生產一個stats.json 文件,再打開webpack analyze 上傳這個文件開始分析。

最後附上這篇文章所講到的webpack總體的配置,分爲開發環境的webpack.config.js和生產環境的webpack-dist.config.js

閱讀原文

相關文章
相關標籤/搜索