webpack-实现loader、plugin

1,071 阅读2分钟

准备工作: npm init -y初始化一个项目 npm i webpack webpack-cli -D 创建一个webpack.config.js文件

const path = require('path')

module.exports = {
    entry: './index.js',
    output: {
        path: path.resolve(__dirname, 'dist')
    },
    mode: 'development',
}

loader实现

通过实现一个简单的字符串替换loader来了解下loader的工作原理

// index.js
console.log('yuxiaoyu')

// 创建一个loader文件夹 先创建个replaceLoader.js
// loader就是一个函数,需要用声明式函数,因为要取到上下文中的this,这个函数接受一个参数,是我们要打包的源码
module.exports = function(source) {
    console.log(source, this.query)
    return source.replace('yuxiaoyu', '于晓俞')
}

// 然后修改webpack.config.js
const path = require('path')

module.exports = {
    entry: './index.js',
    output: {
        path: path.resolve(__dirname, 'dist')
    },
    mode: 'development',
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: path.resolve(__dirname, 'loader/replaceLoader.js'),
                    options: {
                        name: '实现loader'
                    }
                }
            }
        ]
    }
}

npx webpack之后,我们可以在main.js中看到index.js中的字符串已经被替换掉了 使用this.callback来返回多个结果信息,而不仅仅是源码

/* 
    this.callback()接受四个参数
    err,content,sourceMap,meta
 */
// 修改replaceLoader.js
module.exports = function(source) {
    console.log(source, this.query)
    // return source.replace('yuxiaoyu', '于晓俞')
    this.callback(null, source.replace('yuxiaoyu', '于晓俞'))
}

结果同样可以正常编译

处理异步

官方提供this.async处理loader内的异步

// 修改loader
module.exports = function(source) {
    console.log(source, this.query)
    // 我们使用this.async来处理,他会返回this.callback
    // 定义一个异步处理,告诉webpack,这个loader里有异步事件,在里面调用下这个异步
    // callback 就是 this.callback 注意参数的使用
    const callback = this.async()
    setTimeout(() => {
        callback(null, source.replace('yuxiaoyu', '于晓俞'))
    }, 3000)
}

多个loader的使用

// 再创建一个同步的replaceLoaderSync.js
module.exports = function(source) {
    return source.replace('于晓俞', '程序猿')
}

//修改webpack.config.js文件
module: {
    rules: [
        {
            test: /\.js$/,
            use: [
                path.resolve(__dirname, 'loader/replaceLoaderSync.js'),
                {
                    loader: path.resolve(__dirname, 'loader/replaceLoader.js'),
                    options: {
                        name: '实现loader'
                    }
                }
            ]
        }
    ]
}

处理loader的路径问题

我们写的配置文件地址写法过于繁琐,怎么能像我们npm i 下载的loader那样,直接写个loader名称就行了呢?这就需要我们再配置下webpack.config.js

// 配置resolveLoader, 告诉webpack去哪里查找loader,先去node_modules里查找,找不到再去loader文件夹汇总
resolveLoader: {
    modules: ['node_modules', './loader']
},
module: {
    rules: [
        {
            test: /\.js$/,
            use: [
                // 改成文件名
                'replaceLoaderSync',
                {
                    // 改成文件名
                    loader: 'replaceLoader',
                    options: {
                        name: '实现loader'
                    }
                }
            ]
        }
    ]
}

同样可以编译成功!

plugins实现

plugin: 开始打包,在某个时刻,帮助我们处理一些什么事情的机制
一个插件由以下构成:

一个具名 JavaScript 函数。
在它的原型上定义 apply 方法。
指定一个触及到 webpack 本身的 事件钩子。
操作 webpack 内部的实例特定数据。
在实现功能后调用 webpack 提供的 callback。
我们实现一个功能,在编译完成后,在输出文件时,同时输出一个txt文本

// 创建一个plugin文件夹 创建文件TxtWebpackPlugin.js
/* 
    插件是由一个构造函数(此构造函数上的 prototype 对象具有 apply 方法)的所实例化出来的。这个 apply 方法在安装插件时,会被 webpack compiler 调用一次。apply 方法可以接收一个 webpack compiler 对象的引用,从而可以在回调函数中访问到 compiler 对象。
 */
class TxtWebpackPlugin {
    constructor() {}
    apply(compiler) {
        // compiler.hooks中存放这各种钩子,emit在编译成功时输出文件前执行的事件
        // 有些插件 hooks 是异步的。想要 tap(触及) 某些 hooks,我们可以使用同步方式运行的 tap 方法,或者使用异步方式运行的 tapAsync 方法或 tapPromise 方法。
        compiler.hooks.emit.tapAsync('TxtWebpackPlugin', (compilation, callback) => {
            // 处理异步的事情
            let content = '生成的文件列表:\n'
            for (var filename in compilation.assets) {
                content += filename + '\n'
            }
            compilation.assets['filelist.txt'] = {
                source: function () {
                    return content
                },
                size: function () {
                    return content.length
                }
            }
            callback()
        });
    }
}
module.exports = TxtWebpackPlugin