zoukankan      html  css  js  c++  java
  • 实现一个 webpack loader 和 webpack plugin

    loader

    官网上的定义:

    loader 是一个转换器,用于对源代码进行转换。

    例如 babel-loader 可以将 ES6 代码转换为 ES5 代码;sass-loadersass 代码转换为 css 代码。

    一般 loader 的配置代码如下:

    module: {
            rules: [
                {
                    test: /.js$/,
                    use: [
                        // loader 的执行顺序从下到上
                        {
                            loader: path.resolve('./src/loader2.js'),
                        },
                        {
                            loader: path.resolve('./src/loader1.js'),
                        },
                    ]
                }
            ]
        },
    

    rules 数组包含了一个个匹配规则和具体的 loader 文件。

    上述代码中的 test: /.js$/ 就是匹配规则,表示对 js 文件使用下面的两个 loader。

    而 loader 的处理顺序是自下向上的,即先用 loader1 处理源码,然后将处理后的代码再传给 loader2。

    loader2 处理后的代码就是最终的打包代码。

    loader 的实现

    loader 其实是一个函数,它的参数是匹配文件的源码,返回结果是处理后的源码。下面是一个最简单的 loader,它什么都没做:

    module.exports = function (source) {
        return source
    }
    

    这么简单的 loader 没有挑战性,我们可以写一个稍微复杂一点的 loader,它的作用是将 var 关键词替换为 const

    module.exports = function (source) {
        return source.replace(/var/g, 'const')
    }
    

    写完之后,我们来测试一下,测试文件为:

    function test() {
        var a = 1;
        var b = 2;
        var c = 3;
        console.log(a, b, c);
    }
    
    test()
    

    wepback.config.js 配置文件为:

    const path = require('path')
    
    module.exports = {
        mode: 'development',
        entry: {
            main: './src/index.js'
        },
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist')
        },
        module: {
            rules: [
                {
                    test: /.js$/,
                    use: [
                        {
                            loader: path.resolve('./src/loader1.js'),
                        },
                    ]
                }
            ]
        },
    }
    

    运行 npm run build,得到打包文件 bundle.js,我们来看一看打包后的代码:

    eval("function test() {
        const a = 1;
        const b = 2;
        const c = 3;
        console.log(a, b, c);
    }
    
    test()
    
    //# sourceURL=webpack:///./src/index.js?");
    

    可以看到,代码中的 var 已经变成了 const

    异步 loader

    刚才实现的 loader 是一个同步 loader,在处理完源码后用 return 返回。

    下面我们来实现一个异步 loader:

    module.exports = function (source) {
        const callback = this.async()
    
        // 由于有 3 秒延迟,所以打包时需要 3+ 秒的时间
        setTimeout(() => {
            callback(null, `${source.replace(/;/g, '')}`)
        }, 3000)
    }
    

    异步 loader 需要调用 webpack 的 async() 生成一个 callback,它的第一个参数是 error,这里可设为 null,第二个参数就是处理后的源码。当你异步处理完源码后,调用 callback 即可。

    下面来试一下异步 loader 到底有没生效,这里设置了一个 3 秒延迟。我们来对比一下打包时间:

    在这里插入图片描述
    在这里插入图片描述
    上图是调用同步 loader 的打包时间,为 141 ms;下图是调用异步 loader 的打包时间,为 3105 ms,说明异步 loader 生效了。

    如果想看完整 demo 源码,请点击我的 github

    plugin

    webpack 在整个编译周期中会触发很多不同的事件,plugin 可以监听这些事件,并且可以调用 webpack 的 API 对输出资源进行处理。

    这是它和 loader 的不同之处,loader 一般只能对源文件代码进行转换,而 plugin 可以做得更多。plugin 在整个编译周期中都可以被调用,只要监听事件。

    对于 webpack 编译,有两个重要的对象需要了解一下:

    Compiler 和 Compilation
    在插件开发中最重要的两个资源就是 compiler 和 compilation 对象。理解它们的角色是扩展 webpack 引擎重要的第一步。

    compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。

    compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。

    这两个组件是任何 webpack 插件不可或缺的部分(特别是 compilation),因此,开发者在阅读源码,并熟悉它们之后,会感到获益匪浅。

    plugin 的实现

    我们看一下官网的定义,webpack 插件由以下部分组成:

    1. 一个 JavaScript 命名函数。
    2. 在插件函数的 prototype 上定义一个 apply 方法。
    3. 指定一个绑定到 webpack 自身的事件钩子。
    4. 处理 webpack 内部实例的特定数据。
    5. 功能完成后调用 webpack 提供的回调。

    简单的说,一个具有 apply 方法的函数就是一个插件,并且它要监听 webpack 的某个事件。下面来看一个简单的示例:

    function Plugin(options) { }
    
    Plugin.prototype.apply = function (compiler) {
        // 所有文件资源都被 loader 处理后触发这个事件
        compiler.plugin('emit', function (compilation, callback) {
            // 功能完成后调用 webpack 提供的回调
            console.log('Hello World')
            callback()
        })
    }
    
    module.exports = Plugin
    

    写完插件后要怎么调用呢?

    先在 webpack 配置文件中引入插件,然后在 plugins 选项中配置:

    const Plugin = require('./src/plugin')
    
    module.exports = {
    	...
        plugins: [
            new Plugin()
        ]
    }
    

    这就是一个简单的插件了。

    下面我们再来写一个复杂点的插件,它的作用是将经过 loader 处理后的打包文件 bundle.js 引入到 index.html 中:

    function Plugin(options) { }
    
    Plugin.prototype.apply = function (compiler) {
        // 所有文件资源经过不同的 loader 处理后触发这个事件
        compiler.plugin('emit', function (compilation, callback) {
            // 获取打包后的 js 文件名
            const filename = compiler.options.output.filename
            // 生成一个 index.html 并引入打包后的 js 文件
            const html = `<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="${filename}"></script>
    </head>
    <body>
        
    </body>
    </html>`
            // 所有处理后的资源都放在 compilation.assets 中
            // 添加一个 index.html 文件
            compilation.assets['index.html'] = {
                source: function () {
                    return html
                },
                size: function () {
                    return html.length
                }
            }
    
            // 功能完成后调用 webpack 提供的回调
            callback()
        })
    }
    
    module.exports = Plugin
    

    OK,执行一下,看看效果。

    在这里插入图片描述
    完美,和预测的结果一模一样。

    完整 demo 源码,请看我的 github

    想了解更多的事件,请看官网介绍 compiler 钩子

    参考资料

  • 相关阅读:
    OpenGL在图形管道中调用了什么用户模式图形驱动程序(UMD)?
    MLIR算子量化Quantization
    最大限度地减少块输出中间结果的计算和存储
    Echarts(一)
    Oracle部署安装
    JS使用
    sqlplus导入sql,dmp导入导出
    一款强大的Visual Studio插件!CodeRush v19.1.9全新来袭
    Web界面开发必看!Kendo UI for jQuery编辑功能指南第二弹
    报表开发神器!DevExpress Reporting v19.1全平台新功能解析
  • 原文地址:https://www.cnblogs.com/woai3c/p/13654874.html
Copyright © 2011-2022 走看看