最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 模板编译的前世今生

    正文概述 掘金(涂鸦天一)   2021-02-20   528

    作者: 涂鸦-寒汀

    来涂鸦工作: job.tuya.com/


    前世

    版本年份
    html1990html41997html52014

    template 作为html5 提供的新标签,意为“模板”。

    <template>
        <h1>我是放在template标签里的模板</h1>
    </template>
    

    template标签在css中默认自带display:none,不会在页面上显示,也不会发起任何请求。

    const tpl = document.querySelector('template')
    console.log(tpl.childNodes)          //NodeList []
    console.log(tpl.content.childNodes)  //NodeList(3) [text, h1, text]
    console.log(t.nodeType)  //1 - Element
    console.log(t.content.nodeType)  //11 - DocumentFragment
    

    节点类型对照表

    序号节点类型描述子节点
    1Element代表元素Element, Text, Comment, ProcessingInstruction, CDATASection, EntityReference2Attr代表属性Text, EntityReference3Text代表元素或属性中的文本内容None4CDATASection代表文档中的 CDATA 部分(不会由解析器解析的文本)None5EntityReference代表实体引用Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference6Entity代表实体Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference7ProcessingInstruction代表处理指令None8Comment代表注释None9Document代表整个文档(DOM 树的根节点)Element, ProcessingInstruction, Comment, DocumentType10DocumentType向为文档定义的实体提供接口None11DocumentFragment代表轻量级的 Document 对象,能够容纳文档的某个部分Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference12Notation代表 DTD 中声明的符号None
    <script type="text/template">
         <h1>我是放在script标签里的模板</h1>
    </script>
    

    注:

    今生

    一、Vue template 模板编译

       <div id="app">
         <h1>我是放在vue template标签里的模板</h1>
         <my-tpl></my-tpl>
      </div>
      <template id="my-tpl">
        <h2>现在的时间是{{time}}</h2>
      </template>
    <script>
      new Vue({
        el: '#app',
        data() {
          return {
               time : Date.now()
            }
        },
        template: '#my-tpl'
     })
    </script>
    

    模板编译的前世今生 大家会想,在使用模板的时候,经常会使用一些js表达式或者一些指令等,然后在html语法中这些功能是不存在的,为何在类似Vue的模板中就可以使用呢?这就是通过模板编译实现的。 模板编译的作用就是将模板解析成渲染函数,渲染函数的作用就是生成一份vnode。

    模板编译的前世今生

    1、 模板解析(解析器)

    • 将模板解析为AST
    <div>
      <h1>{{title}}</h1>
    </div>
    

    通过vue-template-compiler@2.6.11转换后得到的AST

    {
      "type": 1,  //1 元素类型  2 变量text  3 普通文本(普通文字/空格/换行) ...
      "tag": "div",
      "attrsList": [],
      "attrsMap": {},
      "rawAttrsMap": {},
      "children": [
        {
          "type": 1,
          "tag": "h1",
          "attrsList": [],
          "attrsMap": {},
          "rawAttrsMap": {},
          "parent": "[Circular ~]",
          "children": [
            {
              "type": 2,
              "expression": "_s(title)",
              "tokens": [
                {
                  "@binding": "title"
                }
              ],
              "text": "{{title}}",
              "start": 12,
              "end": 21,
              "static": false
            }
          ],
          "start": 8,
          "end": 26,
          "plain": true,
          "static": false,
          "staticRoot": false
        }
      ],
      "start": 0,
      "end": 33,
      "plain": true,
      "static": false,
      "staticRoot": false
    }
    
    解析器具体分为以下几种类型

    Vue框架主要通过complier/parser目录下三个文件完成 html-parser.js text-parser.js filter-parser.js

    主要思路是利用了栈(stack)的先进后出/后进先出的特性,完成对模板的解析工作。

    //complier/parser/index.js
    const stack = []
    parseHTML(template, {
        start (tag, attrs, unary, start, end) {
          stack.push(element)
        },
        end (tag, start, end) {
          const element = stack[stack.length - 1]
          // pop stack
          stack.length -= 1
          closeElement(element)
        },
        chars (text: string, start: number, end: number) {},
        comment (text: string, start, end) {}
    })
    
    export function parseHTML(html, options) {
      const stack = [];
      const expectHTML = options.expectHTML;
      const isUnaryTag = options.isUnaryTag || no;
      const canBeLeftOpenTag = options.canBeLeftOpenTag || no;
      let index = 0;
      let last, lastTag;
      while (html) {
          if (options.chars && text) {
            options.chars(text, index - text.length, index);
          }
          // End tag:
          const endTagMatch = html.match(endTag);
          parseEndTag(endTagMatch[1], curIndex, index);
          // Start tag:
          const startTagMatch = parseStartTag();
          handleStartTag(startTagMatch);
      }
    
      function handleStartTag(match) {
        if (options.start) {
          options.start(tagName, attrs, unary, match.start, match.end);
        }
      }
    
      function parseEndTag(tagName, start, end) {
        options.end(tagName, start, end);
      }
    }
    
    

    至此完成ASTElement对象的生成

    2、模板优化(优化器)

    • 递归遍历AST标记静态节点
    optimize(ast, options)
    
    //complier/optimizer.js
    function markStatic (node: ASTNode) {
      node.static = isStatic(node)
      if (node.type === 1) {
        // do not make component slot content static. this avoids
        // 1. components not able to mutate slot nodes
        // 2. static slot content fails for hot-reloading
        if (
          !isPlatformReservedTag(node.tag) &&
          node.tag !== 'slot' &&
          node.attrsMap['inline-template'] == null
        ) {
          return
        }
        for (let i = 0, l = node.children.length; i < l; i++) {
          const child = node.children[i]
          markStatic(child)
          if (!child.static) {
            node.static = false
          }
        }
        if (node.ifConditions) {
          for (let i = 1, l = node.ifConditions.length; i < l; i++) {
            const block = node.ifConditions[i].block
            markStatic(block)
            if (!block.static) {
              node.static = false
            }
          }
        }
      }
    }
    
    function markStaticRoots (node: ASTNode, isInFor: boolean) {
      if (node.type === 1) {
        if (node.static || node.once) {
          node.staticInFor = isInFor
        }
        // For a node to qualify as a static root, it should have children that
        // are not just static text. Otherwise the cost of hoisting out will
        // outweigh the benefits and it's better off to just always render it fresh.
        if (node.static && node.children.length && !(
          node.children.length === 1 &&
          node.children[0].type === 3
        )) {
          node.staticRoot = true
          return
        } else {
          node.staticRoot = false
        }
        if (node.children) {
          for (let i = 0, l = node.children.length; i < l; i++) {
            markStaticRoots(node.children[i], isInFor || !!node.for)
          }
        }
        if (node.ifConditions) {
          for (let i = 1, l = node.ifConditions.length; i < l; i++) {
            markStaticRoots(node.ifConditions[i].block, isInFor)
          }
        }
      }
    }
    

    递归标记static / staticRoot的过程

    function isStatic (node: ASTNode): boolean {
      if (node.type === 2) { // expression
        return false
      }
      if (node.type === 3) { // text
        return true
      }
      return !!(node.pre || (
        !node.hasBindings && // no dynamic bindings
        !node.if && !node.for && // not v-if or v-for or v-else
        !isBuiltInTag(node.tag) && // not a built-in
        isPlatformReservedTag(node.tag) && // not a component
        !isDirectChildOfTemplateFor(node) &&
        Object.keys(node).every(isStaticKey)
      ))
    }
    

    当模板被解析器解析成AST时,会根据不同的元素类型设置不同的type值。 type: 1 元素节点 type: 2 带变量的动态文本节点 type: 3 不带变量的纯文本节点 当type为3时,很好理解必然是静态节点,当type为1时说明是一个元素节点,此时判断稍有复杂。当有v-pre即可判断是一个静态节点,否则就必须满足以下条件才会判定是一个静态节点。

    function isDirectChildOfTemplateFor (node: ASTElement): boolean {
      while (node.parent) {
        node = node.parent
        if (node.tag !== 'template') {
          return false
        }
        if (node.for) {
          return true
        }
      }
      return false
    }
    
    

    3、代码生成(代码生成器)

    • 使用AST生成渲染函数,编译的最后就是把优化的AST树转换成可执行的代码
    const compiler = require("vue-template-compiler");
    const info = compiler.compile("<div></div>");
    

    render: "with(this){return _c('div')}", _c 函数定义在 src/core/instance/render.js

    vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
    

    模板编译的前世今生 jscode 转换流程 ?

    入口文件,源码如下:src/compiller/index.js

    /* @flow */
    
    import { parse } from './parser/index'
    import { optimize } from './optimizer'
    import { generate } from './codegen/index'
    import { createCompilerCreator } from './create-compiler'
    
    // `createCompilerCreator` allows creating compilers that use alternative
    // parser/optimizer/codegen, e.g the SSR optimizing compiler.
    // Here we just export a default compiler using the default parts.
    export const createCompiler = createCompilerCreator(function baseCompile (
      template: string,
      options: CompilerOptions
    ): CompiledResult {
      const ast = parse(template.trim(), options)
      if (options.optimize !== false) {
        optimize(ast, options)
      }
      const code = generate(ast, options)
      return {
        ast,
        render: code.render,
        staticRenderFns: code.staticRenderFns
      }
    })
    

    二、模板引擎

    通常我们在赋值界面新的数据的时候,经常会以下方式实现,这也是最原始的实现。看似没什么问题,但如果数据很多很复杂的情况下,通过字符串拼接的模式就会显得非常麻烦,累赘,最后一定苦不堪言。

    const name = "peter"
    document.body.innerHTML = "<h1>My name is "+name+"</h1>"
    

    随着前端应用变得日益复杂的背景下,数据与界面分离的必要性越来越大,很多JS的模板引擎因此而生。如用模板引擎实现方式,代码如下:

    <script id="tpl" type="text/template">
      <h1>My name is <%= name %></h1>
    </script>
    <script>
    const tpl = document.getElementById('tpl').innerHTML;
    template(tpl, {name: "peter"});  //template模板引擎函数
    
    function template(dom, data) {
      // do something
     //返回拼接好的字符串
    } 
    </script>
    

    模板引擎函数就是通过一系列解析拼接过程,返回一个可执行的渲染函数,主要步骤具体如下: 1、模板获取 2、将DOM结构与js变量、表达式等分离,词法分析生成AST 3、组装完成的字符串通过Function生成动态HTML代码

    目前市面上已经出了有很多类型的模板引擎,性能对比如图所示:dark_sunglasses:

    模板编译的前世今生

    baiduTemplate:  baidufe.github.io/BaiduTempla…

    artTemplate: github.com/aui/artTemp…

     juicer:github.com/PaulGuo/Jui…

    doT:olado.github.com/doT/

    tmpl:github.com/BorisMoore/…

    handlebars:handlebarsjs.com

    easyTemplate:github.com/qitupstudio…

    underscoretemplate: underscorejs.org/

    mustache:github.com/janl/mustac…

    kissytemplate:github.com/ktmud/kissy


    来涂鸦工作: job.tuya.com/


    起源地下载网 » 模板编译的前世今生

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元