2
头图
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 .因为我们要的是propsTokentype就是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

Looking forward to your use and feedback ❤️~


京东设计中心JDC
696 声望1k 粉丝

致力为京东零售消费者提供完美的购物体验。以京东零售体验设计为核心,为京东集团各业务条线提供设计支持, 包括线上基础产品体验设计、营销活动体验设计、品牌创意设计、新媒体传播设计、内外部系统产品设计、企...