12

1. The status quo

The Vue framework is widely used in front-end development. When a Vue project developed by multiple people is maintained for a long time, many public components will often precipitate. At this time, one person will often develop a component and other maintainers or new people will take over. But I don't know what this component is for, how to use it, and I have to look through the source code again, or I didn't notice the existence of this component at all, leading to repeated development. At this time, it is very necessary to maintain the corresponding component documents to ensure a good collaboration relationship between different developers.

But the traditional manual maintenance document will bring new problems:

  • The efficiency is low. Writing documents is a time-consuming and laborious manual work. It is hard to think about writing documents after the components are developed.
  • Error-prone, the content of the document is prone to errors, and may be inconsistent with the actual component content.
  • Unintelligent, iteratively update the components while manually synchronizing the changes to the document, which consumes time and is easy to miss.

and the ideal document maintenance method is:

  • The workload is small, and it can be combined with Vue components to automatically obtain relevant information, reducing the workload of writing documents from scratch.
  • The information is accurate, the key information of the component is consistent with the content of the component, and there is no error.
  • Intelligent synchronization. When the Vue component is iteratively upgraded, the document content can be automatically updated synchronously, without the need to manually verify whether the information is consistent.

2. Community solutions

2.1 Business combing

In order to achieve the above-mentioned ideal effect, I searched and studied the solutions in the community. At present, Vue officially provides Vue-press which can be used to quickly build Vue project documents, and there is also a system that can automatically extract information from Vue components. Library.

However, the existing third-party libraries cannot fully meet the needs, and there are mainly the following two problems:

The information is not comprehensive, and some important content cannot be obtained, such as v-model cannot be processed, attribute modifier sync cannot be analyzed, and detailed information of function input parameters in methods cannot be obtained.

For example, in the following example, the value attribute and input event can be combined to form a v-model attribute, but this information is not reflected in the generated document, and readers of the document are required to understand and judge by themselves. And the generated document does not show whether it supports sync.

There are more custom logos, and the naming of the logos is too personalized, which is still relatively intrusive to the original code. For example, in the code in the figure below, in order to mark annotations, additional identifiers such as "@vuese" and "@arg" need to be added to the original business code, making the business code more business-related content.

Three, technical solutions

In response to the problems mentioned in the above article and the deficiencies of the community solution, a small tool has been deposited in our team to obtain Vue component information and output component documents. The general effect is as follows:

The left side of the above figure is a common Vue single file component, and the right side is the generated document. We can see that we have successfully extracted the following information from the components:

  • The name of the component.
  • The description of the component.
  • props, slots, events, methods, etc.
  • The comment content of the component.

Next we will explain in detail how to extract this information from the components.

3.1 Vue file analysis

Since it is to extract information from Vue components, the first question is how to parse Vue components. Vue officially developed the Vue-template-compiler library specifically for Vue parsing. Here we can also deal with it in the same way. By consulting the documentation, we can see that Vue-template-compiler provides a parseComponent method to process the original Vue file.

import { parseComponent } from 'Vue-template-compiler'
const result = parseComponent(VueFileContent, [options])

The processed result is as follows, where template and script correspond to the text content of template and script in the Vue file, respectively.

export interface SFCDescriptor {
  template: SFCBlock | undefined;
  script: SFCBlock | undefined;
  styles: SFCBlock[];
  customBlocks: SFCBlock[];
}

Of course, just getting the text is not enough, and further processing of the text is needed to get more information. After getting the script, we can use babel to compile js into an AST (Abstract Syntax Tree) of js. This AST is an ordinary js object, which can be traversed and read through js. Once we have Ast, we can get from it. Detailed component information.

import { parse } from '@babel/parser';
const jsAst = parse(script, [options]);

Then we look at the template, continue to search the Vue-template-compiler document, we find the compile method, compile is specifically used to compile the template into AST, just to meet the demand.

import { compile } from 'Vue-template-compiler'
const templateAst = compile(template, [options]);

The ast in the result is the compilation result of the template.

export interface CompiledResult {
  ast: ASTElement,
  render: string,
  staticRenderFns: Array<string>,
  errors: Array<string>
}

Through the file parsing in the first step, we successfully obtained the Vue template ast and the js AST in the script. In the next step, we can get the information we want from it.

3.2 Information extraction

According to whether an agreement is required, the information can be divided into two types:

One is that it can be obtained directly from Vue components, such as props, events, etc.

The other is the need for additional convention formats, such as: component description comments, props attribute descriptions, etc. This part can be placed in comments and obtained by parsing the comments.

In order to easily read information from ast, here is a brief introduction to a tool @babel/traverse, this library is officially provided by babel for traversing js AST. The usage is as follows;

import traverse from '@babel/traverse'

traverse(jsAst, options);

By configuring the callback function of the corresponding content in the options, you can get the desired ast node. For specific usage, please refer to official document

3.2.1 Information directly available

The information that can be obtained directly from the code can effectively solve the problem of information synchronization. No matter how the code changes, the key information of the document can be automatically synchronized, eliminating the trouble of manual proofreading.

The information that can be directly obtained:

  • Component property props
  • Provide methods for external calls
  • Events
  • Slots

1 and 2 can be obtained by directly traversing the object nodes named props and methods on the js AST using traverse.

Obtaining the event is a bit more troublesome. You can locate the event by looking up the $emit function, and the $emit function can listen to MemberExpress (complex type nodes) in traverse, and then judge whether the attribute name on the node is'$emit' Whether it is an event. If it is an event, then the arguments field is read in the $emit parent. The first element of arguments is the event name, and the following elements are event parameters.

this.$emit('event', arg);
traverse(jsAst, {
 MemberExpression(Node) {
  // 判断是不是event
  if (Node.node.property.name === '$emit') {
  // 第一个元素是事件名称
    const eventName = Node.parent.arguments[0];
  }
 }
});

After successfully obtaining Events, combining Events and props, you can further determine the two special properties in props:

Whether there is a v-model: Find out whether there is a value attribute in props and whether there is an input event in Events to determine.

Whether a property of props supports sync: Determine whether there is an event beginning with update in the time name of Events, and the event name is the same as the property name.

The slot information is stored in the AST of the template above, and the template AST is recursively traversed to find the node named slots, and then the name can be found on the node.

3.2.2 Information that needs to be agreed

In addition to the component information that can be directly obtained, why do we need to agree on a part of the content? One is that the information content that can be directly obtained is relatively thin, which is not enough to support a relatively complete component document; the other is that we write a lot of comments when we develop components every day. If we can directly extract some of the comments and put them out In the document, the workload of document maintenance can be greatly reduced;

To sort out the content that can be agreed, there are the following:

  • The name of the component.
  • The overall introduction of the components.
  • Text description of props, Events, methods, and slots.
  • Methods tag and detailed description of the input parameters. These contents can be put in comments for maintenance. The reason why they are maintained in comments is because the comments can be easily obtained from the js AST and template AST mentioned above. When we parse the Vue component information, we can Analyze this part of the targeted instructions together.

Next, we will focus on explaining how to correlate extracted comments and comments with the annotated content.

Comments in js can be divided into leadingComments and trailingComments according to different positions. Comments at different positions will be stored in the corresponding fields. The code is shown as follows:

// 头部注释export default {} // 尾部注释

analysis result

const exportNode = {
  type: "ExportDefaultDeclaration",
  leadingComments: [{
    type: 'CommentLine',
    value: '头部注释'
  }],
  trailingComments: [{
    type: 'CommentLine',
    value: '尾部注释'
  }]
}

At the same position, it is divided into single-line comments (CommentLine) and block-level comments (CommentBlock) according to the different comment formats. The difference between the two comments will be reflected in the type field of the comment node:


/**
 * 块级注释
 */
// 单行注释
export default {}

Analysis result

const exportNode = {
  type: "ExportDefaultDeclaration",
  leadingComments: [
    {
      type: 'CommentBlock',
      value: '块级注释'
    },
    {
      type: 'CommentLine',
      value: '单行注释'
    }
  ]
}

In addition, from the analysis results above, we can also see that the annotation node is mounted in the annotated export node, which also solves another problem we mentioned above: how to obtain the relationship between the annotation and the annotated- -In fact, babel has done it for us when compiling the code.

The template method for finding annotations is different from that of the annotated content. The comment node in the template exists as a dom node like other nodes. When traversing the node, it is determined whether it is a comment node by judging whether the value of the isComment field is true. The position of the annotated content is after the sibling node:

<!--template的注释-->
<slot>被注释的节点</slot>

Analysis result

const templateAst = [
  {
    isComment: true,
    text: "template的注释",
    type: 3
  },
  {
    tag: "slot",
    type: 1
  }
]

Knowing how to deal with the content of comments, then we can use comments to do more. For example, you can distinguish whether it is a private method or a public method by arranging a mark @public in the method comment of methods. If you are more detailed, you can also refer to the format of another library js-doc specially used for parsing js comments. The input parameters of the method are further explained, and the content of the document is enriched.

We only need to cut and read the text after getting the comment content, for example:

export default {
  methods: {
    /**
     * @public
     * @param {boolean} value 入参说明
     */
    show(value) {}
  }
}

Of course, in order to avoid too much intrusion into the code, we still need to add as few additional marks as possible. The input description uses the same format as js-doc, mainly because this set of solutions is more commonly used, and the code editor automatically supports easy editing.

Four, summary

Writing component documentation is a thing that can improve the collaboration between front-end development members in the project. A well-maintained document will greatly improve the development experience. And if you can further use tools to automate the process of maintaining documentation, the happiness of development can be improved again.

After a series of explorations and attempts, we successfully found a solution to automatically extract Vue component information, which greatly reduced the workload of maintaining Vue component documents and improved the accuracy of document information. In terms of specific implementation, first use vue-template-compiler to process Vue files to obtain template AST and js AST. After these two ASTs, you can get more detailed information. Let’s sort out what we have generated so far. What can be obtained in the document and how to obtain it:

As for whether to output the content in the form of Markdown or json file after obtaining the content, it depends on the actual development situation.

V. Outlook

What we are talking about here is to get information and output directly from a single Vue file, but like many third-party component libraries such as elementUI documents, there are not only component information but also display examples. If a component library is relatively well maintained, a component should have a corresponding test case, then can the test case of the component also be extracted to realize the automatic extraction of the example part in the component file? This is also a question worth studying.

Author: vivo Internet front-end team-Feng Di

vivo互联网技术
3.3k 声望10.2k 粉丝