`
yiminghe
  • 浏览: 1431464 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

构建前端 DSL

阅读更多

 

目前在传统的软件开发领域 [DSL ]() 已经比较普遍,特别是 [Martin Fowler ]() 的突出贡献。而在前端领域尚较少涉及,而如果在前端开发中合理使用 DSL 同样也可以有效得**减少代码数量,提高可读性**,常见的一个应用场景即前端模板的构建。本质上说模板也是一个微型语言,因此可以从DSL的角度着手,使用工具快速构建一个适合于特定前端框架的模板引擎。本文将以 [KISSY XTemplate ]()为例介绍如何构建前端的 DSL。

 

注:

本文持续更新地址:

 

[xtemplate at github ]().

[xtemplate at docs.kissyui.com ]().

 

DSL 也是初学,敬请勘误.

 

 

 

# 首先 npm 安装 kissy

 

 

     npm install -g kissy
 

 

 

 

# xtemplate 示例代码

 

this is kissy xtemplate: {{date}}
    {{#if n > n*2}}
        {{{no escape}}}
        {{each array}}
            index: {{xindex}}
            count: {{xcount}}
            value: {{value}}
            {{set t = value*2}}
            subValue:
            {{#with this.subValue}}
                {{subSubValue + ../t}}
            {{/with}}
        {{/each}}
    {{else}}
        {{#custom_block param}}
            {{custom_tpl param2}}
        {{/custom_block}}
    {{/if}}
 

 

 

 

# 模板词法/语法

 

这一步主要是为了下一步构建自定义语言的语法树做准备,这里采用使用工具**自动生成语法解析器**(parser)的方向来做,如果你打算手写解析器则可以略过此步(事实上可以略过本文)。

 

由于本文关注前端技术,故词法以及语法都采用 json 格式描述,词法直接采用正则表达式,语法采用变形的 [BNF ]() 形式,例如 xtemplate 的 [词法语法文件 ]()

 

工具采用 kissy 开发的 [LALR ]() 语法解析器生成器 [kison ]().

 

词法关注如何从输入代码中解析出最基本的代码单元(关键词,字符串,数字...),例如 xtemplate 的部分词法

 

{
        state: 't',
        regexp: /^{{/,
        token: 'OPEN'
    },
    {
        state: 't',
        regexp: /^}}/,
        token: 'CLOSE'
    },
    {
        state: 't',
        regexp: /^<=/,
        token: 'LE'
    },
    {
        state: 't',
        regexp: /^\+/,
        token: 'PLUS'
    },
    {
        state: 't',
        regexp: /^[a-zA-Z0-9_$-]+/,
        token: 'ID'
    },
 

 

其中 state 表示单个状态,词法解析过程也是一个状态机变换状态的过程.

 

而语法解析关注与从词法单元中识别出有效的程序结构,即语法解析树,例如 xtemplate 的部分语法描述:

 

 

{

        symbol: 'Expression',

        rhs: ['ConditionalOrExpression']

    },

    {

        symbol: 'ConditionalOrExpression',

        rhs: ['ConditionalAndExpression']

    },

    {

        symbol: 'program',

        rhs: ['statements', 'inverse', 'statements']

    },

    {

        symbol: 'statement',

        rhs: ['openBlock', 'program', 'closeBlock']

    }
 

 

 

其中对应 BNF 形式中: symbol ::= rhs

 

# 构建模板抽象语法树

 

语法词法只是描述了如何识别模板语言,而构建语法树的过程则需要在语法识别过程中由调用者自行构建,kison 支持在每个语法规则项中添加动作函数,通过工具在识别语言过程中(遍历[语法解析树 ]())同时有选择性得构建异型[抽象语法树 ](),例如 xtemplate 的树节点构建过程:

 

 

{

        symbol: 'program',

        rhs: ['statements', 'inverse', 'statements'],

        action: function () {

            return new this.yy.ProgramNode(this.lexer.lineNumber, this.$1, this.$3);

        }

    },

    {

        symbol: 'PrimaryExpression',

        rhs: ['path']

    },

    {

        symbol: 'RelationalExpression',

        rhs: ['RelationalExpression', 'LE', 'AdditiveExpression'],

        action: function () {

            return new this.yy.RelationalExpression(this.$1, '<=', this.$3);

        }

    }
 

 

 

其中 最基本的表达式(PrimaryExpression)可以直接是变量词法单元的值,而复杂的比较表达式以及整个程序则是自底向上由子树构建起来.

 

最后使用 **kissy-kison** 命令

 

 

kissy-kison -g parser.kison -m xtemplate/parser
 

 

就可以生成模板解析函数模块,大致为:

 

 

KISSY.add('xtemplate/parser', function(){

        function parse(code){

            // ...

        }

        return parse;

    });
 

 

 

# 模板编译

 

最后一步即是模板编译过程,将模板代码编译为 javascript 代码,填入数据执行后即可得到真正的渲染 html.

 

## 调用 parse

 

经过上一步得到解析函数后,调用

 

 

parse(tempalteCode)
 

 

即得到一棵抽象语法树,例如 xtemplate 的一段代码:

 

 

{{#each data}}

    {{#if n === ../n2 * 5}}

    {{n + 10.1}}

    {{/if}}

    {{/each}}
 

 

 

对应的抽象语法树:

 

 

## 翻译代码

 

接着就可以采用 [visitor ]() 模式将生成具体代码的逻辑写入 visitor 对象,遍历 ast 将对应的子树或节点转换成 javascript 代码,

 

这步可以继续优雅得采用代码模板,将代码模板的数据替换成模板对应的 javascript 单元。

不过为了不折磨大脑,最后放松下,可以直接采用原生的代码拼接:

 

 

 

visitor.tplNode=function(node){

        if(node.escapeHTML){

            codes.push("if("+node.id+" in data) { ret.push(KISSY.escapeHTML(data."+node.js+");) }"+

            " else { KISSY.warn('not found')!; }");

        }else{

        }

    };
 

 

 

不过确实还是挺折磨.

 

## 离线编译

 

大多数 DSL 都是推荐在使用前就转换成目标语言,而客户端在不太注重性能的情况下也可以在终端用户使用时在线编译。

 

xtemplate 通过 **kissy-xtemplate** 命令支持将模板代码离线编译为模板函数模块,这样客户端可以直接require该模块,省去了客户端编译过程,同时开发中直接面对 html 类似的模板代码,省去了字符串嵌入模板的繁琐。

 

例如 t-tpl.html

 

 

  {{ offline }} compile
 

 

运行

 

 

kissy-xtemplate -t t-tpl.html -m tests/t -w
 

 

可得到 t.js

 

 

KISSY.add('tests/t',function(){

        function render(data){

        }

        return render;

    });
 

 

 

离线编译的一个缺点是编译出来的代码肯定比原生模板大很多,这也正体现了 DSL 节省代码,易读的特性(代码肯定不可读了)。

 

# 下一步

 

目前存在两大问题:

 

## 体积较大

 

    压缩前 130k, 不过 gzip+compress 后由于生成的重复代码比较多,降到 10k,不过仍然需要优化生成代码: 减少模板解析器的代码。同时也可优化模板转化为最终代码的大小,这在离线编译情况下很有用。

 

## xtemplate 模块需要拆分

 

    当选择离线编译,实际上 xtemplate 的编译代码可以不用下载,可拆分为两个模块: xtemplate/runtime 以及 xtemplate/compiler

 

    这样当选择离线编译时直接use xtemplate/runtime 载入模板的功能基础设施即可。

 

# xtemplate 文档

 

[api ]()

 

[demo ]()

 

[tutorial ]()

 

 

# 推荐书籍

 

感谢这些作者,没有这些书籍, 这个任务不可能完成

 

[Compilers: Principles,Techniques and Tools ]()

 

[DSL In Action ]()

 

[Language Implementation Patterns: Create Your Own Domain-Specific and General Programming Languages ]()

 

# 致谢

 

在开发过程中参考一了下工具:

 

[velocity ]()

 

[closure templates ]()

 

[bison ]()

 

[jison ]()

 

[handlebar ]()

 

[mustache ]()

 

 

 

分享到:
评论
2 楼 yiminghe 2012-10-12  
luolonghao 写道
docs.kissyui.com 被墙了吗,无法访问,上次给过我的IP再发给我吧。


你翻下墙不就知道了么...: 204.232.175.78

改到 dnspod 了,应该马上就会恢复解析
1 楼 luolonghao 2012-10-12  
docs.kissyui.com 被墙了吗,无法访问,上次给过我的IP再发给我吧。

相关推荐

    前端项目-reactive-coffee.zip

    前端项目-reactive-coffee,一个轻量级的coffeescript库/dsl,用于反应式编程和声明性地构建可伸缩的web uis。

    sweetbuild:适用于Broccoli的Sweet.js DSL,最好的前端资产管道

    于的 DSL,最好的前端资产管道。 用法 $ npm install --save-dev sweet.js sweetbuild broccoli broccoli-merge-trees 如果您使用 将以下内容放入Brocfile.js : require ( 'sweet.js' ) . loadMacro ( '...

    motion.cr:Motion是一个框架,用于在Amber应用程序中使用可重用,可测试和封装的纯Crystal来构建React性,实时前端UI组件

    Motion是一个框架,用于在Amber应用程序中使用可重用,可测试和封装的纯Crystal来构建React性,实时的前端UI组件。 为简便起见,我们将它们称为MotionComponents。 运动是一个面向对象的视图层 与您现有的前端和平...

    ElasticUI, 用于Elasticsearch的AngularJS指令.zip

    ElasticUI, 用于Elasticsearch的AngularJS指令 ElasticUIElasticUI是一套让开发者能够在Elasticsearch之上快速构建前端的AngularJS指令。 它构建在 Elasticsearch DSL的elastic.js 实现之上。用1 英镑的概念是为了在...

    黎明:编译器工具链,可为地球物理流体动力学模型生成高级DSL

    黎明 编译器工具链,可为地球物理流体动力学模型生成高级DSL Dawn是用于地球物理流体动力学模型的优化器和代码生成库,而GTClang是使用此工具链的DSL前端。 GTClang首先将定制的易于理解的语言转换为相对简单的模板...

    full-control:基于Om和Bootstrap的clojurescript Web UI DSL

    基于和clojurescript Web UI DSL。 完全控制您的前端开发,编写更干净,更快速的代码。 安装 当前只有快照可用,将其包括在project.clj依赖项中: [full-control " 0.1.0-SNAPSHOT " ] 请阅读Om的以获取更多说明。...

    snarky:OCaml DSL用于可验证的计算

    狡猾的snarky是用于编写R1CS SNARK的OCaml前端。 它在后端SNARK库之上是模块化的,并带有来自后端。 免责声明:此代码尚未经过全面审核,不应在生产系统中使用。 CAVEAT该存储库包含大量过时的代码。 Mina项目的早期...

    bower-formstamp:Formstamp 凉亭包

    FormStamp 是一个纯 AngularJS 小部件库,专为丰富的前端 Web 应用程序而设计。 FormStamp的核心原则是: 所有小部件都是从头开始编写的; 最大的 AngularJS 兼容性(支持 ngDisabled、ngModel 和其他标准指令)...

    超级堆栈:超级堆栈ALPHA https:hyperstack.org

    Hyperstack是基于Ruby的DSL和现代Web工具包,用于快速构建壮观的交互式Web应用程序! 在客户端和服务器中使用一种语言。 将所有Ruby代码自动编译为JavaScript。 Webpacker和Yarn工具用于使用Ruby源映射的现代,...

    jsonclasses:适用于人工智能的现代声明式数据流框架

    就像React.js如何改变前端开发的范式一样,JSON Classes旨在引导行业后端开发标准的转变。 JSON类如何工作? JSON类基于Python数据类构建。 借助Python数据类提供的出色的元编程功能,我们可以轻松地将其扩展为...

    xlsx_parsing-:.xlsx 文档解析器 (Sinatra + Roo) 和仪表板 (Chartkick)

    它是用 Ruby 构建的,包含 OOP 元素——并且它有一个功能正常、几乎响应式的 HTML/CSS/JS 前端。 查看正在运行的应用程序的屏幕截图 (/screenshots) 和视频文件 (/screenshots/chain_stores_demo.gif) 守则的结构 ...

    antlr4权威指南

     而实现DSL的主要困难就在编译器前端。编译器被称为软件工程皇冠上的明珠。一直以来,对于普通的开发者而言,编译器的设计与实现都如同诗中描述的那样:“白云在青天,可望不可即。”  ANTLR改变了这一切。ANTLR...

    InDiv:an angular like web mvvm library.一个类 angular Web mvvm库。https

    它能帮你构建 Web 应用。InDiv 集字符串模板、HTML模板、依赖注入和一些其他实践于一身。 InDiv 采用与 angular 类似的项目结构,提供相似的概念和 api 以减少学习成本。 InDiv 是单词 individual 的缩写,我撸它的...

    springboot参考指南

    在前端代理服务器后使用Tomcat ix. 64.9. 使用Jetty替代Tomcat x. 64.10. 配置Jetty xi. 64.11. 使用Undertow替代Tomcat xii. 64.12. 配置Undertow xiii. 64.13. 启用Undertow的多监听器 xiv. 64.14. 使用Tomcat7 i...

Global site tag (gtag.js) - Google Analytics