webpack4:連奏中的進化

webpack4在2月底的時候發佈,此次webpack4有了一個名字"Legato",也就是"連奏"的意思,寓意webpack在不斷進化,並且是無縫(no-gaps)的進化。webpack的進化點是經過捐贈者和用戶投票來決定的,以前在介紹webpack3的時候,曾給出過投票數在前幾名的優化點,集中在用戶體驗、構建性能(速度和產出大小)、通用和適配性(es module、typescript、web assemble)等。webpack4發佈了,下面將結合文檔和實踐,驗證一下webpack是否兌現了當初的諾言。css

webpack4中的新特性

webpack3官方發佈的時候,提到了下個版本可能的改動點,翻譯過來以下所示:html

  • 高性能的構建緩存
  • 提高初始化速度和增量構建效率
  • 更好的支持Type Script
  • 修訂長期緩存
  • 支持WASM 模塊支持
  • 提高用戶體驗

webpack4官方發佈的文檔之中主要說起了如下新特性:vue

支持零配置(Zero Configuration)

該特性主要用於解決webpack的門檻高問題,webpack是一個配置聲明式的操做模式,npm、gulp是指令式的,須要描述每一步是幹什麼的,而webpack的配置項凌亂且無序,讓不少開發者頭疼。
webpack4提供了零配置方案,默認入口屬性爲./src,默認輸出路徑爲./dist,再也不須要配置文件,實現了開箱即用的封裝能力,更通俗的講,webpack會自動查找項目中src目錄下的index.js文件,而後選擇的模式進行相應的打包操做,最後新建dist目錄並生成一個main.js文件。此外針對開發環境和線上環境提供了兩種打包模式:"production""development""production"模式內置了項目產出時的基本配置項,"development"模式基本知足了快速構建和開發體驗。使用的方法是在運行webapck命令的時候,設置好mode參數的值便可,默認是production屬性。node

"scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production" 
}

具體的案例能夠前往github進行下載webpack

下面根據官方的文檔介紹一下兩種模式。ios

  • Production模式

提供了發佈程序時的優化配置項,旨在更小的產出文件、更快的運行速度、不暴露源碼和路徑。會默認採用代碼壓縮(minification),做用域提高(scope hoisting),tree-shaking,NoEmitOnErrorsPlugin,無反作用模塊修剪(side-effect-free module pruning)等。git

  • Development模式

旨在提高開發調試過程當中的體驗,如更快的構建速度、調試時的代碼易讀性、暴露運行時的錯誤信息等。會默認採用bundle的輸出包含路徑名和eval-source-map等,提高代碼的可讀性和構建速度。es6

兩種模式就是兩個箱子,箱子裏面就是各類插件工具,只是有些是開啓的,有些是關閉的,具體有哪些工具能夠參考這篇文章github

廢棄CommonsChunkPlugin

webpack4廢棄了CommonsChunkPlugin,引入了optimization.splitChunksoptimization.runtimeChunk,旨在優化chunk的拆分。先介紹一下code splitting下的CommonsChunkPlugin有什麼缺點,而後仔介紹一下chunk split是怎麼進行優化的。web

  • CommonsChunkPlugin的問題

CommmonsChunkPlugin的思路是Create this chunk and move all modules matching minChunks into the new chunk,即將知足minChunks配置想所設置的條件的模塊移到一個新的chunk文件中去,這個思路是基於父子關係的,也就是這個新產出的new chunk是全部chunk的父親,在加載孩子chunk的時候,父親chunk是必需要提早加載的。舉個例子:

example:
entryA:  vue  vuex  someComponents
entryB:  vue axios someComponents
entryC: vue vux axios someComponents
minchunks: 2

產出後的chunk:

vendor-chunk:vue vuex axios
chunkA~chunkC: only the components

帶來的問題是:對entryA和entryB來講,vendor-chunk都包含了多餘的module。
CommonsChunkPlugin另一個問題是:對異步的模塊不友好。下面也舉例說明:

example:
entryA:  vue  vuex  someComponents
asyncB:vue axios someComponents
entryC: vue vux axios someComponents
minchunks: 2

產出後的chunk:

vendor-chunk:vue vuex 
chunkA: only the components
chunkB: vue axios someComponents
chunkC: axios someComponents

帶來的問題是:若是asyncB在entryA中動態引入,則會引入多餘的module。
總的來講CommonsChunkPlugin有如下三個問題:

  1. 產出的chunk在引入的時候,會包含重複的代碼;
  2. 沒法優化異步chunk;
  3. 高優的chunk產出須要的minchunks配置比較複雜。
  • SplitChunksPlugin

與CommonsChunkPlugin的父子關係思路不一樣的是,SplitChunksPlugin引入了chunkGroup的概念,在入口chunk和異步chunk中發現被重複使用的模塊,將重疊的模塊以vendor-chunk的形式分離出來,也就是vendor-chunk可能有多個,再也不受限因而全部chunk中都共同存在的模塊,原理的示意以下圖所示:

其中,能夠發現SplitChunksPlugin產出的vendor-chunk有多個,對於入口A來講,引入的代碼只有chunkA、vendor-chunkA-B、vendor-chunkA-C、vendor-chunkA-B-C;這時候chunkA、vendor-chunkA-B、vendor-chunkA-C、vendor-chunkA-B-C造成了一個chunkGroup。下面舉個列子:

example:
entryA:  vue  vuex  someComponents
entryB:vue axios someComponents
entryC: vue vux axios someComponents

產出後的chunk:

vendor-chunkA-C:vuex 
vendor-chunkB-C:axios
vendor-chunkA-B-C:vue
chunkA: only the components
chunkB: only the components
chunkC: only the components

SplitChunksPlugin可以解決掉CommonsChunkPlugin中提到的三個問題,SplitChunksPlugin在production模式下是默認開啓的,可是它默認只做用於異步chunk,若是要做用於入口chunk的話,須要配置optimization.splitChunks.chunks: "all",同時webpack自動split chunks是有幾個限制條件的:

  1. 新產出的vendor-chunk是要被共享的,或者模塊來自npm包;
  2. 新產出的vendor-chunk的大小得大於30kb;
  3. 並行請求vendor-chunk的數量不能超出5個;
  4. 對於entry-chunk而言,並行加載的vendor-chunk不能超出3個。

這些限制能夠在SplitChunks的默認配置項中能夠一一對應的看到。

splitChunks: {
    chunks: "async",
    minSize: 30000,
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    name: true,
    cacheGroups: {
        default: {
            minChunks: 2,
            priority: -20
            reuseExistingChunk: true,
        },
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        }
    }
}

其實不難理解這些限制,由於SplitChunksPlugin產生的結果就是原來chunk被拆分了,引入的文件數量會變多,所以須要在文件數量上進行限制。

  • runtimeChunkPlugin

在使用CommonsChunkPlugin的時候,咱們一般會把webpack runtime的基礎函數提取出來,單獨做爲一個chunk,畢竟code splitting想把不變的代碼單獨抽離出來,方便瀏覽器緩存,提高加載速度。webpack4廢棄了CommonsChunkPlugin,採用了runtimeChunkPlugin能夠將每一個entry chunk中的runtime部分的函數分離出來,只須要一個簡單的配置:optimization.runtimeChunk: true

sideEffects

在webapck2開始支持ESModule後,webpack提出了tree-shaking進行無用模塊的消除,主要依賴ES Module的靜態結構。在webapck4以前,主要經過在.babelrc文件中設置"modules": false來開啓無用的模塊檢測,該方法顯然比較粗暴。webapck4靈活擴展瞭如何對某模塊開展無用代碼檢測,主要經過在package.json文件中設置sideEffects: false來告訴編譯器該項目或模塊是pure的,能夠進行無用模塊刪除。
官方的github上提供了一個sideEffects的demo示例供參考,若是對tree-shaking的概念不是太瞭解,可去官方的文檔中tree-shaking部分詳細瞭解。下面是官方提供的一個減包效果示例,僅供欣賞。

支持壓縮ES6+代碼

在webapck4以前,webpack.prod.conf.js中關於UglifyJsPlugin的註釋會有這麼一段話:

// UglifyJs do not support ES6+, you can also use babel-minify for better treeshaking: https://github.com/babel/minify

意思就是UglifyJs沒法對ES6+的代碼進行壓縮,需使用babel-minify獲取更好的treeshaking效果。webapck4目前已經支持壓縮ES6+的代碼。

// 源代碼
import {sayHello} from './libs/js/util.js'
let output = sayHello('hwm');
console.log(output);

// 產出
...let r=(e=>`Hello ${e}!`)("hwm");console.log(r)}...

支持更多的模塊類型

webpack4支持以importexport形式加載和導出本地的WebAssembly模塊,這一塊本人實際項目並未使用到,暫不作介紹;此外,webpack4支持json模塊和tree-shaking,以前json文件的加載須要json-loader的支持,webpack4已經可以支持json模塊(JSON Module),不須要額外的配置;此外,當json文件用ESModule的語法import引入的時候,webpack4還能支持對json模塊進行tree-shaking處理,把用不到的字段過濾掉,起到減包的做用。下面是個示例:

// 引用數據的三種方法
let jsonData = require('./data/test.json');

import jsonData from './data/test.json'

// 打包時只會提取test.json文件中onePart部分。
import { onePart } from './data/test.json'

如何遷移升級到webpack4

0配置的侷限性

webpack4聲稱可以0配置,可是0配置有不少侷限性,好比只能是單入口的項目,入口和產出的文件名是固定的,entry是src目錄下的index.js,產出是dist目錄下的main.js,很明顯不能知足實際項目應用。因而,開發者仍是得本身配置webpack.config.js文件。

webpack4配置文件的變化點

如何配置webpack4下的配置文件,須要大體瞭解webapck4的配置項的改動點。
mode:開發模式 development

  • 開啓dev-tool,方便瀏覽器調試
  • 提供詳細的錯誤提示
  • 利用緩存機制,實現快速構建
  • 開啓output.pathinfo,在產出的bundle中顯示模塊路徑信息
  • 開啓NamedModulesPlugin
  • 開啓NoEmitOnErrorsPlugin

mode:生產模式 production

  • 啓動各類優化插件(ModuleConcatenationPlugin、optimization.minimize、ModuleConcatenationPlugin、Tree-shaking),壓縮、合併、拆分,產出更小體積的chunk
  • 關閉內存緩存
  • 開啓NoEmitOnErrorsPlugin

plugin

  • 內置optimization.minimize來壓縮代碼,不用再顯示引入UglifyJsPlugin;
  • 廢棄CommonsChunkPlugin插件,使用optimization.splitChunks和optimization.runtimeChunk來代替;
  • 使用optimization.noEmitOnErrors來替換NoEmitOnErrorsPlugin插件
  • 使用optimization.namedModules來替換NamedModulesPlugin插件

loader

  • 廢棄json-loader,友好支持json模塊,以ESMoudle的語法引入,還能夠對json模塊進行tree-shaking處理;

其餘的改動信息建議查看webpack的中文升級日誌

vue-cli項目如何改造

介紹完了webpack4中核心配置項的變化,接下來結合vue-cli示例項目介紹一下,如何配置webpack.conf.js文件。
1.升級npm包版本

建議升級webpack4,同時升級涉及到的loaders和plugins。webpack4 中 cli 工具分離成了 webpack 核心庫 與 webpack-cli 命令行工具兩個模塊,須要使用 CLI ,必安裝 webpack-cli 至項目中。

npm i webpack webpack-cli webpack-dev-server -D

涉及到的插件有:

"vue-loader": "^15.0.10",
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.5.16",
"copy-webpack-plugin": "^4.0.1",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"html-webpack-plugin": "^3.1.0",
"optimize-css-assets-webpack-plugin": "^4.0.0",
"webpack-bundle-analyzer": "^2.11.1",
"webpack-dev-middleware": "^3.1.2",
"webpack-dev-server": "^3.1.3",
"webpack-merge": "^4.1.0"

2.修改webpack.base.conf.js

webpack4推薦使用了最新版本的vue-loader("vue-loader": "^15.0.10"),可是最新的vue-loader須要在webapck config文件中設置VueLoaderPlugin插件,不然會報如下錯誤:

vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.

webpack.base.conf.js文件中的改動主要是添加VueLoaderPlugin插件。

const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
    ...
    plugins: [
        // 添加VueLoaderPlugin,以響應vue-loader
        new VueLoaderPlugin()
  ],
    ...
}

3.修改webpack.dev.conf.js

添加mode屬性,並設置爲development模式;而後註釋掉 webpack4開發模式已經內置的插件,如webpack.NamedModulesPluginwebpack.NoEmitOnErrorsPlugin 插件。

4.修改webpack.prod.conf.js

添加mode屬性,並設置爲production模式;而後註釋掉 webpack4生產模式已經內置的插件,如CommonsChunkPluginuglifyjs-webpack-pluginModuleConcatenationPlugin插件;最後根據webpack4提供的文檔配置optimization,使用splitChunksruntimeChunk完成chunk的提取和優化。

const webpackConfig = merge(baseWebpackConfig, {
...
    optimization: {
    // 採用splitChunks提取出entry chunk的chunk Group
    splitChunks: {
      cacheGroups: {
        // 處理入口chunk
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          chunks: 'initial',
          name: 'vendors',
        },
        // 處理異步chunk
        'async-vendors': {
          test: /[\\/]node_modules[\\/]/,
          minChunks: 2,
          chunks: 'async',
          name: 'async-vendors'
        }
      }
    },
    // 爲每一個入口提取出webpack runtime模塊
    runtimeChunk: { name: 'manifest' }
  }
...
})

通過以上操做,咱們已經基本完成了vue-cli項目的改造。運行項目的時候,注意看控制檯的報錯,是哪一個插件報的錯就去升級那個插件,若是存在找不到模塊之類的錯誤就去升級對應的loader。vue-cli項目webapck4下配置demo已經上傳到github,歡迎下載

實例說話—webpack4的性能如何

webapck4旨在開發模式下提高構建速度、提高用戶體驗,在生產模式下減少產出包的大小,提高加載和運行速度,下面是webapck3和webapck4下vue-cli的webapck模板項目的實際效果截圖:
開發者模式下:


由上圖能夠知道:webapck4下的構建速度是3703ms,明顯因爲webapck3下的5617ms;

生產模式下:


由上圖能夠知道:webapck4下的app-chunk的大小是933byte,明顯小於webapck3下的11.6K;webapck4下vendor-chunk的大小是109K,小於webapck3下的112K。
兩種模式下,webapck4性能上的確是精進很多,雖然有各類坑,仍是建議把坑填了,升級到webpack4。

webpack的將來

想了解webpack的將來,建議先過一下webpack的歷史。
webpack1支持CMD和AMD,同時擁有豐富的plugin和loader,webpack逐漸獲得普遍應用。
webpack2相對於webpack最大的改進就是支持ES Module,能夠直接分析ES Module之間的依賴關係,而webpack1必須將ES Module轉換成CommonJS模塊以後,才能使用webpack進行下一步處理。除此以外webpack2支持tree sharking,與ES Module的設計思路高度契合。
webpack3相對於webpack2,過渡相對平穩,可是新的特性大都圍繞ES Module提出,如Scope Hoisting和Magic Comment。
webpack4集中發力在用戶體驗、構建性能(速度和產出大小)、通用和適配性(es module、typescript、web assemble)。
展望將來,webpack的將來確定持續提高用戶體驗、下降使用門檻,進一步提高構建速度和產出代碼的性能,同時webpack的團隊已經承諾會根據社區的投票來決定新特性開發優先權。如下是公告中給出的將來的重點關注點:

  • 繼續修訂長期緩存
  • webapck任務多線程化,提高初始化速度和增量構建效率
  • 提高CSS到一等公民,引入CSS Module Type ,廢棄ExtractTextWebpackPlugin
  • 提高HTML到一等公民,引入HTML Module Type 
  • 繼續擴展0CJS(0配置文件),加入更多擴展
  • 優化支持WASM 模塊,從實驗階段過渡到穩定階段
  • 持續提高用戶體驗

參考文獻

webapck4官方medium pr稿
webpack4中文升級日誌
webpack4升級指南以及從webpack3.x遷移
Webpack4 新特性 及 Vue-cli項目升級
Webpack4官方指導教程
webpack4.0打包優化策略整理
webapck3新特性

相關文章
相關標籤/搜索