文档章节

自动化schema的研究

BryanYang
 BryanYang
发布于 2017/11/17 09:56
字数 1526
阅读 14
收藏 0

当前的一些问题

一直以来,中台开发提效是我们努力的方向。 最近看到有个分享利用babel插件来实现文本提取。既然可以用来进行文本提取,那是不是也可以用来进行配置点提取呢。

目前手写schema是开发遇到的一个痛点问题,至少在我看来是一个问题。在不参考示例schema的情况下,开发过程手写schema有一定的难度(除了标准schema的规范比较多。开发者脑海中需要清晰这份schema渲染出来的表单)在写业务逻辑的同时,还要去编写schema. 又引入了schema正确性的调试等工作。

我认为理想的情况应该是,开发者在编写组件时对scema这件事无感知,只需要遵循少量的规范来开发组件,按照开发一般组件的思路开发即可。

改变一下思路

想象下我们开发组件时代码是这样的:

import React from 'react';
import R from 'R';

const {record, getSchema, getNumber } = R;

R.getNumber('数字')(value => <h1>title</h1>)
R.getBoolen('是否')(value => <h1>title</h1>)
R.getString('标题')(value => <h1>title</h1>)
getNumber('数字2')(value => <h1>{value}</h1>)

R.getSingle('单选功能', ['a','b','c'])(selected => {
	return [
	  R.when(selected.a, <a/>),
	  R.when(selected.b, <b/>),
	  R.when(selected.c, <c/>)
	]
})

// checkbox
R.getMultiple('多选功能', ['a','b','c'])(selected => {
	return [
	  R.when(selected.a, <a/>),
	  R.when(selected.b, <b/>),
	  R.when(selected.c, <c/>)
	].filter(s => !!s)
})

// 复杂对象
const Good = R.record(R.getScheam({a:1, b: true, c:'3'}));
// 可变数组
R.getArray('集合', Good)(goods => goods.map(renderGood))

我引入了一个外部依赖库:R (暂且叫这个名字) R库提供了一系列的方法来帮助我们编写带配置功能业务代码。每个方法的使用高阶函数,入参为配置项名,返回一个渲染方法,开发者自己去实现。 比如我的组件需要一个标题由外部配置进来。那么我可以这样写:

<div>
	<h1>{R.getString('标题')()}</h1>
</div>

或者

<div>
	{R.getString('标题')(v => <h1>{v}</h1>)}
</div>

这样我们就完成了一个带配置功能的组件的编写。 编写完成后,我们使用babel插件 babel-plugin-schema 来提取配置项,生成我们要的schema.json文件。 上述代码运行后,生成的schema.json如下:

{
	"标题":{
		"type":"string",
		"title":"标题"
	}
}

整个开发流程如下:

用户借助R开发组件 --> 编译时使用工具 --> 组件提交发布

其中编译阶段集成到脚手架,用户无感知。可以认为只一个侵入,就是使用R工具来开发配置业务。

回来再来回顾下组件的开发过程: 代码 R.getNumber('数字')(value => <h1>title</h1>)可以被分为2部分,

第一部分是配置部分getNumber('数字')

第2部分是渲染部分(value => <h1>title</h1>)

配置部分:

R提供了以下的api,来完成不同的配置:

- getNumber    <input type="number"/>
- getString    <input />
- getBoolen    <input type="radio"/>
- getSingle    <input type="radio"/>
- getMultiple  <input type="checkbox"/>
- getSchema 用来生成复合对象
- (getDate? getRange? 待扩展)

开发阶段

用户只需要关心我需要在代码中哪些地方插入配置,以及我配置的数据类型(bool?number?). 不需要再关心其它细节。

编译阶段

首先babel内核将代码拆分成ast, 在进行转换时。插件介入,通过对特定的ast节点进行提取,将用户定义的配置提取并缓存,最终生成json schema. 此过程为静态解析,相比使用正则:好处是更加灵活和准确,可以追溯变量的最终引用。也就尽可能得减少开发时规范约束,用户可以随意写正确的js代码。

渲染阶段

R会解析React组件中的props,并通过用户定义的配置,获取对应的配置值,然后调用用户定义的渲染方法渲染出最终的页面。所以用户定义的配置即可作为编译时生成schema的依据,也可作为渲染时获取值的途径。一次定义,2次使用。

编写规范

一个要遵循的规范是,R不可以被别名引用

// 正确
import R from 'R';
R.getSchema('');

// 错误
import R from 'R';
const S = R;
S.getSchema('');

R的方法不要被同名变量引用,以下写法可能会解析出来错误的schema

import R from 'R';
let myfun = R.getNumber;
myfun('数字')();
myfun = R.getString;
myfun('字符串')();

难点

难点在于静态解析部分,提取用户的配置,理论上看,用户的代码我们都可以访问到,只要我们的解析程序够全面,总是可以提取到正确和完成的配置。上述提到的2个规范也就可以忽略。但是为了提高解析到效率和准确性,降低解析程序的复杂度,还是通过一些规范约束开发者的代码风格,同时,通过规范,也提升了代码的可维护性。

下面是解析的代码,可以更完善:

const glob = require('glob');
const transformFileSync = require('babel-core').transformFileSync;
const fs = require('fs');
const _ = require('lodash');



function run (path){
    glob('./src.js', {},(err, files) => {
        files.forEach(fileName => {
            if (fileName.includes('node_modules')) {
                return;
            }
            transformFileSync(fileName, {
                presets: ['babel-preset-es2015', 'babel-preset-stage-0', 'babel-preset-react'].map(require.resolve),
                plugins: [
                    require.resolve('babel-plugin-transform-decorators-legacy'),
                    scan,
                ]
            });
        })
        console.log(JSON.stringify(result, null, 2));
    })
}

let R = '';
const result = {};
// R下的变量
const variables = [];

function isRcallee(path, t){
    const type = _.get(path, 'node.callee.type');
    if(type == 'Identifier'){
        const name = isRmember(_.get(path, 'node.callee.name'));
        const args = path.node.arguments;
        if(name){
            parse(name, args);
        }
    } else if(type == 'MemberExpression'){
        if(_.get(path, 'node.callee.object.name') == R){
            const methodMame = path.node.callee.property.name;
            const args = path.node.arguments;
            parse(methodMame, args);
        };
        
    }
}

function parse(methodMame, args){
    if(methodMame == 'getNumber'){
        const itemName = args[0].value;
        result[itemName] = {
            type: 'number',
            title: itemName,
        }
    }
    if(methodMame == 'getString'){
        const itemName = args[0].value;
        result[itemName] = {
            type: 'string',
            title: itemName,
        }
    }
    if(methodMame == 'getBoolen'){
        const itemName = args[0].value;
        result[itemName] = {
            type: 'boolean',
            title: itemName,
        }
    }
    if(methodMame == 'getSingle'){
        const itemName = args[0].value;
        const items = args[1].elements.map(e => e.value);
        result[itemName] = {
            type: 'string',
            title: itemName,
            enum: items, 
        }
    }
    if(methodMame == 'getMultiple'){
        const itemName = args[0].value;
        const items = args[1].elements.map(e => e.value);
        result[itemName] = {
            type: 'array',
            title: itemName,
            items:{
                type: "string",
                enum: items, 
            }
        }
    }
}

function parseIdentifier(){

}

function parseVariable(path) {
    const init = _.get(path, 'node.init');
    const id = _.get(path, 'node.id')
    if(init && init.object && init.object.name== "R"){
        variables.push({
            key: id.name,
            value: init.property.name,
        }) 
        
    }
}


// 方法是否是R成员
function isRmember(funName){
    const fn = variables.find(v => v.key == funName);
    if(fn) {
        return fn.value
    }
    return '';
}

function scan({types: t}) {
    return {
        visitor:{
            ImportDeclaration: (path)=>{
                if(_.get(path, 'node.source.value') == 'R'){
                    R = _.get(path, 'node.specifiers[0].local.name')
                }
            },
            VariableDeclarator: (path) => {
                parseVariable(path);
            },
            CallExpression: (path) => {
                isRcallee(path, t)
            },
            
        }
    }
}


run('.');

© 著作权归作者所有

BryanYang
粉丝 16
博文 165
码字总数 52036
作品 0
石景山
程序员
私信 提问
加载中

评论(0)

AI研究新动向——“知识工人”很有可能会遭受未来自动化的极大冲击 - 知乎

作者 | Rani Molla 编译 | CDA数据科学研究院 原文 | "Knowledge workers" could be the most impacted by future automation 一项新的人工智能研究表明,薪资更高,文化程度更高的工人受自动...

大数据分析·人工智能
2019/12/30
0
0
华中科技大学人工智能研究院成立,与自动化学院一体化运行

雷锋网(公众号:雷锋网) AI 科技评论按,1 月 26 日,华中科技大学人工智能与自动化学院、人工智能研究院成立。湖北省人民政府副省长陈安丽,校党委书记邵新宇、校长李元元共同为人工智能与自...

王雪佩
2019/01/29
0
0
中国自动化学会平行智能专业委员会成立

     摘要:2018年8月18日上午,中国自动化学会平行智能专业委员会成立大会在中国科学院自动化研究所报告厅隆重举行。   2018年8月18日上午,中国自动化学会平行智能专业委员会成立大会...

人工智能学家
2018/08/22
0
0
CAAI吴奖人物丨方勇纯:我国欠驱动机器人系统控制的先行者

     获奖人物小传   方勇纯,男,1973年生,南开大学计算机与控制工程学院教授、副院长,国家杰出青年科学基金获得者。现为中国自动化学会理事、中国图象图形学学会常务理事、天津市图...

中国人工智能学会
2017/12/12
0
0
机器人“攻占”建筑业,成败在此一举

雷锋网(公众号:雷锋网)注:【 图片来源:WIRED 所有者:ED FREEMAN/GETTY IMAGES 】 人们在讨论有关未来的就业前景时,常常将科技描绘成恶棍,因为它会导致某些职工失业。 最近有一项研究预...

伍文靓
2019/04/08
0
0

没有更多内容

加载失败,请刷新页面

加载更多

在两个日期之间查找对象MongoDB

我一直在围绕在mongodb中存储推文,每个对象看起来像这样: {"_id" : ObjectId("4c02c58de500fe1be1000005"),"contributors" : null,"text" : "Hello world","user" : { "following......

javail
19分钟前
35
0
《aelf经济和治理白皮书》重磅发布:为DAPP提供治理高效、价值驱动的生态环境

2020年2月17日,aelf正式发布《aelf经济和治理白皮书》,这是aelf继项目白皮书后,在aelf网络经济模型和治理模式方面的权威论述。 《aelf经济和治理白皮书》描述了aelf生态中各个角色及利益的...

AELF开发者社区
30分钟前
44
0
EditText的首字母大写

我正在开发一个小小的个人待办事项列表应用程序,到目前为止,一切都运行良好。 我想知道一个小怪癖。 每当我去添加一个新项目时,我都会看到一个带有EditText视图的Dialog。 当我选择EditT...

技术盛宴
34分钟前
30
0
战疫 | 高德工程师如何在3天上线“医护专车”

新冠状病毒肺炎疫情突袭,无数医护人员放弃与家人团聚,明知凶险,仍然奋战在一线。但因为武汉公交、地铁、网约车停运,医护人员上下班很难。白衣天使疾呼打车难。 (截图摘自《财经国家周刊...

amap_tech
42分钟前
41
0
img在IE中无法按比例显示

在IE浏览器中使用img标签当给img标签设置width:98%时,显示时还是会把img的原始高度显示出来 解决方式给父标签设置width,但width不能使用100%需要指定一个值 <div style="width:900px;"> ...

有理想的鸭子
42分钟前
45
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部