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