天天看點

精讀《webpack4.0 更新指南》

本周精讀的是 webpack4.0 一些變化,以及 typescript 該怎麼做才能最大化利用 webpack4.0 的所有特性。

1 引言

前段時間嘗試了 parcel 作為建構工具,就像農村人享受了都市的生活,就再也回不去了一樣,發現無配置真是前端建構工具的大趨勢,用起來非常友善快捷,再也不想碰 webpack 的配置了。

可是實踐一段實踐後,發現 parcel 還是不夠成熟,主要展現在暫時不支援一些 rollup 優秀特性:Tree shaking、Scope Hoist,大型項目打包速度反而比 webpack3.0 慢。由于筆者完全零配置,當發現建構速度急速下降時,自然把矛頭指向了 parcel :p.

就在前幾周,webpack4.0 釋出了,也擁抱了零配置,我想,是時候再回到 webpack 了。可是,文檔好少,怎麼遷移呢?

就在這幾天,webpack 文檔釋出了 4.0 版本,雖然遺留了大量舊文檔,不過也足夠參考了。

2 精讀

筆者嘗試了

webpack node api

,嘗試了很久,發現被坑了。文檔裡隻字未提

mode

模式,4.0 環境下

compiler

總是提示沒有

mode

的 warning。

讀了一些文檔,發現 webpack4.0 大力度宣傳的是 cli 方式啟動,裡面提到了最重要的

webpack --mode

模式,可見 webpack4.0 更推崇的是讓開發者使用高度封裝的 cli,而不是使用 node 方式開發(那 node 文檔也應該更新呀)。筆者又看了一圈,發現

webpack-dev-server

的 webpack 版本升到了 4.0,

ts-loader

也更新到了 4.0,可能生态已經全部準備好了。

使用 webpack cli、webpack-dev-server cli

安裝

webpack^4.1.1

webpack-cli^2.0.10

webpack-dev-server^3.1.0

,以及建立一個公共配置檔案

webpack.config.ts

:

export default {
  entry,

  output,

  module: {
    rules
  },

  resolve,

  resolveLoader,

  devServer: {
    https: true,
    open: true,
    overlay: {
      warnings: true,
      errors: true
    },
    port
  }
}           

記得用

tsc

轉換為

webpack.config.js

作為 cli 入口。

開發模式下使用

webpack-dev-server

webpack-dev-server --mode development --progress --hot --hotOnly --config ./webpack.config.js           

生産環境 build 使用

webpack

webpack --mode production --progress --config ./webpack.config.js           

開發/生産模式,都以

webpack.config.ts

作為配置,其中

devServer

項僅在開發模式下,對

webpack-dev-server

生效。

一旦開啟了

--mode production

,會自動開啟代碼壓縮、scope hoist 等插件,以及自動傳遞環境變量給 lib 包,是以已經不需要

plugins

這個配置項了。同理,開啟了

--mode development

會自動開啟 sourceMap 等開發插件,我們隻要關心更簡單的配置,這就是 4.0 零配置的重要改變。

mode=production

,

mode=development

具體内置了哪些配置,可以參考這篇文章: webpack 4 終于知道「約定優于配置」了 。恰恰有意思的是,webpack4 這麼做,就是不想我們浪費時間了解這些機制,社群應該會慢慢習慣零配置的開發方式。

當然,雖然說零配置,但配置檔案基本三闆斧還是非常有必要配置:

entry

output

module

我們可能還要給配置檔案傳一些參數,比如定制多種開發模式的入口,通過

--env

傳遞:

webpack-dev-server --mode development --env.entry ./src/main.tsx           

webpack.config.ts

接收:

const entry = yargs.argv.env.entry           

使用 typescript + webpack

簡單來說,隻需要

ts-loader

就夠了。在

webpack.config.ts

中增加新的

rules

{
  module: {
    rules: [{
      test: /\.(tsx|ts)?$/,
      use: ["ts-loader"]
    }]
  }
}           

注意

tsconfig.json

中子產品解析政策使用:

"module": "esnext"

原因是 webpack 需要 es6 import 語句,才能進行 tree shaking 或者動态 import 優化,我們不再讓

ts-loader

包辦子產品設定,換句話說,我們采用白名單方式看待

typescript

以及

babel

,隻讓他做我們需要的工作,剩下的丢給 webpack 處理,可以獲得最大程度性能優化。

如果僅使用 webpack + typescript,建議将 ts 編譯輸出模式調整為

es3

,因為 webpack 自帶的壓縮工具對 es6 文法還存在報錯,而且也不會做相容處理。

使用 typescript + babel + webpcak

注意處理順序,ts -> babel -> webpack。

因為多出了 babel,我們将 ts 編譯相容模式關閉:

"target": "esnext"

,子產品也不要解析:

"module": "esnext"

ts-loader

僅僅将 typescript 代碼轉換成 js,其他一切優化都不要做,将 esnext 原生代碼直接傳給 babel 處理。

babel 這一層的職責是對代碼進行相容處理,不要壓縮,也不要把 import 轉成 require。筆者發現 babel 直接解析 import 代碼會無法處理,是以需要

stage-2

preset:

{
  presets: [
    ["env", {
      modules: false,
    }],
    ["stage-2"]
  ],
  plugins: [
    ["transform-runtime"]
  ],
  comments: true
}           

從上面配置可以看到,babel 這層對 esnext 的代碼進行了浏覽器相容處理(env 插件),直接透傳

import

(stage-2 插件讓 babel 識别 esModule),以及支援 async await(transform-runtime) 插件。

本來想用 env 替代 transform-runtime 的功能,筆者暫時沒有查詢到可行方式,歡迎讀者補充。

另外要允許 babel 保留注釋(

comments: true

),因為 webpack import 支援自定義 chunkName 是通過注釋的方式:

import(/* webpackChunkName: "src" */ "./src")           

配合

react-loadable

使用更佳:

Loadable({
  loader: () => import(/* webpackChunkName: "src" */ "./src"),
  loading: (): any => null
})           

因為

react-loadable

讓頁面按 chunk 方式打包,而 webpack 又會自動 picke shared chunks,配合給每個 page chunks 通過

webpackChunkName

定義名稱,webpack 可以給每個共享 chunks 更加可讀的名字,比如:

vendor~src,about,login

,你就知道這個是

src

about

login

三個頁面間公共子產品。

可能已經有人看出瑕疵了,給每個檔案增加

webpackChunkName

注釋既麻煩又不優雅,而且隻要有一個開發者沒有加這個注釋,上面說的可讀 chunks 可能就缺少了某個子產品名。

這就要筆者之前一篇精讀來看了:

精讀《Rekit Studio》

,項目可以通過約定的方式定義頁面,入口檔案通過 cli 自動生成,不就既減少業務代量,又統一加上了

webpackChunkName

嘛?

這裡小小安利下內建了這個思路的項目腳手架

pri

,使用了 ts + babel + webpack4.0,上述的小優化也是内置的功能之一。

webpack4 帶來的是适配成本的大幅優化

社群似乎有部分聲音在抱怨,webpack 又發新版本,我們又要适配一輪。其實 webpack 這麼做恰恰沒有帶來适配成本,出問題的在于我們對 webpack 的使用方式與理念。

如果我們開始就将 webpack 當作一體化打包方案,開發調試使用

webpack-dev-server cli

,開發環境編譯使用

webpack cli

,那麼 webpack4 其實隻是補充了開發環境這個最重要的配置變量而已。類比

parcel

的兩個指令:

parcel index.html
parcel build index.html           

對應:

webpack-dev-server --mode development
webpack --mode production           

是以 webpack4 幾乎是有史以來最友善使用與遷移的版本,前提是使用思維得正确,舍得将編譯環節全權交給兩個官方的 Cli。

3 總結

隻要合理的使用 typescript、babel,讓各自隻發揮最小功能,将原生的子產品化代碼抛給 webpack,再配合

--mode production

配置,webpack 會自動開啟一切可能的插件優化你的項目,而我們再不需要閱讀形形色色的 webpack 插件了,更令人激動的是,随着 webpack 版本更新,優化會不斷更新,而我們隻要留着

--mode

參數,不需要改一行配置。

總結起來,就是不用關心優化相關的配置,我們隻需要配置業務相關的

entry

output

module

,這就是 webpack4.0.

我以前為了實作第一次編譯完後立即打開浏覽器的功能,寫了一共 200 行的

customCompiler

format-webpack-message

,而且利用 koa 開了一個 server,利用 await 和 flags 等待第一次編譯完的時機,并利用

opn

庫打開網頁。

其實用 cli 隻需要

webpack-dev-server --open

随着新的一波零配置浪潮,真的不應該在編譯配置上花那麼多時間了。

4 番外 - prefetch

讀者自習閱讀就會發現,這不是一篇單純 webpack4 更新指南,仔細閱讀可以發現文中蘊藏的一些工程優化思路。文章末尾再給一波福利,分析一下 prefetch 優化是什麼,以及怎麼做。

現代浏覽器支援了以下兩種文法:

<link rel="preload" />
<link rel="prefetch" />           

相容性自己查

Caniuse

,筆者重點在功能上。

preload

收集目前用到的資源,

prefetch

收集未來用到的資源。

頁面本質上也是未來一種資源,如果認為使用者會點選另一個頁面(如果對産品沒自信,或者 pv 過低可以忽略這個功能),就可以用

prefetch

讓浏覽器在空閑時間下載下傳下一個頁面的 chunk 檔案。

前端包體積優化效率一般和使用者體驗是違背的,既然下一個頁面在另一個 chunk 中,使用者點選後必然會産生 loading。可是如果結合了

prefetch

,魚和熊掌就兼得了(正常使用者不可能頁面還沒加載完就立刻點按鈕跳頁,是以唯一的缺點幾乎不會對正常使用者産生影響)。

api 有了,那麼最大的問題就是,目前頁面怎麼知道要加載哪些 chunks?一般兩種做法:

全量模式 使用比如

preload-webpack-plugin

插件,将所有生成的 chunk 都作為

prefetch

資源,在所有頁面中。幾乎所有規模的項目都不會産生過多的 chunks,是以這個方案理論上不夠優雅,但能解決實際問題。

按需模式,是理論和實踐雙重優雅的方案,是否要這麼做取決于您是否有代碼潔癖。方法是提供一個定制的

Link

标簽,根據 URL 位址按需生成

prefetch

标簽。這種方案最大缺陷是,如果使用者不按照約定使用内置的

Link

prefetch

規則将會無效。

5 更多讨論

讨論位址是: 精讀《webpack4.0 更新指南》 · Issue #66 · dt-fe/weekly

如果你想參與讨論,請

點選這裡

,每周都有新的主題,每周五釋出。

繼續閱讀