亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Webpack簡(jiǎn)單實(shí)現(xiàn)兩個(gè)自定義插件詳解

 更新時(shí)間:2024年04月24日 11:03:11   作者:土豆蛋蛋  
這篇文章主要為大家詳細(xì)介紹了Webpack簡(jiǎn)單實(shí)現(xiàn)兩個(gè)自定義插件的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

基礎(chǔ)理論知識(shí)

1、插件的本質(zhì)是一個(gè)函數(shù)或一個(gè);

2、webpack 在使用插件時(shí),會(huì)初始化一個(gè)插件實(shí)例并調(diào)用其原型對(duì)象上的 apply 方法,apply 方法接收一個(gè) compiler 參數(shù),下文將詳細(xì)介紹這個(gè)參數(shù)。

函數(shù)實(shí)例

    function MyPlugin (options) {
    }
    MyPlugin.prototype.apply = compiler => {
    };
    module.exports = MyPlugin;

類實(shí)例

    class MyPlugin {
        constructor (options) {
        }
        apply (compiler) {
        }
    }
    module.exports = MyPlugin;

compiler 和 compilation

compiler

上文提到的 apply 方法中接收的 compiler 對(duì)象代表了完整的 webpack 環(huán)境配置。

這個(gè)對(duì)象在啟動(dòng) webpack 時(shí)被一次性建立,并配置好所有可操作的設(shè)置,包括 options,loader 和 plugin。

可以簡(jiǎn)單地把它理解為 webpack 實(shí)例,使用它來(lái)訪問(wèn) webpack 的主環(huán)境和配置信息。

另外,compiler 對(duì)象暴露了很多生命周期的鉤子,通過(guò)如下方式使用:

    compiler.hooks.someHook.tap("MyPlugin", params => {
        /* ... */
    });

鉤子的訪問(wèn)方式并不固定為 tap,這取決于鉤子的類型,主要分為同步和異步,訪問(wèn)的方式有:tapAsync, tapPromise 等。

compilation

compilation 對(duì)象是從 compiler 鉤子的回調(diào)函數(shù)中傳遞回來(lái)的。

compilation 對(duì)象代表了一次資源版本構(gòu)建。每當(dāng)檢測(cè)到一個(gè)文件變化,就會(huì)創(chuàng)建一個(gè)新的 compilation,從而生成一組新的編譯資源。

一個(gè) compilation 對(duì)象表現(xiàn)了當(dāng)前的模塊資源、編譯生成資源、變化的文件、以及被跟蹤依賴的狀態(tài)信息。

compilation 對(duì)象也暴露了很多生命周期的鉤子,以供插件做自定義處理時(shí)選擇使用。訪問(wèn)方式與 compiler 相同,此處不再贅述。

小結(jié)

compiler 是一個(gè)全局的對(duì)象,是一整個(gè)構(gòu)建的過(guò)程,可以訪問(wèn) webpack 的環(huán)境和配置。

compilation 是對(duì)于某個(gè)模塊而言的,它可以更加精細(xì)地處理各個(gè)模塊的構(gòu)建過(guò)程。

簡(jiǎn)單實(shí)現(xiàn)自定義插件

官方文檔中給了一個(gè) File List Plugin 自定義插件的示例,該插件主要展示了如何獲取構(gòu)建過(guò)程中的資源。

除此之外,我們?cè)賮?lái)實(shí)現(xiàn)兩個(gè)簡(jiǎn)單的自定義插件。

Watch Plugin

這個(gè)插件的作用是:在 webpack 的監(jiān)視(watch)模式下,輸出每次修改變更的資源文件信息。

在查閱官方文檔后,發(fā)現(xiàn)有個(gè) watchRun 的鉤子很符合我們的需求:“在監(jiān)聽(tīng)模式下,一個(gè)新的 compilation 觸發(fā)之后,但在 compilation 實(shí)際開(kāi)始之前執(zhí)行。”。

也就是說(shuō),項(xiàng)目發(fā)生了改動(dòng),會(huì)進(jìn)行一次新的構(gòu)建,生成一個(gè)新的 compilation,并且在這個(gè) compilation 執(zhí)行之前觸發(fā)。

watchRun 的訪問(wèn)方式是 tapAsync,所以除了接收 compiler 參數(shù)外,還會(huì)接收一個(gè)回調(diào)函數(shù),我們需要在邏輯執(zhí)行完畢后調(diào)用這個(gè)回調(diào)函數(shù)。

代碼如下:

/**
 * 在 webpack 的 watch 模式下觸發(fā)
 */
class WatchPlugin {
    
    apply (webpackCompiler) {
        
        // watchRun - 在監(jiān)聽(tīng)模式下觸發(fā),在一個(gè) compilation 出現(xiàn)后,在 compilation 執(zhí)行前觸發(fā)
        webpackCompiler.hooks.watchRun.tapAsync("WatchPlugin", (compiler, callback) => {
            console.log(" 監(jiān)聽(tīng)到了! ");

            const mtimes = compiler.watchFileSystem.watcher.mtimes;
            if (!mtimes) return;
            // 通過(guò)正則處理,避免顯示 node_modules 文件夾下依賴的變化
            const mtimesKeys = Object.keys(mtimes).filter(path => !/(node_modules)/.test(path));
            if (mtimesKeys.length) {
                console.log(` 本次改動(dòng)了 ${mtimesKeys.length} 個(gè)文件,路徑為:\n `, mtimes);
            }

            callback();
        });

        // watchClose - 在一個(gè)監(jiān)聽(tīng)中的 compilation 結(jié)束時(shí)觸發(fā)
        webpackCompiler.hooks.watchClose.tap("WatchPlugin", () => {
            console.log(" 監(jiān)聽(tīng)結(jié)束,再見(jiàn)! ");
        });

    }

}

module.exports = WatchPlugin;

Clean Plugin

模仿 clean-webpack-plugin 實(shí)現(xiàn)一個(gè)每次構(gòu)建時(shí)將上一次構(gòu)建結(jié)果中不再需要的文件刪除。

梳理邏輯

項(xiàng)目文件改動(dòng)后,構(gòu)建結(jié)果的文件 hash 值會(huì)發(fā)生變化,此時(shí)將舊文件刪除;如果沒(méi)有發(fā)生改動(dòng),則無(wú)需刪除。

實(shí)現(xiàn)邏輯

1、考慮在什么時(shí)機(jī)執(zhí)行邏輯?

要根據(jù) hash 判斷文件是否發(fā)生了變化,所以要拿到新、舊文件,那么可以在新的一次構(gòu)建完成后執(zhí)行,此時(shí)可以獲取新構(gòu)建出的文件。

查閱文檔后,compiler.done 這個(gè)鉤子會(huì)在 compilation 完成后觸發(fā),符合我們的需求。

2、如何獲取上一次構(gòu)建出的舊文件?

先獲取 output 的路徑,根據(jù)路徑就可以獲取到 output 文件夾下所有的文件了。這個(gè)操作要放在構(gòu)建開(kāi)始之前。

上文說(shuō)過(guò) compiler 可以訪問(wèn) webpack 的環(huán)境與配置,因此通過(guò) compiler 可以獲取 output 的路徑:

apply (compiler) {
    const outputPath = compiler.options.output.path;
}

獲取 output 文件夾中的文件,可以通過(guò) fs.readdirSync 方法和 fs.statSync 方法。

前者是用于讀取某一路徑下所有的文件名,包括文件和文件夾。

后者是用于判斷某一路徑是文件還是文件夾。

使用這兩個(gè)方法,我們可以遞歸獲取 output 文件夾以及其子級(jí)文件夾下所有的文件

/**
 * 獲取文件夾下所有的文件名(包括子級(jí)文件夾中的文件)
 * @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、如何獲取新構(gòu)建出的文件?

compiler.done 這個(gè)鉤子的回調(diào)函數(shù)接收一個(gè)參數(shù) stat,通過(guò) stat 可以獲取到最新構(gòu)建出的 assets。

這幾個(gè)問(wèn)題解決后,我們將新、舊文件路徑統(tǒng)一化就可以進(jìn)行對(duì)比了。篩選出需要?jiǎng)h除的文件,用 fs.unlinkSync 方法就可以直接刪除了。

代碼如下:

const fs = require("fs");
const path = require("path");

/** 每次編譯時(shí)刪除上一次編譯結(jié)果中不再需要的文件 */
class CleanPlugin {

    constructor (options) {
        this.options = options;
    }

    apply (compiler) {
        const pluginName = CleanPlugin.name;

        // 編譯輸出文件的路徑,根據(jù)此路徑可獲取對(duì)應(yīng)目錄下的所有文件
        const outputPath = compiler.options.output.path;
        const outputPathPrefix = path.basename(outputPath);
        const oldFiles = this.readAllFiles(outputPath);

        // console.log(" old files ", oldFiles);

        // done - 完成新的編譯后執(zhí)行,此時(shí)能獲取新的輸出文件與現(xiàn)有文件進(jìn)行對(duì)比
        compiler.hooks.done.tap(pluginName, stats => {

            // 新的一次編譯完成后的輸出文件的相對(duì)路徑
            const newFiles = stats.toJson().assets.map(assets => fs.realpathSync(`${outputPathPrefix}\\${assets.name}`));

            // console.log(" new files ", newFiles);

            // 新舊文件對(duì)比,篩選出需要?jiǎng)h除的文件
            const removeFiles = [];
            oldFiles.forEach(oldFile => {
                if (newFiles.indexOf(oldFile) === -1) {
                    removeFiles.push(oldFile);
                }
            });

            // 刪除文件
            removeFiles.forEach(removeFile => fs.unlinkSync(removeFile));
        });
    }

    /**
     * 獲取文件夾下所有的文件名(包括子級(jí)文件夾中的文件)
     * @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;

到此這篇關(guān)于Webpack簡(jiǎn)單實(shí)現(xiàn)兩個(gè)自定義插件詳解的文章就介紹到這了,更多相關(guān)Webpack自定義插件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論