美文网首页
手写一个webpack Plugin

手写一个webpack Plugin

作者: 喜剧之王爱创作 | 来源:发表于2020-09-15 16:00 被阅读0次
webpackplugin.jpg

回顾Plugin

如果你之前了解过Plugin的用法,那么或许多Plugin有一定的了解,这里我做一个简单的总结,Plugin是在打包的某个时间节点上,干一些特定的事,比如,我们可以在打包结束后在生成的目录下创建一个html文件,这时候,我们就用到html-webpack-plugin,比如我们在打包开始前要对打包目录做一个清空,这时候我们就需要用到clean-webpack-plugin,还有其他的一些插件等,他们都是基于webpack打包时间节点去做一些事情的,让打包更加的便利,省去了一些人工,这就是plugin的作用和运行机制,这里简单概括了一下,如果你对Plugin的基本概念和使用还不了解的话, 建议先回头看一下我之前关于plugin的文章讲解再回头来看这篇。webpack核心,Plugins让打包更快捷。下面我们将给大家讲解,如果编写一个自己的简单的plugin。

开始

plugin的编写,是基于“发布订阅”的,也就是“事件驱动”模式的,我们在写plugin的过程中,不管要关注其核心代码的编写,更要关注其对“发布订阅”模式的应用实践。
我们创建一个项目目录,并对其做npm初始化,并安装Webpack和webpack-cli,这里不再赘述过程。最终我们的项目目录如下

|--src
  |--index.js
|--package.json
|--webpack.config.js

index.js

console.log('hello world')
目标

我们现在想通过插件实现一个“打包结束后,在打包文件夹下生成一个版权文件”

创建插件文件

在src下新建/plugins/copyright-webpack-plugin.js
插件的本质是一个,我们在定义一个plugin时,实际上在定义一个类。

class CopyrightWebpackPlugin {
    constructor () {
      console.log('插件被使用了')
    }
    apply (compiler) { // 其中compiler为webpack实例,下面我们详细讲

    }
}
module.exports = CopyrightWebpackPlugin

上面我们就定义了一个插件,接下来,我们将根据我们的需求,对他进行扩展。
先在webpack配置中使用这个插件

const CopyrightWebpackPlugin = require('./plugins/copyright-webpack-plugin')
module.exports = {
    plugins: [
        new CopyrightWebpackPlugin()
    ]
}

我们做打包先


webpackPlugin.png

这说明,我们的插件创建成功了,并且也在打包中生效了,下面我们接着完善,有时候,我们在使用一些插件的时候,会涉及到传参,那么我们怎么也能让我们自己的插件具有接受参数的能力呢?
我们在实例化插件的时候传参给插件如下

const CopyrightWebpackPlugin = require('./plugins/copyright-webpack-plugin')
module.exports = {
    plugins: [
        new CopyrightWebpackPlugin({
          name: 'myPlugin'
    })
    ]
}

这时候,我们通过构造函数的参数,就可以接收到对应的参数了

class CopyrightWebpackPlugin {
    constructor (options) {
        console.log('插件被使用了')
        console.log(options)
    }

    apply (compiler) {

    }
}
module.exports = CopyrightWebpackPlugin

再打包,结果如下


pluginOptions.png

所以,如果你需要在插件中传入一些参数,你应该通过构造函数的参数来接受,这就是constrctor的作用,当然了,如果你不需要传参,那么你不必使用,我们本次的目的是在打包目录下创建一个版权文件,不需要参数,我们可以不使用,这里只是给大家先演示一下。

apply函数

我们主要的功能还是要在apply函数中做,其中上面也介绍到了,它有一个参数叫‘compiler’,我们可以将其理解为Webpack实例,具体的参数介绍,我们去参照一下官网,我们可以看到,有这样一个参数,叫‘hooks’,也就是钩子,如果你熟悉react或者vue,那么他们对应的生命周期函数其实就hooks,它能让js组件在不同的生命周期内执行不同的方法,这里的compiler.hooks也是这样的作用,他能让打包过程中在不同的时间节点执行不同的方法。我们可以在官网上看到各种时间节点的钩子,其中有一个emit的钩子,代表'生成资源到 output 目录之前。',这正符合我们的插件目的,在将打包资源放到目录之前,我们生成一个版权文件,将其放到output目录中。那么我们如何去用呢?

class CopyrightWebpackPlugin {
    apply (compiler) {
        compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, callback) => {
            console.log('生成资源到 output 目录之前')
            callback()
        })
    }
}
module.exports = CopyrightWebpackPlugin

上面的代码简单使用了一下, 下面我说一下具体的参数,
从官网上可以看到,emit是一个异步的钩子,我们在使用的时候就要区别对待,需要在后面调用tapAsync方法,该方法接受两个参数,第一个参数为‘插件的名字’,第二个参数是一个函数,这个函数接受两个参数,其中compilation代表在当前打包时的相关配置,这里要区别去apply的compiler参数,compiler是代表的整个打包过程的相关配置。callback是需要我们在执行完相关的方法后要进行手动的调用。
我们经过上面的简单改造后,再次打包

webapckEmit.png
没有问题,在预期内执行了,我们接着去完善他。
class CopyrightWebpackPlugin {
    apply (compiler) {
        compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, callback) => {
            console.log(compilation.assets)
            callback()
        })
    }
}
module.exports = CopyrightWebpackPlugin

接下来,我们看一下,在本次打包中,他的compilation.assets内容是什么。你也可以先看看compilation的内容是什么,可以看到内容有很多,包括很多webpack的相关配置,对于我们将要实现的插件,我们只关注assets

compilationAssest.png
可以看出来,上面的大概内容是,‘我们本次要生成的文件叫main,然后里面有一些内容’,所以说,我们打包要生成的文件,实际上是放在compilation.assets中的,那么我们要在打包文件里加一些内容,就变得很简单了。
class CopyrightWebpackPlugin {
    apply (compiler) {
        compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, callback) => {
            compilation.assets['Copyright.txt'] = {
                source: function () {
                    return '这是一个版权文件'
                },
                size: function () {
                    return 8
                }
            }
            callback()
        })
    }
}
module.exports = CopyrightWebpackPlugin

我们往compilation.assets中新加入一个键名,其为生成的文件的名字,内容是一个对象,其中source代表里面的内容(代码),size代表文件的大小,这时候,再去做一个打包

copyrighttxt.png
可以看出来,新生成的文件,已经有一个我们创建的文件了,这时候,再到打包目录下,这个文件已经创建成功,内容也是我们预设的内容。
其他钩子

上面我们使用emit钩子实现了我们的需求,我们看官网会发现,钩子有很多种,代表着各个不同的时刻,同时这些钩子也是有同步有异步,对于异步的钩子,写法和emit一样,下面我们找一个同步的钩子,来演示一下同步的钩子怎么写,这里我们用compile钩子。
其代表的时间节点是‘一个新的编译(compilation)创建之后,钩入(hook into) compiler’。

...
    compiler.hooks.compile.tap('CopyrightWebpackPlugin', compilation => {
        console.log('compileHooks')
    })
...

我们不需要在调用tapAsync函数了,也不需要在后面函数中传入‘callback’了,这样配置后,打包看看吧~我们就可以在这一 节点下做一些事情了,这就是同步钩子的写法。

debug

到这里,我们已经实现了自己的插件,并介绍了同步钩子和异步钩子的不同写法,实际上,在我们写一些比较复杂的插件的时候,是需要做一些调试的,这里我再介绍一种debug方法。
在package.json中假如下面的指令

"debug": "node --inspect --inspect-brk node_modules/webpack/bin/webpack.js",

我们使用node来执行这个文件,实际上他和我们执行"build": "webpack"命令一样,只不过这样,我们就可以调试了。通过上面的命令后,我们通过--inspect--inspect-brk开启了node的调试模式,这时候,我们运行debug指令,就会进入node的调试模式,我们打开浏览器控制台,就会有这样的图标

nodedebug.png
点击后会进入一个source 面板如下
source.png
因为我们上面的指令,会默认在第一行打一个断点,这时候我们就可以看到打包的全过程了,如果想要看到我们自己封装的插件的调试,可以在插件的对应行写入'debugger',这样,重复上面的,走断点,就可以看到我们自己封装的插件中的参数和运行情况了
myplugindebugger.png

写在最后

我们通过本文的讲解,介绍了一个简单的Webpack插件的实现,并介绍了一些其他的api和调试方法,如果我们有相关的需求就可以去学习封装自己的插件了!!

相关文章

网友评论

      本文标题:手写一个webpack Plugin

      本文链接:https://www.haomeiwen.com/subject/hzixyktx.html