Previously on

One day not long ago, my senior came to me and said: “Tian 覇, are you familiar with internationalization?” . I thought, “Well, context plus hook?”

But can not be proud, so modest with the elder brother said used. “Our Ant-Design-Pro has internationalization, but we don’t use it in many scenarios. Now we need to support code that can handle internationalization when we don’t need it.” My mind was racing and I had two ideas:

  • Rewrite formatMessage
  • Kill the code with the AST

But rewriting formatMessage will still put internationalization code in a package. How can a serious programmer put up with that?

So the solution is still AST. At this point, you have already processed some of the code through the AST, and now you need to thoroughly handle the various scenarios in which internationalization occurs. Aye? Processing part of the code? Something was wrong

Second, the AST

What is the AST

An AST is an Abstract Syntax Tree, which is an Abstract representation of the syntactic structure of the source code. It represents the syntactic structure of a programming language as a tree, with each node in the tree representing a structure in the source code. In plain English, the AST represents code in JSON.

AST role
  • Editor error prompt, code formatting, code highlighting, code auto-completion;
  • Elint, pretiier checks for code errors or styles;
  • Webpack translates javascript syntax through Babel;
The AST generated

Currently, code is converted to AST through Babel-parse, which generates AST mainly through lexical and syntactic parsing

const ast = parser.parse(code, {
    sourceType: 'module',
    plugins: ['jsx', 'typescript', 'dynamicImport', 'classProperties', 'decorators-legacy'],
  });
Copy the code
  • lexing

The lexical analysis phase transforms the string code into a stream of tokens. You can think of a token as a flat array of syntax fragments.

N * n / / / / input output / * * [{type: {...}, value: "n", start: 0, end: 1, the loc: {...}}, {type: {...}, value: "*", start: 2, end: 3, loc: { ... } }, { type: { ... }, value: "n", start: 4, end: 5, loc: { ... } }, ] */ /** { type: { label: 'name', keyword: undefined, beforeExpr: false, startsExpr: true, rightAssociative: false, isLoop: false, isAssign: false, prefix: false, postfix: false, binop: null, updateContext: null }, ... } * /Copy the code
  • Syntax parsing

Parsing is transforming tokens into AST based on the results of lexical analysis.

{ "type": "File", "start": 0, "end": 3, "loc": { "start": { "line": 1, "column": 0 }, "end": { "line": 1, "column": 3 } }, "errors": [], "program": { "type": "Program", "start": 0, "end": 3, "loc": { "start": { "line": 1, "column": 0 }, "end": { "line": 1, "column": 3 } }, "sourceType": "module", "interpreter": null, "body": [ { "type": "ExpressionStatement", "start": 0, "end": 3, "loc": { "start": { "line": 1, "column": 0 }, "end": { "line": 1, "column": 3 } }, "expression": { "type": "BinaryExpression", "start": 0, "end": 3, "loc": { "start": { "line": 1, "column": 0 }, "end": { "line": 1, "column": 3 } }, "left": { "type": "Identifier", "start": 0, "end": 1, "loc": { "start": { "line": 1, "column": 0 }, "end": { "line": 1, "column": 1 }, "identifierName": "n" }, "name": "n" }, "operator": "*", "right": { "type": "Identifier", "start": 2, "end": 3, "loc": { "start": { "line": 1, "column": 2 }, "end": { "line": 1, "column": 3 }, "identifierName": "n" }, "name": "n" } } } ], "directives": [] }, "comments": []}Copy the code

AST traversal

Depending on the requirements, we need to remove all the internationalization code from the code, such as

import {useIntl, FormatedMessage} from 'umi' function App() { const Intl = useIntl() alert(Intl.formatMessage({id: 'key'})) alert(useIntl().formatMessage({id: 'key'})) return ( <> <FormatedMessage /> <div>{Intl.formatMessage({id: 'key'})} < / div > < div data - lang > {useIntl && Intl. FormatMessage ({id: 'key'}} < / div > < / a >)} / / Format related to delete, situation is very complicated...Copy the code

Of course, Babel’s high downloads of course provide a way for us to handle AST, which is basically a JSON, which is essentially a traverse, Babel provides babel-traverse

Traverse. Default (ast, {enter(path) {// traverse entry}, exit(path) {}, CallExpression(path) {} //... more })Copy the code

Fourth, AST processing

The AST node itself has some API to traverse to the target node, need to delete the target node processing, babel-type provides a full set of capabilities github.com/jamiebuilds… Example:

traverse.default(ast, { CallExpression(p) { if (p.container?.property?.name === 'formatMessage') { const parent = p.parentPath const { arguments: formatMessageArguments } = parent.container; if (arguments && arguments.length) { const params = {}; formatMessageArguments.forEach(node => { node.properties.forEach(property => { params[property.key.name] = property.value.value; }); }); const message = genMessage(params, localeMap); parent.parentPath.replaceWith(t.identifier(`'${message}'`)); } } }, enter(p) { // import {useIntl} form 'umi' if (path.isImportDeclaration()) { const { specifiers } = path.node; if (path.node.specifiers) { path.node.specifiers = specifiers.filter(({ imported }) => { if (imported) { return imported.name ! == 'useIntl'; } return true; }); } } if (path.node.source? .value === 'umi' && ! path.node.specifiers.length) { path.remove() return } } })Copy the code

Five, the pit

  • Node. container indicates the contents of the container node where node resides
  • The attribute node of a node cannot be deleted directly
  • Enter, other callback, and exit. Do not attempt to process child nodes on the parent node
  • The API’s input parameters are basically node types
t.JSXOpeningElement(t.JSXIdentifier('span'), [t.JSXAttribute(t.JSXIdentifier('data-lang-tag'))], true)
Copy the code
  • Unable to handle dynamic internationalization

Best practices

Although some codes can be processed through AST, they are matched for some specific syntax. N kinds of syntax often appear in the development process. It is not guaranteed to cover all the codes that need to be processed

<div data-lang>{formatMessage({id: key})}</div>
<FormatedMessage data-lang/>
Copy the code

So for the need to remove internationalization, there are some specifications for code programming