webpack 爲何這麼難用?

現在對於每個前端工程師來講,webpack 已經成爲了一項基礎技能,它基本上包辦了本地開發、編譯壓縮、性能優化的全部工做,從這個角度上來講,webpack 確實是偉大的,它的誕生意味着一整套工程化體系開始普及,而且慢慢統一了前端自動構建的讓前端開發完全告別了以前的刀耕火種時代。如今 webpack 之於前端開發,正如同 gcc/g++ 之於 C/C++,是一個你不管如何都繞不開的工具。css

可是,即便它如此偉大,也有一個巨大的問題,那就是 webpack 實在是太難用了!!!html

我從多年前的 webpack 1.0 時代就一直在用它,如今也不能說徹底掌握了它,不少時候真的讓我產生了懷疑,到底是由於個人能力不足,仍是由於 webpack 自身的設計就太難用?隨着我接觸到愈來愈多的前端項目,聽到愈來愈多的吐槽,我也愈加地相信,是 webpack 自身的問題,致使它變得如此複雜又難用。前端

舉個簡單的例子,一個 vue-cli 生成的最簡單的腳手架項目,開發、構建相關的文件就有 14 個之多,代碼超過 800 行,而真實的項目只會比這個更多: vue

因此,既然這篇文章的標題是《webpack 爲何這麼難用?》,那咱們就好好在這裏分析一下,webpack 難用的根本緣由。node


1、文檔極其不完善

是的,這就是第一位的緣由。react

我做爲參加過 webpack 中文文檔翻譯的人,真的想說 webpack 即便通過了這麼多年的不斷迭代,現在的文檔依然仍是是一坨那啥。做爲一個開源項目,設計好很差、易用性怎麼樣、擴展性怎麼樣這些問題都是仁者見仁智者見智的,但文檔寫得很爛這一點上,真的沒有任何能夠開脫的理由。webpack

對於使用者的不友好

好比,webpack 的插件體系能夠說是 webpack 最核心的一部分功能了,基本上一個項目的構建中,大部分任務都是由各類插件完成的。然而,官方文檔上對於插件的介紹只有寥寥幾句話:webpack · Plugins,甚至推薦你直接去看 webpack 的源碼:git

更糟的是,現有的文檔裏(包括 webpack 一些插件的文檔也是),大部份內容都是在告訴你 「你這樣作就能夠了」,而沒有解釋 「你爲何須要這麼作」 以及 「你這麼作了會有哪些後果」。github

好比,在 target 配置上,官方文檔裏列舉了你能夠構建到哪些 target,如 nodenode-webkitelectron-main,但都只是簡單的一句話帶過:web

想知道 target 爲 electron-main 時,和瀏覽器環境的打包有什麼不一樣?對不起,官方文檔不想告訴你,看源碼或者去 stackoverflow 上搜吧。

官方文檔語焉不詳的直接後果就是,當你遇到了任何問題,你都沒辦法在文檔裏獲得直接的回答,而是須要看無數的代碼、github issue、stackoverflow、博客文章,而後在本身的項目裏反反覆覆試了好屢次,才能大體解決問題。而這種所謂的「解決問題」,通常都是我的經驗性的,意味着其它任何一我的想要解決這個問題,都要重複一遍這個流程,時間成本大量上升。

這就是爲何使用 webpack 的時候,常常會出現下面的哲學三問:

  • 這是 webpack 的問題嗎?
  • 我要怎麼解決這個問題?
  • 咦我是怎麼解決的?

對於開發者的不友好

咱們要如何開發一個 webpack 的插件?

官方文檔裏確實寫了一些關於如何開發插件的指南。但這份指南也只有 60 分剛及格的水平,它確實向你介紹了 webpack 插件的基礎範例、基本概念以及一些 API,但當你讀完這份簡短的文檔後想本身真的去開發一個插件時,你會發現文檔裏講的東西真的遠遠不夠。

咱們不妨來看看如今 webpack 生態裏那些成熟的插件是怎麼寫的,以 html-webpack-plugin 爲例,這是一個普遍用於生成 html 文件的插件。在它的源碼裏你會發現,它引用了五個 webpack 內部自帶的插件源碼在這裏):

var NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
var NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
var LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin');
var LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin');
var SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
複製代碼

嗯哼?這五個插件都是用來幹什麼的?

官方文檔上對內置的插件一個字都沒有說起,是的,甚至連 Plugins 這裏都沒有。官方的 wiki 上卻是寫了,但真的真的太簡略了,並且看起來好久沒更新了。

再看另一個一樣經常使用的 uglifyjs-webpack-plugin,它卻是沒依賴 webpack 的內置插件,不過也引用了 webpack 內部的兩個文件:

import RequestShortener from 'webpack/lib/RequestShortener';
import ModuleFilenameHelpers from 'webpack/lib/ModuleFilenameHelpers';
複製代碼

文檔裏一樣沒有對這兩個文件作任何介紹。使人欣慰(?)的是,這兩個文件從文件名上看,起碼是方法庫(實際上也確實是),使用起來不會太複雜。

換句話說,若是你想給 webpack 寫一個廣爲人知的插件,你就必須深刻了解 webpack 的所有,這一點我不反對,畢竟 webpack 開發者webpack 使用者在能力的要求上有高低之分。但即便是有經驗的開發者,遇到一個文檔如此不完善的開源項目,也是很吃力的。不少能幫助開發者的東西本應該正大光明地寫在文檔和指南里,而不是隱藏在源代碼裏。


2、太重的插件體系

插件體系是 webpack 的核心,事實上,webpack 的大部分功能都是經過內部插件或者第三方插件來完成的。能夠說,webpack 的生態就是創建在衆多插件之上的。

但插件體系也一樣有不少問題。

插件數量問題

先問一個問題,一個經過 webpack 構建的項目須要多少插件?

仍是以一個標準的 vue-cli 生成的腳手架項目爲例,一共有 7 個第三方插件:

"copy-webpack-plugin": "^4.0.1",
"extract-text-webpack-plugin": "^3.0.0",
"friendly-errors-webpack-plugin": "^1.6.1",
"html-webpack-plugin": "^2.30.1",
"webpack-bundle-analyzer": "^2.9.0",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"uglifyjs-webpack-plugin": "^1.1.1",
複製代碼

以及 7 個 webpack 內置插件:

  • HashedModuleIdsPlugin
  • ModuleConcatenationPlugin
  • CommonsChunkPlugin
  • DefinePlugin
  • HotModuleReplacementPlugin
  • NamedModulesPlugin
  • NoEmitOnErrorsPlugin

總共 14 個插件,咱們按照平均一個插件含有 2-3 個配置項(這已是往低了算了)來計算,14 個插件就有 30 多項配置,這已是一個現代 webpack 開發、構建使用的很基礎的配置了,真實的項目只會比這個更多。

要注意到,30 多個配置項帶來的複雜程度是遠勝於 30 行代碼的。 由於配置項已經具備了比較高的抽象性,一項配置包含的反作用是要遠大於一行代碼的。好比下面是經常用於提取公共模塊的 CommonsChunkPlugin 的配置:

new webpack.optimize.CommonsChunkPlugin({
    name: 'app',
    async: 'vendor-async',
    children: true,
    minChunks: 3
})
複製代碼

若是你不是一個 webpack 老手的話,看到這 4 項配置確定是一臉懵逼的:

  • name 該填什麼?隨便命個名就好嗎?
  • async 是什麼?異步模塊?那爲何是個字符串?
  • children 是個啥?爲何不是 Array 而是個 boolean?
  • minChunks 這個數字是什麼?chunk 又是什麼?

而後你就去看了 CommonsChunkPlugin 的文檔,十五分鐘艱難的閱讀以後,你會發現這四項配置都不簡單,每一項的更改會給構建帶來很大的影響。

然而壞消息是,像這樣的配置在項目裏整整有 30 多處!

因此我每次改一個項目的構建時,基本都是這樣的:

面向配置的插件

在討論這個話題以前,先回答兩個問題:

  • webpack 的插件前後順序會影響構建結果嗎?

  • 若是插件順序不一樣,會影響哪些東西?

實際上,這兩個問題我找遍了官方文檔,也沒有提到插件的順序會影響哪些東西,stackoverflow 上卻是找到了一個問題:Webpack: Does the order of plugins matter?

因此回答就是:插件的順序有影響,但做用不明。

其實問題不止在插件的順序前後上,就連一個插件到底對構建產生了哪些影響,咱們也很可貴知,除非你極其熟悉這個插件或者就是這個插件的做者。爲何會這樣?根本緣由就是,webpack 的插件是面向配置的,而不是面向過程的

什麼叫面向過程?若是你知道或者使用過 gulp 這個自動化工具的話,應該會記得 gulp 管道的概念,即從源頭那裏獲得源數據(js/css/html 源碼、圖片、字體等等),而後數據經過一個又一個組合起來的管道,最後輸出成爲構建的結果。寫成僞代碼的話,大概是這樣:

gulp.src('某些源文件')
    .pipe(處理一)
    .pipe(處理二)
    .pipe(處理三)
    .dest('構建結果')
複製代碼

這種管道化,或者說面向過程的構建,很是容易 debug 或者修改,由於它構建的每一步過程,都整齊的按照順序展現給你看了。想要修改其中任何一步的心智負擔是很低的,由於它的處理機制很是純函數。

然而若是是 webpack 的話,就相似這樣:

{
    plugins: [
        插件一,
        插件二,
        插件三
    ]
}
複製代碼

這裏,插件一二三是徹底面向配置的,沒有告訴你任何執行順序,它們可能會在 webpack 構建的每一個時間點觸發,你只能從它們的功能上大體猜出它是在哪一個時間點工做的。這就是爲何修改一些 webpack 的配置,就像要解開一條放在包裏好久的耳機線同樣,麻煩又鬧心。

固然,這種配置化的插件也是有好處的,配置化表明了高集成度,當你只有 1-3 個插件時,維護這些配置的心智負擔是能夠接受的,而且比維護面向過程的配置更加方便。但當插件數量超過這個值的時候,構建的複雜程度就會呈指數式上升,咱們以前就已經提到了,一個現代的 webpack 項目起碼會有 14 個以上的插件以及至少 30 多項配置,這種狀況下,面向過程就會好於面向配置,這就是爲何我一直以爲 gulp + webpack 纔是正確解決方案的緣由。

固然仍是要說一句,gulp 和 webpack 並不能直接比較,前者是一個 task runner,然後者是一個 module bundler,它們二者之間都有一些相互不可替代的功能。


3、配置化是銀彈嗎?

在平常業務中,特別是大公司的一些運營性質的業務裏,咱們經常會看到 「某某業務已經實現徹底配置化」 這樣的字眼,在這個語境裏,配置化表明了低維護成本、高靈活性、高封裝性。

在技術的世界裏,配置化也一樣是個好東西,不少工具都會宣稱本身是徹底配置化的,只要你的項目里加入一個配置文件,那麼這個工具就能夠幫你作不少不少的事情,babeleslintstylelint,還有本文討論的 webpack 都是如此。

因此配置化是否是就是全部工具進化的終點了呢?它是否是能解決全部的問題呢?

軟件工程上有一句耳熟能詳的話:「沒有銀彈」,指的是複雜的軟件工程問題沒法靠簡單的答案來解決。在前端工程構建這個問題上,也一樣不例外。

如何解決前端工程的構建?webpack 給出的答案是:經過 webpack + loader + plugin,讓一切資源構建可配置。 這在它誕生的那個時代看來,是很是厲害的,一份簡單的配置文件就幫你搞定了全部資源構建的問題。

可是當時間的推移,一個前端項目的構建變得愈來愈複雜,webpack 的配置也愈來愈多,維護起來愈來愈難,這個時候,也就慢慢誕生了諸如 create-react-appvue-cli 這樣的腳手架工具,在 webpack 的基礎上進一步封裝,來幫你自動生成 webpack 的配置。這個時候,webpack 更多地變成了一個「底層」工具,而這些腳手架纔是你實際上的「構建工具」,或者說,這些腳手架提供的配置,纔是你真正的構建配置

爲何會這樣?

這個問題的根源在於,webpack 如今提供的配置的封裝性已經不夠了,它面對一個現在複雜得多的大型前端工程,僅有的配置已經沒辦法像幾年前那樣爲咱們屏蔽掉大部分的構建細節了,因此在它的基礎上誕生了如此多的腳手架工具幫咱們進一步封裝複雜性。

因此咱們如今能夠回答這一段的標題了:配置化是解決複雜度的銀彈嗎?固然不是,由於配置會隨着複雜度的提高,而也逐漸變得複雜,維護愈來愈難,直到超過某個臨界值,就會須要在它的基礎上進一步封裝,產生新的配置化。


4、前端工程構建的將來

正如我在上一章所說的,隨着複雜度的上升,須要不斷地封裝複雜性,以讓維護配置的心智成本降到能夠接受的程度。而在前端構建工具上,截止到趨勢也正是如此:

  • 前端的遠古時代咱們不須要構建,由於這時的前端項目還很簡單,原始的 html/js/css 就足以應付需求,手工處理這些資源方便又快捷。
  • 隨着前端的複雜化,手工處理的效率愈來愈低,grunt、gulp 這樣的自動化工具就誕生了,它們屏蔽掉了不少資源處理的細節問題,讓資源的處理能夠自動完成。
  • 隨着構建流程愈來愈多、資源種類愈來愈多、ECMAScript 的語言特性越發複雜、開始區分開發/測試/生產環境等等因素,gulpfile/grunt 這樣的工具已經不能知足咱們的需求,咱們須要的是一整套完整的配置化的構建方案,而 webpack 就是這樣一種方案。
  • 隨着 webpack 配置愈來愈複雜,維護成本也愈來愈高,因而誕生了不少腳手架工具,幫你生成 webpack 的配置,封裝起 webpack 的複雜性。

那麼將來的下一代前端構建工具是怎樣的呢?

如今普遍使用的這些腳手架工具,終究依賴的是 webpack,咱們實際上須要的是集成度更高、封裝性更高(甚至零配置)的構建工具。更詳細地說,下一代前端構建工具,必然會有下面的某些特性:

  • 內置的功能更多,好比自帶 babel、dev-server、HMR、sourceMap 等等功能;
  • 配置更少,甚至零配置;
  • 更低成本區分開發、測試、生產環境;
  • 性能更好,整合冗長的構建流程,支持多核 CPU 等;
  • 對於新型模塊的支持:異步模塊、WebAssembly 模塊等。

事實上,這也就是部分 webpack 4.0 將會有的新特性,以及前段時間看到的 parcel 也具備其中的某些特色(雖然它如今看起來還很不成熟)。將來這樣的構建工具只會愈來愈多。


總結

這篇文章好久以前就在構思了,只是近期在工做上集中遇到了不少 webpack 的坑,讓我完全有動力來吐槽一下它的種種不是。

webpack 爲何這麼難用?本文給出的答案濃縮起來就是兩點:

  • 文檔不完善,致使使用者和開發者遇到問題都很難下手;
  • 項目須要使用的插件數量太多,且面向配置,致使維護成本指數級上升。

這些問題將來會有改善嗎?固然。其實,這篇文章其實有標題黨的嫌疑,更準確的標題應該是:

如今的 webpack 爲何這麼難用?》

由於這篇文章裏提到的問題,都會在 webpack 4.0 中獲得改善。

額……至於它的文檔嘛……算了不提了不提了 O__O "…

相關文章
相關標籤/搜索