babel插件、webpack插件、vue-cli插件,为什么那么多优秀的框架都在使用插件体系?什么是插件架构?带来了什么好处?可以应用于哪些场景?1、插件架构的定义插件架构也叫微内核架构,意思是软件的核心比较小,主要功能和业务逻辑都是通过插件来实现的。插件架构一般有两个核心概念:内核和插件。内核(pluginCore)通常只包含系统运行所需的最少功能;插件(plugin)是独立的模块,一般提供单一的功能。内核一般会抽象出所有要做的业务,抽象出最小粒度的基础接口供插件调用。这样一来,插件开发的效率就会大大提高。例如,浏览器就是典型的插件架构。浏览器是核心,页面是插件。这样通过不同的URL地址加载不同的页面,提供非常丰富的功能。而且,我们在开发网页的时候,浏览器提供了很多API和能力,这些接口都是通过window挂载的,比如DOM、BOM、Event、Location等。设计一个完整的插件架构体系,包括三个要素:plugCore:插件核心,提供插件运行时,管理插件加载、运行、卸载的生命周期(类比浏览器);pluginAPI:插件运行时需要的基本接口(类比浏览器例子,相当于window);plugin:一系列具有特定功能的独立模块(类比浏览器的例子,相当于不同的网页)。2.插件架构实践我们将从plugCore、pluginAPI和plugin这三个要素来分析jQuery、Babel和VueCLI这三个优秀的开源库的插件架构实践。2.1jQuery的插件架构jQuery是一个JavaScript库,它大大简化了JavaScript编程,用更少的代码完成更多的工作。早期浏览器的标准并不统一,开发兼容不同浏览器用户的网页是一件非常痛苦的事情。jQuery在适应不同浏览器差异的基础上,为前端开发者完成网页编程提供了更完善、易用的API。使用jQuery编写的网页在一套代码下也可以在不同厂商的浏览器上正常运行。跑步。在MV*框架流行之前,jQuery是绝对的霸道总裁。jQuery是可扩展的,有完整的插件系统。在其生态中可以找到Web开发所需的各种插件。下面来分析一下jQuery插件系统。插件定义:特别说明:$.fn=jQuery.protype(插件本质)。jQuery的插件机制是通过原型链挂载的。插件机制执行过程演示示例$app可以在原型链上找到myPlugin:从三个要素总结:pluginCore:通过原型链的赋值扩展不同的插件,然后获取jQuery实例,然后调用。pluginAPI:jQuery包的核心接口,(jQuery依靠其优秀的Api取胜)plugin:不受限制,可以是JavaScript的一种,一般是实现特定功能的模块,比如日期选择器等。2.2Babel的插件架构Babel是一个工具链,主要用于将ECMAScript2015+版本的代码转换为向后兼容的JavaScript语法,使其可以在当前和旧版本的浏览器或其他环境中运行。代码转换过程中涉及很多特性和语法变化,ECMAScript提案总是不断更新。如何组织大量(且不断增加)的转换规则?让我们来看看Babel是如何工作的。Babel对源码进行改造,分为三个步骤:解析(parse):进行词法分析(LexicalAnalysis)和句法分析(SyntacticAnalysis),生成抽象语法树(AST);transform(转换):遍历AST中的每个节点并进行相应的转换操作,它使用不同的插件来实现各种特征和语法的转换;generate(生成):根据AST生成目标代码。Babel在AST转换过程中(即上图中的第2步)采用了插件式架构。下面将详细解释插件架构在这个转换过程中的使用。插件定义:插件是一个函数,其返回值是一个包含访问者的对象。插件定义的部分概念说明:name:插件名称pluginAPI:APIvisitor:是插件运行时传入的对象。对象的键是AST中每个节点的类型。对象的值是一个函数。AST转换的过程就发生在这里。nodePath:它是一个AST节点的实例对象。具体可以参考:@babel/parser/src/parser/node.js[1],其中type字段:节点的类型,常见类型:VariableDeclaration(变量声明),VariableDeclarator(变量声明表达式),ArrowFunctionExpression(箭头函数表达式)等,详见@babel/types[2]。(作者认为pluginAPI还应该包括nodePath,因为每个节点实例除了语法和词法描述外,还包括所需语法之间的转换方法)将箭头函数转换为普通函数的插件示例:@babel/plugin-transform-arrow-函数[3]源码:插件的执行思路:第一步,执行插件,获取访问者对象;第二步,ATS遍历节点,检测nodePath的类型==='ArrowFunctionExpression',找到访问者对象关键是ArrowFunctionExpression的函数;第三步,将nodePath传入函数调用(这一步修改了AST);单个插件的执行思路很清晰,那么在ATS遍历过程中如何让多个插件协同工作呢?在转换Babel源代码的过程中,插件架构的工作流程如下:第一步:通过解析Babel的配置文件(或命令行的--plugins参数)获取Babel配置的所有插件的描述信息;第二步,将插件的require放入内存,获取插件函数,执行插件函数获取多个包含vistor字段的对象;(详细逻辑:@babel/core/src/config/full.js[4])第三步,将多个包含vistor字段的对象整合成一个大的visitor源码展示(详细逻辑:@babel/core/src/transformation/index.js[5]):合并后的visitor对象:visitor对象中的值变为Array第四步,遍历AST时,每个节点根据NodeType获取visitor[NodeType],并依次执行它们。从三个要素总结:pluginCore:插件加载和集成(即vistor合并),在AST遍历时,调用查找vistor[NodeType]依次调用;pluginAPI:nodePath,提供不同类型节点的接口,用于转换AST节点;插件:访问者[NodeType]=function(nodepath)。2.3vue-cli插件架构VueCLI是一个完整的基于Vue.js的快速开发系统。CLI插件是npm包,为您的Vue项目提供可选功能,例如Babel/TypeScript转译、ESLint集成、单元测试和端到端测试等。名称以@vue/cli-plugin-开头的VueCLI插件(内置插件)或vue-cli-plugin-(社区插件)都非常好用。接下来我们将分析cli插件的定义、执行、安装等过程。插件定义插件必须是vue-cli-plugin-namednpm包,目录结构也必须严格按照文件命名定义。注意:@vue/cli-service[6]会使用项目根目录下package.json中定义的dependencies和devDependencies中符合插件命名约定的npm包作为项目插件。文件命名及内容说明:generator.js:添加插件时会执行,可以安装npm包,修改项目源码等;prompts.js:所有对话逻辑都存储在prompts.js文件中。对话内部通过inquirer[7]实现,调用获取安装选项结果,调用generator.js时作为参数存储;index.js:Service插件的入口,当@vue/cli-service[8]启动时执行,具体执行请参考VueCli插件开发指南[9]我们分了VueCLI插件-in执行分为两种情况:第一种:插件没有安装,添加插件时调用(prompts.js+generator.js)第二种第二种:插件有已安装并在插件系统启动时执行(index.js)。相比Babel的手动安装和添加插件,VueCLI的插件系统提供了命令行安装方式,非常方便。我们在看VueCli插件系统的时候,看看如何实现一行命令添加插件的功能。安装过程的执行思路如下:第一步:从命令行参数中解析出插件名称,使用npm(yarn)installvue-cli-plugin-xxx安装插件,源码位置:@vue/cli/lib/add.js[10]第二步:require('vue-cli-plugin-xxx/prompts'),获取用户安装选项结果pluginOptions,源码位置:@vue/cli/lib/invoke.js[11]第三步:以pluginName和pluginOptions为参数,形成Generator实例[12]对象第四步:执行generator.generate方法。这一步包括三个关键步骤:1)require(vue-cli-plugin-xxx/generator),获取插件的执行函数;2)构建GeneratorAPI(即pluginAPI);3)调用generator.js导出函数。详细代码:_@vue/cli/lib/Generator.js[113]_第五步:将插件的参数添加到vue.config.js文件中。第二种运行流程插件运行流程由插件系统@vue/cli-service[14]定义。这里有两种调用插件:第一种是内置插件@vue/cli-service,跟命令和配置相关。将插件系统功能拆分为多个内置插件,在插件系统中默认调用;第二种项目插件,package.json中定义的npm包名符合插件命名规范)。插件运行逻辑很简单:两个进程的pluginAPI不同。安装过程:@vue/cli/lib/GeneratorAPI[15]运行过程:@vue/cli-service/lib/PluginAPI[16]从三个要素总结:1)安装过程pluginCore:@vue/cli[17]通过了命令行参数获取插件包名,然后安装插件的npm包,执行prompts.js获取用户安装选项结果,然后以选项结果和generator.js为参数构造生成器,并在调用generator.generate.js函数中执行生成器;pluginAPI:GeneratorAPI[18],提供源码修改、npm包管理、模板文件生成等功能;plugin:由prompts.js和generator.js组成,解决项目rely植入某种能力时需要处理的问题。2)运行进程pluginCore:@vue/cli-service[19],从package.json中获取项目插件后,与系统内置插件合并,最后依次执行;pluginApI:PluginAPI[20],提供webpack配置修改和命令管理能力;插件:index.js文件,在不同的命令下工作。一个插件系统可以有多个插件类型,插件系统可以通过命令安装插件。用户在使用插件系统时添加插件也非常方便。3.插件架构的应用3.1应用场景通过上面的例子,总结了插件架构的应用场景。第一种:richpluginAPI场景:代码运行在多个场景,需要磨平场景差异。(jQuery);第二种:丰富的插件场景,插件系统,需求越来越多,适合通过更多的插件来简化系统的代码量(Babel)第三种:丰富的pluginCore和pluginAPI场景、插件系统本身非常复杂,对开发者的要求极高。此时复杂的工作在kernel和pluginApi中实现,简单的编码工作大部分留给了插件端。插件端也可以快速使用pluginApi完成业务开发(VueCLI)3.2开发方向通过建立插件标准,积累研发过程的能力可以插件化编程。整个公司使用一套插件系统(中台),意味着我们不用重复业务轮子,团队和企业可以不断积累自己的插件生态,让软件开发可以构建一个标准化流水线就像汽车等工业制造。