# template-explorer **Repository Path**: sync-github/template-explorer ## Basic Information - **Project Name**: template-explorer - **Description**: 模板引擎 - **Primary Language**: JavaScript - **License**: Not specified - **Default Branch**: master - **Homepage**: https://github.com/CharlieLau/template-explorer - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2021-11-17 - **Last Updated**: 2021-11-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # template-explorer > 每个成熟的前端框架,模板引擎是其中重要的组成部分,提升了视图的可维护性和编写效率,目前市面上的模板引擎分为两种: * string base 基于模板字符串中,加上用户数据变量或者函数,编译成view字符串,通过innerHTML填充到对应节点,借助浏览器能力翻译成具体节点。 * dom base 解析阶段和string base类似,但是强调着render之后,dom和数据间的`联动`关系,数据更新后不依赖手动操作dom来更新view # 工程搭建 安装依赖 ``` shell yarn add rollup @babel/core @babel/plugin-syntax-dynamic-import @babel/preset-env rollup-plugin-babel rollup-plugin-commonjs rollup-plugin-node-resolve rollup-plugin-serve --dev ``` rollup.config.js ```node import resolve from 'rollup-plugin-node-resolve' import babel from 'rollup-plugin-babel' import commonjs from 'rollup-plugin-commonjs' import serve from 'rollup-plugin-serve' export default { input: 'src/index.js', output: { file: './lib/template-exporer.js', format: 'esm', name: 'tExporer', sourcemap: true, }, plugins: [ resolve(), commonjs(), babel({ exclude: /node_modules/ }), process.env.SERVE ? serve({ open: true, contentBase: '', openPage: '/public/index.html', host: '127.0.0.1', port: '9999' }) : null ] } ``` # string base 大致过程 > template-> split = `lines` -> compile = function +data -> `string` -> innerHTML = dom 要实现如下的模板: ```html
<% if (test > 1 ) { %> <%= test %> <% } %>
``` 我们需要按照: ```javascript const TYPE_TAG = 1; // 标签节点 const TYPE_TEXT = 2; // 文本节点 const TYPE_VAR = 3; // 变量和语句节点 const TYPE_COM = 4; // 注释节点 ``` 类型对html进行spilt,最终的lines: ```javascript [ '
', '<% if (test > 1 ) { %>', '<%= test %>', '<% } %>', '
' ] ``` 接下来,根据不同的节点类型,来拼凑不同的解析规则,最终compile函数,大致如下: ``` javascript let code = 'const l=[];' code += 'with(obj){' code += ` l.push('
'); if (test > 1 ) { l.push(test); } l.push('
'); } return l.join('') ` // compile fn: const fn = new Function('obj',`return ${code}`) fn.call(scope) ``` # dom base 参考 Vue的渲染 > template -> parse = AST -> compile=> data + render function => vitual dom -> dom 要实现这样的模板: ``` html

{{name}}

年龄:{{age}}
``` 首先转换成AST: ``` javascript { tag:'div', type:1, attrs: { id:'app' }, children:[{ tag:'p', type:1, children:[{ type:3, text:'{{name}}' }] },{ tag:'span', type:1, children:[{ type:3, text:'年龄:' },{ type:3, text:'{{age}}' }] }] } ``` compile render函数: ```javascript // 函数体 const code = _c('div',{id:'app'},_c("p",undefined,_v('年龄:'+_s(name))),_s(age)) // 主函数 function render() { with(this) { return _c('div', { attrs: { "id": "app" } }, [_c('p', [_v(_s(name))]), _c('span', [_v("年龄:" + _s(age))])]) } } ``` 然后在`渲染Watcher` 通过render函数,解析成vnode,创建真实dom, 再次视图更新通过dom diff部分更新dom节点: ``` _c :createElement _v: createTextNode _s: stringify ``` dom diff Vue的diff算法是基于snabbdom改造过来的,Vue的diff算法是`同级的vnode间做diff`,递归地进行同级vnode的diff,最终实现整个DOM树的更新. patch函数会维护oldStart+oldEnd,newStart+newEnd这样2对指针,分别对应oldVdom和newVdom的起点和终点。起止点之前的节点是待处理的节点,Vue不断对vnode进行处理同时移动指针直到其中任意一对起点和终点相遇。处理过的节点Vue会在oldVdom和newVdom中同时将它标记为已处理。Vue通过以下措施来提升diff的性能。 参考: * https://johnresig.com/blog/javascript-micro-templating/ * https://github.com/whxaxes/mus * https://github.com/yanhaijing/template.js * https://blog.csdn.net/m6i37jk/article/details/78140159 * https://github.com/muwoo/blogs/blob/master/src/Vue/11.md