最近有機會系統地總結一下 webpack 相關的東西,寫篇文章來記錄一下。寫這篇文章一半是爲了本身看,一半給別人看。若是有人看了這篇文章感受頗有用,真是我莫大的榮幸。 首先這不是 《深刻淺出 webpack》, 也不是《一篇文章帶你玩轉 webpack》,更不是 《學習 webpack 這一篇就夠了》。css
看文檔是最簡單,最直接的方法,不過官方文檔有些地方寫的不太清楚,這時候,那些文章和博客的做用就比較明顯了,畢竟這些是文章的做者們摸索出來的。好比這篇文章沒有大篇幅複製官方文檔,而是力求多介紹些沒有在文檔中沒體現的部分。html
webpack 是如今最流行的打包工具,即便如今的 webpack 也能夠像 parcel 同樣實現零配置打包;vue-cli3 也提供了開箱即用的零配置打包方案。可是,這些方案不必定能知足本身的需求。不少時候還須要咱們本身根據本身的需求配置 webpack。vue
npx
命令運行局部的 webpack 和 webpack-cli在命令行使用 npx 命令 調用 webpack:
npx webpack
npx 會調用node_modules/.bin
下的webpack
文件, 在這個文件裏,會先調用webpack-cli
以讀取命令行參數。node
官方解釋 大概說明了這幾個概念;jquery
從前後順序 module:模塊,webpack 模塊(項目的資源) chunck: 代碼塊 (中間產物) bundle: 打包後的結果webpack
從「包含」 關係 module <= chunck <= bundlegit
在 webpack 中任何資源都被做爲模塊處理,webpack 支持 js 各類模塊化標準的模塊,和經過 loader 轉化來的模塊。github
chunkname, webpack 中每一個 chunk 都會有一個名稱:web
CommonsChunkPlugin
, mini-css-extract-plugin
插件產生的 chunk,須要在插件配置中指定 chunk 的名稱。webpack 是個 JavaScript 打包器,bundle 是指 webpack 打包後的結果。正則表達式
本質上,
webpack
是一個現代 JavaScript 應用程序的靜態模塊打包器(module bundler)。當webpack
處理應用程序時,它會遞歸地構建一個依賴關係圖(dependency graph),其中包含應用程序須要的每一個模塊,而後將全部這些模塊打包成一個或多個bundle
。
配置文件爲: webpack.config.js
或 webpackfile.js
;
基本配置:
const config = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}
};
複製代碼
基礎配置:
{
output: {
filename: '[name].js',
path: '/home/proj/public/assets'
}
}
複製代碼
注意: path
需爲絕對路徑
注意,這是第三方的模塊, 須要另外安裝。 webpack-dev-server 不是構建本地開發環境的惟一選擇,其餘方式有看這裏。 其實 webpack-dev-server 會啓動一個 express 服務,因此想要更到的自由度,能夠直接使用 express 的 webpack-dev-middleware。
devServer
選項用來配置 wepack-dev-server,基本配置:
{
devServer: {
contentBase: path.join(__dirname, 'dist'), // 提供靜態文件的目錄
compress: true, // gzip 壓縮
publicPath: '/assets/', // bundle 的可訪問路徑
port: 9000
}
}
複製代碼
webpack-dev-server 默認已經實現了 live reloading,無需額外配置。 CSS 的 HMR 可使用 style-loader。 Js 的 HMR ,須要在項目代碼裏寫重載代碼。通常藉助開發框架提供的提供的 HMR 方案, 好比 vue 提供的 vue-loader。
webpack4 新增了 mode 選項,能夠指定開發模式/生產模式。
{
mode: 'production'
}
複製代碼
若是值爲 development
或 production
, 會啓用 webpack 內置的優化策略。
在 webpack 中使用非 js 模塊(或須要特殊處理的 js 模塊,好比 ES 模塊)時,須要使用 loader 進行轉換;使用 module.rules
配置 loader 的規則:
module: {
rules: [
{
test: /\.html$/,
use: 'html-withimg-loader'
}
]
}
複製代碼
webpack 的配置比較靈活,不少的配置項均可以接受不一樣數據類型的值,好比,上面的 test
屬性,能夠接受字符串、正則表達式,數組,對象,甚至是函數。 一樣,use
屬性的配置也比較靈活,當只使用一個 loader 時,能夠只寫一個字符串(如 css-loader
);當使用多個 loader 時,能夠寫一個數組(['css-loader', 'less-loader']
),當須要給每一個 loader 一些其餘的配置時,能夠把上面的字符串替換爲一個對象:
{
test: /\.(png|jpe?g|gif|svg)$/,
use: {
loader: 'url-loader',
options: {
limit: 8192,
fallback: 'file-loader',
name: 'img/[name]-[hash].[ext]'
}
}
}
複製代碼
注意: 多個 loader 的執行順序是:自右向左,自下向上; 更多 loader 。
配置 webpack,很大一部分是在配置插件。 基本的作法是,new
一個插件實例,傳入一些配置信息:
{
plugins: [
new HtmlWebpackPlugin({
title: 'My App'
})
]
}
複製代碼
更多插件及相關配置 看這裏。
常見的幾個插件;
HtmlWebpackPlugin
,建立 HTML 文件TerserJSPlugin
,使用 UglifyJS
壓縮混淆 js 代碼MiniCssExtractPlugin
, 提取 CSSCleanWebpackPlugin
, 清除文件webpack.DefinePlugin
, 定義全局常量注意,隨着 webpack 的升級,一些老項目和網上的一些文章的插件已通過時了,好比:
抽離 CSS 爲獨立文件,使用 mini-css-extract-plugin, 而再也不建議使用 extract-text-webpack-plugin
。
webpack 默認安裝 terser-webpack-plugin
, 再也不須要使用 uglifyjs-webpack-plugin
。由於後者對 ES6+ 的處理問題。
抽離公共代碼的 CommonsChunkPlugin
如今使用功能更強大的 optimization.splitChunks
代替。
webpack 與 Nodejs 的模塊解析類似,查找規則基本相同,前者在功能上有所加強。 模塊路徑解析相關的配置都在 resolve
字段下,經常使用的有:
resolve.alias
, 設置模塊別名resolve.extensions
,自動解析肯定的擴展名resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'service': path.resolve(__dirname, 'src/js/service'),
'utils': path.resolve(__dirname, 'src/js/utils')
}
}
複製代碼
經過 devtool
配置,也可使用 SourceMapDevToolPlugin
進行更細粒度的配置。 關於 devtool
, 文檔 給出了多種方式,每一種的運算速度都不同,有的適合開發環境有的適合生產環境,能夠根據本身的需求選擇合適的配置。
須要在樣式處理的各個 loader 中打開 source map 配置。
loader 能夠經過 include
和 exclude
配置來縮小文件的搜索範圍。
babel-loader 官方提供了還提供了 cacheDirectory
配置,緩存編譯結果,避免重複編譯。
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
cacheDirectory: true
}
}
}
複製代碼
配置 webpack 去哪一個目錄下尋找第三方模塊。其默認值爲, ['node_modules']
。 和 Nodejs 的模塊查找機制相似,webpack 會先從當前目錄的 ./node_modules
目錄開始查找想要的模塊,若是沒找到,就去 ../node_modules
中查找,再沒有就去 ../../node_modules
, 以此類推。 通常狀況下,咱們的第三方模塊都是安裝在根目錄的 node_modules
目錄下的,此時能夠經過 resolve.modules
指定固定的目錄,以減小尋找。
modules: [path.resolve(__dirname, 'node_modules')]
複製代碼
建立 import
或 require
的別名,使模塊引入變得更簡單。
有些庫,安裝到 node_modules
目錄下的文件包括兩部分:
當第三方模塊的完整性較強且模塊入口爲模塊化代碼,咱們不但願 webpack 再次編譯的時候,可使用 resolve.alias
將導入模塊換成使用已打包好的文件。
反之亦然,當只使用模塊一部分功能,但願有效利用優化(如 Tree Shaking),且第三方模塊入口爲已打包的單獨文件時,可使用 resolve.alias
將導入的模塊換成使用模塊化的代碼。
當導入預計沒有後綴時,webpack 會自動加上後綴去嘗試詢問文件是否存在。resolve.extensions
即是用於配置在嘗試過程當中用到的後綴名列表。
讓 webpack 忽略對部分沒有采用模塊化 的文件的遞歸解析。
項目中引入的第三方模塊,每一次打包都要進行編譯,特別消耗性能。DLLPlugin 提升打包速度的基本原理是,單獨打包一次這些第三方模塊,生成一個單獨的文件,以後在正式打包時就能夠直接從這個文件中引入以前打包好的第三方模塊。
注意: DLL 經常使用於打包第三方模塊,但不侷限於第三方模塊。
先從基本原理提及,好比咱們要單獨打包 A,B 兩個庫:
對應的配置:
script
標籤引入動態連接庫,最笨的方法手寫(:。例子:官方提供的例子
externals
配置選項提供了「從輸出的 bundle 中排除依賴」的方法。相反,所建立的 bundle 依賴於那些存在於用戶環境(consumer's environment)中的依賴。
externals
的優點在於,能夠從 CND 引入資源,有效利用瀏覽器緩存。對於 jQuery 這樣的庫來講 externals
是一個不錯的選擇。
以 externals
最終的打包結果(以 jQuery 爲例):
jquery: function(module, exports) {
eval('module.exports = jQuery;');
}
複製代碼
也就是 webpack 對 externals
的處理僅僅是把全局變量(jQuery
)做爲一個依賴模塊進行管理,因此 externals
不具有管理依賴的依賴的能力,也就沒法避免依賴的依賴重複打包。
DLL 也有相似的結果 (假如以var __dll__vendor
導出動態連接庫):
'dll-reference __dll__vendor': function(module, exports) {
eval('module.exports = __dll__vendor;');
}
複製代碼
打開頁面,能夠在控制檯訪問到這些打包進動態連接庫的模塊:
__dll__vendor.m[id]; // 經過id能夠訪問已經打包進動態連接庫的模塊
複製代碼
與 externals
相同,DLL 也把一個全局的變量做爲一個依賴進行管理。不一樣的是,這個變量的結構和 webpack 運行時模塊管理的結構是同樣的,其中,依賴和依賴的依賴,均可以經過 ID 訪問到,能夠避免重複打包。
optimization.splitChunks
選項經過配置 SplitChunksPlugin 來實現提取公共代碼。通常的應用場景是:
webpack 在生產環境的提取公共代碼策略和如下因素有關:
更多細節請移步官方文檔
咱們在作公共代碼的提取相關配置的時候,也是從上面的幾個角度去配置的。
即懶加載,在初始化頁面的時候不一次性加載全部代碼,當須要更多內容時,再對用到的內容進行即時加載。
目前可用寫法:
import()
語法(推薦)require.ensure
import(/* webpackChunkName: "print" */ './print').then(module => {
// ES6 模塊的 import() 方法引入模塊時,必須指向模塊的 .default 值
var print = module.default;
});
複製代碼
上面代碼中,/* webpackChunkName: "print" */
是以行內註釋的方式爲動態生成的 chunk 賦一個名稱。此處還能夠寫更多的參數如:
webpackPrefetch
預取模塊webpackPreload
預加載模塊關於上面代碼中的 module.default
,須要注意:
webpack v4 開始,在 import CommonJS 模塊時,不會再將導入模塊解析爲
module.exports
的值,而是爲 CommonJS 模塊建立一個 artificial namespace object(人工命名空間對象),關於其背後緣由的更多信息,請閱讀 webpack 4: import() 和 CommonJs。
總結來講:
import()
,對 CommonJS 模塊處理方式不太同樣;import()
時,解析的都是一個命名空間對象了。把靜態資源上傳到CDN服務上,能得到更快的響應速度。因爲 CDN 服務通常會爲資源開啓很長時間的緩存,因此HTML文件通常不放到 CND 上,並且關閉緩存。而對於靜態的JavaScript、CSS、圖片等文件傳到 CDN 上,同時爲文件名帶上由文件內容 計算出的 hash 指紋。
另外瀏覽器在同一時刻對同一域名的並行請求有限制,通常的解決方案是:把這些靜態資源分散到不一樣域名下的CDN服務上,如:JavaScript文件放到 js.cdn.xx.com 域名下,圖片放到 img.cdn.xx.com 域名下。
同時多個域名又會增長域名解析的成本,這個也須要權衡。固然也能夠經過也能夠在HTML的head 標籤中,增長
<link rel="dns-prefetch" href="//js.cdn.xx.com" />
預解析域名,以減小域名解析帶來的延遲。
配置:
output.publicPath
設置 JavaScript 的基地址。publicPath
設置 CSS 的基地址。publicPath
設置圖片等文件的的基地址。文件 hash 指紋在利用瀏覽器緩存中發揮了很重要的做用,在 webpack 中可用的 hash 的類型有 hash, chunkhash 和 contenthash,下面簡要介紹其主要區別。
用來消除死碼。webpack 在生產環境,已經默認開啓了 tree-shaking,注意,爲了發揮tree-shaking 的做用,項目中必需使用 ES6 的模塊引用和導出規則。
CSS 的 「tree-shaking」 能夠經過 optimize-css-assets-webpack-plugin 實現。
Scope Hoisting 可讓 Webpack 打包出來的代碼文件更小、運行的更快;webpack默認在生產模式已經開啓 Scope Hoisting。
大概的原理: 早期的 webpack 模塊打包,把模塊打包到一個個做用域隔離的函數中,而後把這些函數表達式放到一個數組裏,做爲參數傳遞給 webpack 運行時模塊解析的函數。其中運行這些函數建立了大量做用域,增長了內存開銷。 ModuleConcatenationPlugin (webpack內置實現Scope Hoisting的插件)經過分析出模塊之間的依賴關係,能夠將一個模塊之內聯函數的形式注入到另外一個模塊。
注意,此插件僅適用於由 webpack 直接處理的 ES6 模塊)。在使用轉譯器(transpiler)時,你須要禁用對模塊的處理(例如 Babel 中的 modules 選項)。
happyPack 能夠實現多進程併發處理 loader 解析。 隨着 webpack 性能的提高,happyPack 會被逐漸淘汰。
Is it necessary for Webpack 4?
Short answer: maybe not.
Long answer: there's now a competing add-on in the form of a loader for processing files in multiple threads, exactly what HappyPack does. The fact that it's a loader and not a plugin (or both, in case of H.P.) makes it much simpler to configure. Look at thread-loader and if it works for you - that's great, otherwise you can try HappyPack and see which fares better for you.
以上引自 happypack GitHub 的 FAQ。
如今能夠嘗試使用 webpack 提供的 thread-loader, 配置也很簡單。
文中有不少都是我的的觀點,若是有啥不對,你們能夠評論區指出。