@aligov/gov-venom-render

dsl render

Usage no npm install needed!

<script type="module">
  import aligovGovVenomRender from 'https://cdn.skypack.dev/@aligov/gov-venom-render';
</script>

README

政务配置化引擎 - venom dsl 渲染引擎

@aligov/gov-venom-render

venom dsl 浏览器端渲染引擎,在线体验下:地址

快速上手

有两种接入方式:

  • JIT:即在浏览器端对符合协议的 schema 数据(json 或者 jsx 格式,目前仅支持 jsx)进行实时的解析渲染的方式;
  • AOT:通过提供 webpack loader,在云端构建的方式,将符合协议的 schema 数据转化成可以执行的 js/css 代码并发布至 cdn,然后嵌到 html 中执行渲染,即对 schema 协议数据的编译解析放到了工程侧完成。

JIT

浏览器端对 schema 数据进行编译解析及页面渲染。

对此,我们统一提供了@aligov/gov-venom-render组件,用法示例可以参考demo

他是 dsl 的浏览器端渲染引擎。具体用法说明如下:

返回一个组件

import render from '@aligov/gov-venom-render';
import ReactDOM from 'react-dom';

const dsl = `
    <div>123</div>
`; // 可以放本地,也可以从后端取回

const App = render(dsl);

ReactDOM.render(<App />, mountNode);

直接渲染进 DOM

import render from '@aligov/gov-venom-render';

const dsl = `
    <div>123</div>
`; // 可以放本地,也可以从后端取回

render(dsl, {
    container: '#container',
});

目前,render 中已默认内置了以下组件:

  • State 组件:用于辅助声明可配置数据部分,他只有一个 model 属性,model 属性里可以放置你的数据操作相关的内容,他是一个对象,里面包含了三个固定的属性:
    • state: 数据声明
    • methods: 通用处理函数,目前已内置 message 方法,用于信息提示;如果放置了 init 方法在 methods 对象中,则会在页面初始化后自动执行该方法,在 UI 中通过**$store.methods.xxx()**的方式来直接调用
    • actions: 数据操作处理,可以有同步操作,也可以使用有副作用的异步请求操作,异步请求操作可以使用 es7 的 async/await;每一个 actions 里的函数,会内置:state 及 payload 两个参数,state 里有上面声明的数据项,还有 set 方法来设置数据,在使用 set 设置数据后会触发 UI 渲染;actions 里的方法通过在 UI 的表达式中以**$state.dispatch('xxx', args)**的方式来触发,payload 就是触发时传递过来的参数,这一部分的实现直接内置了轻量级的数据流管理方案roy.js,当然是可替换的,比如也可以替换为 dva 之类的
  • fusion next UI 组件,如:Button、Dialog、Input 等;
  • 部分业务组件,如:@aligov/components-table-actions 等;
  • 集团统一表单方案:Formily的表单及 UI 组件,该部分的 UI 组件不会与 next 的组件重复;
  • 集团统一 search list 组件:Alist

如果不够用,可以自己注册组件:

import React from 'react';

// 自开发组件示例
const MyComp = (props) => {
    return <div>开发者自己的组件示例 {props.name}</div>;
};

// 可以本地手动维护,也可以由后端提供,还可以在平台侧维护,平台还在开发中(手动捂脸)
const dsl = `
    <MyComp
        name='张三'
    />
`;

render(dsl, {
    components: {
        MyComp,
    },
    container: '#container',
});

组件式用法

为了进一步简化使用,我们还提供了组件式的使用方式,如:

import { Venom } from '@aligov/gov-venom-render';

const dsl = `
<>
    <State
        model={{
            methods: {

            },

            state: {
                name: '点我',
                count: 0
            },

            actions: {
                async asyncFetch(state, payload) {
                    this.methods.fetch(state);

                    this.methods.setMyName('my new name');

                    state.set({
                        name: 'hello world~'
                    });

                    setTimeout(() => {
                      this.methods.setMyName('3s change');
                    }, 3000);
                }
            }
        }}
    />

    <>
    <button onClick={() => {
        $store.dispatch('asyncFetch');
    }}>{$state.name}</button>

    <div>{$state.count}</div>

    <TextComp />
  </>
</>
`;

const TextComp = (props) => {
    return <div>text component test</div>;
};

export default (props) => {
    return (
        <Venom
            dsl={dsl}
            init={({ methods, ...others }, ...args) => {
                console.log('init args:', args);
            }}
            components={{
                TextComp,
            }}
            methods={{
                setMyName: (myName) => {
                    this.setState({ myName });
                },

                async fetch(state, payload) {
                    await new Promise((resolve, reject) => {
                        setTimeout(() => {
                            state.set('count', 100);

                            resolve();
                        }, 2000);
                    });
                },
            }}
        />
    );
};

当然,也支持直接传入 url 来完成渲染,并支持 format 做格式化:

import { Venom } from '@aligov/gov-venom-render';

export default (props) => {
    return (
        <Venom
            url='https://www.fastmock.site/mock/8b5ab209e9d13691117cba3b7baea9c4/dsl/venom/dsl'
            format={(res) => res}
            init={({ methods, ...others }, ...args) => {
                console.log('init args:', args);
            }}
            methods={{
                setMyName: (myName) => {
                    this.setState({ myName });
                },

                async fetch(state, payload) {
                    await new Promise((resolve, reject) => {
                        setTimeout(() => {
                            state.set('count', 100);

                            resolve();
                        }, 2000);
                    });
                },
            }}
        />
    );
};

model 的两种写法

我们把渲染引擎分为了 UI 跟 Model 两部分。model 部分既可以单独维护,也可以直接存放在 DSL 中。

单独维护

// model.ts
export default {
    state: {
        name: '张三',
    },
};
// index.html
<div>{$state.name}</div>
// index.ts
import model from './model';
import dsl from 'raw-loader!./index.html'; // 什么后缀都可以,这里只是示例
import render from '@aligov/gov-venom-render';

render(dsl, model, {
    container: '#container',
});

写在 DSL 中

也可以直接将 model 写在 DSL 中,示例如下:

// index.html
<>
    <State
        model={{
            state: {
                name: '张三',
            },
        }}
    />

    <div>{$state.name}</div>
</>
import dsl from 'raw-loader!./index.html'; // 什么后缀都可以,这里只是示例
import render from '@aligov/gov-venom-render';

render(dsl, {
    container: '#container',
});

Form 表单注册组件

表单方案我们使用的是集团统一中后台方案Formily,支持在入口方法中注册表单组件。

使用纯 DSL

纯 DSL 使用演示,适用于对 DSL 做统一管理的场景。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import render from '@aligov/gov-venom-render';

const dsl = `
<>
    <State
        model={{
            methods: {
                init({ React, formily, components, registerComponents }) {
                    const { registerFormField, createControllerBox, connect } = formily;
                    const { Checkbox } = components;

                    registerFormField(
                        'custom-string',
                        connect()(props => <input {...props} value={props.value || ''} />)
                    );

                    const FormLayout = createControllerBox('controller-form-layout', props => {
                        return (
                            <div>
                                {props.children}
                                {props.schema['x-component-props']['attr']}
                            </div>
                        );
                    });

                    registerComponents({FormLayout});
                }
            }
        }}
    />

    <SchemaForm>
        <FormLayout attr='hello'>
            <Field type="custom-string" name="custom-string" title="Custom Field" />
        </FormLayout>
    </SchemaForm>
</>
`;

console.log('render:', render);

const App = render(dsl, {
    components: {},
});

ReactDOM.render(<App />, mountNode);

DSL 外注册组件

DSL 外注册组件使用演示,适用于在应用中部分使用 DSL,而非对 DSL 做统一管理。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import render, { formily, components } from '@aligov/gov-venom-render';

const dsl = `
<SchemaForm>
    <FormLayout attr='hello'>
        <Field type="custom-string" name="custom-string" title="Custom Field" />
    </FormLayout>
</SchemaForm>
`;

const { registerFormField, createControllerBox, connect } = formily;
const { Checkbox } = components;

registerFormField(
    'custom-string',
    connect()((props) => <input {...props} value={props.value || ''} />)
);

const FormLayout = createControllerBox('controller-form-layout', (props) => {
    console.log('props:', props);

    return (
        <div>
            {props.children}
            {props.schema['x-component-props']['attr']}
        </div>
    );
});

const App = render(dsl, {
    components: {
        FormLayout,
    },
});

ReactDOM.render(<App />, mountNode);

AOT

工程侧的解析编译转换引擎,作为 webpack loader 存在,为:@aligov/gov-venom-loader

配合 build-scripts 工程构建使用,需要单独提供 build-scripts 的插件:

// build-plugin-venom.js
module.exports = async ({ onGetWebpackConfig, context }, pluginOptions = {}) => {
    onGetWebpackConfig((config) => {
        config.module
            .rule('venom')
            .test(/\.vnm$/)
            .use('@aligov/gov-venom-loader')
            .loader('@aligov/gov-venom-loader');
    });
};

然后记得在 build.json 中引入:

{
    "plugins": [
        [
            "build-plugin-fusion",
            {
                "themePackage": "@alifd/theme-design-pro"
            }
        ],
        [
            "build-plugin-moment-locales",
            {
                "locales": ["zh-cn"]
            }
        ],
        "@ali/build-plugin-ice-def",
        // 自定义插件
        [
            "./build-plugin-venom.js",
            {
                "forceBind": true
            }
        ]
    ]
}

然后就可以在项目中应用起来了,可以参考示例:gov-venom-example

TODO

[] formily 跟 alist 的注入需要统一封装实现,而不是类似现在的写死;