【本文主旨】仅探讨插件系统的实现,不针对某个具体功能的插件。
【关键词】依赖与被依赖者的管理、赋能、vue、webpack、babel
对插件最直观的认识,顾名思义,即插即用,具有很高的灵活性;另外插件完全解耦于使用者,只要满足对接口的约定即可。 本文主要从目前使用较多的几个工具入手,简单介绍下插件的使用,以及它们对插件的约定格式。
-
前言
从简单的例子开始:function pluginA(App) { App.protoType.a = 1; } function pluginB(App) { App.protoType.b = 1; } class App { } //对App原型进行增强 pluginA(App); pluginB(App);这种写法,从语义上看,plugin是主动施与者,为了统一对外接口,较多的写法为:
class App { use(plugins) { let type = Object.protoType.toString.call(plugins); if(type === '[object Function]') { plugins.call(null, this); }else if(type === '[object Array]') { plugins.forEach(plugin => { pulgin.call(null, this); }); } } } //用法一 App.use(pluginA); App.use(pluginB); //用法二 App.use([pluginA, pluginB]);由使用者提供接口,语义上更友好。App:"我需要pluginA"。pluginA便为之所用。是不是有些IOC的意思?
-
webpack插件
插件向第三方开发者提供了 webpack 引擎中完整的能力。使用阶段式的构建回调,开发者可以引入它们自己的行为到 webpack 构建流程中。-
插件写法
function MyPlugin(options) { }; // 在插件函数的 prototype 上定义一个 `apply` 方法。 MyPlugin.prototype.apply = function(compiler) { // 指定一个挂载到 webpack 自身的事件钩子。 compiler.plugin('webpacksEventHook', function(compilation, callback) { // 功能完成后调用 webpack 提供的回调。 callback(); }); }; module.exports = MyPlugin;顺便一提:
compiler:代表的是不变的webpack环境,是针对webpack的;
compilation:针对的是随时可变的项目文件,只要文件有改动,compilation就会被重新创建。 -
用法
var MyPlugin = require('my-plugin'); var webpackConfig = { ..., plugins: [ new MyPlugin({options: true}) ] }; -
深入理解
- 插件的初始化时机
为了让插件对webpack构建生命周期的事件节点,做出相应的反应,在读取webpack.config.js文件后,会首先执行配置文件中插件(plugin)的实例化,为webpack事件流挂上自定义钩子。 - apply方法的约定
查看源码,插件的应用如下:由此看出,webpack内部对插件的统一处理:获取插件的apply方法,将compiler注入,以完成事件钩子的注册。此处apply便是webpack处理插件的通用接口,因此编写webpack插件,必须提供apply方法。if (options.plugins && Array.isArray(options.plugins)) { for (const plugin of options.plugins) { //调用plugin的apply方法 plugin.apply(compiler); } }
- 插件的初始化时机
-
-
Vue插件
- 插件写法
//1. 若插件为对象,必须提供install方法 MyPlugin.install = function (Vue, options) { } export default MyPlugin; //2. 若为函数,会被作为install方法 export default function(Vue) { } - 用法
var MyPlugin = require('my-plugin'); Vue.use(MyPlugin, options);use的内部处理和webpack类似:MyPlugin.install(Vue),都是使用约定的函数,将使用者注入,进行功能增强。 另外提一下,Regular中的Component也是由一个use函数来统一'使用'插件。
- 插件写法
-
Babel插件
Babel 虽然开箱即用,但是什么动作都不做。它基本上类似于const babel = code => code;,将代码解析之后再输出同样的代码。如果想要 Babel 做一些实际的工作,就需要为其添加插件。- 插件写法
上面定义了一个简单的//babel-plugin-demo/index module.exports = function() { return { //访问者 visitor: { BinaryExpression(path, state) { //对AST对象进行变换操作 } /** *等价于 * BinaryExpression: { * enter() { * //进入节点,相应的存在exit() * } * } * **/ } } }访问者(visitor),当遍历AST树的过程中遇到type为BinaryExpression的节点,就会调用BinaryExpression()方法。 - 使用
//.babelrc { ... "plugin": [ [ "demo", //等价于babel-plugin-demo { ... //state.opts } ] ] }
- 插件写法
-
Eslint插件
- 创建规则
//使用yo和generator-eslint生成模板 ... 1. lib/rules/x.js module.exports = { meta: { type: '', docs: {}, fixable: '', schema:[], message: { //mcontext.report中的essageId对应的值配置 } }, create: function(context) { return { //键名是AST的选择器(和babel的写法类似) //使用context.report抛出问题以及fix设置 } } } 2. lib/index.js module.exports = { rule: { 'x': require('./rules/x') }, config: { recommended: { rules: { 'demo/x': 2 //或者eslint-plugin-demo } } } }- 使用
//安装 npm i eslint-plugin-demo -D //.eslintrc.js配置 //使用1:presets "extends": [ "eslint:recommended", "plugin:eslint-plugin-demo/recommended" //使用插件的recommended配置 ], //使用2 "plugin": [ "demo" ], "rules": [ "demo/recommended": "error" //针对插件的每个规则自定义处理 ] -
总结
通过上面的总结,可以看到不同框架对插件的实现大同小异:不固化依赖,而是通过对外提供"插槽"的方式,由使用者灵活的按需注入。在其中,看到了
依赖注入和装饰者模式的影子。
日常开发中,随处可见插件的应用,比如redux的中间件等,这里不做赘述。如果能够灵活处理依赖与被依赖两者的关系,有利于提高代码的复用性,对编程思维也是一种提升。

欢迎关注公众号,不定时更新哦~
【欢迎留言】本文是否对你有帮助,亦或有所
遗漏笔误等,烦请告知。