前端插件化架構的探索和實踐

babel插件、webpack插件、vue-cli插件,爲啥這麼多的優秀框架都是使用插件系統?插件化架構是什麼?帶來了什麼好處?能夠應用到什麼場景呢?html

1. 插件化架構定義

插件化架構又稱微核架構,指的是軟件的內核相對較小,主要功能和業務邏輯都經過插件實現。插件化架構通常有兩個核心的概念:內核和插件。前端

  • 內核(pluginCore)一般只包含系統運行的最小功能;vue

  • 插件(plugin)則是互相獨立的模塊,通常會提供單一的功能。node

內核通常會將要完成的全部業務進行抽象,抽象出最小粒度的基礎接口,供插件方來調用。這樣,插件開發的效率將會極大的提升。比方說,瀏覽器就是一個典型的插件化架構,瀏覽器是內核,頁面是插件,這樣經過不一樣的URL地址加載不一樣的頁面,來提供很是豐富的功能。並且,咱們開發網頁時候,瀏覽器會提供不少API和能力,這些接口經過 window來掛載, 好比,DOM、BOM、Event、Location等等。webpack

設計一個完善的插件化架構的系統,包含三要素:git

  • plugCore:插件內核,提供插件運行時,管理插件的加載、運行、卸載等生命週期(類比瀏覽器);github

  • pluginAPI:插件運行時須要的基礎接口(類比瀏覽器例子,至關於window);web

  • plugin:一系列特定功能的獨立模塊(類比瀏覽器例子,至關於不一樣的網頁)。vue-cli

2. 插件化架構的實踐

咱們將從plugCorepluginAPIplugin三要素,來解析jQuery、Babel和Vue CLI這三大優秀的開源庫其插件化架構的實踐。npm

2.1 jQuery的插件化架構

jQuery 是一個 JavaScript 庫,極大地簡化了JavaScript 編程,用更少的代碼完成更多工做。早期瀏覽器的標準不統一,開發網頁須要兼容不一樣瀏覽器的用戶使用是一件十分痛苦的事情。jQuery在適配了不一樣瀏覽器的差別的基礎上提供了更加完善易用API,供前端開發人員完成網頁編程,使用jQuery編寫的網頁,在一套代碼下也能夠在不一樣廠商的瀏覽器上正常運行。在 MV* 框架流行以前,jQuery是絕對的扛霸子。jQuery是可擴展的,其擁有完善的插件體系,網頁開發所須要的各類插件在其生態均可以找到。咱們解析一下jQuery插件體系。

插件定義:

特別說明:$.fn = jQuery.protype(插件精髓)。jQuery的插件機制經過原型鏈來掛載。

插件機制執行過程

demo 示例

$app即可以在原型鏈上查找到myPlugin

從三要素來總結:

  • pluginCore:經過原型鏈賦值來擴展不一樣的插件,再得到jQuery實例後能夠被調用。

  • pluginAPI:jQuery包的核心接口,(jQuery依靠其優異的Api取勝)

  • plugin:無限制,能夠是JavaScript的類型,通常是實現具體功能的模塊,好比,日期選擇器等。

2.2 Babel的插件化架構

Babel 是一個工具鏈,主要用於將 ECMAScript 2015+ 版本的代碼轉換爲向後兼容的 JavaScript 語法,以便可以運行在當前和舊版本的瀏覽器或其餘環境中。在代碼轉換的過程當中會涉及許多特性和語法的轉換,並且ECMAScript的提案老是不斷地更新。如何組織大量(不斷增長)的轉換規則呢?咱們來看看 Babel的工做原理。

Babel轉換源碼,分爲三個步驟:

  • 解析(parse):進行詞法分析(Lexical Analysis)和語法分析(Syntactic Analysis)以生成抽象語法樹(AST);

  • 轉換 (transform):遍歷AST中每一個節點並進行相應的轉換操做,該過程經過使用不一樣的插件來實現各類特性和語法的轉換;

  • 生成 (generate):根據AST生成目標代碼。

Babel在AST轉換的過程(即上圖的第2步),便使用插件化架構,下面將會詳細講解這個轉換過程的插件化架構的使用。

插件定義:

插件是一個函數,返回值是一個包含visitor的對象。插件定義的部分概念說明:

  • name:插件名

  • pluginAPI: 插件運行時傳入的API

  • visitor: 是一個對象,對象的key是AST的每節點的類型,對象的值是一個函數,AST轉換的過程便在這裏發生的。

  • nodePath:是一個AST的節點的實例對象,詳細能夠參考:@babel/parser/src/parser/node.js [1],其中, type字段 : 該節點的類型,常見類型:VariableDeclaration(變量聲明)、VariableDeclarator(變量聲明表達式)、ArrowFunctionExpression(箭頭函數表達式)等等,詳細能夠參考@babel/types [2]。

(筆者認爲pluginAPI還應包括nodePath,由於,每一個節點實例除了語法和詞法描述,還包含需求語法間的轉換方法)

插件示例

箭頭函數轉換成普通函數的插件:@babel/plugin-transform-arrow-functions [3]源碼:

插件的執行思路:

  • 第一步,執行該插件,獲取到包含visitor對象;

  • 第二步,ATS遍歷節點,檢測nodePath的type === 'ArrowFunctionExpression',尋找到vistor對象的中key爲 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<function(nodePath)>

  • 第四步,AST遍歷時,每一個節點根據 NodeType,來獲取 visitor[NodeType],並依次執行。

從三要素來總結:

  • pluginCore:插件加載並整合(即vistor合併),AST遍歷期間是調用查找vistor[NodeType]並依次調用;

  • pluginAPI:nodePath,提供不一樣類型節點的接口來轉換AST節點;

  • plugin:visitor[NodeType]=function(nodepath)。

2.3 vue-cli的插件化架構

Vue CLI 是一個基於 Vue.js 進行快速開發的完整系統。CLI插件是向你的 Vue 項目提供可選功能的 npm 包,例如 Babel/TypeScript 轉譯、ESLint 集成、單元測試和 end-to-end 測試等。Vue CLI 插件的名字以 @vue/cli-plugin- (內建插件) 或 vue-cli-plugin- (社區插件) 開頭,很是容易使用。下面,咱們將會解析cli插件的定義、執行、安裝等過程。

插件定義

插件必須是vue-cli-plugin-命名的npm包,而且目錄結構也是要嚴格遵循文件命名來定義。

注意:@vue/cli-service [6],會經過 項目根目錄下package.json中dependencies和devDependencies中定義的 npm包中符合插件命名規範的 npm包做爲項目的插件。

文件命名和內容說明:

  • generator.js:會在插件被添加時執行,能夠安裝npm包、修改項目源碼等功能;

  • prompts.js:全部的對話邏輯都存儲在 prompts.js 文件中。對話內部是經過 inquirer [7] 實現,在調用其得到安裝選項結果,並在 generator.js調用時做爲參數存入;

  • index.js:Service插件的入口,@vue/cli-service [8]啓動時被執行

詳情能夠去看Vue Cli 插件開發指南 [9]

咱們把Vue CLI的插件執行分紅兩種狀況:

  • 第一種:插件未安裝,插件被添加的時候調用(prompts.js + generator.js

  • 第二種:插件已安裝,插件系統啓動時被執行(index.js

第一種 安裝流程

相比Babel的手動安裝添加插件方式,Vue CLI的插件系統提供命令行的安裝方式就顯得很方便了。咱們看看Vue Cli插件系統時怎麼實現一行命令添加插件的功能。

安裝流程的執行思路以下:

  • 第一步:從命令行參數解析出插件名,使用npm(yarn)install vue-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,並在調用generator.generate中執行generator.js函數;

  • pluginAPI:GeneratorAPI [18],提供了源碼修改、npm包管理、模版文件生成等功能;

  • plugin:由prompts.jsgenerator.js組成,解決某種能力植入項目時,要處理的依賴。

2)運行流程

  • pluginCore:@vue/cli-service [19],經過package.json中得到項目插件後,與系統內置插件合併,最後依次執行;

  • pluginApI:PluginAPI [20],提供webpack配置修改和命令管理的能力;

  • pluginindex.js文件,在不一樣命令下進行工做。

一個插件系統是能夠很少個插件類型,而且插件系統經過命令安裝插件的實現,用戶在使用插件系統時添加插件也是十分方便的。

3. 插件化架構的應用

3.1 應用場景

經過上述的實例,總結處理插件架構的應用場景。

  • 第一種:富pluginAPI場景:代碼在多種場景中運行,須要抹平場景中差別。(jQuery);

  • 第二種:富plugin場景,插件系統,可預期需求會愈來愈多,適合經過更多的插件來簡化系統的代碼量(Babel)

  • 第三種:富pluginCorepluginAPI場景,插件系統自己很是複雜,須要對開發人員要求極高,這時候,將複雜的工做放到內核和中pluginApi實現,剩下大部分的簡單的編碼工做留給插件方實現,插件方藉助pluginApi也能夠快速完成業務開發(Vue CLI)

3.2 發展方向

經過創建一個插件標準,將研發流程沉澱的能力進行插件化編程,整個公司經過使用一套的插件系統(中臺),這樣意味着,咱們不用重複造業務輪子,團隊和企業能夠持續積累本身的插件生態,讓軟件開能夠像汽車等工業製造同樣,打造一條標準化裝配的流水線。


參考連接:

1. https://github.com/babel/babel/blob/31b361b736ee519180f9ea341e2a3e4e710d2ef4/packages/babel-parser/src/parser/node.js
2. https://babeljs.io/docs/en/babel-types
3. https://babeljs.io/docs/en/next/babel-plugin-transform-arrow-functions
4. https://github.com/babel/babel/blob/fa975bf7cd2b9054faaff107a79e41dcaad305b1/packages/babel-core/src/config/full.js
5. https://github.com/babel/babel/blob/fa975bf7cd2b9054faaff107a79e41dcaad305b1/packages/babel-core/src/transformation/index.js#L107
6. https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-service
7. https://github.com/SBoudrias/Inquirer.js
8. https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-service
9. https://cli.vuejs.org/zh/dev-guide/plugin-dev.html
10. https://github.com/vuejs/vue-cli/blob/aee9e178e7c9598f317ceeed2940c94ac82e8340/packages/%40vue/cli/lib/add.js#L52
11. https://github.com/vuejs/vue-cli/blob/aee9e178e7c9598f317ceeed2940c94ac82e8340/packages/%40vue/cli/lib/invoke.js#L69
12. https://github.com/vuejs/vue-cli/blob/aee9e178e7c9598f317ceeed2940c94ac82e8340/packages/%40vue/cli/lib/Generator.js#L77
13. https://github.com/vuejs/vue-cli/blob/aee9e178e7c9598f317ceeed2940c94ac82e8340/packages/%40vue/cli/lib/Generator.js#L149
14. https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-service
15. https://github.com/vuejs/vue-cli/blob/aee9e178e7c9598f317ceeed2940c94ac82e8340/packages/%40vue/cli/lib/GeneratorAPI.js
16. https://github.com/vuejs/vue-cli/blob/aee9e178e7c9598f317ceeed2940c94ac82e8340/packages/%40vue/cli-service/lib/PluginAPI.js
17. https://github.com/vuejs/vue-cli/tree/aee9e178e7c9598f317ceeed2940c94ac82e8340/packages/%40vue/cli
18. https://github.com/vuejs/vue-cli/blob/aee9e178e7c9598f317ceeed2940c94ac82e8340/packages/%40vue/cli/lib/GeneratorAPI.js
19. https://github.com/vuejs/vue-cli/tree/aee9e178e7c9598f317ceeed2940c94ac82e8340/packages/%40vue/cli-service
20. https://github.com/vuejs/vue-cli/blob/aee9e178e7c9598f317ceeed2940c94ac82e8340/packages/%40vue/cli-service/lib/PluginAPI.js


☆ END ☆


招聘信息

OPPO互聯網基礎技術團隊招聘一大波崗位,涵蓋C++、Go、OpenJDK、Java、DevOps、Android、ElasticSearch等多個方向,請點擊這裏查看詳細信息及JD


你可能還喜歡

OPPO自研ESA DataFlow架構與實踐

數據同步一致性保障:OPPO自研JinS數據同步框架實踐

微服務全鏈路異步化實踐

Dubbo協議解析與ESA RPC實踐

自研代碼審查系統火眼Code Review實踐

更多技術乾貨

掃碼關注

OPPO互聯網技術

 

我就知道你「在看」

本文分享自微信公衆號 - OPPO互聯網技術(OPPO_tech)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索