├── .gitignore ├── .travis.yml ├── README.md ├── design ├── attributes-with-expression │ ├── expect.js │ └── source.html ├── attributes │ ├── expect.js │ └── source.html ├── child │ ├── expect.js │ └── source.html ├── compile │ ├── amd │ │ ├── expect.js │ │ ├── options.json │ │ └── source.html │ ├── angular │ │ ├── expect.js │ │ ├── options.json │ │ └── source.html │ ├── cmd │ │ ├── expect.js │ │ ├── options.json │ │ └── source.html │ └── global │ │ ├── expect.js │ │ ├── options.json │ │ └── source.html ├── complex │ ├── expect.js │ └── source.html ├── event-bind │ ├── expect.js │ └── source.html ├── for-and-if │ ├── expect.js │ └── source.html ├── for-for │ ├── expect.js │ └── source.html ├── for-track-by │ ├── expect.js │ └── source.html ├── for │ ├── expect.js │ └── source.html ├── if-else │ ├── expect.js │ └── source.html ├── if-with-siblings │ ├── expect.js │ └── source.html ├── import │ ├── expect.js │ └── source.html ├── innerHTML │ ├── expect.js │ └── source.html ├── output │ ├── expect.js │ └── source.html ├── text-pipe │ ├── expect.js │ └── source.html ├── text │ ├── expect.js │ └── source.html └── two-way-bind │ ├── expect.js │ └── source.html ├── package.json ├── src ├── et.ts ├── middlewares │ ├── attributes-translator.ts │ ├── node-checker.ts │ ├── node-compiler.ts │ ├── node-creator.ts │ ├── node-formatter.ts │ └── node-rebuilder.ts ├── nodes │ ├── basic.ts │ ├── child.ts │ ├── element.ts │ ├── else.ts │ ├── elseif.ts │ ├── factory.ts │ ├── for.ts │ ├── html.ts │ ├── if.ts │ ├── import.ts │ ├── origin.ts │ └── text.ts ├── parsers │ ├── attributes │ │ ├── attributes-result.ts │ │ ├── attributes-state.ts │ │ └── index.ts │ ├── dep.ts │ ├── for.ts │ ├── index.ts │ ├── origin │ │ ├── index.ts │ │ ├── origin-helper.ts │ │ ├── origin-node.ts │ │ └── origin-state.ts │ ├── value │ │ ├── index.ts │ │ ├── value-item.ts │ │ ├── value-result.ts │ │ └── value-states.ts │ └── wrap.ts ├── templates │ └── compile │ │ ├── amd.ts │ │ ├── angular.ts │ │ ├── cmd.ts │ │ ├── common.ts │ │ ├── global.ts │ │ ├── index.ts │ │ └── template.ts └── util.ts ├── test ├── server │ ├── apps │ │ ├── index.js │ │ └── parser.js │ └── views │ │ ├── index.html │ │ └── parser.html └── specs │ ├── design_spec.js │ ├── parsers │ ├── attributes_spec.js │ ├── dep_spec.js │ ├── for_spec.js │ ├── origin_spec.js │ ├── value_spec.js │ └── wrap_spec.js │ └── util_spec.js ├── tsconfig.json ├── tslint.json ├── typings.json ├── typings ├── all.d.ts ├── et-template.d.ts └── interfaces │ ├── node.d.ts │ ├── options.d.ts │ └── value.d.ts └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.seed 2 | *.log 3 | *.csv 4 | *.dat 5 | *.out 6 | *.pid 7 | *.gz 8 | *.bat 9 | *~ 10 | .idea 11 | *.iml 12 | .vscode 13 | .DS_Store 14 | .tmp 15 | 16 | dist 17 | logs 18 | node_modules 19 | es5 20 | typings/browser* 21 | typings/main* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "5.0" 5 | - "4.0" 6 | 7 | before_script: 8 | - "npm run setup && npm run build" 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # et-template [![NPMversion](https://img.shields.io/npm/v/et-template.svg?style=flat-square)](https://www.npmjs.com/package/et-template) [![Travis](https://travis-ci.org/et-studio/et-template.svg?style=flat-square)](https://travis-ci.org/et-studio/et-template) 2 | 3 | **et-template** 是一个供Web使用的Dom模板,他的设计启发于[doT](https://github.com/olado/doT)。 4 | 但是那是一个字符串模板,字符串模板在Web应用中难免会遇见很多问题,比如说Dom的生命周期在每次更新的时候都无法控制, 5 | 然而正常的期望在更新模板的时候能够进行差异更新的时候最小程度上的属性变化,这样既能提高性能又能合理控制Dom的生命周期。 6 | 所以,**et-template** 设计的初衷就是在模板中控制Dom的生命周期,尽可能的高性能进行差异更新,并且保留模板具备的简单接口容易使用的特性。 7 | 8 | 这是一个编译工具,将html代码转换成一个固定的js结构,然后根据依赖的模板原型函数进行初始化,就像[babel](https://github.com/babel/babel)把es6代码编译成es5代码一样。 9 | 10 | 11 | ## Installation 12 | ```console 13 | npm install et-template --save-dev 14 | ``` 15 | 16 | 17 | ## Usage 18 | [所有模板设计](https://github.com/et-studio/et-template/tree/master/design/et) 19 | 20 | [TodoMVC源码](https://github.com/et-studio/et-studio.github.io) 21 | 22 | **et-template** 是一个类函数,它需要被实例化,而不同的实例化方式取决于依赖的原型类函数。基本的ET模板类需要制定一个初始化上下文对象。定义模板的时候,进行插值或者事件绑定的时候都需要使用的对象。模板内部默认一个可访问对象,命名为it,指代初始化时传入的上下文对象。 23 | 24 | ### 基本语法 25 | ```html 26 |
27 | Hello, {{it.name}}! 28 |
29 |
30 | [#if it.isTrue] 31 | It is true. 32 | [#elseif it.isElseIf] 33 | It is else if. 34 | [#else] 35 | It is else. 36 | [/#if] 37 |
38 |
39 | [#for item, index in it.list] 40 | It is for loop {{index}}. 41 | [/#for] 42 |
43 | ``` 44 | 45 | 插入值的标记是装大括号包裹住的部分,中间部分会解释称为运行的取值代码。 46 | 47 | if条件判读和for遍历都使用了比较特殊的节点方式,[#]这样的节点进行设计的。 48 | 49 | 这样设计的原因是把html语法判断使用节点的方式进行包裹,让作用域清晰,易于自定义语法拓展。 50 | 51 | 52 | ### 事件绑定 53 | ```html 54 | (click) 55 | on-click 56 | ``` 57 | 这里有两种绑定事件的方式,一种是on-前缀,另一种是把事件名用()包裹起来。 58 | 59 | 事件绑定对应的值可以是一个函数对象,也可以是一个函数执行表达式。函数执行的时候,可以使用'$event'这个关键值传递Dom Event。 60 | 61 | 注意: 62 | 63 | 函数执行表达式是通过‘()’来判断的,所以这里不建议使用动态函数,而是使用上下文中的函数。 64 | 65 | ### 反向输出 66 | ```html 67 | 68 | 69 | ``` 70 | 反向绑定将会在change和input事件的时候触发,读取当前Dom节点的属性,并且赋值给绑定的对象属性。就比如上面的例子,实际的运行代码会是这样的: 71 | ```javascript 72 | it.name = this.value 73 | it.file = this.file 74 | ``` 75 | 76 | ### 双向绑定 77 | ```html 78 | 79 | 80 | ``` 81 | 双向绑定其实是一种便捷解释,目前只能使用[(value)]这样的关键值,并且会在编译过程中被解释称为相对应的插值和反向绑定。 82 | 83 | 84 | ### child语法 85 | ```html 86 |
87 | [#child ./models/user, it.userContext] 88 |
89 | ``` 90 | child 是引用子模板的语法,后面跟着的第一个参数是子模板的引用路径。 91 | 从第二个参数是初始化这个模板需要的上下文对象,这里可以是一个求值表达式;也可以不传递指定对象,这时会使用当前对象的上下文进行初始化。 92 | 93 | 通常情况下来说,child使用的模板的上下文应该在当前模板中始终取到一个固定的对象,而不应该是动态的。 94 | 95 | 96 | ### html语法 97 | ```html 98 |
99 | [#html] 100 | Hello {{it.name}} 101 | [/#html] 102 |
103 |
104 | [#html] 105 | Hello world. 106 | [/#html] 107 |
108 | ``` 109 | html 使用时会将内容字符或者变量作为innerHTML赋值给对应的父节点,所以这个语法使用有很多限制: 110 | * 必须拥有父节点。 111 | * 父节点的nodeType必须是1,是一个element对象。 112 | * 没有兄弟节点,意味着父节点只有这一个子节点。 113 | * html 子内容会被当作字符串处理,只能做字符串判断,而不能进行复杂的逻辑处理。 114 | 115 | 116 | ## et.compile(source: string, options: Object) 117 | ### options.modules 118 | 119 | 编译结果的运行格式 120 | 121 | 可选参数: 'common', 'cmd', 'amd', 'global', 'angular' 122 | 123 | 默认值: 'common' 124 | 125 | 126 | ### options.depPath 127 | 128 | 编译结果的依赖对象的引用地址 129 | 130 | 默认值: 'et-dependency' 131 | 132 | 133 | ### options.moduleId 134 | 135 | 在 amd, global, angular 的模式下需要显示指定每个结果函数的地址命名 136 | 137 | 默认值: 'Tempalte' 138 | 139 | 140 | 141 | ## License 142 | MIT 143 | -------------------------------------------------------------------------------- /design/attributes-with-expression/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | 5 | var _dep_template = _dep.template; 6 | var _dep_element = _dep.element; 7 | var _dep_concatMatrix = _dep.concatMatrix; 8 | var _dep_concatMatrixOthers = _dep.concatMatrixOthers; 9 | var _dep_setAttributesCondition = _dep.setAttributesCondition; 10 | var _dep_updateAttr = _dep.updateAttr; 11 | 12 | var Template_0 = _dep_template(function(_template) { 13 | _dep_element(_template, 0, 1, 'DIV', [ 14 | ['disabled', ''], 15 | ['class', 'class-true'] 16 | ]); 17 | }, function (it, _args) { 18 | var _patches = []; 19 | 20 | var _conditions = []; 21 | if (it.isTrue) _conditions[0] = 0; 22 | _patches[0] = _conditions; 23 | 24 | _patches[1] = [it.id, it.getSrc()]; 25 | _patches[2] = (function(){return it.a + it.b})(); 26 | 27 | return _patches; 28 | }, function (_template, it, _args, _patches, _cache) { 29 | var _tmp = _patches[0]; 30 | if (_cache(0, _tmp)) { 31 | var _includes = []; 32 | var _excludes = []; 33 | 34 | var _matrix0 = [['class']]; 35 | _dep_concatMatrix(_includes, _matrix0, _tmp[0]); 36 | _dep_concatMatrixOthers(_excludes, _matrix0, _tmp[0]); 37 | 38 | _dep_setAttributesCondition(_template, 1, _includes, _excludes); 39 | } 40 | 41 | var _tmp = _patches[1]; 42 | if (_cache(1, _tmp)) { 43 | var _value0 = _tmp[0]; 44 | _value0 = util.format(_value0); 45 | var _value1 = _tmp[1]; 46 | _dep_updateAttr(_template, 1, 'id', 'aaa' + _value0 + 'bbb' + _value1); 47 | } 48 | 49 | var _tmp = _patches[2]; 50 | if (_cache(2, _tmp)) { 51 | _dep_updateAttr(_template, 1, 'data-type', _tmp); 52 | } 53 | }); 54 | 55 | module.exports = Template_0; 56 | -------------------------------------------------------------------------------- /design/attributes-with-expression/source.html: -------------------------------------------------------------------------------- 1 |
5 |
6 | -------------------------------------------------------------------------------- /design/attributes/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | 5 | var _dep_template = _dep.template; 6 | var _dep_element = _dep.element; 7 | 8 | var Template_0 = _dep_template(function (_template) { 9 | _dep_element(_template, 0, 1, 'DIV', [ 10 | ['id', 'test'], 11 | ['data-title', 'Sorry, you can\'t do it.'] 12 | ]); 13 | _dep_element(_template, 0, 2, 'BR'); 14 | }); 15 | 16 | module.exports = Template_0; 17 | -------------------------------------------------------------------------------- /design/attributes/source.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /design/child/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var Template_2 = require('./models/user'); 4 | var _dep = require('et-dependency'); 5 | 6 | var _dep_template = _dep.template; 7 | var _dep_element = _dep.element; 8 | var _dep_child = _dep.child; 9 | var _dep_text = _dep.text; 10 | 11 | var Template_0 = _dep_template(function(_template) { 12 | _dep_element(_template, 0, 1, 'DIV'); 13 | _dep_child(_template, 1, 2, Template_2, function (it, _args) { 14 | return it.userContext; 15 | }); 16 | _dep_text(_template, 1, 3, '1234567890'); 17 | }); 18 | 19 | module.exports = Template_0; 20 | -------------------------------------------------------------------------------- /design/child/source.html: -------------------------------------------------------------------------------- 1 |
2 | [#child ./models/user, it.userContext] 3 | 1234567890 4 |
5 | -------------------------------------------------------------------------------- /design/compile/amd/expect.js: -------------------------------------------------------------------------------- 1 | ;define('Template', ['et-dependency'], function(_dep) { 2 | 3 | var _dep_template = _dep.template; 4 | var _dep_element = _dep.element; 5 | var _dep_text = _dep.text; 6 | 7 | var Template_0 = _dep_template(function(_template) { 8 | _dep_element(_template, 0, 1, 'DIV'); 9 | _dep_text(_template, 1, 2, 'test'); 10 | }); 11 | 12 | return Template_0; 13 | }); 14 | -------------------------------------------------------------------------------- /design/compile/amd/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": "amd" 3 | } 4 | -------------------------------------------------------------------------------- /design/compile/amd/source.html: -------------------------------------------------------------------------------- 1 |
test
2 | -------------------------------------------------------------------------------- /design/compile/angular/expect.js: -------------------------------------------------------------------------------- 1 | angular.module('et.template').factory('Template', ['et-dependency', function(_dep) { 2 | 3 | var _dep_template = _dep.template; 4 | var _dep_element = _dep.element; 5 | var _dep_text = _dep.text; 6 | 7 | var Template_0 = _dep_template(function(_template) { 8 | _dep_element(_template, 0, 1, 'DIV'); 9 | _dep_text(_template, 1, 2, 'test'); 10 | }); 11 | 12 | return function(option) { 13 | return new Template_0(option); 14 | } 15 | }]); 16 | -------------------------------------------------------------------------------- /design/compile/angular/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": "angular" 3 | } 4 | -------------------------------------------------------------------------------- /design/compile/angular/source.html: -------------------------------------------------------------------------------- 1 |
test
2 | -------------------------------------------------------------------------------- /design/compile/cmd/expect.js: -------------------------------------------------------------------------------- 1 | ;define(function(require, exports, module) { 2 | 3 | var _dep = require('et-dependency'); 4 | 5 | var _dep_template = _dep.template; 6 | var _dep_element = _dep.element; 7 | var _dep_text = _dep.text; 8 | 9 | var Template_0 = _dep_template(function(_template) { 10 | _dep_element(_template, 0, 1, 'DIV'); 11 | _dep_text(_template, 1, 2, 'test'); 12 | }); 13 | 14 | module.exports = Template_0; 15 | }); 16 | -------------------------------------------------------------------------------- /design/compile/cmd/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": "cmd" 3 | } 4 | -------------------------------------------------------------------------------- /design/compile/cmd/source.html: -------------------------------------------------------------------------------- 1 |
test
2 | -------------------------------------------------------------------------------- /design/compile/global/expect.js: -------------------------------------------------------------------------------- 1 | ;(function(global) { 2 | var _dep = global['et-dependency']; 3 | 4 | var _dep_template = _dep.template; 5 | var _dep_element = _dep.element; 6 | var _dep_text = _dep.text; 7 | 8 | var Template_0 = _dep_template(function(_template) { 9 | _dep_element(_template, 0, 1, 'DIV'); 10 | _dep_text(_template, 1, 2, 'test'); 11 | }); 12 | 13 | global['Template'] = Template_0; 14 | })(window); 15 | -------------------------------------------------------------------------------- /design/compile/global/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": "global" 3 | } 4 | -------------------------------------------------------------------------------- /design/compile/global/source.html: -------------------------------------------------------------------------------- 1 |
test
2 | -------------------------------------------------------------------------------- /design/complex/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | 5 | var _dep_template = _dep.template; 6 | var _dep_element = _dep.element; 7 | var _dep_eif = _dep.eif; 8 | var _dep_text = _dep.text; 9 | var _dep_efor = _dep.efor; 10 | var _dep_updateText = _dep.updateText; 11 | 12 | var Template_0 = _dep_template(function (_template) { 13 | _dep_element(_template, 0, 1, 'DIV', [ 14 | ['id', 'content'] 15 | ]); 16 | _dep_eif(_template, 1, 2, function (it, _args) { 17 | if (it.isTrue) return [0, Template_2]; 18 | else return [1, Template_4]; 19 | }); 20 | _dep_element(_template, 1, 6, 'P'); 21 | _dep_text(_template, 6, 7, 'It is P label.'); 22 | 23 | _dep_efor(_template, 1, 8, Template_8, function (it, _args) { 24 | return it.matrix[it.members[1]]; 25 | }); 26 | }); 27 | 28 | var Template_2 = _dep_template(function (_template) { 29 | _dep_text(_template, 2, 3, 'It is true.'); 30 | }); 31 | 32 | var Template_4 = _dep_template(function (_template) { 33 | _dep_text(_template, 4, 5, 'It is else.'); 34 | }); 35 | 36 | var Template_8 = _dep_template(function (_template) { 37 | _dep_text(_template, 8, 9); 38 | }, function (it, _args) { 39 | var item = _args[0]; 40 | var index = _args[1]; 41 | 42 | var _patches = []; 43 | _patches[0] = index; 44 | return _patches; 45 | }, function (_template, it, _args, _patches, _cache) { 46 | var item = _args[0]; 47 | var index = _args[1]; 48 | 49 | var _tmp = _patches[0]; 50 | if (_cache(0, _tmp)) { 51 | _dep_updateText(_template, 9, 'it is for loop ' + _tmp); 52 | } 53 | }); 54 | 55 | module.exports = Template_0; 56 | -------------------------------------------------------------------------------- /design/complex/source.html: -------------------------------------------------------------------------------- 1 |
2 | [#if it.isTrue] 3 | It is true. 4 | [#else] 5 | It is else. 6 | [/#if] 7 |

8 | It is P label. 9 |

10 | [#for item, index in it.matrix[it.members[1]]] 11 | it is for loop {{index}} 12 | [/#for] 13 |
14 | -------------------------------------------------------------------------------- /design/event-bind/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | 5 | var _dep_template = _dep.template; 6 | var _dep_element = _dep.element; 7 | var _dep_text = _dep.text; 8 | 9 | var Template_0 = _dep_template(function (_template) { 10 | 11 | _dep_element(_template, 0, 1, 'DIV'); 12 | 13 | _dep_element(_template, 1, 2, 'A', 0, [ 14 | ['click', function ($event, it, _args) { 15 | return it.onClick(it.getName(), item, $event, index); 16 | }] 17 | ]); 18 | _dep_text(_template, 2, 3, 'on-click'); 19 | 20 | _dep_element(_template, 1, 4, 'A', 0, [ 21 | ['click', function ($event, it, _args) { 22 | return it.onClick(); 23 | }] 24 | ]); 25 | _dep_text(_template, 4, 5, '(click)'); 26 | }); 27 | 28 | module.exports = Template_0; 29 | -------------------------------------------------------------------------------- /design/event-bind/source.html: -------------------------------------------------------------------------------- 1 |
2 | on-click 3 | (click) 4 |
5 | -------------------------------------------------------------------------------- /design/for-and-if/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | 5 | var _dep_template = _dep.template; 6 | var _dep_element = _dep.element; 7 | var _dep_efor = _dep.efor; 8 | var _dep_eif = _dep.eif; 9 | var _dep_text = _dep.text; 10 | var _dep_updateText = _dep.updateText; 11 | 12 | var Template_0 = _dep_template(function (_template) { 13 | _dep_element(_template, 0, 1, 'UL', [ 14 | ['class', 'list'] 15 | ]); 16 | _dep_efor(_template, 1, 2, Template_2, function (it, _args) { 17 | return it.list; 18 | }); 19 | }); 20 | 21 | var Template_2 = _dep_template(function (_template) { 22 | _dep_element(_template, 2, 3, 'LI', [ 23 | ['class', 'item'] 24 | ]); 25 | _dep_eif(_template, 3, 4, function (it, _args) { 26 | var item = _args[0]; 27 | var index = _args[1]; 28 | if (index === 0) return [0, Template_4]; 29 | }); 30 | _dep_text(_template, 3, 6); 31 | }, function (it, _args) { 32 | var item = _args[0]; 33 | var index = _args[1]; 34 | 35 | var _patches = []; 36 | _patches[0] = item; 37 | return _patches; 38 | 39 | }, function (_template, it, _args, _patches, _cache) { 40 | var item = _args[0]; 41 | var index = _args[1]; 42 | 43 | var _tmp = _patches[0]; 44 | if (_cache(0, _tmp)) { 45 | _dep_updateText(_template, 6, _tmp); 46 | } 47 | }); 48 | 49 | var Template_4 = _dep_template(function (_template) { 50 | _dep_text(_template, 4, 5, 'It is 0.'); 51 | }); 52 | 53 | module.exports = Template_0; 54 | -------------------------------------------------------------------------------- /design/for-and-if/source.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /design/for-for/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | var _dep_template = _dep.template; 5 | var _dep_efor = _dep.efor; 6 | var _dep_text = _dep.text; 7 | var _dep_updateText = _dep.updateText; 8 | 9 | var Template_0 = _dep_template(function(_template) { 10 | _dep_efor(_template, 0, 1, Template_1, function(it, _args) { 11 | return it.matrix; 12 | }); 13 | }); 14 | var Template_1 = _dep_template(function(_template) { 15 | _dep_efor(_template, 1, 2, Template_2, function(it, _args) { 16 | var list = _args[0]; 17 | var index1 = _args[1]; 18 | return list; 19 | }); 20 | }); 21 | var Template_2 = _dep_template(function(_template) { 22 | _dep_text(_template, 2, 3); 23 | }, function (it, _args) { 24 | var list = _args[0]; 25 | var index1 = _args[1]; 26 | var item = _args[2]; 27 | var index2 = _args[3]; 28 | 29 | var _patches = []; 30 | _patches[0] = [index2, item]; 31 | return _patches; 32 | 33 | }, function (_template, it, _args, _patches, _cache) { 34 | var list = _args[0]; 35 | var index1 = _args[1]; 36 | var item = _args[2]; 37 | var index2 = _args[3]; 38 | 39 | var _tmp = _patches[0]; 40 | if (_cache(0, _tmp)) { 41 | var _value0 = _tmp[0]; 42 | var _value1 = _tmp[1]; 43 | _dep_updateText(_template, 3, 'It is for loop ' + _value0 + ':' + _value1 + '.'); 44 | } 45 | }); 46 | module.exports = Template_0; -------------------------------------------------------------------------------- /design/for-for/source.html: -------------------------------------------------------------------------------- 1 | [#for list, index1 in it.matrix] 2 | [#for item, index2 in list] 3 | It is for loop {{index2}}:{{item}}. 4 | [/#for] 5 | [/#for] 6 | -------------------------------------------------------------------------------- /design/for-track-by/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | 5 | var _dep_template = _dep.template; 6 | var _dep_efor = _dep.efor; 7 | var _dep_element = _dep.element; 8 | var _dep_text = _dep.text; 9 | var _dep_updateText = _dep.updateText; 10 | 11 | var Template_0 = _dep_template(function (_template) { 12 | _dep_efor(_template, 0, 1, Template_1, function (it, _args) { 13 | return it.users; 14 | }, function (it, _args) { 15 | var user = _args[0]; 16 | var index = _args[1]; 17 | return user.id; 18 | }); 19 | }); 20 | 21 | var Template_1 = _dep_template(function (_template) { 22 | _dep_element(_template, 1, 2, 'H1'); 23 | _dep_text(_template, 2, 3); 24 | _dep_element(_template, 1, 4, 'P'); 25 | _dep_text(_template, 4, 5); 26 | 27 | }, function (it, _args) { 28 | var user = _args[0]; 29 | var index = _args[1]; 30 | 31 | var _patches = []; 32 | _patches[0] = [user.id, user.name]; 33 | _patches[1] = user.description; 34 | return _patches; 35 | 36 | }, function (_template, it, _args, _patches, _cache) { 37 | var user = _args[0]; 38 | var index = _args[1]; 39 | 40 | var _tmp = _patches[0]; 41 | if (_cache(0, _tmp)) { 42 | var _value0 = _tmp[0]; 43 | var _value1 = _tmp[1]; 44 | _dep_updateText(_template, 3, _value0 + ': ' + _value1); 45 | } 46 | 47 | var _tmp = _patches[1]; 48 | if (_cache(1, _tmp)) { 49 | _dep_updateText(_template, 5, _tmp); 50 | } 51 | }); 52 | 53 | module.exports = Template_0; 54 | -------------------------------------------------------------------------------- /design/for-track-by/source.html: -------------------------------------------------------------------------------- 1 | 2 | [#for user, index in it.users track by user.id] 3 |

{{user.id}}: {{user.name}}

4 |

{{user.description}}

5 | [/#for] 6 | -------------------------------------------------------------------------------- /design/for/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | 5 | var _dep_template = _dep.template; 6 | var _dep_efor = _dep.efor; 7 | var _dep_text = _dep.text; 8 | var _dep_updateText = _dep.updateText; 9 | 10 | var Template_0 = _dep_template(function (_template) { 11 | _dep_efor(_template, 0, 1, Template_1, function (it, _args) { 12 | return it.matrix[it.members[1]]; 13 | }, function (it, _args) { 14 | var item = _args[0]; 15 | var index = _args[1]; 16 | return item.id; 17 | }); 18 | }); 19 | 20 | var Template_1 = _dep_template(function (_template) { 21 | _dep_text(_template, 1, 2); 22 | 23 | }, function (it, _args) { 24 | var item = _args[0]; 25 | var index = _args[1]; 26 | 27 | var _patches = []; 28 | _patches[0] = index; 29 | return _patches; 30 | 31 | }, function (_template, it, _args, _patches, _cache) { 32 | var item = _args[0]; 33 | var index = _args[1]; 34 | 35 | var _tmp = _patches[0]; 36 | if (_cache(0, _tmp)) { 37 | _tmp = it.format(_tmp); 38 | _dep_updateText(_template, 2, 'it is for loop ' + _tmp); 39 | } 40 | }); 41 | 42 | module.exports = Template_0; 43 | -------------------------------------------------------------------------------- /design/for/source.html: -------------------------------------------------------------------------------- 1 | [#for item, index in it.matrix[it.members[1]] track by item.id] 2 | it is for loop {{index | it.format}} 3 | [/#for] 4 | -------------------------------------------------------------------------------- /design/if-else/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | var _dep_template = _dep.template; 5 | var _dep_element = _dep.element; 6 | var _dep_eif = _dep.eif; 7 | var _dep_text = _dep.text; 8 | 9 | var Template_0 = _dep_template(function (_template) { 10 | _dep_element(_template, 0, 1, 'DIV'); 11 | _dep_eif(_template, 1, 2, function (it, _args) { 12 | if (it.isTrue) return [0, Template_2]; 13 | else if (it.elseTrue) return [1, Template_4]; 14 | else return [2, Template_6]; 15 | }); 16 | }); 17 | 18 | var Template_2 = _dep_template(function (_template) { 19 | _dep_text(_template, 2, 3, 'It is true.'); 20 | }); 21 | 22 | var Template_4 = _dep_template(function (_template) { 23 | _dep_text(_template, 4, 5, 'It is elseTrue.'); 24 | }); 25 | 26 | var Template_6 = _dep_template(function (_template) { 27 | _dep_text(_template, 6, 7, 'It is else.'); 28 | }); 29 | 30 | module.exports = Template_0; 31 | -------------------------------------------------------------------------------- /design/if-else/source.html: -------------------------------------------------------------------------------- 1 |
2 | [#if it.isTrue] 3 | It is true. 4 | [#elseif it.elseTrue] 5 | It is elseTrue. 6 | [#else] 7 | It is else. 8 | [/#if] 9 |
10 | -------------------------------------------------------------------------------- /design/if-with-siblings/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | 5 | var _dep_template = _dep.template; 6 | var _dep_text = _dep.text; 7 | var _dep_eif = _dep.eif; 8 | 9 | var Template_0 = _dep_template(function (_template) { 10 | _dep_text(_template, 0, 1, 'It is before.'); 11 | _dep_eif(_template, 0, 2, function (it, _args) { 12 | if (it.isNumber && it.isEven) return [0, Template_2]; 13 | }); 14 | }); 15 | 16 | var Template_2 = _dep_template(function (_template) { 17 | _dep_text(_template, 2, 3, 'It is number and is even'); 18 | }); 19 | 20 | module.exports = Template_0; 21 | -------------------------------------------------------------------------------- /design/if-with-siblings/source.html: -------------------------------------------------------------------------------- 1 | It is before. 2 | [#if it.isNumber && it.isEven] 3 | It is number and is even 4 | [/#if] 5 | -------------------------------------------------------------------------------- /design/import/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var util = require('./util'); 4 | var util1 = require('./util1'); 5 | var util2 = require('./util2'); 6 | var _dep = require('et-dependency'); 7 | 8 | var _dep_template = _dep.template; 9 | var _dep_element = _dep.element; 10 | var _dep_text = _dep.text; 11 | var _dep_updateText = _dep.updateText; 12 | 13 | var Template_0 = _dep_template(function (_template) { 14 | _dep_element(_template, 0, 4, 'A'); 15 | _dep_text(_template, 4, 5); 16 | 17 | }, function (it, _args) { 18 | var _patches = []; 19 | _patches[0] = util.add(it.a, it.b); 20 | return _patches; 21 | 22 | }, function (_template, it, _args, _patches, _cache) { 23 | var _tmp = _patches[0]; 24 | if (_cache(0, _tmp)) { 25 | _dep_updateText(_template, 5, _tmp); 26 | } 27 | }); 28 | 29 | module.exports = Template_0; 30 | -------------------------------------------------------------------------------- /design/import/source.html: -------------------------------------------------------------------------------- 1 | 2 | [#import util from './util'] 3 | [#import util1 from ./util1] 4 | [#import util2 from "./util2"] 5 | 6 | {{util.add(it.a, it.b)}} 7 | -------------------------------------------------------------------------------- /design/innerHTML/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | var _dep_template = _dep.template; 5 | var _dep_element = _dep.element; 6 | var _dep_html = _dep.html; 7 | 8 | var Template_0 = _dep_template(function (_template) { 9 | _dep_element(_template, 0, 1, 'DIV'); 10 | 11 | }, function (it, _args) { 12 | var _patches = []; 13 | _patches[0] = it.html; 14 | return _patches; 15 | 16 | }, function (_template, it, _args, _patches, _cache) { 17 | var _tmp = _patches[0]; 18 | if (_cache(0, _tmp)) { 19 | _dep_html(_template, 1, '
aaa' + _tmp + 'bbb'); 20 | } 21 | }); 22 | 23 | module.exports = Template_0; 24 | -------------------------------------------------------------------------------- /design/innerHTML/source.html: -------------------------------------------------------------------------------- 1 |
2 | [#html] 3 |
4 | aaa{{it.html}}bbb 5 | [/#html] 6 |
7 | -------------------------------------------------------------------------------- /design/output/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | var _dep_template = _dep.template; 5 | var _dep_element = _dep.element; 6 | 7 | var Template_0 = _dep_template(function (_template) { 8 | _dep_element(_template, 0, 1, 'DIV'); 9 | _dep_element(_template, 1, 2, 'INPUT', [ 10 | ['type', 'file'] 11 | ], [ 12 | ['change input', function ($event, it, _args) { 13 | it.file = this.file; 14 | it.value = this.value; 15 | }] 16 | ]); 17 | }); 18 | 19 | module.exports = Template_0; 20 | -------------------------------------------------------------------------------- /design/output/source.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /design/text-pipe/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var util = require('./util'); 4 | var _dep = require('et-dependency'); 5 | 6 | var _dep_template = _dep.template; 7 | var _dep_element = _dep.element; 8 | var _dep_text = _dep.text; 9 | var _dep_updateText = _dep.updateText; 10 | 11 | var Template_0 = _dep_template(function (_template) { 12 | _dep_element(_template, 0, 2, 'A'); 13 | _dep_text(_template, 2, 3); 14 | }, function (it, _args) { 15 | var _patches = []; 16 | _patches[0] = it.isMe ? 'ME' : 'YOU | HE'; 17 | return _patches; 18 | 19 | }, function (_template, it, _args, _patches, _cache) { 20 | var _tmp = _patches[0]; 21 | if (_cache(0, _tmp)) { 22 | _tmp = util.format(_tmp); 23 | _tmp = util.trim(_tmp, ' | ', ' , '); 24 | _dep_updateText(_template, 3, _tmp); 25 | } 26 | }); 27 | 28 | module.exports = Template_0; 29 | -------------------------------------------------------------------------------- /design/text-pipe/source.html: -------------------------------------------------------------------------------- 1 | [#import util from './util'] 2 | 3 | {{it.isMe?'ME':'YOU | HE' | util.format | util.trim, ' | ', ' , '}} 4 | -------------------------------------------------------------------------------- /design/text/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | 5 | var _dep_template = _dep.template; 6 | var _dep_element = _dep.element; 7 | var _dep_text = _dep.text; 8 | var _dep_updateText = _dep.updateText; 9 | 10 | var Template_0 = _dep_template(function (_template) { 11 | _dep_element(_template, 0, 1, 'DIV'); 12 | _dep_text(_template, 1, 2); 13 | _dep_element(_template, 0, 3, 'P'); 14 | _dep_text(_template, 3, 4, 'Sorry, you can\'t do it.\n\n\n bbbbbbbbb'); 15 | _dep_element(_template, 0, 5, 'P'); 16 | _dep_text(_template, 5, 6); 17 | 18 | }, function (it, _args) { 19 | var _patches = []; 20 | _patches[0] = it.src; 21 | _patches[1] = it.unreadCount > 99 ? '99+' : it.unreadCount; 22 | return _patches; 23 | 24 | }, function (_template, it, _args, _patches, _cache) { 25 | var _tmp = _patches[0]; 26 | if (_cache(0, _tmp)) { 27 | _dep_updateText(_template, 2, 'aaaa[' + _tmp + ']'); 28 | } 29 | 30 | var _tmp = _patches[1]; 31 | if (_cache(1, _tmp)) { 32 | _dep_updateText(_template, 6, _tmp); 33 | } 34 | }); 35 | 36 | module.exports = Template_0; 37 | -------------------------------------------------------------------------------- /design/text/source.html: -------------------------------------------------------------------------------- 1 |
2 | aaaa[{{it.src}}] 3 |
4 |

5 | Sorry, you can't do it. 6 | 7 | 8 | bbbbbbbbb 9 |

10 |

11 | {{it.unreadCount > 99 ? '99+' : it.unreadCount}} 12 |

13 | -------------------------------------------------------------------------------- /design/two-way-bind/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var _dep = require('et-dependency'); 4 | var _dep_template = _dep.template; 5 | var _dep_element = _dep.element; 6 | var _dep_updateAttr = _dep.updateAttr; 7 | 8 | var Template_0 = _dep_template(function (_template) { 9 | _dep_element(_template, 0, 1, 'DIV'); 10 | _dep_element(_template, 1, 2, 'INPUT', 0, [ 11 | ['change input', function ($event, it, _args) { 12 | it.name = this.value; 13 | }] 14 | ]); 15 | }, function (it, _args) { 16 | var _patches = []; 17 | _patches[0] = it.name; 18 | return _patches; 19 | 20 | }, function (_template, it, _args, _patches, _cache) { 21 | var _tmp = _patches[0]; 22 | if (_cache(0, _tmp)) { 23 | _dep_updateAttr(_template, 2, 'value', _tmp); 24 | } 25 | }); 26 | 27 | module.exports = Template_0; 28 | -------------------------------------------------------------------------------- /design/two-way-bind/source.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "et-template", 3 | "version": "0.4.0", 4 | "description": "A template for web.", 5 | "author": "suyu34", 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:et-studio/et-template.git" 9 | }, 10 | "main": "./es5/et.js", 11 | "typings": "./typings/et-tempalte.d.ts", 12 | "scripts": { 13 | "setup": "typings install", 14 | "clean": "rm -rf es5", 15 | "lint": "standard && tslint src/**/*.ts", 16 | "start": "webpack-dev-server --content-base test/server/views --hot --progress", 17 | "prebuild": "npm run clean && npm run lint", 18 | "build": "tsc", 19 | "postbuild": "npm run test", 20 | "test": "mocha test/**/*_spec.js", 21 | "deploy": "npm run build && npm publish" 22 | }, 23 | "engines": { 24 | "node": "4.0.x" 25 | }, 26 | "dependencies": { 27 | "et-parser": "^0.1.8", 28 | "lodash": "^4.11.1" 29 | }, 30 | "devDependencies": { 31 | "js-beautify": "^1.6.2", 32 | "mocha": "^2.4.5", 33 | "should": "^8.3.1", 34 | "standard": "^6.0.8", 35 | "ts-loader": "^0.8.2", 36 | "tslint": "^3.7.4", 37 | "typescript": "^1.8.10", 38 | "typings": "^0.7.12", 39 | "webpack": "^1.12.14", 40 | "webpack-dev-server": "^1.14.1" 41 | }, 42 | "standard": { 43 | "ignore": [ 44 | "design", 45 | "src-old" 46 | ] 47 | }, 48 | "keywords": [ 49 | "template", 50 | "et", 51 | "ET" 52 | ], 53 | "files": [ 54 | "es5", 55 | "src", 56 | "typings/et-template.d.ts", 57 | "README.md" 58 | ], 59 | "license": "MIT" 60 | } 61 | -------------------------------------------------------------------------------- /src/et.ts: -------------------------------------------------------------------------------- 1 | 2 | import {pick, extend} from 'lodash'; 3 | import {parseOrigin} from './parsers' 4 | import {create as createNode} from './middlewares/node-creator' 5 | import {translate as translateAttributes} from './middlewares/attributes-translator' 6 | import {rebuild as rebuildNode} from './middlewares/node-rebuilder' 7 | import {check as checkNode} from './middlewares/node-checker' 8 | import {compile as compileNode} from './middlewares/node-compiler' 9 | import {format as formatResult} from './middlewares/node-formatter' 10 | import {eachNode} from './util' 11 | 12 | const PROP_LIST = ['modules', 'depName', 'depPath'] 13 | const PROP_DEFAULT = { 14 | modules: 'common', // ['common', 'cmd', 'amd', 'global', 'angular'] 15 | depName: '_dep', 16 | depPath: 'et-dependency', 17 | moduleId: 'Template' 18 | } 19 | 20 | export function compile (source: string, options: IOptions) { 21 | options = extend({}, PROP_DEFAULT, options) 22 | 23 | // parse string to origin node tree 24 | let origin = parseOrigin(source) 25 | 26 | // handle node tree 27 | let root = createNode(origin, options) 28 | eachNode(root, node => node.parse()) 29 | root.addDependency(options.depName, options.depPath) 30 | root = translateAttributes(root) 31 | root = rebuildNode(root) 32 | root = checkNode(root) 33 | 34 | // handle string 35 | let result = compileNode(root, options) 36 | result = formatResult(result) 37 | 38 | return result 39 | } 40 | -------------------------------------------------------------------------------- /src/middlewares/attributes-translator.ts: -------------------------------------------------------------------------------- 1 | 2 | import {eachNode} from '../util' 3 | import {ElementNode} from '../nodes/element' 4 | 5 | const TWO_WAY_KEY = '[(value)]' 6 | const EVENT_REG = /^\(([\s\S]*)\)$|^on-([\s\S]*)/ 7 | const OUTPUT_REG = /^\[([\s\S]*)\]$/ 8 | const METHOD_LEFT = '(' 9 | const METHOD_RIGHT = ')' 10 | const EVENT_SPLIT = ',' 11 | 12 | export function translate (last: INode) { 13 | eachNode(last, (node) => { 14 | if (node.nodeType === 1 && node instanceof ElementNode) { 15 | translateTwoWayBind(node) 16 | translateElementAttributes(node) 17 | } 18 | }) 19 | return last 20 | } 21 | 22 | function translateTwoWayBind (element: ElementNode) { 23 | let attrs = element.attributes 24 | attrs.forEach((value, key) => { 25 | if (key === TWO_WAY_KEY) { 26 | attrs.delete(key) 27 | attrs.set('value', `{{${value}}}`) 28 | attrs.set('[value]', value) 29 | } 30 | }) 31 | } 32 | function translateElementAttributes (element: ElementNode) { 33 | let attrs = element.attributes 34 | attrs.forEach((value, key) => { 35 | let eventName = getEventFromKey(key) 36 | let outputName = getOutputFromKey(key) 37 | 38 | if (eventName) { 39 | let event = parseEventFromExpression(value) 40 | element.addEvent(eventName, event.expression, event.args) 41 | attrs.delete(key) 42 | } else if (outputName) { 43 | element.addOutput(outputName, value) 44 | attrs.delete(key) 45 | } 46 | }) 47 | } 48 | 49 | function getEventFromKey (key: string) { 50 | let matches = EVENT_REG.exec(key) 51 | // parse event like: on-click or (click) 52 | if (matches) { 53 | return matches[1] || matches[2] || null 54 | } else { 55 | return null 56 | } 57 | } 58 | function parseEventFromExpression (value: string) { 59 | let startIndex = value.indexOf(METHOD_LEFT) 60 | let endIndex = value.lastIndexOf(METHOD_RIGHT) 61 | 62 | if (!(startIndex < endIndex)) { 63 | return {expression: value, args: []} 64 | } 65 | 66 | let methodExprssion = value.substring(0, startIndex) 67 | let argsExpression = value.substring(startIndex + 1, endIndex) 68 | let args = [] 69 | argsExpression.split(EVENT_SPLIT).map((item) => { 70 | let tmp = item.trim() 71 | if (tmp) args.push(tmp) 72 | }) 73 | return {expression: methodExprssion, args} 74 | } 75 | function getOutputFromKey (key: string) { 76 | let matches = OUTPUT_REG.exec(key) 77 | 78 | if (matches) { 79 | return matches[1] || null 80 | } else { 81 | return null 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/middlewares/node-checker.ts: -------------------------------------------------------------------------------- 1 | 2 | import {eachNode} from '../util' 3 | import {HtmlNode} from '../nodes/html' 4 | 5 | export function check (node: INode) { 6 | eachNode(node, (item) => { 7 | if (item instanceof HtmlNode) { 8 | checkHtmlNode(item) 9 | } 10 | }) 11 | return node 12 | } 13 | 14 | function checkHtmlNode (node: HtmlNode) { 15 | let message = '' 16 | if (!node.parent) { 17 | message = 'html node need a parent' 18 | } 19 | if (node.parent.nodeType !== 1) { 20 | message = 'the parent of html node should be element node' 21 | } 22 | if (node.parent.children.length > 1) { 23 | message = 'html node should not has siblings' 24 | } 25 | if (message) { 26 | throw new Error(message) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/middlewares/node-compiler.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as compiler from '../templates/compile' 3 | 4 | export function compile (root: INode, options: IOptions) { 5 | switch (options.modules) { 6 | case 'angular': 7 | return compiler.angular(root, options) 8 | case 'cmd': 9 | return compiler.cmd(root, options) 10 | case 'amd': 11 | return compiler.amd(root, options) 12 | case 'global': 13 | return compiler.global(root, options) 14 | default: 15 | return compiler.common(root, options) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/middlewares/node-creator.ts: -------------------------------------------------------------------------------- 1 | 2 | import {each} from 'lodash' 3 | import {BasicNode} from '../nodes/basic' 4 | import {createNode as createFromOrigin} from '../nodes/factory' 5 | 6 | export function create (last: IOriginNode, options: IOptions) { 7 | let index = 0 8 | function createNode (origin?: IOriginNode, isExpression?: boolean) { 9 | let result: BasicNode 10 | if (origin) { 11 | result = createFromOrigin(origin, options) 12 | if (!isExpression) result.setIndex(index++) 13 | } else { 14 | result = createFromOrigin(null, options) 15 | if (!isExpression) index++ 16 | } 17 | return result 18 | } 19 | function createChildren (parent: INode, origin: IOriginNode) { 20 | each(origin.children, (child) => { 21 | let node = createNode(child) 22 | parent.append(node) 23 | createChildren(node, child) 24 | }) 25 | each(origin.expressions, (item) => { 26 | let expNode = createNode(item, true) 27 | parent.expressions.push(expNode) 28 | createChildren(expNode, item) 29 | }) 30 | return parent 31 | } 32 | 33 | let root = createNode() 34 | return createChildren(root, last) 35 | } 36 | -------------------------------------------------------------------------------- /src/middlewares/node-formatter.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | const LINE_SPLIT = '\n' 4 | 5 | export function format (last: string) { 6 | removeComments('') 7 | return last 8 | } 9 | function removeComments (content: string) { 10 | let list = content.split(LINE_SPLIT) 11 | let results = [] 12 | for (let i = 0, len = list.length; i < len; i++) { 13 | let item = list[i].trim() 14 | if (item.indexOf('//') !== 0) results.push(item) 15 | } 16 | return results.join(LINE_SPLIT) 17 | } 18 | -------------------------------------------------------------------------------- /src/middlewares/node-rebuilder.ts: -------------------------------------------------------------------------------- 1 | 2 | import {clone, each} from 'lodash' 3 | import {eachNode} from '../util' 4 | import {IfNode} from '../nodes/if' 5 | import {ElseIfNode} from '../nodes/elseif' 6 | import {ElseNode} from '../nodes/else' 7 | 8 | export function rebuild (node: INode) { 9 | eachNode(node, (item) => { 10 | if (item instanceof IfNode) { 11 | rebuildIf(item) 12 | } 13 | }) 14 | return node 15 | } 16 | 17 | function rebuildIf (node: IfNode) { 18 | let isChange = false 19 | let duplicate = clone(node.children) 20 | let currentNode: INode = node 21 | 22 | each(duplicate, (child) => { 23 | if (child instanceof ElseIfNode || child instanceof ElseNode) { 24 | currentNode.after(child) 25 | currentNode = child 26 | isChange = true 27 | } else { 28 | currentNode.append(child) 29 | } 30 | }) 31 | return isChange 32 | } 33 | -------------------------------------------------------------------------------- /src/nodes/basic.ts: -------------------------------------------------------------------------------- 1 | 2 | import {extend, each, pick, uniq} from 'lodash' 3 | import {OriginNode} from './origin' 4 | import {wrap} from '../parsers' 5 | 6 | export class BasicNode extends OriginNode implements INode { 7 | id = 0 8 | name = 'Template_0' 9 | isTemplate = false 10 | 11 | previous: BasicNode 12 | next: BasicNode 13 | parent: BasicNode 14 | origin: IOriginNode 15 | 16 | attributes = new Map() 17 | arguments = new Array() 18 | dependencies = new Map() 19 | expressions = new Array(); 20 | children = new Array() 21 | 22 | private valueId = 0 23 | 24 | constructor (origin?: IOriginNode) { 25 | super() 26 | this.origin = origin 27 | if (!origin) return this 28 | 29 | let props = [ 30 | 'nodeType', 31 | 'nodeName', 32 | 'source', 33 | 'html', 34 | 'tail' 35 | ] 36 | extend(this, pick(origin, props)) 37 | } 38 | 39 | parse () { 40 | // to be override 41 | } 42 | setIndex (index: number) { 43 | this.id = index 44 | this.name = 'Template_' + this.id 45 | } 46 | addDependency (variable: string, path: string) { 47 | let root = this.getRoot() 48 | root.dependencies.set(variable, path) 49 | } 50 | getArguments () { 51 | let args = this.arguments 52 | let last = this.getLastTemplate() 53 | if (last) { 54 | args = last.getArguments().concat(args) 55 | } 56 | return uniq(args) 57 | } 58 | getCreateList () { 59 | let results: (string | ICreate)[] = [] 60 | each(this.children, (child) => { 61 | let tmp = child.create() 62 | if (tmp) results.push(tmp) 63 | 64 | if (!child.isTemplate) { 65 | results = results.concat(child.getCreateList()) 66 | } 67 | }) 68 | return results.filter(item => { 69 | if (typeof item === 'string') { 70 | return !!item.trim() 71 | } 72 | return true 73 | }) 74 | } 75 | 76 | protected checkRoot () { 77 | let parent = this.parent 78 | if (!parent) return true 79 | if (parent.id === 0) return true 80 | if (parent.isTemplate) return true 81 | return false 82 | } 83 | 84 | protected addArguments (...list: string[]) { 85 | let args = this.arguments 86 | each(list, (item) => { 87 | if (args.indexOf(item) < 0) { 88 | args.push(item) 89 | } 90 | }) 91 | } 92 | 93 | protected getLastTemplate () { 94 | if (!this.parent) return null 95 | 96 | let root = this.parent 97 | while (root.parent && !root.isTemplate) { 98 | root = root.parent 99 | } 100 | return root 101 | } 102 | protected getRoot () { 103 | let root: BasicNode = this 104 | while (root.parent) { 105 | root = root.parent 106 | } 107 | return root 108 | } 109 | protected createFunction (expression: string, isIncludeSelf?: boolean) { 110 | let argExps: string[] 111 | if (isIncludeSelf === false) { 112 | argExps = this.parent.getArguments().map((arg, index) => { 113 | return `var ${arg} = _args[${index}];` 114 | }) 115 | } else { 116 | argExps = this.getArguments().map((arg, index) => { 117 | return `var ${arg} = _args[${index}];` 118 | }) 119 | } 120 | 121 | return `function (it, _args) { 122 | ${argExps.join('\n')} 123 | 124 | ${expression} 125 | }` 126 | } 127 | protected getValueSet (valueResult: IValueResult, updateExp: string, updateArgs: string[]) { 128 | const patches: string[] = [] 129 | const updates: string[] = [] 130 | if (!valueResult.isDynamic) return {patches, updates} 131 | 132 | const valueId = this.getValueId() 133 | const dynamicValues: {expression: string, pipes: string[][]}[] = [] 134 | each(valueResult.values, value => { 135 | if (typeof value !== 'string') dynamicValues.push(value) 136 | }) 137 | 138 | if (dynamicValues.length === 1) { 139 | const value = dynamicValues[0] 140 | patches.push(`_patches[${valueId}] = ${value.expression};`) 141 | 142 | const pipes: string[] = [] 143 | each(value.pipes, pipe => { 144 | const args = pipe.slice(0) 145 | const method = args.shift() 146 | args.unshift('_tmp') 147 | pipes.push(`_tmp = ${method}(${args.join(', ')});`) 148 | }) 149 | updates.push(` 150 | var _tmp = _patches[${valueId}]; 151 | if (_cache(${valueId}, _tmp)) { 152 | ${pipes.join('\n')} 153 | ${updateExp}(${updateArgs.join(', ')}, ${this.translateValue(valueResult, dynamicValues)}); 154 | } 155 | `) 156 | } else if (dynamicValues.length > 1) { 157 | const exps = dynamicValues.map(item => item.expression) 158 | patches.push(`_patches[${valueId}] = [${exps.join(', ')}];`) 159 | 160 | const pipes: string[] = [] 161 | each(dynamicValues, (value, valueIndex) => { 162 | pipes.push(`var _value${valueIndex} = _tmp[${valueIndex}];`) 163 | each(value.pipes, pipe => { 164 | const args = pipe.slice(0) 165 | const method = args.shift() 166 | args.unshift(`_value${valueIndex}`) 167 | pipes.push(`_value${valueIndex} = ${method}(${args.join(', ')});`) 168 | }) 169 | }) 170 | updates.push(` 171 | var _tmp = _patches[${valueId}]; 172 | if (_cache(${valueId}, _tmp)) { 173 | ${pipes.join('\n')} 174 | ${updateExp}(${updateArgs.join(', ')}, ${this.translateValue(valueResult, dynamicValues)}); 175 | } 176 | `) 177 | } 178 | return {patches, updates} 179 | } 180 | protected createEventFunction (expression: string) { 181 | let argExps = this.getArguments().map((arg, index) => { 182 | return `var ${arg} = _args[${index}];` 183 | }) 184 | 185 | return `function ($event, it, _args) { 186 | ${argExps.join('\n')} 187 | 188 | ${expression} 189 | }` 190 | } 191 | protected create (): string | ICreate { 192 | // to be override 193 | return '' 194 | } 195 | 196 | protected getValueId () { 197 | let last = this.getLastTemplate() 198 | if (last) { 199 | return last.valueId++ 200 | } else { 201 | return this.valueId++ 202 | } 203 | } 204 | protected translateValue (valueResult: IValueResult, dynamicValues: {expression: string, pipes: string[][]}[]) { 205 | let results: string[] = [] 206 | each(valueResult.values, item => { 207 | if (typeof item === 'string') { 208 | results.push(wrap(item)) 209 | } else { 210 | if (dynamicValues.length === 1) { 211 | results.push(`_tmp`) 212 | } else { 213 | results.push(`_value${dynamicValues.indexOf(item)}`) 214 | } 215 | } 216 | }) 217 | return results.join(' + ') 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/nodes/child.ts: -------------------------------------------------------------------------------- 1 | 2 | import {BasicNode} from './basic'; 3 | const PARAMETER_SPLIT = ','; 4 | 5 | export class ChildNode extends BasicNode { 6 | 7 | childContext = '' 8 | 9 | parse () { 10 | let list = this.source.split(PARAMETER_SPLIT) 11 | 12 | let path = (list[0] || '').trim() 13 | let c1 = path[0] 14 | let c2 = path[path.length - 1] 15 | let isSingleQuotation = (c1 === '\'' && c2 === '\'') 16 | let isDoubleQuotation = (c1 === '"' && c2 === '"') 17 | if (isSingleQuotation || isDoubleQuotation) { 18 | path = path.slice(1, path.length - 1) 19 | } 20 | 21 | this.childContext = (list[1] || '').trim() 22 | this.addDependency(this.name, path) 23 | } 24 | create () { 25 | let context = this.childContext 26 | let args = [this.parent.id, this.id, this.name] 27 | if (this.childContext) { 28 | args.push(this.createFunction(`return ${context};`)) 29 | } 30 | return `_dep.child(_template, ${args.join(', ')});` 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/nodes/element.ts: -------------------------------------------------------------------------------- 1 | 2 | import {each} from 'lodash' 3 | import {BasicNode} from './basic' 4 | import {fillTo, addMapToMap} from '../util' 5 | import {IfNode} from './if' 6 | import {wrap, parseAttributes, parseValue} from '../parsers' 7 | 8 | export class ElementNode extends BasicNode { 9 | nodeType = 1 10 | namespace = 'element' 11 | outputs = new Map() 12 | events = new Map() 13 | attributes = new Map() 14 | 15 | parse () { 16 | this.attributes = parseAttributes(this.source) 17 | each(this.expressions, (node) => { 18 | if (node instanceof IfNode) { 19 | addMapToMap(this.attributes, parseAttributes(node.getExpressionSource())) 20 | } 21 | }) 22 | } 23 | addOutput (prop: string, expression: string) { 24 | this.outputs.set(prop, expression) 25 | } 26 | addEvent (name: string, expression: string, args: string[]) { 27 | this.events.set(name, {expression, args}) 28 | } 29 | 30 | protected create (): string | ICreate { 31 | const args: (string | number)[] = [this.parent.id, this.id, wrap(this.nodeName.toUpperCase())] 32 | 33 | const attrs = this.createAttributes() 34 | if (attrs) args.push(attrs) 35 | 36 | if (this.events.size || this.outputs.size) { 37 | fillTo(args, 4, 0) 38 | args.push(this.createEvent()) 39 | } 40 | 41 | const create = `_dep.element(_template, ${args.join(', ')});` 42 | const set = this.calculateDynamic() 43 | if (set.patch) { 44 | return {create, patch: set.patch, update: set.update} 45 | } else { 46 | return create 47 | } 48 | } 49 | 50 | private createAttributes () { 51 | const attrs: string[] = [] 52 | this.attributes.forEach((value, name) => { 53 | const valueResult = parseValue(value) 54 | if (!valueResult.isDynamic) { 55 | attrs.push(`[${wrap(name)}, ${wrap(value)}]`) 56 | } 57 | }) 58 | 59 | if (attrs.length) { 60 | return `[${attrs.join(',\n')}]` 61 | } else { 62 | return '' 63 | } 64 | } 65 | private calculateDynamic () { 66 | const patches: string[] = [] 67 | const updates: string[] = [] 68 | 69 | const conditionSet = this.calculateConditions() 70 | if (conditionSet.patch) patches.push(conditionSet.patch) 71 | if (conditionSet.update) updates.push(conditionSet.update) 72 | 73 | this.attributes.forEach((value, key) => { 74 | const valueResult = parseValue(value) 75 | if (valueResult.isDynamic) { 76 | const args = ['_template', this.id.toString(), wrap(key)] 77 | const set = this.getValueSet(valueResult, '_dep.updateAttr', args) 78 | if (set.patches.length) patches.push(set.patches.join('\n')) 79 | if (set.updates.length) updates.push(set.updates.join('\n')) 80 | } 81 | }) 82 | return {patch: patches.join('\n'), update: updates.join('\n')} 83 | } 84 | private calculateConditions () { 85 | if (this.expressions.length <= 0) { 86 | return {patch: '', update: ''} 87 | } 88 | 89 | const patches: string[] = [] 90 | const updates: string[] = [] 91 | const valueId = this.getValueId() 92 | each(this.expressions, (node, index) => { 93 | if (node instanceof IfNode) { 94 | const matrix: string[] = [] 95 | patches.push('var _conditions = [];') 96 | each(node.getExpressions(), (item, itemIndex) => { 97 | if (item.tag) { 98 | patches.push(`${item.tag} (${item.condition}) _conditions[${index}] = ${itemIndex};`) 99 | } else { 100 | patches.push(`else _conditions[${index}] = ${itemIndex};`) 101 | } 102 | matrix.push(`[${item.attrs.map(attr => wrap(attr)).join(', ')}]`) 103 | }) 104 | patches.push(`_patches[${valueId}] = _conditions;`) 105 | 106 | updates.push(` 107 | var _matrix${index} = [${matrix.join(', ')}]; 108 | _dep.concatMatrix(_includes, _matrix${index}, _tmp[${index}]); 109 | _dep.concatMatrixOthers(_excludes, _matrix${index}, _tmp[${index}]); 110 | `) 111 | } 112 | }) 113 | 114 | const update = ` 115 | var _tmp = _patches[${valueId}]; 116 | if (_cache(${valueId}, _tmp)) { 117 | var _includes = []; 118 | var _excludes = []; 119 | ${updates.join('\n')} 120 | _dep.setAttributesCondition(_template, ${this.id}, _includes, _excludes); 121 | } 122 | ` 123 | return {patch: patches.join('\n'), update} 124 | } 125 | private createEvent () { 126 | let events: string[] = [] 127 | this.events.forEach((event, type) => { 128 | let typeStr = wrap(type) 129 | let fnStr = this.createEventFunction(`return ${event.expression}(${event.args.join(', ')});`) 130 | events.push(`[${typeStr}, ${fnStr}]`) 131 | }) 132 | if (this.outputs.size) { 133 | let outputs: string[] = [] 134 | this.outputs.forEach((output, key) => { 135 | outputs.push(`${output} = this.${key};`) 136 | }) 137 | events.push(`['change input', ${this.createEventFunction(outputs.join('\n'))}]`) 138 | } 139 | return `[${events.join(',\n')}]` 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/nodes/else.ts: -------------------------------------------------------------------------------- 1 | 2 | import {BasicNode} from './basic' 3 | 4 | export class ElseNode extends BasicNode { 5 | tag = 'else' 6 | isTemplate = true 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/nodes/elseif.ts: -------------------------------------------------------------------------------- 1 | 2 | import {BasicNode} from './basic' 3 | 4 | export class ElseIfNode extends BasicNode { 5 | tag = 'else if' 6 | isTemplate = true 7 | 8 | get condition () { 9 | return this.source.trim() 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/nodes/factory.ts: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import {ElementNode} from './element' 4 | import {TextNode} from './text' 5 | import {BasicNode} from './basic' 6 | import {IfNode} from './if' 7 | import {ElseIfNode} from './elseif' 8 | import {ElseNode} from './else' 9 | import {ForNode} from './for' 10 | import {HtmlNode} from './html' 11 | import {ChildNode} from './child' 12 | import {ImportNode} from './import' 13 | 14 | export function createNode (origin: IOriginNode, options: IOptions) { 15 | let result: BasicNode 16 | if (!origin) { 17 | result = new BasicNode() 18 | } else if (origin.nodeType === 'ET') { 19 | switch (origin.nodeName) { 20 | case '#if': 21 | result = new IfNode(origin) 22 | break 23 | case '#elseif': 24 | result = new ElseIfNode(origin) 25 | break 26 | case '#else': 27 | result = new ElseNode(origin) 28 | break 29 | case '#for': 30 | result = new ForNode(origin) 31 | break 32 | case '#html': 33 | result = new HtmlNode(origin) 34 | break 35 | case '#child': 36 | result = new ChildNode(origin) 37 | break 38 | case '#import': 39 | result = new ImportNode(origin) 40 | break 41 | } 42 | } else { 43 | switch (origin.nodeType) { 44 | case 1: 45 | result = new ElementNode(origin) 46 | break 47 | case 3: 48 | result = new TextNode(origin) 49 | break 50 | } 51 | } 52 | return result 53 | } 54 | -------------------------------------------------------------------------------- /src/nodes/for.ts: -------------------------------------------------------------------------------- 1 | 2 | import {BasicNode} from './basic' 3 | import {parseFor} from '../parsers' 4 | 5 | export class ForNode extends BasicNode { 6 | 7 | isTemplate = true 8 | 9 | forExpression = '' 10 | forItem = 'item' 11 | forIndex = '_i' 12 | forTrackBy = '' 13 | 14 | parse () { 15 | let result = parseFor(this.source) 16 | this.forExpression = result.expression 17 | 18 | if (result.item) { 19 | this.forItem = result.item 20 | } 21 | if (result.index) { 22 | this.forIndex = result.index 23 | } 24 | if (result.trackBy) { 25 | this.forTrackBy = result.trackBy 26 | } 27 | 28 | this.addArguments(result.item) 29 | this.addArguments(result.index) 30 | } 31 | 32 | create () { 33 | let args = [this.parent.id, this.id, this.name] 34 | 35 | args.push(this.createFunction(`return ${this.forExpression};`, false)) 36 | if (this.forTrackBy) { 37 | args.push(this.createFunction(`return ${this.forTrackBy};`)) 38 | } 39 | return `_dep.efor(_template, ${args.join(', ')});` 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/nodes/html.ts: -------------------------------------------------------------------------------- 1 | 2 | import {BasicNode} from './basic' 3 | import {parseValue, wrap} from '../parsers' 4 | 5 | export class HtmlNode extends BasicNode { 6 | nodeName = '#html' 7 | 8 | create (): string | ICreate { 9 | const result = parseValue(this.html) 10 | if (!result.isDynamic) { 11 | const args = [this.parent.id.toString()] 12 | args.push(wrap(this.html)) 13 | return `_dep.html(_template, ${args.join(', ')});` 14 | } else { 15 | const args = ['_template', this.parent.id.toString()] 16 | const create = '' 17 | const set = this.getValueSet(result, '_dep.html', args) 18 | const patch = set.patches.join('\n') 19 | const update = set.updates.join('\n') 20 | return {create, patch, update} 21 | } 22 | } 23 | 24 | getCreateList () { 25 | return [] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/nodes/if.ts: -------------------------------------------------------------------------------- 1 | 2 | import {each} from 'lodash' 3 | import {BasicNode} from './basic' 4 | import {ElseIfNode} from './elseif' 5 | import {ElseNode} from './else' 6 | import {parseAttributes} from '../parsers' 7 | 8 | export class IfNode extends BasicNode { 9 | tag = 'if'; 10 | isTemplate = true; 11 | 12 | get condition () { 13 | return this.source.trim() 14 | } 15 | 16 | getExpressionSource () { 17 | let re = '' 18 | each(this.children, (child) => { 19 | if (child.nodeType === 3) re += (' ' + child.source) 20 | }) 21 | return re 22 | } 23 | 24 | getExpressions () { 25 | let expressions: {tag: string, condition: string, attrs: string[]}[] = [] 26 | 27 | let current = {tag: 'if', condition: this.condition, attrs: new Array()} 28 | each(this.children, (child) => { 29 | if (child instanceof ElseIfNode) { 30 | expressions.push(current) 31 | current = {tag: 'else if', condition: child.condition, attrs: new Array()} 32 | } else if (child instanceof ElseNode) { 33 | expressions.push(current) 34 | current = {tag: 'else', condition: '', attrs: new Array()} 35 | } else { 36 | parseAttributes(child.source).forEach((value, key) => current.attrs.push(key)) 37 | } 38 | }) 39 | expressions.push(current) 40 | return expressions 41 | } 42 | 43 | protected create () { 44 | let args: (string | number)[] = [this.parent.id, this.id] 45 | let exps: string[] = [] 46 | this.getConditions().forEach((node, index) => { 47 | if (node instanceof IfNode || node instanceof ElseIfNode) { 48 | exps.push(`${node.tag} (${node.condition}) return [${index}, ${node.name}];`) 49 | } else { 50 | exps.push(`else return [${index}, ${node.name}];`) 51 | } 52 | }) 53 | args.push(this.createFunction(exps.join('\n'))) 54 | return `_dep.eif(_template, ${args.join(', ')});` 55 | } 56 | private getConditions () { 57 | let results: INode[] = [this] 58 | let current: INode = this 59 | 60 | while (true) { 61 | current = current.next 62 | if (current instanceof ElseIfNode || current instanceof ElseNode) { 63 | results.push(current) 64 | } else { 65 | break 66 | } 67 | } 68 | return results 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/nodes/import.ts: -------------------------------------------------------------------------------- 1 | 2 | import {BasicNode} from './basic' 3 | const PARAMETER_SPLIT = ' from ' 4 | 5 | export class ImportNode extends BasicNode { 6 | namespace = 'import'; 7 | nodeName = '#import'; 8 | 9 | parse () { 10 | let list = this.source.split(PARAMETER_SPLIT) 11 | 12 | let name = (list[0] || '').trim() 13 | let path = (list[1] || '').trim() 14 | 15 | let c1 = path[0] 16 | let c2 = path[path.length - 1] 17 | let isSingle = (c1 === '\'' && c2 === '\'') 18 | let isDouble = (c1 === '"' && c2 === '"') 19 | if (isSingle || isDouble) { 20 | path = path.slice(1, path.length - 1) 21 | } 22 | this.addDependency(name, path) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/nodes/origin.ts: -------------------------------------------------------------------------------- 1 | 2 | import {removeAt, insertAfter, insertBefore} from '../util' 3 | 4 | export class OriginNode implements IOriginNode { 5 | nodeType: string | number = 'ET'; 6 | nodeName = ''; 7 | source = ''; 8 | header = ''; 9 | html = ''; 10 | tail = ''; 11 | 12 | parent: OriginNode = null; 13 | previous: OriginNode = null; 14 | next: OriginNode = null; 15 | expressions = new Array(); 16 | children = new Array(); 17 | 18 | append (childNode: OriginNode) { 19 | childNode.remove() 20 | 21 | let children = this.children 22 | if (children.length > 0) { 23 | let last = children[children.length - 1] 24 | last.next = childNode 25 | childNode.previous = last 26 | } 27 | 28 | children.push(childNode) 29 | childNode.next = null 30 | childNode.parent = this 31 | } 32 | prepend (childNode: OriginNode) { 33 | childNode.remove() 34 | 35 | let children = this.children 36 | if (children.length > 0) { 37 | let first = children[0] 38 | first.previous = childNode 39 | childNode.next = first 40 | } 41 | 42 | children.unshift(childNode) 43 | childNode.previous = null 44 | childNode.parent = this 45 | } 46 | after (nextNode: OriginNode) { 47 | if (!this.parent) return 48 | 49 | if (!this.next) { 50 | return this.parent.append(nextNode) 51 | } 52 | 53 | nextNode.remove() 54 | nextNode.parent = this.parent 55 | nextNode.previous = this 56 | nextNode.next = this.next 57 | 58 | let currentNext = this.next 59 | if (currentNext) currentNext.previous = nextNode 60 | this.next = nextNode 61 | 62 | insertAfter(this.parent.children, this, nextNode) 63 | } 64 | before (previousNode: OriginNode) { 65 | if (!this.parent) return 66 | 67 | if (!this.previous) { 68 | return this.parent.prepend(previousNode) 69 | } 70 | 71 | previousNode.remove() 72 | previousNode.parent = this.parent 73 | previousNode.next = this 74 | previousNode.previous = this.previous 75 | 76 | let currentPrevious = this.previous 77 | if (currentPrevious) currentPrevious.next = previousNode 78 | this.previous = previousNode 79 | 80 | insertBefore(this.parent.children, this, previousNode) 81 | } 82 | remove () { 83 | let previous = this.previous 84 | let next = this.next 85 | if (previous) previous.next = next 86 | if (next) next.previous = previous 87 | 88 | if (this.parent) { 89 | removeAt(this.parent.children, this) 90 | } 91 | } 92 | getOuterHTML () { 93 | switch (this.nodeType) { 94 | case 1: 95 | let results: string[] = []; 96 | results.push(this.header) 97 | results.push(this.html) 98 | results.push(this.tail) 99 | return results.join('') 100 | case 3: 101 | return this.getInnerHTML() 102 | } 103 | } 104 | getInnerHTML () { 105 | return this.html 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/nodes/text.ts: -------------------------------------------------------------------------------- 1 | 2 | import {parseValue, wrap} from '../parsers' 3 | import {BasicNode} from './basic' 4 | 5 | export class TextNode extends BasicNode { 6 | nodeType = 3 7 | 8 | create (): string | ICreate { 9 | const text = this.source 10 | const result = parseValue(text) 11 | 12 | const args = [this.parent.id.toString(), this.id.toString()] 13 | if (!result.isDynamic) { 14 | args.push(wrap(text)) 15 | return `_dep.text(_template, ${args.join(', ')});` 16 | } else { 17 | const updateArgs = ['_template', this.id.toString()] 18 | const create = `_dep.text(_template, ${args.join(', ')});` 19 | const set = this.getValueSet(result, '_dep.updateText', updateArgs) 20 | const patch = set.patches.join('\n') 21 | const update = set.updates.join('\n') 22 | return {create, patch, update} 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/parsers/attributes/attributes-result.ts: -------------------------------------------------------------------------------- 1 | 2 | import {each} from 'lodash' 3 | import {ATTRIBUTES_STATE} from './attributes-state' 4 | 5 | export class AttributesResult { 6 | list = new Array<{key: string, value: string}>() 7 | state = ATTRIBUTES_STATE.Scan 8 | 9 | addToken (token: string) { 10 | switch (this.state) { 11 | case ATTRIBUTES_STATE.Key: 12 | this.addKey(token) 13 | break 14 | case ATTRIBUTES_STATE.Value: 15 | this.addValue(token) 16 | break 17 | } 18 | } 19 | createOne () { 20 | this.list.push({key: '', value: ''}) 21 | } 22 | getAttributes () { 23 | let attrs = new Map() 24 | each(this.list, (item) => { 25 | attrs.set(item.key, item.value) 26 | }) 27 | return attrs 28 | } 29 | 30 | private addKey (token: string) { 31 | let last = this.getLastOne() 32 | last.key += token 33 | } 34 | private addValue (token: string) { 35 | let last = this.getLastOne() 36 | last.value += token 37 | } 38 | private getLastOne () { 39 | let len = this.list.length 40 | if (len) { 41 | return this.list[len - 1] 42 | } else { 43 | this.createOne() 44 | return this.list[0] 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/parsers/attributes/attributes-state.ts: -------------------------------------------------------------------------------- 1 | 2 | export const ATTRIBUTES_STATE = { 3 | Scan: 0, 4 | Key: 1, 5 | Value: 2 6 | } 7 | -------------------------------------------------------------------------------- /src/parsers/attributes/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Parser} from 'et-parser' 3 | import {ATTRIBUTES_STATE} from './attributes-state' 4 | import {AttributesResult} from './attributes-result' 5 | 6 | const parser = new Parser(` 7 | | scan | key | keyEnd | valueStart | value{{ | value | value' | value" | _str 8 | ------ | ------ | --- | ------ | ---------- | ------- | ----- | ------ | ------ | ---- 9 | :[\\s] | scan | keyEnd | keyEnd | valueStart | value{{ | valueEnd:scan | value' | value" | 10 | ' | keyStart:key | key | keyStart:key | ignore:value' | value{{ | value | value'End:scan | value" | 11 | " | keyStart:key | key | keyStart:key | ignore:value" | value{{ | value | value' | value"End:scan | 12 | = | keyStart:key | valueStart | valueStart | value | value{{ | value | value' | value" | 13 | \\' | keyStart:key | key | keyStart:key | value | value{{ | value | value' | value" | 14 | \\" | keyStart:key | key | keyStart:key | value | value{{ | value | value' | value" | 15 | {{ | keyStart:key | key | keyStart:key | value{{ | value{{ | _str | _str | _str | 16 | }} | keyStart:key | key | keyStart:key | value | value | value | value' | value" | _ 17 | | keyStart:key | key | keyStart:key | value | value{{ | value | value' | value" | 18 | `) 19 | 20 | export function parseAttributes (source: string) { 21 | let result = new AttributesResult() 22 | parser.parse(source, (state, token) => { 23 | switch (state) { 24 | case 'scan': 25 | case 'keyEnd': 26 | case 'ignore': 27 | break 28 | case '_str': 29 | case 'key': 30 | case 'value{{': 31 | case 'value': 32 | case 'value\'': 33 | case 'value"': 34 | result.addToken(token) 35 | break 36 | case 'keyStart': 37 | result.createOne() 38 | result.state = ATTRIBUTES_STATE.Key 39 | result.addToken(token) 40 | break 41 | case 'valueStart': 42 | result.state = ATTRIBUTES_STATE.Value 43 | break 44 | case 'valueEnd': 45 | case 'value\'End': 46 | case 'value"End': 47 | result.state = ATTRIBUTES_STATE.Scan 48 | break 49 | default: 50 | throw new Error(`The state: '${state}' is not defined.`) 51 | } 52 | }) 53 | return result.getAttributes() 54 | } 55 | -------------------------------------------------------------------------------- /src/parsers/dep.ts: -------------------------------------------------------------------------------- 1 | 2 | import {forEachRight, uniq} from 'lodash' 3 | import {Parser} from 'et-parser' 4 | 5 | const SYMBOL = '_dep.' 6 | 7 | const parser = new Parser(` 8 | | text | method | _str1 | _str2 9 | ------- | ------ | ------ | ----- | ----- 10 | ${SYMBOL} | start:method | start | | 11 | ( | text | end:text | | 12 | ' | _str1 | method | _ | 13 | " | _str2 | method | | _ 14 | \\\' | text | method | | 15 | \\\" | text | method | | 16 | :[\\s] | text | cancel:text | | 17 | | text | method | | 18 | `) 19 | 20 | export function parseDep (source: string) { 21 | let result = '' 22 | let methods: string[] = [] 23 | let method = ''; 24 | parser.parse(source, function (state, token) { 25 | switch (state) { 26 | case '_str1': 27 | case '_str2': 28 | case 'text': 29 | result += token 30 | break; 31 | case 'start': 32 | case 'cancel': 33 | result += (method + token) 34 | method = ''; 35 | break; 36 | case 'method': 37 | method += token; 38 | break; 39 | case 'end': 40 | result = result.substr(0, result.length - SYMBOL.length) 41 | result += `_dep_${method}${token}` 42 | methods.push(method.trim()); 43 | method = ''; 44 | break; 45 | default: 46 | throw new Error(`The state: '${state}' is not defined.`) 47 | } 48 | }) 49 | 50 | forEachRight(uniq(methods), (item) => { 51 | if (item) { 52 | result = `var _dep_${item} = _dep.${item};\n` + result 53 | } 54 | }) 55 | return result 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/parsers/for.ts: -------------------------------------------------------------------------------- 1 | import {Parser} from 'et-parser' 2 | 3 | const parser = new Parser(` 4 | | ignore | item | indexPre | index | indexEnd | expression | trackBy | _str1 | _str2 5 | -------------------- | ------ | -------- | -------- | -------- | -------- | ---------- | ------- | ----- | ----- 6 | :[\\s] | ignore | indexPre | indexPre | indexEnd | indexEnd | expression | trackBy | | 7 | :[,;] | error1 | indexPre | indexPre | index | error2 | expression | trackBy | | 8 |  in  | ::: | ignore:expression | ignore:expression | ignore:expression | ignore:expression | expression | trackBy | | 9 |  track by  | ::: | ::: | ::: | ::: | error2 | ignore:trackBy | trackBy | | 10 | ' | item | item | index | index | error2 | _str1 | trackBy | _ | 11 | " | item | item | index | index | error2 | _str2 | trackBy | | _ 12 | \\' | item | item | index | index | error2 | expression | trackBy | | 13 | \\" | item | item | index | index | error2 | expression | trackBy | | 14 | | item | item | index | index | error2 | expression | trackBy | | 15 | `) 16 | 17 | export function parseFor (source: string) { 18 | let item = ''; 19 | let index = ''; 20 | let expression = ''; 21 | let trackBy = ''; 22 | 23 | parser.parse(source, (state, token) => { 24 | switch (state) { 25 | case 'ignore': 26 | case 'indexPre': 27 | case 'indexEnd': 28 | break; 29 | case 'item': 30 | item += token; 31 | break; 32 | case 'index': 33 | index += token; 34 | break; 35 | case '_str1': 36 | case '_str2': 37 | case 'expression': 38 | expression += token; 39 | break; 40 | case 'trackBy': 41 | trackBy += token; 42 | break; 43 | default: 44 | throw new Error(`parse_for_${state}`) 45 | } 46 | }) 47 | 48 | if (!index) index = '_index'; 49 | return {item, index, expression, trackBy} 50 | } 51 | -------------------------------------------------------------------------------- /src/parsers/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './dep' 3 | export * from './for' 4 | export * from './wrap' 5 | 6 | export * from './attributes' 7 | export * from './origin' 8 | export * from './value' 9 | 10 | -------------------------------------------------------------------------------- /src/parsers/origin/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Parser} from 'et-parser' 3 | import {NODE_STATE} from './origin-state' 4 | import {Node} from './origin-node' 5 | import {translateNode} from './origin-helper' 6 | 7 | const parser = new Parser(` 8 | | text | start | name | source | tail | etStart | etName | etSource | etTail | _str[ | _str{{ | _str1 | _str2 | _ignore 9 | ------ | ---- | ----- | ---- | ------ | ---- | ------- | ------ | -------- | ------ | ----- | ------ | ----- | ----- | -------- 10 | < | start | | | source | | | | etSource | | | | | | 11 | | text | | nodeEnd:text | nodeEnd:text | tailEnd:text | | | etSource | | | | | | 13 | /> | text | | nodeEnd:text | nodeEnd:text | tailEnd:text | | | etSource | | | | | | 14 | [# | etStart | | | etStart | | | | etSource | | | | | | 15 | [/# | etTail | | | source | | | | etSource | | | | | | 16 | ] | text | | | source | | | etEnd:text | etEnd:text | etTailEnd:text | _ | | | | 17 | [ | text | | | source | | | | _str[ | | _str[ | | | | 18 | ' | text | | | _str1 | | | | _str1 | | | | _ | | 19 | " | text | | | _str2 | | | | _str2 | | | | | _ | 20 | {{ | _str{{ | | | _str{{ | | | | etSource | | | | | | 21 | \\' | text | | | source | | | | etSource | | | | | | 22 | \\" | text | | | source | | | | etSource | | | | | | 23 | }} | text | | | source | | | | etSource | | | _ | | | 24 | :[\\s] | text | | nameEnd:source | source | | | etNameEnd:etSource | etSource | | | | | | 25 | :[\\S] | text | name | name | source | tail | etName | etName | etSource | etTail | | | | | 26 | | text | | | source | | | | etSource | | | | | | _ 28 | | text | | | source | | | | etSource | | | | | | 29 | `) 30 | 31 | // 1. token归属到 nodeName header tail 中去 32 | // 2. 当前节点几个状态 [header, body, closed] 33 | // 3. html = header + 子辈html + tail 34 | export function parseOrigin (source: string) { 35 | let root = new Node() 36 | let current = root 37 | let tail = '' 38 | 39 | parser.parse(source, (state: string, token: string, index: number) => { 40 | switch (state) { 41 | case '_ignore': 42 | break 43 | case 'text': 44 | case '_str[': 45 | case '_str{{': 46 | case '_str1': 47 | case '_str2': 48 | case 'name': 49 | case 'source': 50 | case 'etName': 51 | case 'etSource': 52 | current.addSource(token) 53 | break 54 | case 'start': 55 | current = current.createChild() 56 | current.addSource(token) 57 | current.state = 1 58 | break 59 | case 'etStart': 60 | current = current.createChild() 61 | current.addSource(token) 62 | current.nodeName = '#' 63 | current.state = NODE_STATE.NodeName 64 | break 65 | case 'nameEnd': 66 | case 'etNameEnd': 67 | current.state = NODE_STATE.Source 68 | current.addSource(token) 69 | break 70 | case 'nodeEnd': 71 | case 'etEnd': 72 | current.state = NODE_STATE.Header 73 | current.addSource(token) 74 | current.state = NODE_STATE.Body 75 | break 76 | case 'tail': 77 | case 'etTail': 78 | tail += token 79 | break 80 | case 'tailEnd': 81 | current = current.closeNode(tail + token) 82 | tail = '' 83 | break 84 | case 'etTailEnd': 85 | current = current.closeNode(tail + token) 86 | tail = '' 87 | if (current.state === NODE_STATE.Source) { 88 | // 如果关闭之后的父节点头还没有读完 89 | // 证明这是一个从中间分离出去的 90 | current.saveExpressions() 91 | return 'source' 92 | } 93 | break 94 | default: 95 | throw new Error(`The state: '${state}' is not defined.`) 96 | } 97 | }) 98 | root.closeNode('') 99 | return translateNode(root, true) 100 | } 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/parsers/origin/origin-helper.ts: -------------------------------------------------------------------------------- 1 | 2 | import {each} from 'lodash' 3 | import {OriginNode} from '../../nodes/origin' 4 | import {Node} from './origin-node' 5 | 6 | // help methods to translate node 7 | export function getHtml (node: Node) { 8 | let html = '' 9 | each(node.children, (child) => { 10 | if (child.isTextNode) { 11 | html += child.source.trim() 12 | } else { 13 | html += child.header 14 | html += getHtml(child) 15 | html += child.tail 16 | } 17 | }) 18 | return html 19 | } 20 | 21 | export function translateChildren (parent: OriginNode, children: Node[]) { 22 | let resuts = new Array() 23 | let last = null 24 | 25 | each(children, (child) => { 26 | if (!child.nodeName && !child.source.trim()) { 27 | return 28 | } 29 | let current = translateNode(child) 30 | current.parent = parent 31 | current.previous = last 32 | if (last) last.next = current 33 | 34 | resuts.push(current) 35 | last = current 36 | }) 37 | return resuts 38 | } 39 | 40 | export function translateNode (node: Node, isRoot?: boolean) { 41 | let result = new OriginNode() 42 | result.nodeName = node.nodeName 43 | result.source = node.source.trim() 44 | result.header = node.header 45 | result.html = getHtml(node) 46 | result.tail = node.tail 47 | result.children = translateChildren(result, node.children) 48 | result.expressions = translateChildren(result, node.expressions) 49 | 50 | if (!isRoot) { 51 | let nodeName = result.nodeName 52 | if (!nodeName) { 53 | result.nodeType = 3 // text 54 | } else if (nodeName.indexOf('#') === 0) { 55 | result.nodeType = 'ET' 56 | } else if (nodeName === '