The outline

  1. Comparison of problem scenarios and solutions
  2. What is Babel?
  3. To solve the process
  4. The remaining issues
  5. Currently implement functional API
  6. reference

Comparison of problem scenarios and solutions

At present, we use the framework of ANTD + React (UMI) for business development. In the process of business development, there will be more frequent and highly similar scenes, such as the increase, deletion, change and check based on a table, which I believe everyone is very familiar with. When receiving a new business demand, I believe that many people will choose to copy a similar function of the code and then based on this code to transform to meet the current business, of course, I am doing so ~

The idea of extracting this functionality into a common component has been around for a long time, but more recently we started building the base component and started doing this. After a week or so to complete the preparation of the basic components.

View the function point apis supported by the base.

The basic idea is to generate some abstract configuration from JSON, and then eventually generate the page by parsing the abstract configuration of JSON + renderer. Json configuration covers the basic requirements of 80% of current business scenarios, but scalability is low. For example, some complex business scenarios: form association verification, data association display, multi-level list drilling and so on. While it is possible to incorporate these functions with some complicated processing, the resulting components will be too large to maintain.

So, can I use this JSON configuration to generate the corresponding code through some tool? This eliminates all of the problems mentioned above, because it’s exactly the same as the code we wrote, and the tool just does the initialization for us. Therefore, I thought of many methods later. At first, I adopted the template string method, which is relatively simple and crude. It is nothing more than to output code through the judgment of nested variables in string. But in the actual writing found many problems, such as

  1. function(json. stringify ignores function)
  2. How do I get the final rendered node code after multi-layer function nesting
  3. How to implement embedded variables, how to generate additional dictionary queries in UMI-models-effects/Reducer, etc..

Finally, I learned some code generation tools such as Angular-CLI and some articles on JS code generation. I learned how to deal with this problem mainly through this discussion on Zhihu. It was decided to use Babel’s ecological chain to solve the above problems.

At present, we write general CRUD templates based on ANTD + React (UMI), and then use the code generator to parse the configuration in JSON to generate the corresponding code. The general process is as follows:

React –> JavaScript AST —> Code Generator –> Compiler –> Page

At present, the function is only a preliminary version, and it will be open source after the application has been used in the project for a period of time

What is Babel?

Babel is a toolchain for compiling ECMAScript 2015+ code into backward-compatible JavaScript that runs in a variety of browsers. Main functions:

  1. The syntax conversion
  2. The missing Polyfill feature in the environment
  3. Source code conversion
  4. Check out more Babel features

Understanding ASTs by Building Your Own Babel Plugin

The basic Babel flow and an article introducing AST are provided above.

Transform babel.transform will convert the code into an Object containing the AST(Abstract Syntax Tree). You can also reverse the AST to code using @babel/ Generator. For example, a variable declaration code:

const a = 1;
Copy the code

The structure after parsing is:

{
  "type": "Program"."start": 0."end": 191,
  "body": [{"type": "VariableDeclaration"."start": 179,
      "end": 191,
      "declarations": [{"type": "VariableDeclarator"."start": 185,
          "end": 190,
          "id": {
            "type": "Identifier"."start": 185,
            "end": 186,
            "name": "a"
          },
          "init": {
            "type": "Literal"."start": 189,
            "end": 190,
            "value": 1,
            "raw": "1"}}]."kind": "const"}]."sourceType": "module"
}
Copy the code

First, the type is VariableDeclaration. First, its type is const. You can click on the API to see other values such as let and var. The second is the declarations section, where the values are arrays because we can define multiple variables at the same time. The values in the array are of type VariableDeclarator and contain id and init as the variable name and value, respectively. The type of id is Identifier, which translates to a variable name. Init Literal is a constant, such as stringLiteral, numericliteral, and Booleanliteral. This completes the variable assignment process.

Of course, this is just a simple syntax conversion. If you want to learn more about conversions and types, please refer to the following two official links:

  • babel-types
  • Ast conversion tool

To solve the process

Start by defining the directory structure:

. ├ ─ ─ genCode / / the code generator | ├ ─ ─ genDetail / / need to open the new page separate detail catalog | └ ─ ─ genIndex / / homepage | └ ─ ─ genModels / / umi models | └ ─ ─ GenServices / / umi services | └ ─ ─ genTableFilter / / table screening area | └ ─ ─ genTableForm / / the new page model, New/updated modal box | └ ─ ─ genUpsert / / new page mode, New/update page | └ ─ ─ genUtils / / generation tool ├ ─ ─ schema / / model definition file | ├ ─ ─ the table / / current to generate a model | └ ─ ─ ├ ─ ─ config. Js based configuration / / | └ ─ ─ └ ─ ─ dataSchema js / / list, add, update, configuration | └ ─ ─ └ ─ ─ querySchema. Js / / screening item configuration ├ ─ ─ scripts / / generation script | ├ ─ ─ generateCode. Js / / generates a master file | └ ─ ─ Index. | js / / entrance └ ─ ─ utils. Js / / tools ├ ─ ─ toCopyFiles / / generated when the need to copy files, such as less └ ─ ─ index. The js / / the main entryCopy the code

The main process is as follows:

  1. Specify the path to generate code.
  2. According to the current JSON configuration path in the schema, call the code generation method of each module in the genCode directory to obtain the corresponding code.
  3. Writes the corresponding file to the specified path.
  4. performeslint ${filePath} --fixFormat the generated code.
  5. Copy dependent files such as less from the toCopyFiles folder to the corresponding folder based on the configuration.

The main module is the process of generating code according to json configuration in the genCode folder. In the case of genModels, first extract the parts that can be done with template String, reducing the amount of code parsing.

module.exports = (tableConfig) => {
  return `
        import { message } from 'antd';
        import { routerRedux } from 'dva/router'
        import { parse } from 'qs'
        ${dynamicImport(dicArray, namespace)}

        export default {
            namespace: '${namespace}',
            state: {
                ...
            },
            effects: {
                *fetch({ payload }, { call, put }) {
                    const response = yield call(queryData, payload);
                    if (response && response.errorCode === 0) {
                        yield put({
                            type: 'save',
                            payload: response.data,
                        });
                    } else {
                        message.error(response && response.errorMessage || 'Request failed')}},... .${dynamicYieldFunction(dicArray)}
            },

            reducers: {
                save(state, action) {
                    return{... state, data: action.payload, }; },... .${dynamicReducerFunction(dicArray)}}}; `}Copy the code

The import, Effects, and Reducers modules all have code that needs to be dynamically generated based on configuration, since the list data may have dictionary entries that fetch values from the background to display accordingly. Take dynamecImport as an example:

functionDynamicImport (dicArray, namespace) {// Base API importlet baseImport = [
      'queryData'.'removeData'.'addData'.'updateData'.'findById'] // Check whether the JSON data needs to be loaded from the backgroundif(dicArray && dicArray.length) { baseImport = baseImport.concat(dicArray.map(key => getInjectVariableKey(key))) } // Const _importationArray = map(specifier => _importDeclarationArray.push(t.importSpecifier(t.identifier(specifier), T. identifier(specifier))))) // definition importDeclaration Const ast = T.iMPortDeclaration (_importDeclarationArray, t.stringLiteral(`.. /services/${namespace}// Generate code const {code} = generate(ast) with @babel/generatorreturn code
  }

Copy the code

Other code generation logic is similar, if you are not sure how to generate the part, you can refer to the link provided above to complete the code conversion and then generate.

If you have code that cannot be generated by the Babel transformation, you can do it with the re.

For example, the following UMi-Models code:

*__dicData({ payload }, { call, put }) {
      const response = yield call(__dicData, payload);
      if (response && response.errorCode === 0) {
        yield put({
          type: 'updateDic',
          payload: response.data,
        });
      } else {
        message.error(response && response.errorMessage || 'Request failed')}}Copy the code

The basic code can be generated through yieldExpression, but without the * symbol after function, there is no solution after repeated review of the documentation, and finally the generated code can only be solved using regular replacement. If you have encountered similar problems welcome to discuss ~

The problem

  1. The editor component currently in use is braft-Editor, but using initialValue with ANTD does not take effect and setFieldsValue must be used. Eslint react-hooks/ Enumer-deps are disabled when using useEffects and the props. Form is used to change and trigger an infinite loop.
useEffect(() => {
    props.form.setFieldsValue({
      editorArea: BraftEditor.createEditorState(current.editorArea),
      editorArea2: BraftEditor.createEditorState(current.editorArea2)
    });
  }, [current.editorArea, current.editorArea2]);

Copy the code
  1. How does the generated code remove unused dependencies? Using ESLint –fix does not remove unused variable definitions.
  2. How to modify the code after initialization? Because the current method will only complete the code initialization process, there is no idea to solve the process of future modification.

API functions

Parameter Specifications Reference The react-antd-admin function configuration consists of three basic configuration files:

  • Config. json Configures basic properties
  • Json configuration list and added modified fields
  • Queryschema. json configures the filter field

config.json

Configuration list

parameter mandatory type The default value instructions
namespace true string null The namespace
showExport false boolean true Whether to display the export
showCreate false boolean true Whether to show create
showDetail false boolean true Yes display view
showUpdate false boolean true Whether to show changes
showDelete false boolean true Whether to show delete
newRouterMode false boolean false Add/edit/view details on the new page. It is recommended that this value be set to true if a rich text editor is included; rich text does not look very nice in the modal box.
showBatchDelete false boolean true Whether to display batch deletion, multiSelection must be true
multiSelection false boolean true Whether multiple selection is supported
defaultDateFormat false string ‘YYYY-MM-DD’ The date format
upload false object null Upload related configuration, upload pictures and upload ordinary files to configure respectively. See upload properties below
pagination false object null For pagination configuration, see Pagination properties below
dictionary false array null Dictionary items that need to be requested, for drop-down boxes or when treeSelect values are fetched from the back end, can be used in dataSchema and querySchema, as described in the Dictionary property below

upload

parameter mandatory type The default value instructions
uploadUrl false string null Default upload interface. Priority Image /fileApiUrl > uploadUrl > global.apipath
imageApiUrl false string null Default image upload interface
fileApiUrl false string null Default file upload interface
image false string ‘/uploadImage’ Default image upload interface
imageSizeLimit false number 1500 Default image size limit, in KB
file false string ‘/uploadFile’ Default file upload interface
fileSizeLimit false number 10240 Default file size limit, in KB

pagination

parameter mandatory type The default value instructions
pageSize false number 10 Display quantity per page
showSizeChanger false boolean false Whether pageSize can be changed
pageSizeOptions false array [‘ 10 ‘, ’20’, ’50’, ‘100’) Specifies how many columns can be displayed per page
showQuickJumper false boolean false Whether to jump to a page quickly
showTotal false boolean true Whether to display totals

dictionary

parameter mandatory type The default value instructions
key true string null Variables identify
url true string null Request data address

dataSchema.json

Configuration list

parameter mandatory type The default value instructions
key true string null Unique identifier
title true string null The display name
primary false boolean false Primary key If the primary key is not specified, you cannot update/delete, but you can insert.

If a primary key is specified, the value of the primary key cannot be specified during insert/update.
showType false string input Display type

input/textarea/inputNumber/datePicker/rangePicker/radio/select/checkbox/multiSelect/image/file/cascader/editor
disabled false boolean false Whether this column in the form forbids editing
addonBefore false string/ReactNode null If showType is input, the preceding label can be set
addonAfter false string/ReactNode null If showType is input, you can set the tag
placeholder false string null Default prompt text
format false string null Format of the date type
showInTable false boolean true Whether this column should be displayed in the table
showInForm false boolean true Whether to display in a new or edited form
validator false boolean null Set up the check rules, refer to https://github.com/yiminghe/async-validator#rules
width false string/number null The column width
options false array null Format :[{key: “, value: “}] or string. When showType is cascader, this field does not support Array and data can only be obtained asynchronously.
min false number null The minimum value for digital input
max false number null The maximum value of digital input
accept false string null Upload file format restrictions
sizeLimit false number 20480 Upload file format restrictions
url false string null Upload image URL. The image upload interface can be configured separately for each upload component. If it is not configured separately, use the default value in config.js. If the URL starts with HTTP, use the interface directly; Otherwise, the system determines whether to add host based on the configuration in config.js
sorter false boolean false Whether the sorting
actions false array null operation

actions

parameter mandatory type The default value instructions
keys false array null Which fields are allowed to update, and if keys are not set, all fields are allowed to update
name true string null Show the title
type false string null update/delete/newLine/component

querySchema.json

Configuration list

parameter mandatory type The default value instructions
key true string null Unique identifier
title true string null The display name
placeholder false string null The clues
showType false string input Display types. Enumerable fields, such as Type, can be displayed as checkboxes or drop-down boxes

Input, which is a normal input field, can omit the showType field

Currently available showType: input/inputNumber/datePicker/rangePicker/select/radio/checkbox/multiSelect/cascader
addonBefore false string/ReactNode null If showType is input, the preceding label can be set
addonAfter false string/ReactNode null If showType is input, you can set the tag
defaultValue false string/array/number null The multi-select defaultValue is an array
min false number null When showType is set to inputNumber, the minimum value can be set
max false number null When showType is set to inputNumber, the maximum value can be set
options false array null The key of options must be string; otherwise, warning will be displayed

normal-format: [{“key”: “”, “value”: “”}]

cascader-format: [{“value”: “”, “label”: “”, children: [“value”: “”, “label”: “”, children: []]}]

If the value is string, the value of the key in the current namespace is obtained asynchronously
defaultValueBegin false string null If showType is rangePicker, you can set the default start value
defaultValueEnd false string null If showType is rangePicker, you can set the default end value
placeholderBegin false string Start date If showType is rangePicker, you can set the default start prompt
placeholderEnd false string End date If showType is rangePicker, you can set the default end prompt
format false string null Date filter format
showInSimpleMode false boolean false In simple query mode, if there is a value in the data that contains this field and is true, the simple/complex filter switch is enabled

reference

  • react-antd-admin
  • AST syntax converter
  • babel-types-api