text: 
  const babylon = require('babylon');
  const t = require('@babel/types');
  const traverse = require('@babel/traverse')['default'];
  const generate = require('@babel/generator')['default'];
  const {
    tagMap
  } = require('../common/cml-map.js')
  const utils = require('./utils');
  
  exports.startCallback = function(matchStart, type, options) {
    let usingComponents = options.usingComponents || [];
    let buildInComponents = options.buildInComponents || {};
    if (type === 'alipay') {
      let isComponent = usingComponents.find((comp) => comp.tagName === matchStart.tagName) || Object.keys(buildInComponents).includes(matchStart.tagName);
      let leftAttrsOnComponent = matchStart.attrs;// 遗留在组件上的属性,默认值是所有属性，如果是组件，那么还需要过滤
      if (isComponent) { // 如果是组件，那么需要将组件的c-if c-else去掉
        let filtersAttrs = ['c-if', 'c-else', 'c-else-if', 'v-if', 'v-else', 'v-else-if']
        leftAttrsOnComponent = matchStart.attrs.filter((attr) => !filtersAttrs.includes(attr[1]))
      }
  
      let attrString = (leftAttrsOnComponent || []).reduce((result, item) => result = result + (item[0] || ''), '')
      return attrString;
    }
  }
  
  /*
  @description:因为阿里的组件上的样式会被直接忽略掉，所以编译层要做一层标签的包裹处理；
  注意只处理alipay端的组件，其他的不作处理
  */
  exports.preParseAliComponent = function(source, type, options) {
    if (type === 'alipay') {
      let usingComponents = options.usingComponents || [];
      let buildInComponents = options.buildInComponents || {};
      let exceptTags = ['carousel-item', 'c-tab-item', 'checkbox', 'radio'];// 用于包括哪些组件标签不用被view标签包裹
      let callbacks = {startCallback: exports.startCallback};
      let htmlArr = exports.preParseHTMLtoArray(source, type, options, callbacks);
      let newHtmlArr = [];
      htmlArr.forEach((item) => {
        let isExpectTags = exceptTags.includes(item.tagName)
        if (item.type === 'tagContent') { // 标签内置直接push内容
          newHtmlArr.push(item.content);
        }
        if (item.type === 'tagEnd') { // 结束标签的话，先将该标签的内容push,然后判断是否是组件
          newHtmlArr.push(item.content);
          let isComponent = usingComponents.find((comp) => comp.tagName === item.tagName) || Object.keys(buildInComponents).includes(item.tagName);
          if (isComponent && !isExpectTags) {
            newHtmlArr.push('</view>');
          }
        }
        if (item.type === 'tagStart') {
          // 先 push view标签，然后再push组件标签
          let isComponent = usingComponents.find((comp) => comp.tagName === item.tagName) || Object.keys(buildInComponents).includes(item.tagName);
          let inheritNodes = (item.attrs || []).filter((attr) => {
            let inheritAttrsFromComp = ['c-if', 'c-else', 'c-else-if', 'v-if', 'v-else', 'v-else-if', 'class', 'style', 'v-bind:style', 'v-bind:class', ':style', ':class', 'c-show', 'v-show', 'id'];
            // eslint-disable-next-line
            let inheritEvent = ['c-bind:click', 'c-bind:tap', 'c-bind:touchstart', 'c-bind:touchmove', 'c-bind:touchend', 'c-bind:touchcancel', 'c-catch:click', 'c-catch:tap', 'c-catch:touchstart', 'c-catch:touchmove', 'c-catch:touchend', 'c-catch:touchcancel'];
            let isInherit = inheritAttrsFromComp.includes(attr[1]) || inheritEvent.includes(attr[1]) || /^data-/.test(attr[1])
            return isInherit;
          });
          let inheritString = inheritNodes.reduce((result, styleClassNode) => result = result + (styleClassNode[0] || ''), '');
          if (isComponent && !isExpectTags) { // 如果是组件需要从组件继承一些属性过来
            if (!item.isunary) { // 如果不是一元标签，那么只在该标签前面push一个view
              newHtmlArr.push(`<view ${inheritString} >`);
              newHtmlArr.push(item.content);
            }
            if (item.isunary) { // 如果是一元标签，那么在该标签前后都要push view
              newHtmlArr.push(`<view ${inheritString} >`);
              newHtmlArr.push(item.content);
              newHtmlArr.push('</view>')
            }
          } else { // 不是内置组件直接push
            newHtmlArr.push(item.content)
          }
        }
      });
      return newHtmlArr.join('')
  
    }
    return source;
  }
  
  /* @description:提供一个将模板转化为数组的方法，以后各个端如果对模板的处理在jsx中做不了的话，可以通过处理这个数组进行解决
   解析的数组元素分为三种类型 tagStart  tagEnd  和 tagContent,其中tagStart有可能是自闭标签
   @params:html模板
   @return:html模板对应的字符串
   */
  exports.preParseHTMLtoArray = function(html, type, options, callbacks) {
    let {startCallback} = callbacks;
    // 需要考虑问题 单标签和双标签
    let stack = [];
    // id="value" id='value'  class=red    disabled
    const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
    const ncname = '[a-zA-Z_][\\w\\-\\.]*'
    const qnameCapture = `((?:${ncname}\\:)?${ncname})`
    // 标签的匹配，这些正则都不是g这种全局匹配，所以仅仅会匹配第一个遇到的标签；
    // const startTag = new RegExp(`^<${qnameCapture}([\\s\\S])*(\\/?)>`);
    // const startTag = /^<([a-zA-Z-:.]*)[^>]*?>/;
    const startTagOpen = new RegExp(`^<${qnameCapture}`) // 匹配开始open
    const startTagClose = /^\s*(\/?)>/ // 匹配开始关闭；单标签的关闭有两种情况，第一就是 > 第二个就是 />,可以通过捕获分组 / 来判断是单闭合标签还是双开标签的开始标签的闭合
    const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);
    // eslint-disable-next-line
    let index = 0;
    while (html) {
      let textEnd = html.indexOf('<')
      // 解析标签内容，包括开始标签以及结束标签
      if (textEnd === 0) { // 以 < 开头的html
        const startTagMatch = parseStartTag();
        if (startTagMatch) {
          stack.push(startTagMatch);
          continue;
        }
        const endTagMatch = parseEndTag();
        if (endTagMatch) {
          stack.push(endTagMatch);
          continue;
        }
      }
      // 解析标签中间的内容
      let text, rest, next
      if (textEnd >= 0) {
        rest = html.slice(textEnd)
        while (
          !endTag.test(rest) &&
            !startTagOpen.test(rest)
        ) {
          // < in plain text, be forgiving and treat it as text
          next = rest.indexOf('<', 1)
          if (next < 0) {break}
          textEnd += next
          rest = html.slice(textEnd)
        }
        let matchText = {
          type: 'tagContent'
        };
        text = html.substring(0, textEnd)
        matchText.content = text;
        matchText.isText = true;
        stack.push(matchText);
        advance(textEnd);
        continue;
      }
      if (textEnd < 0) {
        text = html;
        html = '';
        const matchText2 = {
          type: 'tagContent',
          content: text
        }
        stack.push(matchText2)
        continue;
  
      }
    }
    return stack;
    function advance (n) {
      index += n
      html = html.substring(n)
    }
    function parseStartTag () {
      // 开始标签也可能是一元标签 通过isunary字段进行区分
      const start = html.match(startTagOpen)
      if (start) {
        const matchStart = {
          type: 'tagStart',
          tagName: start[1],
          attrs: []
        }
        advance(start[0].length);
  
        let end, attr
        // 这里处理标签的属性值；
        while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
          advance(attr[0].length)
          matchStart.attrs.push(attr)
        }
        if (end) {
          matchStart.isunary = !!utils.trim(end[1] || '');// 标记是否是一元标签
          advance(end[0].length)
          let attrString = startCallback(matchStart, type, options) || '';
          let content ;
          if (matchStart.isunary) {
            content = `<${matchStart.tagName} ${attrString} />`
          } else {
            content = `<${matchStart.tagName} ${attrString} >`
          }
          matchStart.content = content;// 每个数组中这个值用于拼接；
          return matchStart
        }
      }
    }
    function parseEndTag() {
      const end = html.match(endTag);
      if (end) {
        const matchEnd = {
          type: 'tagEnd',
          tagName: end[1],
          content: end[0]
        }
        advance(end[0].length)
        return matchEnd;
      }
    }
  
  }
  // 注意，所有要再次使用babylon解析html模板的，必须再次将模板转化为符合jsx语法
  exports.preParseDiffPlatformTag = function(htmlContent, type) {
    let activeTags = tagMap.diffTagMap[type] || [];
    let deadTags = []
    Object.keys(tagMap.diffTagMap).forEach(key => {
      if (key !== type) {
        deadTags = deadTags.concat(tagMap.diffTagMap[key])
      }
    })
  
    activeTags.forEach(tag => {
      htmlContent = exports.activeTagHandler(tag, htmlContent);
    })
    deadTags.forEach(tag => {
      htmlContent = exports.deadTagHandler(tag, htmlContent);
    })
    return htmlContent
  }
  exports.deadTagHandler = function(tag, content) {
    var contentReg = new RegExp(`<\\s*${tag}[\\s\\S]*?>[\\s\\S]*?<\\s*\\/\\s*${tag}[\\s\\S]*?>`, 'ig')
    content = content.replace(contentReg, '')
    return content
  }
  exports.activeTagHandler = function (tag, content) {
    let startreg = new RegExp(`<\\s*${tag}[\\s\\S]*?>`, 'ig')
    let endreg = new RegExp(`<\\s*\\/\\s*${tag}[\\s\\S]*?>`, 'ig')
  
    content = content.replace(startreg, '')
    content = content.replace(endreg, '')
  
    return content;
  }
  
  // 模板前置处理器
  // 预处理:属性  :name="sth" ==> v-bind:name="sth",因为jsx识别不了 :name="sth"
  exports.preParseBindAttr = function (content) {
    content = content.replace(/(\s+):([a-zA-Z_\-0-9]+?\s*=\s*["'][^"']*?["'])/ig, (m, $1, $2) => `${$1}v-bind:${$2}`);
    return content;
  }
  
  /**
   * 处理vue的事件
   * <a v-on:click="doSomething"> ... </a>
   * <a @click="doSomething"> ... </a>
   *
   * <a bindclick="doSomething"> ... </a>
   * @param {*} content
   */
  
  exports.preParseVueEvent = function (content) {
    //         v-on | @--> <--  属性名  --><--=-->
    let reg = /(?:v\-on:|@)([^\s"'<>\/=]+?)\s*=\s*/g
    content = content.replace(reg, (m, $1) => {
      if (typeof $1 === 'string' && $1.endsWith('.stop')) {
        $1 = $1.replace('.stop', '');
        return `c-catch:${$1}=`;
      } else {
        return `c-bind:${$1}=`
      }
    });
    return content;
  }
  // 处理 {{}} 里面的 >  < ==>
  exports.preParseGtLt = function(content) {
    let reg = /{{([\s\S]*?)}}/g;
    return content.replace(reg, function(match) {
      return exports._operationGtLt(match);
    })
  }
  // 预处理 标签内的 {{item.id}} 这种语法jsx无法识别，转化为 _cml{item.id}lmc_
  exports.preParseMustache = function (content) {
    let reg = />([\s\S]*?)<[a-zA-Z\/\-_]+?/g;
    return content.replace(reg, function (match, key) {
      return exports._operationMustache(match);
    })
  }
  exports.preDisappearAnnotation = function (content) {
    let annotionReg = /<!--[\s\S]*?-->/g;
    return content.replace(annotionReg, function (match) {
      return '';
    })
  }
  // 将模板预处理符合jsx解析规则，比如 : {{}} 等
  exports.preParseTemplateToSatisfactoryJSX = function(source, callbacks) {
    // 预处理html模板中的注释，jsx不支持，这个需要优先处理，防止解析 < > 的时候出现问题；
    callbacks.forEach((callback) => {
      source = exports[callback](source);
    })
    return source;
  }
  exports.preParseAnimation = function(source, type) {
    // 这个只在小程序端增加callback;
    // if (type === 'wx' || type === 'alipay' || type === 'baidu' || type === 'qq') {
    let miniAppType = ['wx', 'alipay', 'baidu', 'qq', 'tt'];
    if (miniAppType.includes(type)) {
      let callbacks = ['preDisappearAnnotation', 'preParseGtLt', 'preParseBindAttr', 'preParseVueEvent', 'preParseMustache', 'postParseLtGt']
      source = exports.preParseTemplateToSatisfactoryJSX(source, callbacks);
      const ast = babylon.parse(source, {
        plugins: ['jsx']
      })
      traverse(ast, {
        enter(path) {
          let node = path.node;
          if (t.isJSXAttribute(node) && (node.name.name === 'c-animation' || node.name.name === 'v-animation')) {
            let value = utils.trimCurly(node.value.value).trim();
            path.insertAfter(t.jsxAttribute(t.jsxIdentifier('c-bind:transitionend'), t.stringLiteral(`_animationCb('${value}',$event)`)))
          }
        }
      });
      // 这里注意，每次经过babel之后，中文都需要转义过来；
      return exports.postParseUnicode(generate(ast).code);
    }
    return source;
  
  }
  // 语法检查：这个不参与真正的模板编译；
  // vue语法不能写c-bind {{}} c-show  c-if c-model c-text  c-animation c-for
  // cml语法不能写 @     :    v-show  v-if v-model v-text v-animation v-for
  exports.preParseEventSyntax = function (content) {
    let reg = /(?:v\-on:|@)([^\s"'<>\/=]+?)\s*=\s*/g
    content = content.replace(reg, (m, $1) => {
      if (typeof $1 === 'string' && $1.endsWith('.stop')) {
        $1 = $1.replace('.stop', '');
        $1 = $1 === 'click' ? 'tap' : $1;
        return `v-on:${$1}=`;
      } else {
        $1 = $1 === 'click' ? 'tap' : $1;
        return `v-on:${$1}=`
      }
    });
    return content;
  }
  // 仅仅对跨端组件进行语法校验：不能是 .web.cml   .weex.cml   .alipay.cml  .wx.cml  .baidu.cml结尾的
  exports.preCheckTemplateSyntax = function(source, type, options) {
    let {lang, filePath} = options;
    // 多态组件不进行语法校验
    let polymorphicCompSuffix = `.${type}.cml`;
    let crossPlatformSuffix = '.cml';
    let ispolymorphicComp = filePath.endsWith(polymorphicCompSuffix);
    // 跨端组件肯定不能是 .web.cml   .weex.cml   .alipay.cml  .wx.cml  .baidu.cml结尾的
    let iscrossPlatform = !ispolymorphicComp && filePath.endsWith(crossPlatformSuffix);
  
    let errorInfo
    if (lang === 'vue' && iscrossPlatform) {
      try {
        errorInfo = exports.preCheckTemplateSyntaxForVue(source, type, options)
  
      } catch (e) {
        errorInfo = 'vue syntax error '
      }
    }
    if (lang === 'cml' && iscrossPlatform) {
      try {
        errorInfo = exports.preCheckTemplateSyntaxForCml(source, type, options)
  
      } catch (e) {
        errorInfo = 'cml syntax error '
      }
    }
    return errorInfo
  }
  exports.preCheckTemplateSyntaxForVue = function(source, type, options) {
    let {lang} = options;
    if (lang === 'vue') {
      let callbacks = ['preDisappearAnnotation', 'preParseEventSyntax', 'preParseGtLt', 'preParseBindAttr', 'preParseMustache', 'postParseLtGt']
      source = exports.preParseTemplateToSatisfactoryJSX(source, callbacks);
      let errorInfo = '';
      let directiveError = []; let twoWayBindError; let eventBindingError;
      let disabledDirective = ['c-if', 'c-else-if', 'c-else', 'c-show', 'c-text', 'c-model', 'c-animation', 'c-for']
      const ast = babylon.parse(source, {
        plugins: ['jsx']
      })
      traverse(ast, {
        enter(path) {
          let node = path.node;
          if (directiveError.length <= disabledDirective.length && t.isJSXAttribute(node) && disabledDirective.includes(node.name.name)) {
            errorInfo += `${node.name.name} can't be used with vue syntax ;`
            !directiveError.includes(node.name.name) && directiveError.push(directiveError)
          }
          if (!twoWayBindError && t.isJSXAttribute(node) && node.value && utils.isMustacheReactive(node.value.value)) {
            errorInfo += '<div id={{value}}></div> can not be used with vue syntax,please use <div v-bind:id="value"></div> 或者 <div :id="value"></div> '
            twoWayBindError = true;
          }
          if (!eventBindingError && t.isJSXNamespacedName(node.name) && node.name.namespace.name === 'c-bind') {
            errorInfo += "with vue syntax you can not use 'c-bind' to get event binded , please use  @ or v-on;"
            eventBindingError = true
          }
        }
      });
      return errorInfo
    }
  
  }
  exports.preCheckTemplateSyntaxForCml = function(source, type, options) {
    let {lang} = options;
    if (lang === 'cml') {
      let callbacks = ['preDisappearAnnotation', 'preParseEventSyntax', 'preParseGtLt', 'preParseBindAttr', 'preParseMustache', 'postParseLtGt']
      source = exports.preParseTemplateToSatisfactoryJSX(source, callbacks);
      let errorInfo = '';
      let directiveError = []; let twoWayBindError; let eventBindingError;
      let disabledDirective = ['v-if', 'v-else-if', 'v-else', 'v-show', 'v-text', 'v-model', 'v-animation', 'v-for']
      const ast = babylon.parse(source, {
        plugins: ['jsx']
      })
      traverse(ast, {
        enter(path) {
          let node = path.node;
          if (directiveError.length <= disabledDirective.length && t.isJSXAttribute(node) && disabledDirective.includes(node.name.name)) {
            errorInfo += `${node.name.name} can't be used with cml syntax ;`
            !directiveError.includes(node.name.name) && directiveError.push(directiveError)
          }
          if (!twoWayBindError && t.isJSXNamespacedName(node.name) && node.name.namespace.name === 'v-bind') {
            errorInfo += '<div v-bind:id="value"></div> 或者 <div :id="value"></div> can not be used with cml syntax,please use  <div id={{value}}></div> '
            twoWayBindError = true;
          }
          if (!eventBindingError && t.isJSXNamespacedName(node.name) && node.name.namespace.name === 'v-on') {
            errorInfo += "with cml syntax you can not use @ or v-on  to get event binded , please use  'c-bind';"
            eventBindingError = true;
          }
        }
      });
      return errorInfo
    }
  
  }
  // 模板后置处理器
  exports.postParseMustache = function (content) {
    let reg = />([\s\S]*?)<[a-zA-Z\/\-_]+?/g;
    return content.replace(reg, function (match, key) {
      return exports._deOperationMustache(match);
    })
  }
  exports.postParseLtGt = function(content) {
    let reg = /{{([\s\S]*?)}}/g;
    return content.replace(reg, function(match) {
      return exports._deOperationGtLt(match);
    })
  }
  exports.postParseUnicode = function(content) {
    let reg = /\\u/g;
    return unescape(content.replace(reg, '%u'));
  }
  
  /**
   * 校验 template 模板下如果有 cml 标签，则必须是第一层,且在第一层不能有其他标签；
   */
  exports.checkTemplateChildren = function(path) {
    let node = path.node;
    let children = node.children || [];
    let jsxElements = children.filter((child) => t.isJSXElement(child));
    let hasCMLTag = jsxElements.some((ele) => ele.openingElement.name.name === 'cml');
    let hasOtherTag = jsxElements.some((ele) => ele.openingElement.name.name !== 'cml');
    return {hasCMLTag, hasOtherTag, jsxElements};
  }
  
  /*
  校验 cml 标签的父元素必须是 template;
  
  */
  exports.checkCMLParent = function(path) {
    let node = path.node;
    if (node.openingElement.name.name === 'cml') {
      let parentNode = path.parentPath && path.parentPath.node;
      if (parentNode && parentNode.openingElement && parentNode.openingElement.name.name !== 'template') {
        throw new Error('模板多态标签 cml 只允许在 template 标签的第一层');
      }
    }
  }
  
  /**
   * 获取 符合多态模板结构的 template 标签下对应平台的cml标签里的内容；
   * @params:jsxElements  template 节点下所有的 元素节点标签
   * @params:type  当前平台对应的 type，wx baidu alipay 等
   */
  exports.getCurrentPlatformCML = function(jsxElements, type) {
    let currentCML = jsxElements.find((ele) => {
      let typeAttr = ele.openingElement.attributes.find((attr) => attr.name.name = 'type');
      if (!typeAttr) {
        throw new Error('cml 标签必须有 type 属性，标识用于哪端的代码')
      }
      let typeValue = typeAttr && typeAttr.value && typeAttr.value.value;
      return typeValue.includes(type)
    });
    // 有对应平台的 type 找到之后直接return 这个节点
    if (currentCML) {
      return currentCML;
    }
    let baseCML = jsxElements.find((ele) => {
      let typeAttr = ele.openingElement.attributes.find((attr) => attr.name.name = 'type');
      if (!typeAttr) {
        throw new Error('cml 标签必须有 type 属性，标识用于哪端的代码')
      }
      let typeValue = typeAttr && typeAttr.value && typeAttr.value.value;
      return typeValue.includes('base')
    });
  
    if (!currentCML && baseCML) {
      return baseCML;
    }
  
  }
  
  /*
  @source: 源 template 文件
  @type:要编译的平台
  */
  exports.postParseOriginTag = function(source, type) {
    let callbacks = ['preDisappearAnnotation', 'preParseGtLt', 'preParseBindAttr', 'preParseMustache', 'postParseLtGt'];
    source = exports.postParseUnicode(source);
    source = exports.preParseTemplateToSatisfactoryJSX(source, callbacks);
    const ast = babylon.parse(source, {
      plugins: ['jsx']
    })
    traverse(ast, {
      enter(path) {
        let node = path.node;
        if (t.isJSXElement(node) && (node.openingElement.name && typeof node.openingElement.name.name === 'string')) {
          if (node.openingElement.name.name.indexOf('origin-') === 0) {
            let currentTag = node.openingElement.name.name;
            let targetTag = currentTag.replace('origin-', '')
            node.openingElement.name.name = targetTag;
            node.closingElement && (node.closingElement.name.name = targetTag);
          }
        }
      }
    });
    // 这里注意，每次经过babel之后，中文都需要转义过来；
    return exports.postParseUnicode(generate(ast).code);
  }
  
  /* 提供给 chameleon-loader 用于删除多态模板多其他端的不用的代码
  @params:source 模板内容
  @params:type 当前要编译的平台，用于截取多态模板
  @params:options needTranJSX 需要转化为jsx可以解析的模板；needDelTemplate 需要删除template节点
  */
  exports.preParseMultiTemplate = function(source, type, options = {}) {
    try {
      if (options.needTranJSX) { // 当调用这个方法之前没有事先转义jsx,那么就需要转义一下
        let callbacks = ['preDisappearAnnotation', 'preParseGtLt', 'preParseBindAttr', 'preParseVueEvent', 'preParseMustache', 'postParseLtGt'];
        source = exports.preParseTemplateToSatisfactoryJSX(source, callbacks);
      }
      let isEmptyTemplate = false;
      const ast = babylon.parse(source, {
        plugins: ['jsx']
      })
      traverse(ast, {
        enter(path) {
          let node = path.node;
          if (t.isJSXElement(node) && (node.openingElement.name && typeof node.openingElement.name.name === 'string' && node.openingElement.name.name === 'template')) {
            path.stop();// 不要在进行子节点的遍历,因为这个只需要处理template
            let {hasCMLTag, hasOtherTag, jsxElements} = exports.checkTemplateChildren(path);
            if (hasCMLTag && hasOtherTag) {
              throw new Error('多态模板里只允许在template标签下的一级标签是cml');
            }
            if (hasCMLTag && !hasOtherTag) {// 符合多态模板的结构格式
              let currentPlatformCML = exports.getCurrentPlatformCML(jsxElements, type);
              if (currentPlatformCML) {
                currentPlatformCML.openingElement.name.name = 'view';
                // 这里要处理自闭和标签，没有closingElement，所以做个判断；
                currentPlatformCML.closingElement && (currentPlatformCML.closingElement.name.name = 'view');
                node.children = [currentPlatformCML];
                if (options.needDelTemplate) { // 将template节点替换成找到的cml type 节点；
                  path.replaceWith(currentPlatformCML)
                }
              } else {
                // 如果没有写对应平台的 cml type='xxx' 或者 cml type='base',那么报错
                throw new Error('没有对应平台的模板或者基础模板')
              }
            } else { // 不是多态模板
              // 注意要考虑空模板的情况
              if (options.needDelTemplate && jsxElements.length === 1) { // 将template节点替换成找到的cml type 节点；
                path.replaceWith((jsxElements[0]));
              } else {
                isEmptyTemplate = true;
              }
            }
          }
        }
      });
      // 这里注意，每次经过babel之后，中文都需要转义过来；
      if (isEmptyTemplate) {
        return '';
      }
      source = exports.postParseUnicode(generate(ast).code);
      if (/;$/.test(source)) { // 这里有个坑，jsx解析语法的时候，默认解析的是js语法，所以会在最后多了一个 ; 字符串；但是在 html中 ; 是无法解析的；
        source = source.slice(0, -1);
      }
      return source;
    } catch (e) {
      console.log('preParseMultiTemplate', e)
    }
  }
  // cli仓库使用
  exports.analyzeTemplate = function(source, options) {
    try {
      let callbacks = ['preDisappearAnnotation', 'preParseGtLt', 'preParseBindAttr', 'preParseVueEvent', 'preParseMustache', 'postParseLtGt'];// //这些预处理是为了让jsx可以处理
      if (!source) {
        return options;
      }
      source = exports.preParseTemplateToSatisfactoryJSX(source, callbacks);
      source = exports.preParseMultiTemplate(source, options.cmlType, {needDelTemplate: true})
      const ast = babylon.parse(source, {
        plugins: ['jsx']
      })
      traverse(ast, {
        enter(path) {
          let node = path.node;
          let buildInTagMap = options && options.buildInComponents;// {button:"cml-buildin-button"}
          if (t.isJSXElement(node) && buildInTagMap) {
            let currentTag = node.openingElement.name.name;
            let targetTag = buildInTagMap[currentTag];
            // 收集用了哪些内置组件 usedBuildInTagMap:{button:'cml-buildin-button',radio:'cml-buildin-radio'}
            if (targetTag) {
              (!options.usedBuildInTagMap) && (options.usedBuildInTagMap = {});
              options.usedBuildInTagMap[currentTag] = targetTag;
            }
          }
        }
      });
      return options;
    } catch (e) {
      console.log('analyzeTemplate', e)
    }
  }
  // 模块内置方法
  // 这里主要处理1  >{{}}< 双花括号之间的 ==> _cml{}lmc_ ,因为jsx无法识别 {{}}
  // 2 同时将 {{}}内的 _cml_gt_lmc_  _cml_lt_lmc_ 复原  < >
  exports._operationMustache = function (content) {
    let mustacheReg = /{{([\s\S]*?)}}/g
    return content.replace(mustacheReg, function (match, key) {
      key = exports._deOperationGtLt(key);
      return `_cml{${key}}lmc_`
    })
  }
  exports._deOperationMustache = function (content) {
    let deMustacheReg = /_cml{([\s\S]*?)}lmc_/g;
    return content.replace(deMustacheReg, function (match, key) {
      return `{{${key}}}`
    })
  }
  exports._operationGtLt = function(content) {
    let gtltReg = />|</g;
    return content.replace(gtltReg, function(match) {
      if (match === '>') {
        return '_cml_gt_lmc_'
      }
      if (match === '<') {
        return '_cml_lt_lmc_'
      }
      return match;
    })
  }
  exports._deOperationGtLt = function(content) {
    let deGtLtReg = /_cml_gt_lmc_|_cml_lt_lmc_/g;
    return content.replace(deGtLtReg, function(match) {
      if (match === '_cml_gt_lmc_') {
        return '>';
      }
      if (match === '_cml_lt_lmc_') {
        return '<';
      }
      return match;
    })
  }
  exports.transformNativeEvent = function(source) {
    let reg = /__CML_NATIVE_EVENTS__/g;
    return source.replace(reg, '.native');
  }
  