It has been one year since the release of NutUI v3. Both internal and external developers of the group are developing and using them in different business scenarios. While we are proud, we are also under increased pressure, and we are actively working to fix various issues of developers and expand Component functions to meet developers' demands as much as possible. Since the beginning of this year, the multi-technology stack (React), component-level UI customization, internationalization and code intelligent prompting capabilities have been successively added. This article will introduce the function of code intelligent prompt (Vscode plug-in), and give you a detailed analysis of the implementation of NutUI-Vscode.
Intuitive experience
What are code intellisense? In order to give everyone an intuitive understanding, let us first carefully observe the following two gif
picture~
The component library does not have any code hints
After the component library has intellisense
What do you think after seeing the above two pictures? Obviously, after we use smart prompts, whether it is to quickly view documents or view component properties, it will be very convenient to check, of course, the development efficiency must also be significantly improved. So, let's come and experience it for ourselves~
user's guidance
Tips: NutUI official website - development tool support , here is a brief introduction~
- Installed in
vscode
nutui-vscode-extension
plugin
- Install
vetur
plugin. If you don't know about this plugin, here is an introduction
After installing the above two plug-ins, restart our vscode
, and you can experience the intelligent prompt function of NutUI
, is it not too simple~
The experience is also over, is it time to familiarize yourself with its principles with me? Since it is a development vscode
plug-in, first of all, we must be familiar with its development, debugging, and release process. A document for you. look here
After familiar with the basic vscode
development process, follow me step by step to uncover the mystery of this smart prompt function~
360 comprehensive interpretation
Quick view component documentation
As can be seen from the above figure, when we are using NutUI
for development, we have finished writing a component nut-button
, when the mouse Hovers over the component, a prompt will appear, click Prompt to open the official documentation for the Button
component. We can quickly view the corresponding API
to use it to develop.
First of all, we need to find the corresponding hook function activate
vscode
the project generated by ---31a21e76c5af9ff1ab0e1ddc073adead---, and register a Provider
files
it, and then for the defined type file- files
by provideHover
.
const files = ['vue', 'typescript', 'javascript', 'react'];
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.languages.registerHoverProvider(files, {
provideHover
})
);
}
Let's take a look at how provideHover
is implemented?
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);
}
};
Through the vscode
provided by API
and the corresponding regular matching, get the current Hover
components contained in the line, and then return the corresponding components of the different components by traversing MarkDownString
, and finally return the vscode.Hover
object.
If you are careful, you may find that there is also a componentMap
, which is an object that contains the official website link addresses of all components and props
information, its general content is Such:
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=''"]
}
...
}
In order to keep each component update synchronized in time, componentMap
The generation of this object will be executed through a local script and then automatically injected. It will be executed once every time the plugin is updated and published, to ensure that the current stage The components and corresponding information are consistent. The components here and the information they contain need to be obtained from the project directory. The implementation here is similar to some of the content described later. You can think about the implementation first. The specific implementation details will be explained in detail later~
Component autocompletion
We use the NutUI
component library for project development, when we enter nut-
, the editor will give us all the components contained in the current component library, when we use the up and down keys to quickly select them Enter a component, then the editor will automatically complete the selected component for us, and can bring out one of the currently selected components props
, which is convenient for us to quickly define.
For the implementation here, we also need to register a ---1c0ed3f620efbe893536f000146f3060 vscode
in the hook function activate
of Provider
.
vscode.languages.registerCompletionItemProvider(files, {
provideCompletionItems,
resolveCompletionItem
})
Among them, provideCompletionItems
needs to output the components contained in the current component library for auto-completion 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
Define the action triggered when the cursor selects the current auto-completion component, here we need to redefine the position of the cursor.
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;
};
Among them, arguments
represents the position parameter of the cursor. Generally, after we automatically complete the selection, the cursor will be in the quotation marks of props
, which is easy to define. Regularly, the position of the cursor is relatively deterministic here. It is the string length of the closing tag -tagSuffix.length
, and then 2 characters ahead. That is arguments: [-tagSuffix.length - 2]
.
Finally, nutui-move-cursor
the execution of this command needs to be registered in the activate
hook function, and the cursor movement should be performed in moveCursor
. This implements our autocompletion feature.
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);
}
What? Isn't that enough? Is there any more intelligent, I can easily develop without looking at the component documentation. emm~~~, of course, please listen to the explanation below
vetur Smart Tips
vetur
,熟悉Vue
的同学一定不陌生,它是---e91560734f7cfb5233d76097c3d721de Vue
官方开发的插件,具有代码高亮提示、识别Vue
and other functions. With the help of it, we can automatically identify the components in our component library props
and carry out the same detailed instructions as the official website.
For more details on vetur, see here .
Like above, when we use the component Button
, it will automatically prompt all the properties defined in the component. When you press the up and down keys to switch quickly, the detailed description of the currently selected attribute will be displayed on the right side. In this way, we can see the detailed description and default value of each attribute here without viewing the document. This kind of development is really cool to take off~
After reading the document carefully, you can understand that vetur
has provided us with configuration items, we only need to configure it simply, like this:
//packag.json
{
...
"vetur": {
"tags": "dist/smartips/tags.json",
"attributes": "dist/smartips/attributes.json"
},
...
}
tags.json
and attributes.json
need to be included in our packaging directory. We can also look at the current contents of these two files:
// 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'"
},
...
}
Obviously, these two files also need us to be generated by scripts. As mentioned above, components and their associated information are involved here. In order to maintain consistency and maintain a copy, we obtain it through the doc.md
file under the source code of each component. Because, this file contains the components props
and their detailed description and default values.
Component props
Details
tags
, attibutes
, componentMap
all need to get this information.
Let's first take a look at what is included in doc.md
?
## 介绍
## 基本用法
...
### Prop
| 字段 | 说明 | 类型 | 默认值 |
| -------- | ---------------------------------------------------------------- | ------ | ------ |
| size | 设置头像的大小,可选值为:large、normal、small,支持直接输入数字 | String | normal |
| shape | 设置头像的形状,可选值为:square、round | String | round |
...
The md
document of each component, we preview it through the plugin provided by vite
vite-plugin-md
html
The plugin references markdown-it
this module. Therefore, we now want to parse the md
file, and also need to use markdown-it
which is provided by this module parse API
.
// Function getSources
let sources = MarkdownIt.parse(data, {});
// data代表文档内容,sources代表解析出的list列表。这里解析出来的是Token列表。
In Token
, we only care about type
.因为我们要的是props
, Token
的type
就是table_open
和---ef097b353eb3d79e13df52e2bed765e8 table_close
所included part. Consider that there are multiple table
in one document. Here we always take the first one, which is also what our developers need to pay attention to when writing documentation .
After getting the middle part, we only need to screen again on this basis, and select the middle part of tr_open
and tr_close
, and then filter the middle type = inline
part. Finally, take the Token
content
field in this object. Then you can do the corresponding processing according to the different needs of the above three files.
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;
};
Well, the above is all the content of the analysis. To sum it up a few points:
1. Create a project based on vscode
, register different behaviors command
and languages
in the hooks it provides, and implement the corresponding behaviors
2. Combine vetur
, configure packages.json
3. For the map
json
file, you need to provide the corresponding generation script to ensure the consistency of the information. The analysis here md
needs to use markdown-it
provides us with the parse
function.
At last
From the intuitive experience to the actual use to the analysis of the implementation principle, this article will take you step by step to experience the combination of NutUI
and VSCode
, bringing benefits to everyone, so that everyone can develop With a new experience, at the same time, our component library is more and more full of charm. Next, let us work together to make it play a more powerful value~
Related Documentation
- NutUI official website: https://nutui.jd.com/#/
- NutUI-React version: https://jelly.jd.com/article/61d3b7c47cbc44b32c1427c9
- NutUI-UI customization: https://jelly.jd.com/article/623af906f25db001d3f9dc26
- VSCode: https://code.visualstudio.com/docs
Looking forward to your use and feedback ❤️~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。