References:

1. Gitlab boot line instance

Amis low code platform

Github open source project: mometa

What is low code?

Low-code Development (LCD), where developers create applications primarily through graphical user interfaces and configurations, rather than relying on handwritten Code as in the traditional model. Developers with a low code development model typically do not need very specialized coding skills, or coding skills in a specific area, but can achieve specialized code output through the functionality and constraints of the platform.

First, thinking design

1, concept,

Low-code Development Platform (LCDP) is a platform software that facilitates the generation of application programs. The software development environment allows users to write programs with graphical interfaces and configurations instead of using traditional programming and coding methods.

Low code development platform (LCDP) : low code volume, high reuse, high efficiency, easy maintenance & logic control.

Core Competencies or Basic points:

  • Visual configuration panel
  • Extensible capability: component, template, logic reuse
  • Life cycle management: development management, page management, deployment management

2. Application scenarios

Objective: do not do large and complete, for specific areas to do fine;

Application scenarios: high degree of standardization, such as portal, advertising, marketing (for operation use), middle and background page construction of the latter relatively complex page (for development use, low code based secondary development). At present, the products on the market are easy enterprise xiu, cloud butterfly, suitable for.

3. Pattern/process changes

  • Development mode:
    • Change in application pattern => change in development process
  • Role change:
    • Product & Designer: single app design => general specification or industry design;
    • Technologist & Development: Single page development => Domain model abstraction, design, development;
    • QA: Page test machine => keeper of specification & general logic.

4. Architectural design

Generally divided into four modules: physical heap (component library), stage (configuration canvas), edit panel (configuration items), top bar (global/page configuration)

1. Material pile
  • Features: Built-in components and material mart
    • Built-in components: Standardized components
    • Material market: Provides external material components to expand the capabilities of users and provide search and import functions
      • For operations: Target selection
      • For development: CLI tools (combined with CI/CD process), cloud editor online-edition are available
      • CI/CD basic process:
        • Process: development environment, testing, CR(code review), gray release, warehousing
        • Corresponding environments: dev, test, staging, and PROd
  • Architect Design: meta-component, layout component, composite component (meta-component + layout component)
    • Base components: meta-components (non-nested), layout components (nested)
    • Composite components: composed of meta-components + layout components + composite components
  • Development and design:
    • Meta-components: Implemented using normal components
    • Layout components: shell slot or vNode, layout using Grid-layout or Flex layout

2. Main stage

1. Categories:

  • What we see and Get: Integration of rendering engine, advantages and disadvantages: explorable, high complexity, high logic integration;
  • Polymorphic stage: divided into configuration stage and rendering engine, advantages and disadvantages: state separation, high efficiency, maintenance of two sets of people at the same time, EG: configuration using H5, rendering for small program after flutter.

2. Related ideas:

  • Microkernel idea: Operate DSL (JSON tree), F (state)-> View
    • Composition and render layer isolation
    • Render Runtime + DSL (json) = page
  • Events: DND drag, Mouse Event
  • Canvas layering technique: Borrow from the browser rendering model and use the bubble mechanism to go through all layers
    • Div, render(DSL, document.querySelector(“#root”))
    • Second layer: add a div layer that handles only right-click events
    • Third layer: Add a div layer that only deals with shortcut keys
    • Event Bus: All layers can communicate through the Event Bus
  • Canvas function and expansion: infinite canvas, guide line (top grid), adsorption alignment, rotation, shortcut keys, right click, zoom
    • Infinite Canvas: Listens for scroll events, widening the canvas each time
    • Guide line: Draw a div line (height and width 1px), absolute position can be dragged, below
    • Adsorption alignment: Calculate desired DOM nodes, set 6 points and 3 lines, and set the distance to 0 when the distance is close
3. Edit panel
  • Use the same panel for the same type and use the class class
  • A single instance is mounted to vNode with config configuration

4. Operation column

Operation decoupling: Perform hierarchical design, data, view, and operation decoupling

  • Functions: undo, redo, preview, test, release
    • Redo: Use queue, move pointer
    • Preview: render (DSL: {type, key, props {}, the animate {}, actions: {}, attrs: {}, children []})
  • Middle layer: permission control, data manipulation (transformation), exposing part of the API
  • Bottom layer: provides plugins, hooks, and other extensible plug-in mechanisms, using webpack/taple
//webpack/taple, umi/plugins, rollup/plugins
//eg: a.plugin.js
export const a = (dsl = {}, api) = > {
  name: "undo".label: "xxx".apply(){},
  inited(){}, // Lifecycle
  beforePublish(){}, 
  / /...
}

// DSL: tree structure
dsl: {
  type: "".key: "".props: {},
  animate: {},
  actions: {}, // Logical configuration, flow(flow)
  attrs: {}, // Configure the keyboard information
  children: []}Copy the code
5, output,
  • The source code that conforms to the syntax specification can be developed twice, reducing the workload of restoring UI, and the developer can put the exported source code into the project to supplement the logic part.

    • Principle: String concatenation, can use nodeJS back end for concatenation and packaging
    • Extensions: Online editing features, using manoco editor, preview using iframe sandbox isolation display, both communicate via postMessage
    • Template saving: use mongodb database to store database, mongoose driver library
  • Export JSON file: Render Runtime + DSL = page

6. Expand functionality

History and versions, templates, permission pages, shares, themes

Advanced: collaborative editing, scheduled tasks, micro front ends (the ability to integrate into other systems), hybrid development

  • Mixed development: Component (TS, flow) and JSON mixed development
  • Vscode plug-in: open the visual panel and drag the generated code
  • Logical configuration: Use flow diagrams, and finally generate business logic
  • Co-editing: CRDT algorithm using YJS, OT algorithm (Finch)
  • Amis: Json hierarchy, using prototype chains to implement pseudo-scopes (inherit, react context)

Second, basic implementation

1. Materials area

  • Meta components and layout components, Contanier, cInput, cButton
// cButton.vue
<template>
    <el-button>{{ btn }}</el-button>
</template>
<script>
// Default button component
export default {
    name: 'cButton',
    data () {
        return {
            btn: 'I'm a button component'}}}</script>



// cInput.vue
<template>
    <el-input></el-input>
</template>
<script>
// Default input component
export default {
    name: 'cInput',
    data () {
        return {
            value: '123'
        }
    },

    mounted () {
        this.$nextTick(() = > {
            this.$emit('viewMounted'.this)}}}</script>


// container.vue
<template>
    <div class="container" @dragover.prevent @drop.stop="handleDrop">
        <slot></slot>
    </div>
</template>
<script>
// Default input component
export default {
    name: 'container'.props: {
        jsonSchema: {
            type: Object.default: function () {
                return {}
            }
        }
    },
    data () {
       return{}},methods: {
        // The component is put into the Container callback
        handleDrop (e) {
            this.$emit('drop', e, this)}}}</script>
Copy the code
  • The parser
// parser-button.js
import cButton from './cButton'
// Do logical layer processing
export default {
    name: 'CButton'.components: {
        cButton
    },

    render (h, section, children) {
        const _this = this
        const _propsOn = {
            nativeOn: {
                click: e= > {
                    e.stopPropagation()
                    _this.$emit('pickType'.'cButton')}}}return (
            <cButton
            { . _propsOn }
            ></cButton>)}}// parser-input.js
import cInput from './cInput'
import store from '.. /store'
export default {
    name: 'CInput'.components: {
        cInput
    },
    render (h, section, children) {
        const _this = this
        const _propsOn = {
            nativeOn: {
                click: e= > {
                    e.stopPropagation()
                    _this.$emit('pickType'.'cInput')}},on: {
                viewMounted: e= > {
                    store.dispatch('props/addWhere', {
                        id: e._uid,
                        where: e.value
                    })
                }
            }
        }
        return (
            <cInput
            { . _propsOn }
            ></cInput>)}}// parser-container.js
import container from './container'
export default {
    name: 'Container'.components: {
        container
    },

    render (h, section, children) {
        const _this = this
        // From the top down
        const _props = {
            props: {
                jsonSchema: section
            }
        }

        // From the bottom up
        const _propsOn = {
            on: {
                dragover: _this.handleDragOver,
                drop: _this.handleDrop
            },
            nativeOn: {
                click: () = > {
                    _this.$emit('pickType'.'container')}}}return (
            <container
            { . _props }
            { . _propsOn }
            > { children } </container>)}}Copy the code

2. Main stage

1. Encapsulate the rendering engine

  • Add a render component layer to decouple the meta component using require.context to automatically import components and render components
  • Define vNode tree data structure, render function parses the VNode tree step by step, and finally call render function of render component for rendering. JSX can be used to simplify the rendering function.

2. Drag and drop implementation of linkage between material area and main stage: h5 Draggable attribute, onDrop event (@dragover. Prevent, default not to drop), onDrag event implementation.

  • No rendering engine: Render can be switched using dynamic components<component :is="item" />, using the selectType variable or event to determine the drag type
<! -- Materials area -->
<ul>
     <li
     v-for="item in stack"
     :key="item.code"
     class="component"
     :draggable="true"
     @drag="handleDrag(item)"
     >
     {{ item.value }}
     </li>
 </ul>
 <! -- Main stage -->
 <div class="stage block" @dragover.prevent @drop.stop="handleDrop">
     <li v-for="(item, index) in components" :key="index">
           {{ item }}
         <component :is="item" />
     </li>
 </div>Methods :{handleDrag (item) {// Pick up the configured object this.selectedType = item}, Const _type = this.selectedType this.ponents.push (_type)},}, data(){return {components:[], // Component array}}, components:{... Modules // use dynamic components to import all objects}Copy the code
  • Using a rendering engine, the rendering engine passes in jsonSchema, addNode data, the Container component, the Parser-Container component, and the rendering engine add event handling.

    1. Place them all under the root component of the Container
    2. Add a drop event to a Container to push information about the container component to the rendering engine
    3. After the rendering engine receives the event, it adds children to the jsonSchema of the Container component to add the component
// The container component adds events and uses emit to throw events up, receiving arguments through props for component rendering
props: {
   // From top to bottom: Receives parameters from parse-container
   jsonSchema: {
       type: Object.default: function () {
           return {};
       },
   },
},
@dragover.prevent @drop.stop="handleDrop"
handleDrop(e) {
 this.$emit("drop", e, this);
},

//parser-container adds the appropriate events and attributes
render(h, vnode, children) {
   const _this = this
   const _props = {
       props: {
           jsonSchema: vnode,
       },
   };
   const _propsOn = {
       on: {
           drop: _this.handleDrop,
       },
   };
   return (
       <container {. _props} {. _propsOn} >
           {children}
       </container>
   );
}
//renderEngine adds event handling for drag handling
handleDrop(event, vm) {
 const _json = vm.jsonSchema; // The instance being dragged into the container
 if (_json && _json.type === 'container') {
   if(! _json.children) {this.$set(_json, 'children', [])
   }
   _json.children.push({
     type: this.addNode
   })
 }
},
Copy the code

3. Configuration panel

Category 1 components Configure a configuration panel, set the Config folder, and automatically import configuration panel components

  1. Add a configPanel component, add a configuration Panel component that automatically renders a configuration panel of that type based on the pickType data entered.
  2. The parse-XX component adds a click native listening event that triggers a pickType event when clicked to send configuration type data.
  3. A pickType event is added to the mainPage of parse-XX’s parent rendering component, and the currentPickType value is modified in the callback function and passed to configPanel for update.
  4. Extended thinking microkernel idea, data driven + configuration driven
/ / mainPage. Vue components
 <render-engine
     :jsonSchema="currentJson"
     :addNode="selectedType"
     @pickType="handlePickType"
 ></render-engine>
 <config-panel :currentPickType="currentPickType"></config-panel>
 data(){
     return {
         currentPickType: ""}},methods: {handlePickType(type){
         this.currentPickType = type; }}//parser-input.vue
 const _propsOn = {
   nativeOn: {
     click: (e) = > {
       e.stopPropagation();
       this.$emit("pickType"."cInput"); ,}}};return <cInput {. _propsOn} ></cInput>;

 //configPanel.js
 props: {
     pickType: {
     type: String.default: "cButton",}},components: {
     ...components,
 },
 methods: {
     renderPanel(h, type) {
         if(! type)return;
         const components = this.$options.components;
         return components[type].render.call(this, h); }},render(h) {
     const _type = this.pickType;
     let _panel = this.renderPanel(h, _type);
     return _panel;
 },
Copy the code

4. Scene processing component communication

Event transfer between components (above scenario), total coordination (centralized management), the following is the total coordination scenario:

  • Self-built Event Bus (the bus transmits values and collects broadcasts) : Component A sends head events, payload, and target(C). After receiving the events, component C mounts the events.
  • Vuex is used to collect the rendering data of each component and use it in unified render.

Iii. Other related matters

  1. Height: 100 vh and height: 100% : Vh is 1% of the current visible height of the screen, that is, height:100vh == height:100%, but when the element has no content, set height:100%, the element will not be stretched, the height is 0, but set height:100vh, The element will be spread across the screen at the same height.

  2. Require.context () outputs the function webpackContext(req)

  • WebpackContext function, the input parameter is the path, the output parameter is the module, retrievable defalut module
  • Functions are also objects and can have attributes. The output function has three attributes: keys, ID, and resolve
    • Resolve {Function} – Takes request, which is the relative path of the matched file under the test folder, and returns the relative path of the matched file relative to the entire project
    • Keys {Function} – Returns an array of the names of successfully matched modules
    • Id {String} – The id of the execution environment, which returns a String, used mainly in module.hot.accept
  1. array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
  • Total Required, initial value, or return value at the end of the calculation
  • CurrentValue must be the current element
  • CurrentIndex Indicates the index of the current element
  • Arr Optional array object to which the current element belongs
  • InitialValue optional, the initialValue passed to the function
  1. <element draggable="true|false|auto">Drag event reference:Drag drag& drop eventsH5 added the draggable attribute, which specifies whether an element can be dragged.
  • Trigger event ondrag target (source element): ondragstart – trigger ondrag when the user starts dragging an element – trigger ondragend when the element is dragging – trigger after the user finishes dragging the element
  • Events that trigger when a target is released: Ondragenter – This event is triggered when a mouse-dragged object enters its container scope onDragOver – This event is triggered when a dragged object is dragged within the scope of another object container onDragLeave – This event is triggered when a mouse-dragged object leaves its container scope Ondrop – This event is triggered when the mouse key is released during a drag. Note: An onDrag event is triggered every 350 milliseconds while dragging an element.
  1. Array. map(function(currentValue,index,arr), thisValue) map does not change the original array, returns the new array