@aligov/gov-venom-transformer

venom dsl transformed to js code

Usage no npm install needed!

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

README

@aligov/gov-venom-transformer

Venom DSL code转化为React render。

Example

    const { venomToReact, plugin } = require("@aligov/gov-venom-transformer");

    // class
    const code = `
        <div
            :class={{active : true, current: false}}
        >123</div>
    `;

    const transedCode = venomToReact(code);

转化后的结果:

    // transedCode值:

    import React from 'react';
    import _ from 'lodash';
    export default function () {
        return React.createElement('div', {
            'className': _.transform({
                active: true,
                current: false
            }, function (res, value, key) {
                if (value) {
                    res.push(key);
                }
            }, []).join(' ')
        }, '123');
    }

venom-transformer插件机制

@aligov/gov-venom-transformer库通过提供可扩展的插件机制,为我们实现venom以及可定制的DSL语法提供了便利。

目前,@aligov/gov-venom-transformer库的插件机制提供两种能力的可扩展:标签标签属性

标签属性的插件扩展机制

格式如下:

module.exports = {
    type: "prop",
    name: /\:class|className/,
    func(ctx, { value, node }) {
        // console.log("ctx:", ctx, val);
        value = value || "";

        // 支持表达式范式
        if (/^\{\{.+\}\}$/.test(value)) {
            value = value.slice(1, -1);
        }

        // key: 属性名
        // value: 该属性所对应的实际代码值
        return {
            // 如果是定制的样式,需要维持原来的属性名
            [isCustomElement(node.name)
                ? "class"
                : "className"
            ]: classSetTemplate({ classSet: value })
        };
    }
};

插件最终需要提供一个对象格式,对象里有三个属性:

type

告知transformer库是什么类型的插件,可选值:prop/tag

name

表明是对哪个属性的代码转换插件,支持字符串及正则表达式。

func

func需要是一个方法,transformer库会传对应的内部实现参数给到该方法,该方法需要实现对对应标签属性的转化,并返回一个对象,对象名为转化后的属性名字,对象值为转化后的React标签的属性值,字符串属性。

标签的插件扩展机制

格式如下:

import _ from "lodash";

const ifTemplate = _.template("((<%= condition %>)?(<%= body %>):null)");
const ifAttr = `condition`;

module.exports = {
    type: "tag",
    name: /if/i, // 忽略大小写
    func(ctx, {
        node,
        children,
        VenomCodeError
    }) {
        if(_.startsWith(children, ',')) {
            children = children.slice(1);
        }

        const condStr = node.attribs[ifAttr] || 'false'; // 条件不写默认为false

        const data = {
            condition: condStr.trim(),
            body: children,
        };

        return ifTemplate(data);
    }
};

typename跟标签属性的说明一致,主要是func有点区别:

  • func的参数里会多传一个children过来,代表该标签的children已经被转化成的js代码;
  • func方法不再需要返回对象,而是只需要返回被转化后的代码即可。

同时,在实际的插件书写过程中,可能需要用到内部的,用于方便实现上下文的context的部分机制,比如:

  • 如果需要引入一些第三方模块,那么可以使用context的defines属性,该属性是一个数组,只需要将参数组装成有一定格式的对象,然后被push进该数组,最终会生成类似import {Button} from '@alife/next';之类的代码;
  • 如果需要生成一些辅助性的通用函数,且需要借助于render的上下文环境,则需要用到:context.injectedFunctions数组,将生成的函数代码字符串push进该数组即可。

TODO

  • 1、去this写法支持;
  • 2、else/elseif标签支持;
  • 3、编译出的代码的lodash引用去掉;
  • 4、venom-transformer实现无用代码精简;
  • 5、styled-components支持;