天天看點

Webpack配置全解析(基礎篇)

Webpack配置全解析(基礎篇)

  Webpack憑借強大的功能,成為最流行和最活躍的打包工具,也是面試時進階程式員必須掌握的“軟技能”;筆者結合在項目中的使用經驗,介紹webpack的使用;本文是入門篇,主要介紹webpack的入口、輸出和各種loader、plugins的使用以及開發環境的搭建。本文所有的demo代碼均在https://github.com/acexyf/WebpackDemo

概念

  來看一下官網對webpack的定義:

Webpack配置全解析(基礎篇)

本質上,webpack 是一個現代 JavaScript 應用程式的靜态子產品打包器(module bundler)。當 webpack 處理應用程式時,它會遞歸地建構一個依賴關系圖(dependency graph),其中包含應用程式需要的每個子產品,然後将所有這些子產品打包成一個或多個bundle。

  首先webpack是一個靜态子產品打包器,所謂的靜态子產品,包括腳本、樣式表和圖檔等等;webpack打包時首先周遊所有的靜态資源,根據資源的引用,建構出一個依賴關系圖,然後再将子產品劃分,打包出一個或多個bundle。再次白piao一下官網的圖,生動的描述了這個過程:

Webpack配置全解析(基礎篇)

  提到webpack,就不得不提webpack的四個核心概念

Webpack配置全解析(基礎篇)

入口(entry):訓示 webpack 應該使用哪個子產品,來作為建構其内部依賴圖的開始

Webpack配置全解析(基礎篇)

輸出(output):在哪裡輸出它所建立的 bundles

Webpack配置全解析(基礎篇)

loader:讓 webpack 能夠去處理那些非 JavaScript 檔案

Webpack配置全解析(基礎篇)

插件(plugins):用于執行範圍更廣的任務

你的第一個打包器

  我們首先在全局安裝webpack:

npm install webpack webpack-cli –g

  webpack可以不使用配置檔案,直接通過指令行建構,用法如下:

webpack <entry> [<entry>] -o <output>

  這裡的entry和output就對應了上述概念中的入口和輸入,我們來建立一個入口檔案:

Webpack配置全解析(基礎篇)

  有了入口檔案我們還需要通過指令行定義一下輸入路徑dist/bundle.js:

webpack index.js -o dist/bundle.js

  這樣webpack就會在dist目錄生成打包後的檔案。

Webpack配置全解析(基礎篇)

  我們也可以在項目目錄建立一個html引入打包後的bundle.js檔案檢視效果。

配置檔案

  指令行的打包方式僅限于簡單的項目,如果我們的項目較為複雜,有多個入口,我們不可能每次打包都把入口記下來;是以一般項目中都使用配置檔案來進行打包;配置檔案的指令方式如下:

webpack [--config webpack.config.js]

  配置檔案預設的名稱就是

webpack.config.js

,一個項目中經常會有多套配置檔案,我們可以針對不同環境配置不同的檔案,通過

--config

來進行切換:

//生産環境配置

webpack --config webpack.prod.config.js

//開發環境配置

webpack --config webpack.dev.config.js

多種配置類型

  config配置檔案通過

module.exports

導出一個配置對象:

Webpack配置全解析(基礎篇)

  除了導出為對象,還可以導出為一個函數,函數中會帶入指令行中傳入的環境變量等參數,這樣可以更友善的對環境變量進行配置;比如我們在打包線上正式環境和線上開發環境可以通過

env

進行區分:

Webpack配置全解析(基礎篇)

  另外還可以導出為一個Promise,用于異步加載配置,比如可以動态加載入口檔案:

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

入口

  正如在上面提到的,入口是整個依賴關系的起點入口;我們常用的單入口配置是一個頁面的入口:

Webpack配置全解析(基礎篇)

  它是下面的簡寫:

Webpack配置全解析(基礎篇)

  但是我們一個頁面可能不止一個子產品,是以需要将多個依賴檔案一起注入,這時就需要用到數組了,代碼在demo2中:

Webpack配置全解析(基礎篇)

  有時候我們一個項目可能有不止一個頁面,需要将多個頁面分開打包,entry支援傳入對象的形式,代碼在demo3中:

Webpack配置全解析(基礎篇)

  這樣webpack就會建構三個不同的依賴關系。

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

輸出

  

output

選項用來控制webpack如何輸入編譯後的檔案子產品;雖然可以有多個entry,但是隻能配置一個

output

Webpack配置全解析(基礎篇)

  這裡我們配置了一個單入口,輸出也就是bundle.js;但是如果存在多入口的模式就行不通了,webpack會提示

Conflict: Multiple chunks emit assets to the same filename

,即多個檔案資源有相同的檔案名稱;webpack提供了

占位符

來確定每一個輸出的檔案都有唯一的名稱:

Webpack配置全解析(基礎篇)

  這樣webpack打包出來的檔案就會按照入口檔案的名稱來進行分别打包生成三個不同的bundle檔案;還有以下不同的占位符字元串:

Webpack配置全解析(基礎篇)

  在這裡引入Module、Chunk和Bundle的概念,上面代碼中也經常會看到有這兩個名詞的出現,那麼他們三者到底有什麼差別呢?首先我們發現module是經常出現在我們的代碼中,比如module.exports;而Chunk經常和entry一起出現,Bundle總是和output一起出現。

Webpack配置全解析(基礎篇)

module:我們寫的源碼,無論是commonjs還是amdjs,都可以了解為一個個的module

Webpack配置全解析(基礎篇)

chunk:當我們寫的module源檔案傳到webpack進行打包時,webpack會根據檔案引用關系生成chunk檔案,webpack 會對這些chunk檔案進行一些操作

Webpack配置全解析(基礎篇)

bundle:webpack處理好chunk檔案後,最後會輸出bundle檔案,這個bundle檔案包含了經過加載和編譯的最終源檔案,是以它可以直接在浏覽器中運作。

  我們通過下面一張圖更深入的了解這三個概念:

Webpack配置全解析(基礎篇)

總結:

  module,chunk 和 bundle 其實就是同一份邏輯代碼在不同轉換場景下的取了三個名字:我們直接寫出來的是module,webpack處理時是chunk,最後生成浏覽器可以直接運作的bundle。

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

hash、chunkhash、contenthash

  了解了chunk的概念,相信上面表中chunkhash和hash的差別也很容易了解了;

Webpack配置全解析(基礎篇)

hash:是跟整個項目的建構相關,隻要項目裡有檔案更改,整個項目建構的hash值都會更改,并且全部檔案都共用相同的hash值。

Webpack配置全解析(基礎篇)

chunkhash:跟入口檔案的建構有關,根據入口檔案建構對應的chunk,生成每個chunk對應的hash;入口檔案更改,對應chunk的hash值會更改。

Webpack配置全解析(基礎篇)

contenthash:跟檔案内容本身相關,根據檔案内容建立出唯一hash,也就是說檔案内容更改,hash就更改。

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

模式

  在webpack2和webpack3中我們需要手動加入插件來進行代碼的壓縮、環境變量的定義,還需要注意環境的判斷,十分的繁瑣;在webpack4中直接提供了模式這一配置,開箱即可用;如果忽略配置,webpack還會發出警告。

Webpack配置全解析(基礎篇)

  開發模式是告訴webpack,我現在是開發狀态,也就是打包出來的内容要對開發友好,便于代碼調試以及實作浏覽器實時更新。

Webpack配置全解析(基礎篇)

  生産模式不用對開發友好,隻需要關注打包的性能和生成更小體積的bundle。看到這裡用到了很多Plugin,不用慌,下面我們會一一解釋他們的作用。

  相信很多童鞋都曾有過疑問,為什麼這邊DefinePlugin定義環境變量的時候要用

JSON.stringify("production")

,直接用

"production"

不是更簡單嗎?

  我們首先來看下

JSON.stringify("production")

生成了什麼;運作結果是

""production""

,注意這裡,并不是你眼睛花了或者螢幕上有小黑點,結果确實比

"production"

多嵌套了一層引号。

  我們可以簡單的把DefinePlugin這個插件了解為将代碼裡的所有

process.env.NODE_ENV

替換為字元串中的

内容

。假如我們在代碼中有如下判斷環境的代碼:

Webpack配置全解析(基礎篇)

  這樣生成出來的代碼就會編譯成這樣:

Webpack配置全解析(基礎篇)

  但是我們代碼中可能并沒有定義

production

變量,是以會導緻代碼直接報錯,是以我們需要通過JSON.stringify來包裹一層:

Webpack配置全解析(基礎篇)

  這樣編譯出來的代碼就沒有問題了。

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

自動生成頁面

  在上面的代碼中我們發現都是手動來生成index.html,然後引入打包後的bundle檔案,但是這樣太過繁瑣,而且如果生成的bundle檔案引入了hash值,每次生成的檔案名稱不一樣,是以我們需要一個自動生成html的插件;首先我們需要安裝這個插件:

npm install --save-dev html-webpack-plugin

  在demo3中,我們生成了三個不同的bundle.js,我們希望在三個不同的頁面能分别引入這三個檔案,如下修改config檔案:

Webpack配置全解析(基礎篇)

  我們以index.html作為模闆檔案,生成home、list、detail三個不同的頁面,并且通過chunks分别引入不同的bundle;如果這裡不寫chunks,每個頁面就會引入所有生成出來的bundle。

  html-webpack-plugin還支援以下字段:

Webpack配置全解析(基礎篇)

  上面設定title後需要在模闆檔案中設定模闆字元串:

<title><%= htmlWebpackPlugin.options.title %></title>

loader

  loader用于對子產品module的源碼進行轉換,預設webpack隻能識别commonjs代碼,但是我們在代碼中會引入比如vue、ts、less等檔案,webpack就處理不過來了;loader拓展了webpack處理多種檔案類型的能力,将這些檔案轉換成浏覽器能夠渲染的js、css。

  

module.rules

允許我們配置多個loader,能夠很清晰的看出目前檔案類型應用了哪些loader,loader的代碼均在demo4中。

Webpack配置全解析(基礎篇)

  我們可以看到rules屬性值是一個數組,每個數組對象表示了不同的比對規則;test屬性時一個正規表達式,比對不同的檔案字尾;use表示比對了這個檔案後調用什麼loader來處理,當有多個loader的時候,use就需要用到數組。

  多個loader支援鍊式傳遞,能夠對資源進行流水線處理,上一個loader處理的傳回值傳遞給下一個loader;loader處理有一個優先級,從右到左,從下到上;在上面demo中對css的處理就遵從了這個優先級,css-loader先處理,處理好了再給style-loader;是以我們寫loader的時候也要注意前後順序。

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

css-loader和style-loader

  css-loader和style-loader從名稱看起來功能很相似,然而兩者的功能有着很大的差別,但是他們經常會成對使用;安裝方法:

npm i -D css-loader style-loader

  css-loader用來解釋@import和url();style-loader用來将css-loader生成的樣式表通過

<style>标簽

,插入到頁面中去。

Webpack配置全解析(基礎篇)

  然後在入口檔案中将index.css引入,就能看到打包的效果,頁面中插入了三個style标簽,代碼在demo4:

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

sass-loader和less-loader

  這兩個loader看名字大家也能猜到了,就是用來處理sass和less樣式的。安裝方法:

npm i -D sass-loader less-loader node-sass

  在config中進行配置,代碼在demo4:

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

postcss-loader

  都0202年了,小夥伴肯定不想一個一個的手動添加-moz、-ms、-webkit等浏覽器私有字首;postcss提供了很多對樣式的擴充功能;啥都不說,先安裝起來:

npm i -D postcss-loader

  老規矩,還是在config中進行配置:

Webpack配置全解析(基礎篇)

  正當我們興沖沖的打包看效果時,發現樣式還是老樣子,并沒有什麼改變。

Webpack配置全解析(基礎篇)

  這是因為postcss主要功能隻有兩個:第一就是把css解析成JS可以操作的抽象文法樹AST,第二就是調用插件來處理AST并得到結果;是以postcss一般都是通過插件來處理css,并不會直接處理,是以我們需要先安裝一些插件:

npm i -D autoprefixer postcss-plugins-px2rem cssnano

  在項目根目錄建立一個

.browserslistrc

檔案。

> 0.25%

last 2 versions

  我們将postcss的配置單獨提取到項目根目錄下的

postcss.config.js

Webpack配置全解析(基礎篇)

  有了

autoprefixer

插件,我們打包後的css就自動加上了字首。

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

babel-loader

  相容低版本浏覽器的痛相信很多童鞋都經曆過,寫完代碼發現自己的js代碼不能運作在IE10或者IE11上,然後嘗試着引入各種polyfill;babel的出現給我們提供了便利,将高版本的ES6甚至ES7轉為ES5;我們首先安裝babel所需要的依賴:

Webpack配置全解析(基礎篇)

  然後在config添加loader對js進行處理:

Webpack配置全解析(基礎篇)

  同樣的,我們把babel的配置提取到根目錄,建立一個

.babelrc

檔案:

Webpack配置全解析(基礎篇)

  我們可以在index.js中嘗試寫一些es6的文法,看到代碼會被轉譯成es5,代碼在demo4中。由于babel-loader的轉譯速度很慢,在後面我們加入了時間插件後可以看到每個loader的耗時,babel-loader是最耗時間;是以我們要盡可能少的使用babel來轉譯檔案,我們對config進行改進,

Webpack配置全解析(基礎篇)

  正則上使用

$

來進行精确比對,通過exclude将node_modules中的檔案進行排除,include将隻比對src中的檔案;可以看出來include的範圍比exclude更縮小更精确,是以也是推薦使用include。

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

file-loader和url-loader

  file-loader和url-loader都是用來處理圖檔、字型圖示等檔案;url-loader工作時分兩種情況:當檔案大小小于limit參數,url-loader将檔案轉為base-64編碼,用于減少http請求;當檔案大小大于limit參數時,調用file-loader進行處理;是以我們優先使用url-loader,首先還是進行安裝,安裝url-loader之前還需要把file-loader先安裝:

npm i file-loader url-loader -D

  接下來還是修改config:

Webpack配置全解析(基礎篇)

  我們在css中給body添加一個小于10k的居中背景圖檔:

Webpack配置全解析(基礎篇)

  打包後檢視body的樣式可以發現圖檔已經被替換成base64格式的url了,代碼在demo4。

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

html-withimg-loader

  如果我們在頁面上引用一個圖檔,會發現打包後的html還是引用了src目錄下的圖檔,這樣明顯是錯誤的,是以我們還需要一個插件對html引用的圖檔進行處理:

npm i -D html-withimg-loader

  老樣子還是在config中對html進行配置:

Webpack配置全解析(基礎篇)

  然鵝,打開頁面發現卻是這樣的:

Webpack配置全解析(基礎篇)

  這是因為在url-loader中把每個圖檔作為一個子產品來處理了,我們還需要去url-loader中修改:

Webpack配置全解析(基礎篇)

  這樣我們在頁面上的圖檔引用也被修改了,代碼在demo4中。

  html-withimg-loader會導緻html-webpack-plugin插件注入title的模闆字元串

<%= htmlWebpackPlugin.options.title %>

失效,原封不動的展示在頁面上;是以,如果我們想保留兩者的功能需要在配置config中把html-withimg-loader删除并且通過下面的方式來引用圖檔:

<img src="<%=require('./src/bg1.png') %>" alt="" srcset="">

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

vue-loader

  最後說一下一個比較特殊的vue-loader,看名字就知道是用來處理vue檔案的。

npm i -D vue-loader vue-template-compiler

npm i -S vue

  我們首先來建立一個vue檔案,具體代碼在demo5中:

Webpack配置全解析(基礎篇)

  然後在webpack的入口檔案中引用它:

Webpack配置全解析(基礎篇)

  不過vue-loader和其他loader不太一樣,除了将它和

.vue

檔案綁定之外,還需要引入它的一個插件:

Webpack配置全解析(基礎篇)

  這樣我們就能愉快的在代碼中寫vue了。

搭建開發環境

  在上面的demo中我們都是通過指令行打包生成dist檔案,然後直接打開html或者通過

static-server

來檢視頁面的;但是開發中我們寫完代碼每次都來打包會嚴重影響開發的效率,我們期望的是寫完代碼後立即就能夠看到頁面的效果;webpack-dev-server就很好的提供了一個簡單的web伺服器,能夠實時重新加載。

  首先在我們的項目中安裝依賴:

npm i -D webpack webpack-dev-server

  webpack-dev-server的用法和wepack一樣,隻不過他會額外啟動一個express的伺服器。我們在項目中建立一個

webpack.dev.config.js

配置檔案,單獨對開發環境進行一個配置,相關代碼在demo6中:

Webpack配置全解析(基礎篇)

  通過指令行

webpack-dev-server

來啟動伺服器,啟動後我們發現根目錄并沒有生成任何檔案,因為webpack打包到了記憶體中,不生成檔案的原因在于通路記憶體中的代碼比通路檔案中的代碼更快。

  我們在public/index.html的頁面上有時候會引用一些本地的靜态檔案,直接打開頁面的會發現這些靜态檔案的引用失效了,我們可以修改server的工作目錄,同時指定多個靜态資源的目錄:

Webpack配置全解析(基礎篇)

  熱更新(Hot Module Replacemen簡稱HMR)是在對代碼進行修改并儲存之後,webpack對代碼重新打包,并且将新的子產品發送到浏覽器端,浏覽器通過新的子產品替換老的子產品,這樣就能在不重新整理浏覽器的前提下實作頁面的更新。

Webpack配置全解析(基礎篇)

  可以看出浏覽器和webpack-dev-server之間通過一個websock進行連接配接,初始化的時候client端儲存了一個打包後的hash值;每次更新時server監聽檔案改動,生成一個最新的hash值再次通過websocket推送給client端,client端對比兩次hash值後向伺服器發起請求傳回更新後的子產品檔案進行替換。

  我們點選源碼旁的行數看一下編譯後的源碼是什麼樣的:

Webpack配置全解析(基礎篇)

  發現跟我們的源碼差距還是挺大的,本來是一個簡單add函數,通過webpack的子產品封裝,已經很難了解原來代碼的含義了,是以,我們需要将編譯後的代碼映射回源碼;devtool中不同的配置有不同的效果和速度,綜合性能和品質後,我們一般在開發環境使用

cheap-module-eval-source-map

,在生産環境使用

source-map

Webpack配置全解析(基礎篇)

  其他各模式的對比:

Webpack配置全解析(基礎篇)

plugins

  在上面我們也介紹了DefinePlugin、HtmlWebpackPlugin等很多插件,我們發現這些插件都能夠不同程度的影響着webpack的建構過程,下面還有一些常用的插件,plugins相關代碼在demo7中。

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

clean-webpack-plugin

  clean-webpack-plugin用于在打包前清理上一次項目生成的bundle檔案,它會根據output.path自動清理檔案夾;這個插件在生産環境用的頻率非常高,因為生産環境經常會通過hash生成很多bundle檔案,如果不進行清理的話每次都會生成新的,導緻檔案夾非常龐大;這個插件安裝使用非常友善:

npm i -D clean-webpack-plugin

  安裝後我們在config中配置一下就可以了:

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

mini-css-extract-plugin

  我們之前的樣式都是通過style-loader插入到頁面中去,但是生産環境需要單獨抽離樣式檔案,mini-css-extract-plugin就可以幫我從js中剝離樣式:

npm i -D mini-css-extract-plugin

  我們在開發環境使用style-loader,生産環境使用mini-css-extract-plugin:

Webpack配置全解析(基礎篇)

  引入loader後,我們還需要配置plugin,提取的css同樣支援

output.filename

中的占位符字元串。

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

optimize-css-assets-webpack-plugin

  我們可以發現雖然配置了

production

模式,打包出來的js壓縮了,但是打包出來的css确沒有壓縮;在生産環境我們需要對css進行一下壓縮:

npm i optimize-css-assets-webpack-plugin -D

  然後也是引入插件:

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

copy-webpack-plugin

  和demo6中一樣,我們在public/index.html中引入了靜态資源,但是打包的時候webpack并不會幫我們拷貝到dist目錄,是以copy-webpack-plugin就可以很好地幫我做拷貝的工作了

npm i -D copy-webpack-plugin

  在config中配置我們需要拷貝的源路徑和目标路徑:

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

ProvidePlugin

  ProvidePlugin可以很快的幫我們加載想要引入的子產品,而不用require。一般我們加載jQuery需要先把它import:

Webpack配置全解析(基礎篇)

  但是我們在config中配置ProvidePlugin插件後能夠不用import,直接使用

$

Webpack配置全解析(基礎篇)

  但是如果在項目中引入了太多子產品并且沒有require會讓人摸不着頭腦,是以建議加載一些常見的比如jQuery、vue、lodash等。

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

loader和plugin的差別

  介紹了這麼多loader和plugin,我們來回顧一下他們兩者的差別:

loader:由于webpack隻能識别js,loader相當于翻譯官的角色,幫助webpack對其他類型的資源進行轉譯的預處理工作。plugins:plugins擴充了webpack的功能,在webpack運作時會廣播很多事件,plugin可以監聽這些事件,然後通過webpack提供的API來改變輸出結果。

Webpack配置全解析(基礎篇)
Webpack配置全解析(基礎篇)

總結

  最後,介紹了這麼多,本文是webpack基礎篇,還有很多生産環境的優化還沒有寫到;是以各位看官敬請期待優化篇。

繼續閱讀