generate.js 5.1 KB
Newer Older
fxy060608's avatar
fxy060608 已提交
1 2 3 4
const {
  hasOwn
} = require('../util')

fxy060608's avatar
fxy060608 已提交
5 6 7 8
const {
  SELF_CLOSING_TAGS,
  INTERNAL_EVENT_LINK
} = require('../constants')
d-u-a's avatar
d-u-a 已提交
9
const uniI18n = require('@dcloudio/uni-cli-i18n')
fxy060608's avatar
fxy060608 已提交
10 11 12 13

function processElement (ast, state, isRoot) {
  const platformName = state.options.platform.name
  // <template slot="f"></template>
fxy060608's avatar
fxy060608 已提交
14
  if (ast.type === 'template' && hasOwn(ast.attr, 'slot')) {
fxy060608's avatar
fxy060608 已提交
15 16 17
    ast.type = 'view'
  }

18 19 20 21 22 23 24
  // 由于小程序端 default 不等同于默认插槽,统一移除 default 命名
  if (ast.type === 'slot' && hasOwn(ast.attr, 'name') && ast.attr.name === 'default') {
    delete ast.attr.name
  } else if (hasOwn(ast.attr, 'slot') && ast.attr.slot === 'default') {
    delete ast.attr.slot
  }

fxy060608's avatar
fxy060608 已提交
25 26 27
  if (hasOwn(ast.attr, 'textContent')) {
    ast.children = [ast.attr.textContent]
    delete ast.attr.textContent
fxy060608's avatar
fxy060608 已提交
28
  }
fxy060608's avatar
fxy060608 已提交
29
  if (hasOwn(ast.attr, 'innerHTML')) {
fxy060608's avatar
fxy060608 已提交
30 31 32
    ast.children = [{
      type: 'rich-text',
      attr: {
fxy060608's avatar
fxy060608 已提交
33
        nodes: ast.attr.innerHTML
fxy060608's avatar
fxy060608 已提交
34 35 36
      },
      children: []
    }]
fxy060608's avatar
fxy060608 已提交
37
    delete ast.attr.innerHTML
fxy060608's avatar
fxy060608 已提交
38 39 40
  }
  if (state.options.platform.isComponent(ast.type)) {
    if (platformName === 'mp-alipay') {
fxy060608's avatar
fxy060608 已提交
41
      ast.attr.onVueInit = INTERNAL_EVENT_LINK
fxy060608's avatar
fxy060608 已提交
42 43 44 45 46 47 48 49 50 51
    } else if (platformName !== 'mp-baidu') {
      ast.attr['bind:' + INTERNAL_EVENT_LINK] = INTERNAL_EVENT_LINK
    }

    const children = ast.children
    // default slot
    let defaultSlot = false
    const slots = []
    for (let i = children.length - 1; i >= 0; i--) {
      const childElement = children[i]
52 53 54 55 56
      /**
       * 仅百度、字节支持使用 block 作为命名插槽根节点
       * 此处为了统一仅忽略默认插槽
       * <block slot="left"></block> => <view slot="left"></view>
       */
fxy060608's avatar
fxy060608 已提交
57
      if (typeof childElement !== 'string' && childElement.attr.slot) {
58 59
        const slot = childElement.attr.slot
        if (slot && slot !== 'default' && childElement.type === 'block') {
fxy060608's avatar
fxy060608 已提交
60 61
          childElement.type = 'view'
        }
62
        slots.push(slot)
fxy060608's avatar
fxy060608 已提交
63 64 65 66 67 68 69 70 71 72 73
      } else {
        defaultSlot = true
      }
    }
    if (defaultSlot) {
      slots.push('default')
    }
    if (ast.attr.generic) {
      Object.keys(ast.attr.generic).forEach(scopedSlotName => {
        slots.push(scopedSlotName)
      })
P
panyiming.325 已提交
74 75
      if (platformName === 'mp-toutiao' || platformName === 'mp-lark') {
        // 用于字节跳动|飞书小程序模拟抽象节点
76 77 78 79
        ast.attr.generic = `{{${JSON.stringify(ast.attr.generic)}}}`.replace(/"/g, '\'')
      } else {
        delete ast.attr.generic
      }
fxy060608's avatar
fxy060608 已提交
80 81 82 83
    }
    if (slots.length && platformName !== 'mp-alipay') { // 标记 slots
      ast.attr['vue-slots'] = '{{[' + slots.reverse().map(slotName => `'${slotName}'`).join(',') + ']}}'
    }
fxy060608's avatar
fxy060608 已提交
84
    if (ast.attr.id && ast.attr.id.indexOf('{{') === 0) {
Q
qiang 已提交
85
      state.tips.add(uniI18n.__('templateCompiler.idAttribNotAllowInCustomComponentProps', { 0: ast.type }))
fxy060608's avatar
fxy060608 已提交
86
    }
87
    if (hasOwn(ast.attr, 'data') && platformName !== 'mp-toutiao') { // 百度中会出现异常情况
fxy060608's avatar
fxy060608 已提交
88 89
      // TODO 暂不输出
      // state.tips.add(`data 作为属性保留名,不允许在自定义组件 ${ast.type} 中定义为 props`)
fxy060608's avatar
fxy060608 已提交
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
    }
  }
}

function genElement (ast, state, isRoot = false) {
  if (!ast) {
    return ''
  }
  if (typeof ast === 'string') {
    return genText(ast, state)
  }

  processElement(ast, state, isRoot)

  const names = Object.keys(ast.attr)
  const props = names.length
    ? ' ' +
fxy060608's avatar
fxy060608 已提交
107 108 109 110 111 112 113 114
    names
      .map(name => {
        if (name.includes(':else')) {
          return name
        }
        if (ast.attr[name] === '' && name !== 'value') { // value属性需要保留=''
          return name
        }
115 116 117 118
        let value = ast.attr[name]
        // 微信和QQ小程序解析 {{{}}} 报错,需要使用()包裹
        value = value.replace(/(\{\{)(\{.+?\})(\}\})/, '$1($2)$3')
        return `${name}="${value}"`
fxy060608's avatar
fxy060608 已提交
119 120
      })
      .join(' ')
fxy060608's avatar
fxy060608 已提交
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
    : ''
  if (SELF_CLOSING_TAGS.includes(ast.type)) {
    return `<${ast.type}${props}/>`
  }

  let children = ast.children
    .map(child => {
      return genElement(child, state, isRoot && ast.type === 'block') // 如果根节点是 block,则继续 root
    })
    .join('')

  if (ast.scoped) { // 简单处理的 scoped slots 子节点的变量
    children = children.replace(new RegExp(ast.scoped + '.', 'g'), '')
  }
  return `<${ast.type}${props}>${children}</${ast.type}>`
}

function genText (ast, state) {
  return ast
}

142 143 144 145 146 147 148 149 150 151 152
function parsePageMeta (ast, state) {
  // 目前仅 mp-weixin 支持 page-meta
  if (state.options.platform.name === 'mp-weixin') {
    const children = ast.children
    if (Array.isArray(children) && children.find(child => child.type === 'page-meta')) {
      return children
    }
  }
  return ast
}

fxy060608's avatar
fxy060608 已提交
153
module.exports = function generate (ast, state) {
154 155
  ast = parsePageMeta(ast, state)

fxy060608's avatar
fxy060608 已提交
156 157 158 159 160 161 162 163 164
  if (!Array.isArray(ast)) {
    ast = [ast]
  }

  let code = ast.map(ast => genElement(ast, state, true)).join('')

  const replaceCodes = state.options.replaceCodes
  if (replaceCodes) {
    Object.keys(replaceCodes).forEach(key => {
fxy060608's avatar
fxy060608 已提交
165
      code = code.replace(new RegExp(key.replace('$', '\\$'), 'g'), replaceCodes[key])
fxy060608's avatar
fxy060608 已提交
166 167 168 169
    })
  }

  return code
170
}