webpack
是一個現代JavaScript
應用程序的靜態模塊打包器。
當webpack
處理應用程序時,會遞歸構建一個依賴關系圖,其中包含應用程序需要的每個模塊,然后將這些模塊打包成一個或多個bundle
。
核心概念
-
entry
入口 -
output
輸出 -
loader
模塊轉換器,用于把模塊原內容按照需求轉換成新內容; -
plugins
擴展插件,在webpack
構建流程中的特定時機注入擴展邏輯,來改變構建結果或做你想要做的事情。
作用:模塊化打包
webpack
是一個模塊打包工具,一個打包模塊化JavaScript
的工具,是工程化、自動化思想在前端開發中的體現。
webpack
將項目的資源文件當成一個個模塊,模塊之間會有依賴關系,它會對這些有依賴關系的文件進行處理 --
從入口模塊出發,識別出模塊之間的依賴關系(模塊化導入語句),遞歸找出入口文件的所有依賴,將它們打包到一個或多個文件中(bundle
)。
初始化項目
- 新建一個目錄,
npm init -y
- 安裝核心依賴
npm i webpack webpack-cli -D // webpack4.x.x
局部安裝的命令無法像全局安裝那樣直接使用,需要配合 npx
,它會從當前項目的node_modules
中查找,比如 npx webpack -v
(查看版本號)
從 wepack V4.0.0
開始, webpack
是開箱即用的,在不引入任何配置文件的情況下就可以使用。
- 新建
src/index.js
class Animal {
constructor(name) {
this.name = name;
}
}
const dog = new Animal('dog');
- 構建:
npx webpack --mode=development
,默認production
模式。development
模式可以更好的查看打包后的代碼。
(function(module, exports) {
eval("class Animal {\r\n constructor(name) {\r\n ......
})
默認輸出目錄和文件:dist/main.js
,更多默認配置在node_modules/webpack/lib/WebpackOptionsDefaulter.js
查看dist/main.js
可知,src/index.js
中的代碼并沒有被轉譯為低版本代碼,這顯然不是我們想要的。
babel-loader
loader
是webpack
的四大核心之一,用于對源代碼進行轉換,而轉換工具正是Babel
。
babel-loader
是 Babel
在webpack
的使用方式,可以將JS代碼向低版本轉換。
- 安裝
Babel
相關的依賴npm i @babel/core @babel/preset-env @babel/plugin-transform-runtime -D npm i @babel/runtime @babel/runtime-corejs3 -S npm i babel-loader -D
- 新建
webpack
的配置文件webpack.config.js
建議給// ! Webpack是基于NodeJS的,必須使用CommonJS規范導出一個對象 module.exports = { module: { rules: [ { test: /\.js$/, use: ['babel-loader'], exclude: /node_modules/ //排除 node_modules 目錄 } ] } }
loader
指定include
或exclude
,有效提升編譯效率 - 新建
Babel
配置文件.babelrc
{ "presets": ["@babel/preset-env"], "plugins": [ ["@babel/plugin-transform-runtime", { "corejs": 3 }] ] }
重新編譯,發現代碼已經被Babel
降級轉換了。
如果不想創建.babelrc
,還可以在 webpack.config.js
配置 Babel
use: {
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env"],
plugins: [
["@babel/plugin-transform-runtime", { "corejs": 3 }]
]
}
},
loader配置項
loader
需要配置在 module.rules
中,rules
是一個數組。
- 字段
test
表示匹配規則,對項目中符合規則的文件進行處理; -
loader
的配置方式有多種- 只適用于一個
loader
的情況{ test: /\.js$/, loader: 'babel-loader', options: { //... } }
- 使用
use
字段則比較自由,可以是一個字符串{ test: /\.js$/, use: 'babel-loader' }
- 可以是一個數組
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
- 還可以是一個對象,其
options
屬性用于配置loader
{ test: /\.js$/, use: { loader: 'babel-loader', options: { presets: ["@babel/preset-env"] } }, }
- 只適用于一個
mode
mode
用于指定打包構建時的模式,支持兩個配置項
-
development
將process.env.NODE_ENV
的值設置為development
,啟用NamedChunksPlugin、NamedModulesPlugin
- production
將process.env.NODE_ENV
的值設置為production
,啟用FlagDependencyUsagePlugin、FlagIncludedChunksPlugin、ModuleConcatenationPlugin、NoEmitOnErrorsPlugin、OccurrenceOrderPlugin、SideEffectsFlagPlugin、UglifyJsPlugin(JS壓縮)
mode
可以配置在webpack.config.js
中,然后直接使用 npx webpack
進行編譯。
module.exports = {
mode: "development",
// ...
}
cross-env
這是一個運行跨平臺設置和使用環境變量的腳本。
在配置文件中,經常會使用 process.env.NODE_ENV
來判斷當前是development
還是production
,但process.env
默認并沒有NODE_ENV
。
cross-env
可以兼容運行平臺去設置環境變量NODE_ENV
,比如Windows
系統不支持NODE_ENV=development
的設置方式。
cross-env
能夠提供一個設置環境變量的scripts
,讓你能夠以unix
方式設置環境變量,然后在Windows
上兼容運行。
- 安裝
npm i cross-env -D
- 配置
package.json
"scripts": { "dev": "cross-env NODE_ENV=development webpack", "build": "cross-env NODE_ENV=production webpack" }
- 在
webpack.config.js
中使用process.env.NODE_ENV
,如動態配置mode
const ENV = process.env.NODE_ENV module.exports = { mode: ENV, //... }
構建HTML頁面
編譯HTML
的webpack
插件:html-webpack-plugin
- 安裝插件
npm i html-webpack-plugin -D
- 在根目錄下創建
index.html
,快捷鍵html:5
生成HTML
- 配置
webpack.config.js
,plugins
字段是一個數組,存放所有的webpack
插件// 引入插件 const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { //... plugins: [ new HtmlWebpackPlugin({ template: './index.html', filename: 'index.html', //打包后的文件名 minify: { minimize: true, // 是否打包為最小值 removeAttributeQuotes: true, //是否刪除屬性的引號 collapseWhitespace: false, //去除空格 removeComments: true, // 去除注釋 minifyCSS: true, // 壓縮HTML內的CSS minifyJS: true, //壓縮HTML內的JS removeEmptyElements: true //清理內容為空的元素 }, hash: true //引入產出資源時加上哈希,避免緩存 }) ] }
- 執行
npm run dev
構建,生成dist/index.html
,且自動注入了<script>
腳本,引入打包后的js
文件<script src="main.js?ccec26148a41640c2cf0"></script> // hash: true 在引入資源后加上哈希值
html-webpack-plugin
中的 config
妙用
html-webpack-plugin
插件在解析HTML
時,支持類似ejs
語法。同時會向HTML
中注入該插件的對象htmlWebpackPlugin
,使用其中配置的各種屬性。
例如,我們想自由配置開發版和發布版的頁面展示不同的內容;
- 新增一個自定義的配置文件
public/config.js
module.exports = { development: { // 開發配置項 template: { title: 'Hello development', header: false, // 不要頭部和尾部 footer: false } }, production: { // 發布配置項 template: { title: 'Hi production', header: true, // 只要頭部 footer: false } } }
-
webpack.config.js
中配置html-webpack-plugin
的config
屬性const HtmlWebpackPlugin = require('html-webpack-plugin'); const ENV = process.env.NODE_ENV const config = require('./public/config')[ENV] module.exports = { mode: ENV, //... plugins: [ new HtmlWebpackPlugin({ template: './index.html', filename: 'index.html', config: config.template }) ] }
-
index.html
<!DOCTYPE html> <html lang="en"> <head> // ... <% if(htmlWebpackPlugin.options.config.header) { %> <link rel="stylesheet" type="text/css" href="http://common/css/header.css"> <% } %> <title><%= (htmlWebpackPlugin.options.config.title) %></title> </head> <body> hello back </body> <% if(htmlWebpackPlugin.options.config.footer) { %> <script src="http://common/header.min.js" type="text/javascript"></script> <% } %> </html>
- 分別使用
npm run dev
和npm run build
構建,對比dist/index.html
的不同
實時展示頁面
webpack-dev-server
用于配置本地服務器,可以為webpack
打包生成的資源文件提供web
服務
- 為靜態文件提供
web
服務 - 自動刷新和熱替換
(HMR)
npm i webpack-dev-server -D
修改package.json
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server",
"build": "cross-env NODE_ENV=production webpack"
},
webpack.config.js
中進行webpack-dev-server
的其他配置
module.exports = {
//...
devServer: {
open: true, // 自動打開首頁,默認false
port: '3000', // 默認是8080
hot: true, // 熱加載
clientLogLevel: "warning", // 日志等級
compress: true, // 是否啟用 gzip 壓縮
proxy: // 代理
// ...
}
}
執行npm run dev
,現在修改 src/index.js
,頁面控制臺會實時刷新,當前刷新的是整個頁面。
更多的配置可以點擊查看。
devtool
sourcemap
是為了解決實際運行代碼(打包后)出現問題時,無法定位到開發環境中的源代碼;
查看頁面控制臺上打印的log
日志,會發現對應的行號是代碼編譯后的,而不是當前源碼中的行號。
devtool
配置項可以幫助我們將編譯后的代碼映射回源碼中,不同的值會明顯影響構建和重新構建的速度。
綜合構建速度,在開發模式下通常設置為 cheap-module-eval-source-map
;在生產環境下則設置為source-map
// webpack.config.js
module.exports = {
devtool: 'cheap-module-eval-source-map'
}
-
sourcemap
的5種基本devtool
選項-
eval
每個模塊都使用eval()
執行,每個模塊后會增加sourceURL
來關聯模塊處理前后的對應關系;
由于會映射到轉換后的代碼,而不是映射到原始代碼(沒有從loader
中獲取sourcemap
),所以不能正確顯示行號;又因為不需要生成模塊的sourcemap
,所以打包速度很快。 -
source-map
會為模塊生成獨立的sourcemap(.map)
文件,我們可以根據報錯信息和.map
文件進行錯誤分析,定位到源碼; -
inline
不會生成獨立的.map
文件,sourcemap
轉換為DataUrl
后添加到bundle
中; -
cheap
在打包后同樣會為每個模塊生成.map
文件,但與source-map
的區別在于,它生成的.map
文件會忽略原始代碼中的列信息,也不包含loader
的sourcemap
; -
moudle
:包含了loader
模塊之間的sourcemap
,將loader source map
簡化為每行一個映射;
-
- 基本類型之間可以相互搭配,如
eval-source-map、inline-cheap-model-source-map、cheap-module-eval-source-map、hidden-source-map、nosources-source-map
- 開發環境常用:
eval、eval-source-map、cheap-eval-source-map、cheap-module-eval-source-map
- 生產環境常用:
none(省略devtool選項,不生成sourcemap)、source-map、hidden-source-map、nosources-source-map
- 開發環境常用:
-
source-map
和hidden-source-map
都會打包生成單獨的.map
文件。區別在于,source-map
會在打包出的js
文件中增加一個引用注釋,以便開發工具知道在哪里可以找到它;hidden-source-map
則不會在打包的js
中增加引用注釋。
但是我們一般不會直接將 .map
文件部署到CDN
,因為會直接映射到源碼,更希望將 .map
文件傳到錯誤解析系統,然后根據上報的錯誤信息,直接解析到出錯的源碼位置。
CSS樣式
webpack
處理css
也必須借助loader
:style-loader、css-loader
考慮到兼容性問題,還需要 postcss-loader
配合 autoprefixer
插件。
對于Less、Sass
,還需要 less-loader
和 sass-loader
// 以 less 為例
npm i style-loader css-loader less-loader postcss-loader autoprefixer less -D
-
style-loader
動態創建style
標簽,將css
插入到head
中; -
css-loader
負責編譯CSS
,處理@import
等語句; -
postcss-loader
和autoprefixer
,自動生成瀏覽器兼容性前綴; -
less-loader
負責處理編譯.less
文件,將其轉為css
配置 webpack.config.js
test: /\.(le|c)ss$/,
use: ['style-loader', 'css-loader', {
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')({ // 應該配置到 package.json 或 .browserslistrc
overrideBrowserslist: ['ie >= 8', 'Firefox >= 20', 'Safari >= 5',
'Android >= 4', 'Ios >= 6',
'last 4 version' // 瀏覽器最新的4個版本
]
})]
}
}, 'less-loader']
應該將 overrideBrowserslist
配置到package.json
中
// package.json
"browserslist": [
"ie >= 8", "Firefox >= 20", "Safari >= 5",
"Android >= 4", "Ios >= 6", "last 4 version"
]
// webpack.config.js
test: /\.(le|c)ss$/,
use: ['style-loader', 'css-loader', {
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')]
}
}, 'less-loader']
在 src/index.js
中引入CSS
嘗試一下吧:import './base.less';
注意:loader
的執行順序 從右向左。另外,loader
中還有一個指定優先級的參數enforce --> 值 pre(優先執行),post (滯后執行)
文件處理
CSS
中引入圖片、字體等資源文件時,如background-image: url(./a.png)
,需要使用 url-loader
或 file-loader
來處理。
它們功能類似,但url-loader
可以限制文件大小,返回DataURL
。另外,使用url-loader
時也必須安裝file-loader
,但不要同時配置,會沖突。
-
安裝與配置
npm install url-loader file-loader -D //webpack.config.js test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/, use: [ { loader: 'url-loader', options: { limit: 10240, // 10K 的限制 esModule: false } } ]
-
limit
資源小于10K
時,則轉為base64
;超過10K
則將圖片拷貝到dist
目錄。base64
可以較少網絡請求,但base64
數據太大或資源太多,都會導致加載變慢,所以設置limit
時要兩者兼顧; -
esModule
設置為false
,否則<img src={require('XXX.jpg')} />
會出現<img src=[Module Object] />
-
-
在
CSS
中引入圖片資源body { box-sizing: border-box; background-image: url(./images/bg.png); }
-
默認情況下,打包生成的資源名是文件內容的
MD5
哈希值,并會保留其擴展名。// 圖片默認拷貝到 dist 根目錄 background-image: url(c5c9a52347e5c5899c2bca539cd016f2.png);
url-loader options
的更多配置
- 值
-
[hash]
:內容的哈希值,作為打包后的默認文件名,[hash:6]
表示取哈希值的前6
位作為文件名; -
[ext]
:資源文件的原擴展名; -
[name]
:資源文件的原名稱; -
[path]
:資源相對于context
的路徑,即默認相對于webpack.config.js
的路徑。
-
- 屬性
-
name
:配置打包后的文件名,默認值為[hash].[ext]
-
context
:配置文件的上下文,默認為webpack.config.js
與[path]
配合使用可以控制打包后的文件目錄,默認相對于webpack.config.js
1. 假設 bg.png 存放目錄 src/images,src目錄與webpack.config.js同級別 2. 配置文件名 options: { name: '[path][name].[ext]' } 3. 編譯打包后,bg.png 輸出目錄 dist/src/images background-image: url(src/images/bg.png); 4. 配置context,假設項目目錄名為webtest options: { name: '[path][name].[ext]', context: '../' // 向上一級,webpack.config.js上一級目錄是項目目錄 } 5. 編譯打包后,bg.png 輸出目錄 dist/webtest/src/images background-image: url(webtest/src/images/bg.png);
-
publicPath
:文件的public
發布目錄,可以使用該屬性配置資源的CDN
路徑options: { name: '[name]_[hash:6].[ext]', publicPath: 'http://www.xxx.com/img' } background-image: url(http://www.xxx.com/img/bg_c5c9a5.png);
-
outputPath
:文件的output
輸出目錄,默認會拷貝到dist
根目錄options: { name: '[name].[ext]', outputPath: 'assets/' } // 匹配的資源文件會拷貝到 dist/assets 目錄中 background-image: url(assets/bg.png);
-
HTML中的本地資源
標簽<img />
也可以引入圖片資源,但url-loader
是不能直接處理的,需要使用html-loader
。
html-loader
處理HTML
中的<img />
時,還需要結合url-loader/file-loader
才可以將<img />
的路徑正確打包,同時記得配置url-loader
的 esModule: false
<img src="./imgs/a.png" />
npm i html-loader -D
# webpack.config.js
test: /\.html$/,
loader: 'html-loader',
options: {
attributes: false,
// attributes: {
// list: [
// { tag: 'img', attribute: 'src', type: 'src' },
// // ...
// ]
// },
}
除了img src
,html-loader
還可以處理video src
。
但是,html-loader
會導致html-webpack-plugin
解析HTML
中的<% ... %>
失敗。
- 刪除
html-loader
- 修改
img src
圖片會經過<img src="<%= require('./imgs/a.png') %>" />
url-loader
的處理,小于10K(limit: 10240)
將被轉為base64