1.Loader
1.1 Loader作用
把js和json外的其它文件轉(zhuǎn)為Webpack可以識別的模塊
1.2 Loader簡介
1.2.1 Loader類型
1.總類型
pre: 前置loader
normal: 普通loader
inline: 內(nèi)聯(lián)loader
post: 后置loader
2.默認(rèn)類型
默認(rèn)為normal類型
3.修改類型
配置時可以通過enforce修改pre,normal,post類型。
{ enforce: 'post', test: /\.js$/, loader: 'loader' }
1.2.2 Loader順序
1.總順序
類型順序 > 配置順序
舉例:
配置loader:
[A, B, C]
,執(zhí)行順序為:C -> B -> A
配置loader:
[A(enforce: pre), B, C]
,執(zhí)行順序為A -> C -> B
2.類型順序
pre > noraml > inline > post
3.配置順序
從右到左,從下到上(即配置的鏈表的逆序)
1.2.3 Loader使用
1.配置Loader
在webpack.config.js中配置Loader將處理哪些類型的文件
配置方法: 見“Webpack學(xué)習(xí)記錄”
2.內(nèi)聯(lián)Loader
在引入某個文件時指定使用的Loader
內(nèi)聯(lián)方法:
Loader: 多個Loader間用!隔開
參數(shù): 和URL一樣用?和&傳參給Loader
文件: 文件和Loader間用!隔開
優(yōu)先級: 類似于配置Loader中的enforce。
!: 跳過普通Loader
-!: 跳過前置Loader和普通Loader
!!: 跳過前置Loader和普通Loader和后置Loader
注意:內(nèi)聯(lián)Loader在每次引入文件時使用,寫的內(nèi)容太多太分散,且不利于排查問題。不推薦使用。
import test from 'B-loader!A-loader?mode=txt&type=run!./test.txt'
module.exports = function(content) { // 通過loaders獲取內(nèi)聯(lián)的每個loader的具體信息,包括查詢參數(shù) console.log(this.loaders) return content }
3.腳手架Loader
配置方法: 了解即可。下面是對.jade和.css文件使用對應(yīng)的loader
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
1.3 Loader開發(fā)
1.3.1 Loader模式
1.開發(fā)Loader基本概念
函數(shù): Loader是一個函數(shù),涉及this調(diào)用,不要使用箭頭函數(shù)
函數(shù)參數(shù):
content: 文件內(nèi)容
map: 代碼映射
meta: 傳遞給下一個Loader的內(nèi)容
鏈?zhǔn)秸{(diào)用: 不論采用簡潔模式還是普通模式,后續(xù)loader會通過callback或返回值獲取前一個loader處理內(nèi)容繼續(xù)處理
2.同步Loader
注意:同步Loader中不應(yīng)該存在異步操作
簡潔模式
module.exports = function(content, map, meta) { return content }
普通模式
module.exports = function(content, map, meta) { this.callback(null, content, map, meta) }
3.異步Loader
注意:雖然異步Loader中有異步操作,但是鏈?zhǔn)秸{(diào)用時只有異步操作完成,才能繼續(xù)鏈?zhǔn)秸{(diào)用
簡潔模式
module.exports = function(content, map, meta) { return new Promise((res) => { setTimeout(() => { res(content) }, 1000) }) }
普通模式
module.exports = function(content, map, meta) { const callback = this.async() setTimeout(() => { callback(content, map, meta) }, 1000) }
3.Raw Loader
用途: Raw Loader配置raw為true即可,表示接收Buffer格式的文件二進(jìn)制數(shù)據(jù),通常用于處理圖片,音視頻等。
模式: 同步和異步模式Raw Loader都可以使用。
module.exports.raw = true module.exports = function(content, map, meta) { return content }
4.Pitch Loader
用途: Pitch Loader配置pitch為函數(shù)即可,表示提前執(zhí)行pitch函數(shù),可以在函數(shù)中返回一個非undefined值來中斷鏈?zhǔn)秸{(diào)用中后續(xù)Loader的執(zhí)行。
模式: 同步和異步模式Pitch Loader都可以使用。
中斷: Pitch函數(shù)返回值中斷后,會導(dǎo)致無法讀取文件,后續(xù)執(zhí)行的Loader函數(shù)的文件來源是中斷Pitch函數(shù)的返回值。
順序:
- Pitch階段: 按照配置的Loader的鏈表的正序執(zhí)行它們的Pitch函數(shù)。Pitch一旦有返回值,立即執(zhí)行上一個Pitch對應(yīng)的Loader并終止鏈?zhǔn)秸{(diào)用。
- 讀取文件: Pitch階段結(jié)束后Loader讀取文件準(zhǔn)備執(zhí)行Loader函數(shù)。
- Normal階段: Normal階段包括pre,normal,inline,post。Normal階段晚于Pitch階段。按照配置的Loader的鏈表的逆序執(zhí)行它們的Loader函數(shù)。
參數(shù):
- remainingRequest: 當(dāng)前Loader之后要使用的Loader,以內(nèi)聯(lián)Loader格式顯示。
- precedingRequest: 當(dāng)前Loader之前要使用的Loader,顯示Loader位置。
- data: 用于同一對Pitch和Loader通信。設(shè)置data上的屬性,Loader可以在this.data中獲取到。
module.exports.pitch = (remainingRequest, precedingRequest, data) => { // pitch和loader間通信 data.x = 123 // 有返回值提前中斷 return 'result' } module.exports = function(content, map, meta) { // pitch和loader間通信 console.log(this.data.x) return content }
1.3.2 Loader方法
常用方法
方法 描述 用法 this.callback 描述Loader返回結(jié)果 this.callback(null, content, map, meta) this.async 標(biāo)記Loader為異步Loader并返回callback const callback = this.async() this.getOptions 獲取webpack.config.js中配置的Loader的options (注意:schema對象,用于描述校驗規(guī)則,類似于props-type庫) const options = this.getOptions(schema) this.emitFile 輸出文件到打包后的文件夾中 (注意:通常在處理webpack默認(rèn)解析不了的文件,并且想讓它輸出到打包后文件夾中的場景中使用) this.emitFile(name, content, sourceMap) this.utils.contextify 產(chǎn)生一個相對路徑 (注意:Path模塊產(chǎn)生的路徑可能不滿足某些Loader的需求,因此一般使用該方法) this.utils.contextify(content, request) this.utils.absolutify 產(chǎn)生一個絕對路徑 this.utils.absolutify(content, request)
1.3.3 clean-log-loader
實現(xiàn)一個清除所有console.log語句的loader
module.exports = function(content) { return content.repalce(/console\.log\(.*\);?/g, '') }
1.3.4 banner-loader
實現(xiàn)一個添加作者信息的loader,并支持options配置
banner-loader.js
const schema = { // options類型 type: "object", // options屬性 properties: { name: { type: "string", }, }, // options是否可以追加屬性 additionalProperties: false, }; module.exports = function (content) { const options = this.getOptions(schema); const prefix = ` /* * Author: ${options?.name || 'Your Name'} */ `; return prefix + content; };
webpack.config.js
module.exports = { module: { rules: [ { test: /\.js$/, loader: './loader/banner-loader', options: { name: 'Danny' } } ] } }
1.3.5 babel-loader
實現(xiàn)一個babel-loader,做一個控制傳參與調(diào)用的中間層,轉(zhuǎn)譯模塊調(diào)用第三方模塊。
const babel = require("@babel/core"); const schema = { type: "object", properties: { presets: { type: "array", }, }, }; module.exports = function (content) { const callback = this.async(); const options = this.getOptions(schema); babel.transform(content, options, function (err, result) { if (err) callback(err); else callback(null, result.code); }); };
1.3.6 file-loader
實現(xiàn)一個file-loader,讓webpack能夠處理png資源
注意:回顧一下,通常配置webpack的Loader時對于這種資源只配置
type: 'asset'
即可,不用指定Loader
- 重寫文件名: 生成帶有Hash值的文件名稱
- 輸出文件: 輸出資源到打包后文件夾
- 導(dǎo)出文件: 配置資源導(dǎo)出方式
const loaderUtils = require("loader-utils"); // 處理圖片,音視頻,字體等文件,需要處理二進(jìn)制文件 module.exports = function (content) { // 生成哈希值文件名 const interpolatedName = loaderUtils.interpolateName( this, "[hash].[ext][query]", { content } ); // 輸出文件 this.emitFile(interpolatedName, content); // 文件輸出方式修改 return `module.exports = '${interpolatedName}'` }; module.exports.raw = true;
webpack.config.js
module.exports = { module: { rules: [ { test: /\.png$/, loader: './loader/file-loader', // 禁止webpack默認(rèn)處理文件資源,只使用我們自定義的loader type: 'javascript/auto' } ] } }
1.3.7 style-loader
注意:style-loader的實現(xiàn)是一種利用pitch loader解決特殊鏈?zhǔn)秸{(diào)用的解決方案
實現(xiàn)一個style-loader,配合css-loader使用。在實現(xiàn)時請注意這些問題:
style-loader的作用: style-loader實現(xiàn)時把樣式作為集成到style標(biāo)簽中插入文檔。
css-loader的作用: css-loader幫助我們解決了依賴引入等問題,例如背景圖需要使用圖片。
css-loader的返回值: css-loader返回一段JavaScript腳本,包含導(dǎo)入導(dǎo)出語句,因此你無法用eval執(zhí)行獲取結(jié)果。這和其它大部分Loader在鏈?zhǔn)秸{(diào)用中返回文件內(nèi)容不同。
// Imports import ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ from "../node_modules/css-loader/dist/runtime/noSourceMaps.js"; import ___CSS_LOADER_API_IMPORT___ from "../node_modules/css-loader/dist/runtime/api.js"; import ___CSS_LOADER_GET_URL_IMPORT___ from "../node_modules/css-loader/dist/runtime/getUrl.js"; var ___CSS_LOADER_URL_IMPORT_0___ = new URL("./assets/development.png", import.meta.url); var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___); var ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___); // Module ___CSS_LOADER_EXPORT___.push([module.id, `.test { width: 100%; height: 100%; background-image: url(${___CSS_LOADER_URL_REPLACEMENT_0___}) } `, ""]); // Exports export default ___CSS_LOADER_EXPORT___;
注意:其實問題就是在style-loader中怎么引入css-loader返回的腳本的導(dǎo)出值。在Loader函數(shù)中你無法通過入?yún)⒑蛅his上的屬性來獲取導(dǎo)出值的路徑
module.exports = function () {}; module.exports.pitch = function (remainingRequest) { // 通過Pitch Loader獲取內(nèi)聯(lián)Loader調(diào)用目標(biāo)文件的形式 // 通過引用內(nèi)聯(lián)Loader,你可以獲取css-loader腳本的導(dǎo)出值 const relativePath = remainingRequest .split("!") .map((str) => this.utils.contextify(this.context, str)) .join("!"); // 獲取css-loader腳本的導(dǎo)出值,在Loader函數(shù)中獲取不到內(nèi)聯(lián)函數(shù)調(diào)用形式 const script = ` import result from '!!${relativePath}' const style = document.createElement('style'); style.innerHTML = result; document.head.append(style) `; // 終止后續(xù)loader執(zhí)行 return script; };
2.Plugin
2.1 Plugin作用
擴(kuò)展Webpack的功能。
2.2 Plugin簡介
2.2.1 Plugin原理
Plugin在Webpack工作流程插入操作來擴(kuò)展Webpack功能。
2.2.2 Webpack鉤子
1.鉤子
Webpack暴露若干種鉤子,表示W(wǎng)ebpack工作的不同階段。
Plugin可以通過注冊鉤子插入Webpack工作流程。
2.Tapable
Tapable是Webpack內(nèi)部引用的一個庫,幫助Webpack使用鉤子。
Tapable對開發(fā)者無感知,Webpack包裝了Tapable的某些方法供開發(fā)者注冊鉤子。
tap
: 注冊同步鉤子和異步鉤子tapAsync
: 回調(diào)方式注冊異步鉤子tapPromise
: Promise方式注冊異步鉤子
2.2.3 Webpack構(gòu)建對象
1.Compiler
Webpack工作時創(chuàng)建Compiler對象,保存了webpack.config.js等配置信息。
Compiler在API形式定制Webpack和Plugin開發(fā)時會用到。
compiler.options
: webpack.config.js中的配置信息compiler.inputFileSystem
和compiler.outputFileSystem
: 進(jìn)行文件操作,類似fscompiler.hooks
: 注冊鉤子到整個打包過程
2.Compilation
Webpack工作時創(chuàng)建Compilation對象,保存了對模塊編譯的信息。
Compilation在Plugin開發(fā)時會用到。
compilation.modules
: 訪問遇到的模塊(文件)compilation.chunks
: 訪問遇到的chunkscompilation.assets
: 訪問遇到的資源文件compilation.hooks
: 注冊鉤子到編譯過程
2.2.4 Webpack生命周期
Webpack生命周期可以通過鉤子描述,下面結(jié)合Compiler和Compilation的常用鉤子描述Webpack生命周期
compiler.initialize: 初始化。
compiler.run: 開始構(gòu)建。
compiler.compilation: 創(chuàng)建編譯實例。
compiler.make: 開始一次編譯 (每個文件編譯時會觸發(fā),包括下述紅色部分)。
compilation.buildModule: 構(gòu)建模塊。
compilation.seal: 構(gòu)建完成。
compilation.optimize: 模塊優(yōu)化。
compiler.afterCompile: 所有文件編譯完成。
compiler.emit: 輸出資源。
compiler.done: 構(gòu)建過程完成。
2.3 Plugin開發(fā)
2.3.1 Plugin模式
調(diào)用方式: Plugin以構(gòu)造函數(shù)調(diào)用
核心方法: Plugin的核心方法是constructor和apply
- constructor: Webpack加載webpack.config.js的配置時調(diào)用每個plugin的constructor
- apply: Webpack生成配置對象compiler后調(diào)用plugin實例的apply方法
class TestPlugin { constructor() { console.log('TestPlugin constructor') } apply(compiler) { console.log('TestPlugin apply') } } module.exports = TestPlugin
2.3.2 Plugin鉤子
- 注冊方式: Plugin鉤子在apply中注冊
- 鉤子執(zhí)行: 鉤子執(zhí)行分為同步,異步串行,異步并行。執(zhí)行方式由鉤子說明文檔決定。
1.異步串行
注:每個鉤子執(zhí)行完畢后才能執(zhí)行下一個鉤子,鉤子的執(zhí)行會阻塞Webpack的構(gòu)建過程
// compiler.emit鉤子文檔描述是異步串行 compiler.hooks.emit.tap("TestPlugin", (compilation) => { // 參數(shù)是compilation,可以以此注冊compilation鉤子 console.log("TestPlugin emit sync"); }); compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => { setTimeout(() => { console.log("TestPlugin emit async"); callback(); }, 10000); }); compiler.hooks.emit.tapPromise("TestPlugin", (compilation) => { return new Promise((resolve) => { setTimeout(() => { console.log("TestPlugin emit promise"); resolve(); }, 2000) }); });
2.異步并行
注:每個鉤子同時觸發(fā)
// compiler.make鉤子文檔描述是異步并行 compiler.hooks.make.tapAsync("TestPlugin", (compilation, callback) => { setTimeout(() => { console.log("TestPlugin make async1"); callback(); }, 3000); }); compiler.hooks.make.tapAsync("TestPlugin", (compilation, callback) => { setTimeout(() => { console.log("TestPlugin make async2"); callback(); }, 1000); });
2.3.3 banner-webpack-plugin
- 獲取文件資源: 通過
compilation.assets
對象獲取鍵名來得知文件名- 追加內(nèi)容: 設(shè)置
compilation.assets[key]
為對象,實現(xiàn)source
方法和size
方法來描述輸出文件// 給文件添加注釋的插件 class BannerWebpackPlugin { apply(compiler) { compiler.hooks.emit.tapAsync( "BannerWebpackPlugin", (compilation, callback) => { // 1.獲取輸出的文件資源。只保留js和css資源 const assets = Object.keys(compilation.assets).filter((assetPath) => { const extensions = ["css", "js"]; const typeName = assetPath.split(".").slice(-1).join(""); return extensions.includes(typeName); }); // 資源內(nèi)容上追加內(nèi)容 const prefix = ` /* Author: xxx */ `; // 2.在文件上追加內(nèi)容 assets.forEach((asset) => { // 獲取原來內(nèi)容 const source = compilation.assets[asset].source(); // 新內(nèi)容 const content = prefix + source; compilation.assets[asset] = { // 最終資源輸出時調(diào)用source方法 source() { return content; }, // 資源大小 size() { return content.length; }, }; }); callback(); } ); } } module.exports = BannerWebpackPlugin;
2.3.4 clean-webpack-plugin
webpack4中有該插件,但是webpack5內(nèi)置了該功能。在此嘗試實現(xiàn)clean-webpack-plugin。
- 獲取webpack.config.js配置:
compiler.options
- 獲取文件操作工具:
fs = compiler.outputFileSystem
- 獲取目錄下文件和文件夾:
fs.readdirSync
- 獲取文件狀態(tài):
fs.statSync
- 判斷文件是否是目錄:
fs.isDirectory
- 刪除文件:
fs.unlinkSync
// 清理上次打包內(nèi)容插件 class CleanWebpackPlugin { apply(compiler) { // 打包輸出目錄 const outputPath = compiler.options.output.path; // 類似于fs const fs = compiler.outputFileSystem; compiler.hooks.emit.tap("CleanWebpackPlugin", (compilation) => { this.removeFiles(fs, outputPath); }); } removeFiles(fs, filePath) { // 讀取目錄下所有文件和文件夾 const files = fs.readdirSync(filePath); files.forEach((file) => { const path = `${filePath}/${file}`; // 分析文件狀態(tài) const fileStat = fs.statSync(path); // 判斷是否是文件夾(是文件夾則先刪除子文件) if (fileStat.isDirectory()) { this.removeFiles(fs, path); } else { // 同步刪除方法 fs.unlinkSync(path); } }); } } module.exports = CleanWebpackPlugin;
2.3.5 analyze-webpack-plugin
實現(xiàn)一個文件大小分析插件。實現(xiàn)需要API可以參考banner-webpack-plugin。
// 分析文件資源大小插件 class AnalyzeWebpackPlugin { apply(compiler) { compiler.hooks.emit.tap("AnalyzeWebpackPlugin", (compilation) => { // 1.分析輸出資源大小 const assets = Object.entries(compilation.assets); // 2.生成分析md文件 const baseContent = `| 資源名稱 | 資源大小 |\n | --- | --- |`; const content = assets.reduce((content, [filename, file]) => { return content + `\n| ${filename} | ${file.size()} |`; }, baseContent); // 3.輸出分析md文件 compilation.assets["analyze.md"] = { source() { return content; }, size() { return content.length; }, }; }); } } module.exports = AnalyzeWebpackPlugin;
2.3.6 inlineChunk-webpack-plugin
作用
配置了runtimeChunk后webpack打包生成的runtime文件可能非常小,可以考慮直接內(nèi)聯(lián)注入到index.html入口中。
思路
因為輸出index.html是html-webpack-plugin。因此需要借助這個插件向index.html中追加內(nèi)容
實現(xiàn)
- 內(nèi)聯(lián)runtime文件中的內(nèi)容到index.html
- 刪除打包后產(chǎn)生的runtime.js文件
const HtmlWebpackPlugin = require("safe-require")("html-webpack-plugin"); class InlineChunkWebpackPlugin { apply(compiler) { compiler.hooks.compilation.tap( "InlineChunkWebpackPlugin", (compilation) => { // 1.獲取html-webpack-plugin內(nèi)部的自定義hooks const hooks = HtmlWebpackPlugin.getHooks(compilation); // 2.根據(jù)其文檔注冊alterAssetTagGroups鉤子(此時要標(biāo)簽已經(jīng)分好組),將runtime內(nèi)容內(nèi)聯(lián)到index.html hooks.alterAssetTagGroups.tap("InlineChunkWebpackPlugin", (assets) => { /* headTags [ { tagName: 'script', voidTag: false, meta: { plugin: 'html-webpack-plugin' }, attributes: { defer: true, src: 'jsruntime~main.js.js' } } ] */ // 處理頭部標(biāo)簽和身體標(biāo)簽(不確定runtime.js會被html-webpack-plugin放到哪個部分) assets.headTags = this.getInlineChunk( assets.headTags, compilation.assets ); assets.bodyTags = this.getInlineChunk( assets.bodyTags, compilation.assets ); }); // 3.根據(jù)其文檔注冊afterEmit鉤子(此時已經(jīng)輸出資源),將產(chǎn)生的runtime.js刪除 hooks.afterEmit.tap("InlineChunkWebpackPlugin", () => { Object.keys(compilation.assets).forEach((filePath) => { if (/runtime(.*)\.js$/g.test(filePath)) delete compilation.assets[filePath]; }); }); } ); } getInlineChunk(tags, assets) { return tags.map((tag) => { // 不是script標(biāo)簽不處理 if (tag.tagName !== "script") return tag; // 獲取文件資源路徑 const filePath = tag.attributes.src; if (!filePath) return tag; // 不是runtime文件不處理 if (!/runtime(.*)\.js$/g.test(filePath)) return tag; return { tagName: "script", // assets是通過compilation獲取文件資源 innerHTML: assets[filePath].source(), closeTag: true, }; }); } } module.exports = InlineChunkWebpackPlugin;
3.調(diào)試
在構(gòu)建工具中調(diào)試
1.構(gòu)建工具代碼中設(shè)置debugger斷點文章來源:http://www.zghlxwxcb.cn/news/detail-854976.html
2.配置調(diào)試指令文章來源地址http://www.zghlxwxcb.cn/news/detail-854976.html
- -brk: 在第一行代碼停下來
- cli.js: 運行cli.js通過webpack腳手架啟動webpack
{ "scripts": { "debug": "node --inspect-brk ./node_modules/webpack-cli/bin/cli.js" } }
到了這里,關(guān)于Webpack的Loader和Plugin的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!