@aligov/gov-venom-render
venom dsl 浏览器端渲染引擎,在线体验下:地址
有两种接入方式:
浏览器端对 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);
import render from '@aligov/gov-venom-render';
const dsl = `
<div>123</div>
`; // 可以放本地,也可以从后端取回
render(dsl, {
container: '#container',
});
目前,render 中已默认内置了以下组件:
如果不够用,可以自己注册组件:
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);
});
},
}}
/>
);
};
我们把渲染引擎分为了 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',
});
也可以直接将 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',
});
表单方案我们使用的是集团统一中后台方案Formily,支持在入口方法中注册表单组件。
纯 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 做统一管理。
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);
工程侧的解析编译转换引擎,作为 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:
[] formily 跟 alist 的注入需要统一封装实现,而不是类似现在的写死;
基本用法。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import render from '@aligov/gov-venom-render';
const dsl = `<>
<State
model={{
state: {
count: 0,
editVisible: false
},
methods: {
init() {
console.log("init");
}
},
actions: {
add(state, payload) {
const { count } = state;
state.set('count', count + 1);
},
reduce(state, payload) {
const { count } = state;
state.set('count', count - 1);
},
async fetch(state, payload) {
await new Promise((resolve, reject) => {
setTimeout(() => {
state.set('count', 100);
resolve();
}, 2000);
});
}
}
}}
/>
<>
<Button onClick={() => {
$store.dispatch('add');
}}>点击加1</Button>
<Button onClick={() => {
$store.dispatch('reduce');
}}>点击减1</Button>
<Button onClick={() => {
$store.dispatch('fetch');
}}>点击2s后面变100</Button>
<div>{$state.count}</div>
</>
</>`;
console.log('render:', render);
const App = render(dsl, {
components: {},
});
ReactDOM.render(<App />, mountNode);
纯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 => {
console.log('props:', 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);
js代码中使用演示,适用于在应用中部分使用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);
快速实现符合 GOV Design System 的搜索列表页面。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import render from '@aligov/gov-venom-render';
const dsl = `
<>
<State
model={{
state: {
url: 'https://mocks.alibaba-inc.com/mock/alist/data'
}
}}
/>
<>
<List
url={$state.url}
pageSize={5}
>
<Filter>
<Layout>
<Filter.Item type="input" name="username" title="username" />
<Filter.Item type="input" name="age" title="age" />
</Layout>
<Layout.ButtonGroup>
<Search>搜索</Search>
<Clear>重置</Clear>
</Layout.ButtonGroup>
</Filter>
<Table>
<Table.Column title="label" dataIndex="label" />
<Table.Column title="value" dataIndex="value" />
</Table>
<Pagination />
</List>
</>
</>
`;
console.log('render:', render);
const App = render(dsl, {
components: {},
});
ReactDOM.render(<App />, mountNode);
方法注入使用示例。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import render from '@aligov/gov-venom-render';
const dsl = `
<>
<State
model={{
methods: {
},
state: {
name: 'hello',
count: 0
},
actions: {
async asyncFetch(state, payload) {
this.methods.fetch(state);
state.set({
name: 'hello world~'
});
}
}
}}
/>
<div onClick={() => {
$store.dispatch('asyncFetch');
}}>{$state.name}</div>
<div>{$state.count}</div>
</>
`;
console.log('render:', render);
const App = render(dsl, {
components: {},
methods: {
async fetch(state, payload) {
await new Promise((resolve, reject) => {
setTimeout(() => {
state.set('count', 100);
resolve();
}, 2000);
});
},
}
});
ReactDOM.render((
<App />
), mountNode);
render()渲染完毕返回组件使用示例。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import render 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>
</>
`;
console.log('render:', render);
const App = render(dsl, {
components: {},
methods: {
async fetch(state, payload) {
await new Promise((resolve, reject) => {
setTimeout(() => {
state.set('count', 100);
resolve();
}, 2000);
});
},
},
init({ methods, ...others }, ...args) {
console.log('init args:', args);
},
});
class Comp extends Component {
constructor(props, context) {
super(props, context);
this.state = {
myName: 'test'
};
}
render() {
const { myName } = this.state;
return <div>
<div>dsl组件:</div>
<App
methods={{
setMyName: myName => {
this.setState({ myName })
}
}}
init={() => {
console.error('init');
}}
/>
<div>父组件里的变量:{myName}</div>
</div>;
}
}
ReactDOM.render((
<Comp />
), mountNode);
作为组件使用示例
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
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>;
};
class Comp extends Component {
constructor(props, context) {
super(props, context);
this.state = {
myName: 'test'
};
}
render() {
const { myName } = this.state;
return <div>
<div>dsl组件:</div>
<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);
});
},
}}
/>
<div>父组件里的变量:{myName}</div>
</div>;
}
}
ReactDOM.render((
<Comp />
), mountNode);
作为组件使用示例
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Venom } from '@aligov/gov-venom-render';
class Comp extends Component {
constructor(props, context) {
super(props, context);
this.state = {
myName: 'test'
};
}
render() {
const { myName } = this.state;
return <div>
<div>dsl组件:</div>
<Venom
url='https://www.fastmock.site/mock/8b5ab209e9d13691117cba3b7baea9c4/dsl/venom/dsl'
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);
});
},
}}
/>
<div>父组件里的变量:{myName}</div>
</div>;
}
}
ReactDOM.render((
<Comp />
), mountNode);