Loaders
說到webpack,自然離不開他的一大堆loaders,沒有loaders的webpack隻能打包js檔案,得益于衆多的loaders,我們可以打包各種類型的檔案。
loaders實際上可以了解為一種打包方案,webpack本身碰到除了js檔案之外的其他類型檔案時,就不知道該如何打包了,然後打包就會失敗,而loaders則是對應某種檔案類型的檔案一種單獨的打包方案,通過config配置了loaders之後,webpack就可以打包該類型的檔案了
打包圖檔
圖檔可以使用file-loader來進行打包,在項目目錄執行npm i file-loader -D之後進行一些簡單的配置就可以了
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
module: {
rules: [
{
// 要打包的檔案類型
test: /\.(png|jpg|gif)$/,
use: [
{
// 使用file-loader打包
loader: "file-loader",
options: {
// 生成的檔案名是 原檔案名_哈希值.原檔案擴充名
name:'[name]_[hash].[ext]',
// 輸出目錄是在打包目錄下的images檔案夾下
outputPath:'images/'
},
},
],
},
],
},
};
也可以使用url-loader打包,url-loader會多一個limit配置項,低于該大小的檔案會直接被轉換成base64,不會再被打包
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
module: {
rules: [
{
// 要打包的檔案類型
test: /\.(png|jpg|gif)$/,
use: [
{
// 使用file-loader打包
loader: "file-loader",
options: {
// 生成的檔案名是 原檔案名_哈希值.原檔案擴充名
name:'[name]_[hash].[ext]',
// 輸出目錄是在打包目錄下的images檔案夾下
outputPath:'images/',
// 機關是位元組,這裡代表大于10kb的檔案正常打包,小于10kb的檔案轉成base64
limit:10240
},
},
],
},
],
},
};
引入2張圖檔作為測試
// index.js
import sayHi from './sayHi'
// 小的圖檔大小為5kb
import smallImg from './images/small.png'
// 大的圖檔大小為15kb
import bigImg from './images/big.png'
// 将圖檔挂載至頁面
const root = document.getElementById('root');
const smallImage = new Image()
smallImage.src = smallImg
const bigImage = new Image()
bigImage.src = bigImg
root.append(smallImage)
root.append(bigImage)
sayHi()
可以看到打包後的images檔案夾下隻有1張大的圖檔

但是頁面上2張圖檔均可以正常顯示
打包樣式
普通的css需要通過style-loader和css-loader來打包
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
module: {
rules: [
{
test:/\.css$/,
use:[
// style-loader将樣式挂載至dom節點
'style-loader',
// css-loader将css檔案整理為字元串
'css-loader'
]
}
],
},
};
css預編譯語言需要額外使用對應語言的loader來多做一次解析,這裡以less為示例
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
module: {
rules: [
{
test:/\.less$/,
use:[
'style-loader',
{
loader:'css-loader',
options:{
// 如果我們引入的less檔案裡通過@import引入了其他的less檔案,那引入的檔案預設會直接進入css-loader導緻出錯
// 這裡是配置在進入css-loader之前要經過幾個loader,這裡前面隻有1個less-loader,故配置為1
importLoaders: 1,
}
},
'less-loader'
]
}
],
},
};
webpack打包的樣式預設是全局通用的,這也就導緻會出現樣式穿透的問題,我們可以通過modules配置來解決
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
module: {
rules: [
{
test:/\.less$/,
use:[
'style-loader',
{
loader:'css-loader',
options:{
importLoaders: 1,
// 開啟子產品化樣式
modules:true
}
},
'less-loader'
]
}
],
},
};
引入樣式時候就需要發生一些改變
// 非子產品化引用
import './index.less'
// 子產品化引用
import style from './index.less'
// 使用子產品化之後的樣式
el.className = style.xxx
還有諸多其他各種類型的loader,可以讓我們完成各種各樣的打包需求
附上文檔位址
在需要打包不同的檔案時,查閱文檔即可
Plugins
plugins(插件)可以在webpack運作到某些時刻時候,幫你做一些事情,比如clean-webpack-plugin可以在打包開始前幫我們清理掉上一次打包後的檔案,而html-webpack-plugin則可以在打包後自動生成一個引用了打包完成的js檔案的html檔案,并且支援指定模闆
const path = require("path");
// 引入插件
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
module: {
...
},
// 使用插件
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
// 自定義title
title:'html模闆',
// 自定義檔案名
filename:'index.html',
// 自定義使用模闆的路徑
template:'src/public/index.html'
}),
],
};
devServer
webpack-dev-server 提供了一個簡單的 web 伺服器,并且能夠實時重新加載,安裝後在webpack.config中做簡單的配置即可使用
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
devServer:{
// 告訴devServer在哪裡查找檔案
contentBase: './myDist',
// 啟動後自動浏覽器打開
open:true,
// 端口
port:4396
},
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
...
};
但是這裡有一個坑,我用的是webpack5.x版本,運作後報錯了
查閱了文檔的各種配置依舊沒有搞清楚問題所在,遂求助百度
原來是版本的問題
webpack、webpack-cli、webpack-dev-server是需要版本比對的
找到了一個可以運作的版本
“webpack”: “^4.43.0”,
“webpack-cli”: “^3.3.12”,
“webpack-dev-server”: “^3.11.0”
安裝代碼
更新版本之後,dev-server就可以正常使用了
但不得不說webpack的文檔實在是難以恭維…
子產品熱替換(HMR)
子產品熱替換(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一,它允許在運作時更新各種子產品,而無需進行完全重新整理。
現在我們的dev-server在運作的時候,更改代碼雖然效果會更新,但是這個更新是直接重新整理頁面來更新的,而使用了HMR之後,就可以做到不重新整理頁面直接更新了
功能的啟用很簡單,插件也已經内置在webpack内部無需額外安裝
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 引入webpack
const webpack = require("webpack")
module.exports = {
entry: "./src/index.js",
devServer:{
contentBase: './myDist',
open:true,
port:4396,
// 啟用子產品熱更新
hot:true
},
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title:'html模闆',
filename:'index.html',
template:'src/public/index.html'
}),
// 使用插件
new webpack.HotModuleReplacementPlugin()
],
};
這樣就可以實作HMR了,接下來修改樣式部分的代碼,頁面可以做到不重新整理直接更新,但js部分的代碼還是會重新整理的,如果想做到js代碼不重新整理,則需要通過HMR的api accept來手動配置相應的邏輯
首先要在配置檔案裡加多一個hotOnly配置
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 引入webpack
const webpack = require("webpack")
module.exports = {
entry: "./src/index.js",
devServer:{
contentBase: './myDist',
open:true,
port:4396,
// 啟用子產品熱更新
hot:true,
// 隻使用熱更新來更新頁面,不會自動重新整理頁面來更新
hotOnly:true
},
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title:'html模闆',
filename:'index.html',
template:'src/public/index.html'
}),
// 使用插件
new webpack.HotModuleReplacementPlugin()
],
};
然後在引用了其他子產品的代碼裡加上對應處理的邏輯
這裡sayHi添加了一個id為hi的dom節點
// index.js
import sayHi from "./sayHi";
if (module.hot) {
module.hot.accept(
"./sayHi", // 監聽具體哪個子產品的改變
function () {
//手動的删除原來saiHi添加的dom節點,然後重新執行saiHi方法
console.log('這樣就可以不重新整理頁面重新加載js檔案了')
document.getElementById('root').removeChild(document.getElementById("hi"));
sayHi();
} // 用于在子產品更新後觸發的函數
);
}
sayHi();
但是如果引入的子產品比較複雜,這裡的配置也會随之複雜很多,所有具體的情況還是要看業務的需求,重新整理一下頁面其實也問題不大…
Babel
通常我們會配置babel來幫我們将es6的文法轉換成es5的文法,來避免一些奇奇怪怪的問題(ie說的就是你)
首先需要安裝babel以及它的loader
npm install --save-dev babel-loader @babel/core @babel/preset-env
在index.js裡引入babel/polyfill
// 注入低版本浏覽器不支援的一些es6的新方法,諸如promise,map等
import "@babel/polyfill"
然後在loader裡面進行簡單的配置即可
module:{
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
]
}
這時候再進行打包,就可以看到打包後代碼裡es6的文法已經轉換成es5的文法了
也可以做更多的一些适配
module:{
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [["@babel/preset-env",{
// 隻将用到的es6文法整合進去,沒有用到的不整合,減少打包後的大小
useBuiltIns:'usage'
}]],
},
},
]
}
這樣配置之後就可以把引入的babel/polyfill去掉了,他會在需要的時候被自動引入
更多的配置可以參考babel的文檔
SourceMap
SourceMap是一種映射關系,可以幫助我們在代碼出現錯誤的時候定位到錯誤的位置,他的配置對應的是config中的devtool項
先将它設定為none
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
devtool:false,
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
...
};
// index.js
// 在代碼裡添加一句錯誤的語句
console.llog('錯啦')
打包是可以正常執行的,但是我們打開頁面後控制台會報錯
點選檢視source後會發現對應的地方是打包之後的代碼位置,而不是我們源代碼的位置,這對于我們定位錯誤沒有太大幫助,而想要讓他定位到我們源代碼的位置,就需要配置SourceMap
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
devtool:'source-map',
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
...
};
更改devtool配置再次打包後,點選檢視source就會幫我們定位到源碼的位置了,打包的檔案也會多一個map檔案
官方的source-map有很多的配置
inline代表不生成map檔案,将map檔案以base64格式打包至js檔案内
cheap代表是否定位錯誤出現在代碼的第幾行和第幾個字元,帶上cheap代表不定位出現在第幾個字元,隻定位第幾行
module代表是否定位一些子產品的錯誤
eval代表以js的eval形式來執行代碼,不過這種方式在代碼量較大的環境下可能無法準确定位錯誤
開啟SourceMap會造成性能方面的影響,是以通常我們的做法是在開發環境下開啟source-map,正式上線時候關閉
我個人會在開發環境下使用’cheap-module-eval-source-map’這種方式提示的錯誤比較全面,同時打包的速度也比較快
線上環境通常是直接設定為false不開啟,如果出了問題需要迅速定位錯誤的話,使用’cheap-module-source-map’可以幫助我們能準确定位錯誤
關于webpack一些核心的理念基本已經介紹完畢,後續的博文會介紹webpack進階的一些進階概念