NutUI v3 版本釋出至今已經 1 年了,無論是集團內部還是外部開發者,都在各自不同的業務場景中開發使用,我們感到驕傲的同時也是壓力倍增,積極努力修復開發者的各種 Issue,擴充套件元件功能,儘可能滿足開發者訴求。今年以來陸續補充了多技術棧(React)、元件級 UI 定製、國際化及程式碼智慧提示能力。本篇將介紹程式碼智慧提示(Vscode外掛)的功能,並就 NutUI-Vscode 的實現給大家做一個詳盡的剖析。
直觀體驗
什麼是程式碼智慧提示?為了讓大家有一個直觀的認識,讓我們先來仔細觀察下面兩張 gif
圖~
元件庫沒有任何的程式碼提示
元件庫有了智慧提示之後
不知大家在看了上面兩張圖片之後有什麼樣的感想?很明顯,我們使用了智慧提示之後,無論是快速檢視文件,還是檢視元件屬性,都會很方便的進行查閱,當然開發效率上肯定也有了顯著的提升。那麼,讓我們快來親自體驗下吧~
使用指南
Tips:NutUI官網-開發工具支援,這裡也有簡要介紹哦~
- 在
vscode
中安裝nutui-vscode-extension
外掛
- 安裝
vetur
外掛。不瞭解這個外掛的這裡有介紹
安裝完成以上兩個外掛之後,重新啟動我們的 vscode
,就可以愉快的體驗 NutUI
的智慧提示功能啦,是不是簡直不要太簡單~
體驗也結束了,是不是該跟我一起熟悉熟悉它的原理啦。既然是開發 vscode
外掛,那首先我們一定是要先熟悉它的開發以及除錯、釋出流程。一份文件送給你哦。看這裡
熟悉了基本的 vscode
開發流程之後,下面就跟隨我一步步揭開這個智慧提示功能的神祕面紗吧~
360全方位解讀
快速檢視元件文件
從上圖可以看出,當我們在使用 NutUI
進行開發的時候,我們在寫完一個元件 nut-button
,滑鼠 Hover 到元件上時,會出現一個提示,點選提示可以開啟 Button
元件的官方文件。我們可快速檢視對應的 API
來使用它開發。
首先我們需要在 vscode
生成的專案中,找到對應的鉤子函式 activate
,在這裡面註冊一個 Provider
,然後針對定義好的型別檔案 files
通過 provideHover
來進行解析。
const files = ['vue', 'typescript', 'javascript', 'react'];
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.languages.registerHoverProvider(files, {
provideHover
})
);
}
下面我們可以具體看看 provideHover
是如何實現的?
const LINK_REG = /(?<=<nut-)([\w-]+)/g;
const BIG_LINK_REG = /(?<=<Nut-)([\w-])+/g;
const provideHover = (document: vscode.TextDocument, position: vscode.Position) => {
const line = document.lineAt(position); //根據滑鼠的位置讀取當前所在行
const componentLink = line.text.match(LINK_REG) ?? [];//對 nut-開頭的字串進行匹配
const componentBigLink = line.text.match(BIG_LINK_REG) ?? [];
const components = [...new Set([...componentLink, ...componentBigLink.map(kebabCase)])]; //匹配出當前Hover行所包含的元件
if (components.length) {
const text = components
.filter((item: string) => componentMap[item])
.map((item: string) => {
const { site } = componentMap[item];
return new vscode.MarkdownString(
`[NutUI -> $(references) 請檢視 ${bigCamelize(item)} 元件官方文件](${DOC}${site})\n`,
true
);
});
return new vscode.Hover(text);
}
};
通過 vscode
提供的 API
以及 對應的正則匹配,獲取當前 Hover
行所包含的元件,然後通過遍歷的方式返回不同元件對應的 MarkDownString
,最後返回 vscode.Hover
物件。
細心的你們可能發現了,這裡面還包含了一個 componentMap
,它是一個物件,裡面包含了所有元件的官網連結地址以及 props
資訊,它大致的內容是這樣的:
export interface ComponentDesc {
site: string;
props?: string[];
}
export const componentMap: Record<string, ComponentDesc> = {
actionsheet: {
site: '/actionsheet',
props: ["v-model:visible=''"]
},
address: {
site: '/address',
props: ["v-model:visible=''"]
},
addresslist: {
site: '/addresslist',
props: ["data=''"]
}
...
}
為了能夠保持每次元件的更新都會及時同步,componentMap
這個物件的生成會通過一個本地指令碼執行然後自動注入,每次在更新發布外掛的時候都會去執行一次,保證和現階段的元件以及對應的資訊保持一致。這裡的元件以及它所包含的資訊都需要從專案目錄中獲取,這裡的實現和後面講的一些內容相似,大家可以先想一下實現方式,具體實現細節在後面會一起詳解~
元件自動補全
我們使用 NutUI
元件庫進行專案開發,當我們輸入 nut-
時,編輯器會給出我們目前元件庫中包含的所有元件,當我們使用上下鍵快速選中其中一個元件進行回車,這時編輯器會自動幫我們補全選中的元件,並且能夠帶出當前所選元件的其中一個 props
,方便我們快速定義。
這裡的實現,同樣我們需要在 vscode
的鉤子函式 activate
中註冊一個 Provider
。
vscode.languages.registerCompletionItemProvider(files, {
provideCompletionItems,
resolveCompletionItem
})
其中,provideCompletionItems
,需要輸出用於自動補全的當前元件庫中所包含的元件 completionItems
。
const provideCompletionItems = () => {
const completionItems: vscode.CompletionItem[] = [];
Object.keys(componentMap).forEach((key: string) => {
completionItems.push(
new vscode.CompletionItem(`nut-${key}`, vscode.CompletionItemKind.Field),
new vscode.CompletionItem(bigCamelize(`nut-${key}`), vscode.CompletionItemKind.Field)
);
});
return completionItems;
};
resolveCompletionItem
定義游標選中當前自動補全的元件時觸發的動作,這裡我們需要重新定義游標的位置。
const resolveCompletionItem = (item: vscode.CompletionItem) => {
const name = kebabCase(<string>item.label).slice(4);
const descriptor: ComponentDesc = componentMap[name];
const propsText = descriptor.props ? descriptor.props : '';
const tagSuffix = `</${item.label}>`;
item.insertText = `<${item.label} ${propsText}>${tagSuffix}`;
item.command = {
title: 'nutui-move-cursor',
command: 'nutui-move-cursor',
arguments: [-tagSuffix.length - 2]
};
return item;
};
其中, arguments
代表游標的位置引數,一般我們自動補全選中之後游標會在 props
的引號中,便於用來定義,我們結合目前補全的字串的規律,這裡游標的位置是相對確定的。就是閉合標籤的字串長度 -tagSuffix.length
,再往前面 2 個字元的位置。即 arguments: [-tagSuffix.length - 2]
。
最後,nutui-move-cursor
這個命令的執行需要在 activate
鉤子函式中進行註冊,並在 moveCursor
中執行游標的移動。這樣就實現了我們的自動補全功能。
const moveCursor = (characterDelta: number) => {
const active = vscode.window.activeTextEditor!.selection.active!;
const position = active.translate({ characterDelta });
vscode.window.activeTextEditor!.selection = new vscode.Selection(position, position);
};
export function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand('nutui-move-cursor', moveCursor);
}
什麼?有了這些還不夠?有沒有更加智慧化的,我不用看元件文件,照樣可以輕鬆開發。emm~~~,當然,請聽下文講解
vetur 智慧化提示
提到 vetur
,熟悉 Vue
的同學一定不陌生,它是 Vue
官方開發的外掛,具有程式碼高亮提示、識別 Vue
檔案等功能。通過藉助於它,我們可以做到自己元件庫裡的元件能夠自動識別 props
並進行和官網一樣的詳細說明。
像上面一樣,我們在使用元件 Button
時,它會自動提示元件中定義的所有屬性。當按上下鍵快速切換時,右側會顯示當前選中屬性的詳細說明,這樣,我們無需檢視文件,這裡就可以看到每個屬性的詳細描述以及預設值,這樣的開發簡直爽到起飛~
仔細讀過文件就可以瞭解到,vetur
已經提供給了我們配置項,我們只需要簡單配置下即可,像這樣:
//packag.json
{
...
"vetur": {
"tags": "dist/smartips/tags.json",
"attributes": "dist/smartips/attributes.json"
},
...
}
tags.json
和 attributes.json
需要包含在我們的打包目錄中。當前這兩個檔案的內容,我們也可以看下:
// tags.json
{
"nut-actionsheet": {
"attributes": [
"v-model:visible",
"menu-items",
"option-tag",
"option-sub-tag",
"choose-tag-value",
"color",
"title",
"description",
"cancel-txt",
"close-abled"
]
},
...
}
//attributes.json
{
"nut-actionsheet/v-model:visible": {
"type": "boolean",
"description": "屬性說明:遮罩層可見,預設值:false"
},
"nut-actionsheet/menu-items": {
"type": "array",
"description": "屬性說明:列表項,預設值:[ ]"
},
"nut-actionsheet/option-tag": {
"type": "string",
"description": "屬性說明:設定列表項標題展示使用引數,預設值:'name'"
},
...
}
很明顯,這兩個檔案也是需要我們通過指令碼生成。和前面提到的一樣,這裡涉及到元件以及和它們關聯的資訊,為了能夠保持一致並且維護一份,我們這裡通過每個元件原始碼下面的 doc.md
檔案來獲取。因為,這個檔案中包含了元件的 props
以及它們的詳細說明和預設值。
元件 props
詳細資訊
tags
, attibutes
, componentMap
都需要獲取這些資訊。
我們首先來看看 doc.md
中都包含什麼?
## 介紹
## 基本用法
...
### Prop
| 欄位 | 說明 | 型別 | 預設值 |
| -------- | ---------------------------------------------------------------- | ------ | ------ |
| size | 設定頭像的大小,可選值為:large、normal、small,支援直接輸入數字 | String | normal |
| shape | 設定頭像的形狀,可選值為:square、round | String | round |
...
每個元件的 md
文件,我們預覽時是通過 vite
提供的外掛 vite-plugin-md
,來生成對應的 html
,而這個外掛裡面引用到了 markdown-it
這個模組。所以,我們現在想要解析 md
檔案,也需要藉助於 markdown-it
這個模組提供的 parse API
.
// Function getSources
let sources = MarkdownIt.parse(data, {});
// data代表文件內容,sources代表解析出的list列表。這裡解析出來的是Token列表。
在Token
中,我們只關心 type
即可。因為我們要的是 props
,這部分對應的 Token
的 type
就是 table_open
和 table_close
中間所包含的部分。考慮到一個文件中有多個 table
。這裡我們始終取第一個, 這也是要求我們的開發者在寫文件時需要注意的地方 。
拿到了中間的部分之後,我們只要在這個基礎上再次進行篩選,選出 tr_open
和 tr_close
中間的部分,然後再篩選中間 type = inline
的部分。最後取 Token
這個物件中的 content
欄位即可。然後在根據上面三個檔案不同的需求做相應的處理即可。
const getSubSources = (sources) => {
let sourcesMap = [];
const startIndex = sources.findIndex((source) => source.type === TYPE_IDENTIFY_OPEN);
const endIndex = sources.findIndex((source) => source.type === TYPE_IDENTIFY_CLOSE);
sources = sources.slice(startIndex, endIndex + 1);
while (sources.filter((source) => source.type === TR_TYPE_IDENTIFY_OPEN).length) {
let trStartIndex = sources.findIndex((source) => source.type === TR_TYPE_IDENTIFY_OPEN);
let trEndIndex = sources.findIndex((source) => source.type === TR_TYPE_IDENTIFY_CLOSE);
sourcesMap.push(sources.slice(trStartIndex, trEndIndex + 1));
sources.splice(trStartIndex, trEndIndex - trStartIndex + 1);
}
return sourcesMap;
};
好了,以上就是解析的全部內容了。總結起來就那麼幾點:
1、建立一個基於 vscode
的專案,在它提供的鉤子中註冊不同行為的 command
和 languages
,並實現對應的行為
2、結合 vetur
,配置 packages.json
3、針對 map
json
檔案,需要提供相應的生成指令碼,確保資訊的一致性。這裡解析 md
需要使用 markdown-it
給我們提供的 parse
功能。
最後
本文從直觀體驗到實際使用再到實現原理分析,一步步帶著大家感受了 NutUI
和 VSCode
結合,給大家帶來的福利,讓大家能在開發上有了全新的體驗,同時,也讓我們的元件庫越發充滿了魅力。接下來,讓我們共同攜手,讓它發揮出更加強大的價值~
相關文件
- NutUI官網:https://nutui.jd.com/#/
- NutUI-React版:https://jelly.jd.com/article/61d3b7c47cbc44b32c1427c9
- NutUI-UI定製:https://jelly.jd.com/article/623af906f25db001d3f9dc26
- VSCode:https://code.visualstudio.com/docs
期待您的使用與反饋 ❤️~