摘要:下面是我的組件庫(kù)大致的目錄結(jié)構(gòu)如下整個(gè)組件庫(kù)的出口在,里面的內(nèi)容差不多是下面這樣的我的代碼庫(kù)的為。改成下面這樣我們給傳了一個(gè)參數(shù),表示需要處理的,表示組件在組件庫(kù)內(nèi)部的路徑。要完成一個(gè)高質(zhì)量的,還有很多的工作要做。
需求
在最近的開(kāi)發(fā)過(guò)程中,不同的項(xiàng)目、不同的頁(yè)面都需要用到某種UI控件,于是很自然的將這些UI控件拆出來(lái),多帶帶建立了一個(gè)代碼庫(kù)進(jìn)行維護(hù)。下面是我的組件庫(kù)大致的目錄結(jié)構(gòu)如下:
... - lib - components - componentA - index.vue - componentB - index.vue - componentC - index.vue - index.js ...
整個(gè)組件庫(kù)的出口在index.js,里面的內(nèi)容差不多是下面這樣的:
import A from "./lib/componentA"; import B from "./lib/componentB"; import C from "./lib/componentC"; export { A, B, C }
我的代碼庫(kù)的name為:kb-bi-vue-component。在項(xiàng)目中引用這個(gè)組件庫(kù)的時(shí)候,代碼如下:
import { A, B } from "kb-bi-vue-component"; ....
這個(gè)時(shí)候,問(wèn)題出現(xiàn)了,我在頁(yè)面中,僅僅使用了A、B兩個(gè)組件,但是頁(yè)面打包后,整個(gè)組件庫(kù)的代碼都會(huì)被打進(jìn)來(lái),增加了產(chǎn)出的體積,包括了不少的冗余代碼。很容易想到的一個(gè)解決方案是按照以下的方式引用組件。
import A from "kb-bi-vue-component/lib/componentA"; import B from "kb-bi-vue-component/lib/componentB";
這種方法雖然解決了問(wèn)題,我想引用哪個(gè)組件,就引用哪個(gè)組件,不會(huì)有多余的代碼。但是我總覺(jué)得這種寫(xiě)法看起來(lái)不太舒服。有沒(méi)有還能像第一種寫(xiě)法一樣引用組件庫(kù),并且只引用需要的組件呢?寫(xiě)一個(gè)babel-plugin好了,自動(dòng)將第一種寫(xiě)法轉(zhuǎn)換成第二種寫(xiě)法。
Babel的原理本文只是簡(jiǎn)單介紹。想要深入理解代碼編譯,請(qǐng)學(xué)習(xí)<<編譯原理>>這里有一個(gè)不錯(cuò)的Babel教程:https://github.com/jamiebuild...
Babel是Javascript編譯器,更確切地說(shuō)是源碼到源碼的編譯器,通常也叫做『轉(zhuǎn)換編譯器』。也就是說(shuō),你給Babel提供一些Javascript代碼,Babel更改這下代碼,然后返回給你新生成的代碼。
AST在這整個(gè)過(guò)程中,都是圍繞著抽象語(yǔ)法樹(shù)(AST)來(lái)進(jìn)行的。在Javascritp中,AST,簡(jiǎn)單來(lái)說(shuō),就是一個(gè)記錄著代碼語(yǔ)法結(jié)構(gòu)的Object。比如下面的代碼:
function square(n) { return n * n; }
轉(zhuǎn)換成AST后如下,
{ type: "FunctionDeclaration", id: { type: "Identifier", name: "square" }, params: [{ type: "Identifier", name: "n" }], body: { type: "BlockStatement", body: [{ type: "ReturnStatement", argument: { type: "BinaryExpression", operator: "*", left: { type: "Identifier", name: "n" }, right: { type: "Identifier", name: "n" } } }] } }
AST是分層的,由一個(gè)一個(gè)的 節(jié)點(diǎn)(Node) 組成。如:
{ ... type: "FunctionDeclaration", id: { type: "Identifier", name: "square" }, ... }
{ type: "Identifier", name: ... }
每一個(gè)節(jié)點(diǎn)都有一個(gè)必需的 type 字段表示節(jié)點(diǎn)的類(lèi)型。如上面的FunctionDeclaration
、Identifier等等。每種類(lèi)型的節(jié)點(diǎn)都會(huì)有自己的屬性。
Babel的工作過(guò)程Babel的處理過(guò)程主要為3個(gè):解析(parse)、轉(zhuǎn)換(transform)、生成(generate)。
解析
解析主要包含兩個(gè)過(guò)程:詞法分析和語(yǔ)法分析,輸入是代碼字符串,輸出是AST。
轉(zhuǎn)換
處理AST。處理工具、插件等就是在這個(gè)過(guò)程中介入,將代碼按照需求進(jìn)行轉(zhuǎn)換。
生成
遍歷AST,輸出代碼字符串。
解析和生成過(guò)程,都有Babel都為我們處理得很好了,我們要做的就是在 轉(zhuǎn)換 過(guò)程中搞事情,進(jìn)行個(gè)性化的定制開(kāi)發(fā)。
開(kāi)發(fā)一個(gè)babel-plugin這里有詳細(xì)的介紹:https://github.com/jamiebuild...開(kāi)發(fā)方式概述
首先,需要大致了解一下babel-plugin的開(kāi)發(fā)方法。
babel使用一種 訪(fǎng)問(wèn)者模式 來(lái)遍歷整棵語(yǔ)法樹(shù),即遍歷進(jìn)入到每一個(gè)Node節(jié)點(diǎn)時(shí),可以說(shuō)我們?cè)凇冈L(fǎng)問(wèn)」這個(gè)節(jié)點(diǎn)。訪(fǎng)問(wèn)者就是一個(gè)對(duì)象,定義了在一個(gè)樹(shù)狀結(jié)構(gòu)中獲取具體節(jié)點(diǎn)的方法。簡(jiǎn)單來(lái)說(shuō),我們可以在訪(fǎng)問(wèn)者中,使用Node的type來(lái)定義一個(gè)hook函數(shù),每一次遍歷到對(duì)應(yīng)type的Node時(shí),hook函數(shù)就會(huì)被觸發(fā),我們可以在這個(gè)hook函數(shù)中,修改、查看、替換、刪除這個(gè)節(jié)點(diǎn)。說(shuō)起來(lái)很抽象,直接看下面的內(nèi)容吧。
開(kāi)始開(kāi)發(fā)吧下面,根據(jù)我們的需求,來(lái)開(kāi)發(fā)一個(gè)plugin。怎么配置使用自己的babel-plugin呢?我的項(xiàng)目中,是使用.babelrc來(lái)配置babel的,如下:
{ "presets": [ ["es2015"], ["stage-0"] ] }
上面的配置中,只有兩個(gè)預(yù)設(shè),并沒(méi)有使用插件。首先加上插件的配置。由于是在本地開(kāi)發(fā),插件直接寫(xiě)的本地的相對(duì)地址。
{ "presets": [ ["es2015"], ["stage-0"] ], "plugins":["./my-import-babel-plugin"] }
僅僅像上面這樣是有問(wèn)題的,因?yàn)樾枨笫切枰槍?duì)具體的library,所以肯定是需要傳入?yún)?shù)的。改成下面這樣:
{ "presets": [ ["es2015"], ["stage-0"] ], "plugins":[ ["./my-import-babel-plugin", { "libraryName": "kb-bi-vue-component", "alias": "kb-bi-vue-component/lib/components"}] ] }
我們給plugin傳了一個(gè)參數(shù),libraryName表示需要處理的library,alias表示組件在組件庫(kù)內(nèi)部的路徑。
下面是插件的代碼./my-import-babel-plugin.js
module.exports = function ({ types: t }) { return { visitor: { ImportDeclaration(path, source){ const { opts: { libraryName, alias } } = source; if (!t.isStringLiteral(path.node.source, { value: libraryName })) { return; } console.log(path.node); // todo } } } }
函數(shù)的參數(shù)為babel對(duì)象,對(duì)象中的types是一個(gè)用于 AST 節(jié)點(diǎn)的 Lodash 式工具庫(kù),它包含了構(gòu)造、驗(yàn)證以及變換 AST 節(jié)點(diǎn)的方法。 該工具庫(kù)包含考慮周到的工具方法,對(duì)編寫(xiě)處理AST邏輯非常有用。我們多帶帶把這個(gè)types拿出來(lái)。返回的visitor就是我們上文提到的訪(fǎng)問(wèn)者對(duì)象。這次的需求是對(duì) import 語(yǔ)句的修改,所以我們?cè)趘isitor中定義了import的type:ImportDeclaration。這樣,當(dāng)babel處理到代碼里的import語(yǔ)句時(shí),就會(huì)走到這個(gè)ImportDeclaration函數(shù)里面來(lái)。
這里查看Babel定義的所有的AST Node: https://github.com/babel/babe...
ImportDeclaration接受兩個(gè)參數(shù),
path表示當(dāng)前訪(fǎng)問(wèn)的路徑,path.node就能取到當(dāng)前訪(fǎng)問(wèn)的Node.
source表示PluginPass,即傳遞給當(dāng)前plugin的其他信息,包括當(dāng)前編譯的文件、代碼字符串以及我們?cè)?b>.babelrc中傳入的參數(shù)等。
在插件的代碼中,我們首先取到了傳入插件的參數(shù)。接著,判斷如果不是我們需要處理的library,就直接返回了。
這里可以查看babel.types的使用方法:https://babeljs.io/docs/en/ba...
假設(shè)我們的業(yè)務(wù)代碼中的代碼如下:
... import { A, B } from "kb-bi-vue-component" ...
我們運(yùn)行一下打包工具,輸出一下path.node,可以看到,當(dāng)前訪(fǎng)問(wèn)的Node如下:
Node { type: "ImportDeclaration", start: 9, end: 51, loc: SourceLocation { start: Position { line: 10, column: 0 }, end: Position { line: 10, column: 42 } }, specifiers: [Node { type: "ImportSpecifier", start: 18, end: 19, loc: [Object], imported: [Object], local: [Object] }, Node { type: "ImportSpecifier", start: 21, end: 22, loc: [Object], imported: [Object], local: [Object] } ], source: Node { type: "StringLiteral", start: 30, end: 51, loc: SourceLocation { start: [Object], end: [Object] }, extra: { rawValue: "kb-bi-vue-component", raw: ""kb-bi-vue-component"" }, value: "kb-bi-vue-component" } }
稍微解釋一下這個(gè)Node. specifiers是一個(gè)數(shù)組,包含兩個(gè)Node,對(duì)應(yīng)的是代碼import后面的兩個(gè)參數(shù)A和B。這兩個(gè)Node的local值都是Identifier類(lèi)型的Node。source表示的是代碼from后面的library。
接下來(lái),按照需求把這個(gè)ImportDeclaration類(lèi)型的Node替換掉,換成我們想要的。使用path.replaceWithMultiple這個(gè)方法來(lái)替換一個(gè)Node。此方法接受一個(gè)Node數(shù)組。所以我們首先需要構(gòu)造出Node,裝進(jìn)一個(gè)數(shù)組里,然后扔給這個(gè)path.replaceWithMultiple方法。
查閱文檔,
t.importDeclaration(specifiers, source) specifiers: Array(required) source: StringLiteral (required)
可以通過(guò)t.importDeclaration來(lái)構(gòu)造importNode,參數(shù)如上所示。構(gòu)造importNode,需要先構(gòu)造其參數(shù)需要的Node。最終,修改插件的代碼如下:
module.exports = function ({ types: t }) { return { visitor: { ImportDeclaration(path, source) { const { opts: { libraryName, alias } } = source; if (!t.isStringLiteral(path.node.source, { value: libraryName })) { return; } const newImports = path.node.specifiers.map( item => { return t.importDeclaration([t.importDefaultSpecifier(item.local)], t.stringLiteral(`${alias}/${item.local.name}`)) }); path.replaceWithMultiple(newImports); } } } }開(kāi)發(fā)基本結(jié)束
好了,一個(gè)babel-plugin開(kāi)發(fā)完成了。我們成功的實(shí)現(xiàn)了以下的編譯:
import { A, B } from "kb-bi-vue-component"; ↓ ↓ ↓ ↓ ↓ ↓ import A from "kb-bi-vue-component/lib/components/A"; import B from "kb-bi-vue-component/lib/components/B";
babel在工作時(shí),會(huì)優(yōu)先執(zhí)行.babelrc中的plugins,接著才會(huì)執(zhí)行presets。我們優(yōu)先將源代碼進(jìn)行了轉(zhuǎn)換,再使用babel去轉(zhuǎn)換為es5的代碼,整個(gè)過(guò)程是沒(méi)有問(wèn)題的。
當(dāng)然,這是最簡(jiǎn)單的babel-plugin,還有很多其他情況沒(méi)有處理,比如下面這種,轉(zhuǎn)換后就不符合預(yù)期。
import { A as aaa, B } from "kb-bi-vue-component"; ↓ ↓ ↓ ↓ ↓ ↓ import aaa from "kb-bi-vue-component/lib/components/aaa"; import B from "kb-bi-vue-component/lib/components/B";
要完成一個(gè)高質(zhì)量的babel-plugin,還有很多的工作要做。
附:阿里已經(jīng)開(kāi)源了一個(gè)成熟的babel-plugin-import
參考鏈接:
1、https://github.com/jamiebuild...
2、https://babeljs.io/docs/en/ba...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/97902.html
摘要:配置配置使用概率抽樣。采樣率定義了對(duì)跟蹤跨度進(jìn)行采樣的概率,其值可以介于和含之間。例如,以下配置對(duì)象將采樣率更改為即每個(gè)跨度都被采樣,并使用協(xié)議將跟蹤發(fā)送到位于的服務(wù)器文件路徑注將采樣率更改為會(huì)完全禁用跟蹤。目錄手把手教你學(xué)Dapr - 1. .Net開(kāi)發(fā)者的大時(shí)代手把手教你學(xué)Dapr - 2. 必須知道的概念手把手教你學(xué)Dapr - 3. 使用Dapr運(yùn)行第一個(gè).Net程序手把手教你學(xué)Da...
摘要:我們?cè)陂_(kāi)發(fā)應(yīng)用的時(shí)候經(jīng)常美工會(huì)設(shè)計(jì)一些樣式比較特殊的圖表,這對(duì)于前端開(kāi)發(fā)人員來(lái)說(shuō)會(huì)增加開(kāi)發(fā)量,如下圖就是筆者開(kāi)發(fā)過(guò)程中要求制作的帶漸變色效果的柱狀圖今天在這里教大家如何用原生和如何用圖表開(kāi)發(fā)工具來(lái)實(shí)現(xiàn)。 我們?cè)陂_(kāi)發(fā)web應(yīng)用的時(shí)候經(jīng)常美工會(huì)設(shè)計(jì)一些樣式比較特殊的圖表,這對(duì)于前端開(kāi)發(fā)人員來(lái)說(shuō)會(huì)...
摘要:本文將從零開(kāi)始搭建一個(gè)現(xiàn)代化的框架,該框架會(huì)擁有現(xiàn)代框架的一切特征,如單入口,路由,依賴(lài)注入,類(lèi)自動(dòng)加載機(jī)制等等,如同時(shí)下最流行的框架一樣。執(zhí)行控制器文件中的邏輯代碼,最終將數(shù)據(jù)通過(guò)對(duì)應(yīng)的視圖層顯示出來(lái)。 本文將從零開(kāi)始搭建一個(gè)現(xiàn)代化的PHP框架,該框架會(huì)擁有現(xiàn)代框架的一切特征,如單入口,路由,依賴(lài)注入,composer類(lèi)自動(dòng)加載機(jī)制等等,如同時(shí)下最流行的Laravel框架一樣。 一、...
摘要:組件聲明組件分為全局的和局部的。父組件通過(guò)給子組件下發(fā)數(shù)據(jù),子組件通過(guò)事件給父組件發(fā)送消息。以下實(shí)例中子組件已經(jīng)和它外部完全解耦了。 1.vue 組件-聲明 組件分為全局的和局部的。 全局聲明 Vue.component(tagName, options) ** 引用組件 // 注冊(cè) Vue.comp...
摘要:組件聲明組件分為全局的和局部的。父組件通過(guò)給子組件下發(fā)數(shù)據(jù),子組件通過(guò)事件給父組件發(fā)送消息。以下實(shí)例中子組件已經(jīng)和它外部完全解耦了。 1.vue 組件-聲明 組件分為全局的和局部的。 全局聲明 Vue.component(tagName, options) ** 引用組件 // 注冊(cè) Vue.comp...
閱讀 3844·2021-11-25 09:43
閱讀 2188·2021-11-23 10:11
閱讀 1414·2021-09-29 09:35
閱讀 1360·2021-09-24 10:31
閱讀 2050·2019-08-30 15:48
閱讀 2366·2019-08-29 15:28
閱讀 441·2019-08-29 12:36
閱讀 3499·2019-08-28 18:12