Webpack簡單實現(xiàn)兩個自定義插件詳解
基礎理論知識
1、插件的本質是一個函數(shù)或一個類;
2、webpack 在使用插件時,會初始化一個插件實例并調(diào)用其原型對象上的 apply
方法,apply 方法接收一個 compiler
參數(shù),下文將詳細介紹這個參數(shù)。
函數(shù)實例
function MyPlugin (options) { } MyPlugin.prototype.apply = compiler => { }; module.exports = MyPlugin;
類實例
class MyPlugin { constructor (options) { } apply (compiler) { } } module.exports = MyPlugin;
compiler 和 compilation
compiler
上文提到的 apply 方法中接收的 compiler
對象代表了完整的 webpack 環(huán)境配置。
這個對象在啟動 webpack 時被一次性建立,并配置好所有可操作的設置,包括 options,loader 和 plugin。
可以簡單地把它理解為 webpack 實例,使用它來訪問 webpack 的主環(huán)境和配置信息。
另外,compiler 對象暴露了很多生命周期的鉤子,通過如下方式使用:
compiler.hooks.someHook.tap("MyPlugin", params => { /* ... */ });
鉤子的訪問方式并不固定為 tap
,這取決于鉤子的類型,主要分為同步和異步,訪問的方式有:tapAsync
, tapPromise
等。
compilation
compilation
對象是從 compiler 鉤子的回調(diào)函數(shù)中傳遞回來的。
compilation 對象代表了一次資源版本構建。每當檢測到一個文件變化,就會創(chuàng)建一個新的 compilation,從而生成一組新的編譯資源。
一個 compilation 對象表現(xiàn)了當前的模塊資源、編譯生成資源、變化的文件、以及被跟蹤依賴的狀態(tài)信息。
compilation 對象也暴露了很多生命周期的鉤子,以供插件做自定義處理時選擇使用。訪問方式與 compiler 相同,此處不再贅述。
小結
compiler
是一個全局的對象,是一整個構建的過程,可以訪問 webpack 的環(huán)境和配置。
compilation
是對于某個模塊而言的,它可以更加精細地處理各個模塊的構建過程。
簡單實現(xiàn)自定義插件
官方文檔中給了一個 File List Plugin
自定義插件的示例,該插件主要展示了如何獲取構建過程中的資源。
除此之外,我們再來實現(xiàn)兩個簡單的自定義插件。
Watch Plugin
這個插件的作用是:在 webpack 的監(jiān)視(watch)模式下,輸出每次修改變更的資源文件信息。
在查閱官方文檔后,發(fā)現(xiàn)有個 watchRun
的鉤子很符合我們的需求:“在監(jiān)聽模式下,一個新的 compilation 觸發(fā)之后,但在 compilation 實際開始之前執(zhí)行。”。
也就是說,項目發(fā)生了改動,會進行一次新的構建,生成一個新的 compilation,并且在這個 compilation 執(zhí)行之前觸發(fā)。
watchRun 的訪問方式是 tapAsync
,所以除了接收 compiler 參數(shù)外,還會接收一個回調(diào)函數(shù),我們需要在邏輯執(zhí)行完畢后調(diào)用這個回調(diào)函數(shù)。
代碼如下:
/** * 在 webpack 的 watch 模式下觸發(fā) */ class WatchPlugin { apply (webpackCompiler) { // watchRun - 在監(jiān)聽模式下觸發(fā),在一個 compilation 出現(xiàn)后,在 compilation 執(zhí)行前觸發(fā) webpackCompiler.hooks.watchRun.tapAsync("WatchPlugin", (compiler, callback) => { console.log(" 監(jiān)聽到了! "); const mtimes = compiler.watchFileSystem.watcher.mtimes; if (!mtimes) return; // 通過正則處理,避免顯示 node_modules 文件夾下依賴的變化 const mtimesKeys = Object.keys(mtimes).filter(path => !/(node_modules)/.test(path)); if (mtimesKeys.length) { console.log(` 本次改動了 ${mtimesKeys.length} 個文件,路徑為:\n `, mtimes); } callback(); }); // watchClose - 在一個監(jiān)聽中的 compilation 結束時觸發(fā) webpackCompiler.hooks.watchClose.tap("WatchPlugin", () => { console.log(" 監(jiān)聽結束,再見! "); }); } } module.exports = WatchPlugin;
Clean Plugin
模仿 clean-webpack-plugin
實現(xiàn)一個每次構建時將上一次構建結果中不再需要的文件刪除。
梳理邏輯
項目文件改動后,構建結果的文件 hash
值會發(fā)生變化,此時將舊文件刪除;如果沒有發(fā)生改動,則無需刪除。
實現(xiàn)邏輯
1、考慮在什么時機執(zhí)行邏輯?
要根據(jù) hash 判斷文件是否發(fā)生了變化,所以要拿到新、舊文件,那么可以在新的一次構建完成后執(zhí)行,此時可以獲取新構建出的文件。
查閱文檔后,compiler.done
這個鉤子會在 compilation 完成后觸發(fā),符合我們的需求。
2、如何獲取上一次構建出的舊文件?
先獲取 output 的路徑,根據(jù)路徑就可以獲取到 output 文件夾下所有的文件了。這個操作要放在構建開始之前。
上文說過 compiler 可以訪問 webpack 的環(huán)境與配置,因此通過 compiler 可以獲取 output 的路徑:
apply (compiler) { const outputPath = compiler.options.output.path; }
獲取 output 文件夾中的文件,可以通過 fs.readdirSync
方法和 fs.statSync
方法。
前者是用于讀取某一路徑下所有的文件名,包括文件和文件夾。
后者是用于判斷某一路徑是文件還是文件夾。
使用這兩個方法,我們可以遞歸獲取 output 文件夾以及其子級文件夾下所有的文件:
/** * 獲取文件夾下所有的文件名(包括子級文件夾中的文件) * @param {string} dir 文件夾路徑 */ readAllFiles (dir) { let fileList = []; const files = fs.readdirSync(dir); files.forEach(file => { const filePath = path.join(dir, file); const stat = fs.statSync(filePath); if (stat.isDirectory()) { fileList = fileList.concat(this.readAllFiles(filePath)); } else { fileList.push(filePath); } }); return fileList; }
3、如何獲取新構建出的文件?
compiler.done 這個鉤子的回調(diào)函數(shù)接收一個參數(shù) stat
,通過 stat 可以獲取到最新構建出的 assets
。
這幾個問題解決后,我們將新、舊文件路徑統(tǒng)一化就可以進行對比了。篩選出需要刪除的文件,用 fs.unlinkSync
方法就可以直接刪除了。
代碼如下:
const fs = require("fs"); const path = require("path"); /** 每次編譯時刪除上一次編譯結果中不再需要的文件 */ class CleanPlugin { constructor (options) { this.options = options; } apply (compiler) { const pluginName = CleanPlugin.name; // 編譯輸出文件的路徑,根據(jù)此路徑可獲取對應目錄下的所有文件 const outputPath = compiler.options.output.path; const outputPathPrefix = path.basename(outputPath); const oldFiles = this.readAllFiles(outputPath); // console.log(" old files ", oldFiles); // done - 完成新的編譯后執(zhí)行,此時能獲取新的輸出文件與現(xiàn)有文件進行對比 compiler.hooks.done.tap(pluginName, stats => { // 新的一次編譯完成后的輸出文件的相對路徑 const newFiles = stats.toJson().assets.map(assets => fs.realpathSync(`${outputPathPrefix}\\${assets.name}`)); // console.log(" new files ", newFiles); // 新舊文件對比,篩選出需要刪除的文件 const removeFiles = []; oldFiles.forEach(oldFile => { if (newFiles.indexOf(oldFile) === -1) { removeFiles.push(oldFile); } }); // 刪除文件 removeFiles.forEach(removeFile => fs.unlinkSync(removeFile)); }); } /** * 獲取文件夾下所有的文件名(包括子級文件夾中的文件) * @param {string} dir 文件夾路徑 */ readAllFiles (dir) { let fileList = []; const files = fs.readdirSync(dir); files.forEach(file => { const filePath = path.join(dir, file); const stat = fs.statSync(filePath); if (stat.isDirectory()) { fileList = fileList.concat(this.readAllFiles(filePath)); } else { fileList.push(filePath); } }); return fileList; } } module.exports = CleanPlugin;
到此這篇關于Webpack簡單實現(xiàn)兩個自定義插件詳解的文章就介紹到這了,更多相關Webpack自定義插件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
uniapp實現(xiàn)下拉刷新與上拉觸底加載功能的示例代碼
這篇文章主要記錄一下uniapp實現(xiàn)下拉刷新與上拉觸底加載功能的示例代碼,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2023-04-04JavaScript實現(xiàn)網(wǎng)頁tab欄效果制作
這篇文章主要為大家詳細介紹了JavaScript實現(xiàn)網(wǎng)頁tab欄效果制作,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-11-11javascript實現(xiàn)劃詞標記劃詞搜索功能修正版
javascript實現(xiàn)劃詞標記劃詞搜索功能修正版...2006-12-12