util.js 7.9 KB
Newer Older
fxy060608's avatar
fxy060608 已提交
1 2 3
const t = require('@babel/types')
const babelTraverse = require('@babel/traverse').default
const babelGenerate = require('@babel/generator').default
d-u-a's avatar
d-u-a 已提交
4
const uniI18n = require('@dcloudio/uni-cli-i18n')
fxy060608's avatar
fxy060608 已提交
5 6

const {
7 8
  METHOD_RENDER_LIST,
  METHOD_RESOLVE_SCOPED_SLOTS
fxy060608's avatar
fxy060608 已提交
9 10
} = require('./constants')

fxy060608's avatar
fxy060608 已提交
11
function cached (fn) {
fxy060608's avatar
fxy060608 已提交
12
  const cache = Object.create(null)
fxy060608's avatar
fxy060608 已提交
13
  return function cachedFn (str) {
fxy060608's avatar
fxy060608 已提交
14 15 16 17 18 19 20 21 22 23 24 25
    const hit = cache[str]
    return hit || (cache[str] = fn(str))
  }
}
const customizeRE = /:/g
const camelizeRE = /-(\w)/g
const hyphenateRE = /\B([A-Z])/g

const camelize = cached((str) => {
  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})

fxy060608's avatar
fxy060608 已提交
26
function getCode (node) {
fxy060608's avatar
fxy060608 已提交
27 28 29 30 31 32 33 34 35
  return babelGenerate(t.cloneDeep(node), {
    compact: true,
    jsescOption: {
      quotes: 'single',
      minimal: true
    }
  }).code
}

fxy060608's avatar
fxy060608 已提交
36
function traverseKey (ast, state) {
fxy060608's avatar
fxy060608 已提交
37 38 39
  let forKey = false
  babelTraverse(ast, {
    noScope: true,
fxy060608's avatar
fxy060608 已提交
40
    ObjectProperty (path) {
fxy060608's avatar
fxy060608 已提交
41 42 43 44 45 46 47 48
      if (forKey) {
        return
      }
      if (path.node.key.name === 'key') {
        forKey = path.node.value
        path.stop()
      }
    },
fxy060608's avatar
fxy060608 已提交
49
    CallExpression (path) {
fxy060608's avatar
fxy060608 已提交
50 51
      if (path.node.callee.name === METHOD_RENDER_LIST) {
        path.stop()
52 53
      } else if (path.node.callee.name === METHOD_RESOLVE_SCOPED_SLOTS) {
        path.skip()
fxy060608's avatar
fxy060608 已提交
54 55 56 57 58 59
      }
    }
  })
  return forKey
}

fxy060608's avatar
fxy060608 已提交
60
function traverseFilter (ast, state) {
61 62 63 64 65 66 67
  const filterModules = state.options.filterModules
  if (!filterModules.length) {
    return false
  }
  let isFilter = false
  babelTraverse(ast, {
    noScope: true,
fxy060608's avatar
fxy060608 已提交
68
    Identifier (path) {
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
      if (filterModules.includes(path.node.name)) {
        const parentNode = path.parent
        if ( // t.msg || t['msg']
          t.isMemberExpression(parentNode) &&
          parentNode.object === path.node &&
          (
            t.isIdentifier(parentNode.property) ||
            t.isLiteral(parentNode.property)
          )
        ) {
          isFilter = true
          path.stop()
        }
      }
    }
  })
  return isFilter
}

fxy060608's avatar
fxy060608 已提交
88
function wrapper (code, reverse = false) {
fxy060608's avatar
fxy060608 已提交
89 90 91
  return reverse ? `{{!(${code})}}` : `{{${code}}}`
}

fxy060608's avatar
fxy060608 已提交
92
function genCode (node, noWrapper = false, reverse = false, quotes = true) {
fxy060608's avatar
fxy060608 已提交
93 94 95 96 97 98 99 100 101 102 103 104
  if (t.isStringLiteral(node)) {
    return reverse ? `!(${node.value})` : node.value
  } else if (t.isIdentifier(node)) {
    return noWrapper ? node.name : wrapper(node.name, reverse)
  }
  let code = getCode(node)
  if (quotes) {
    code = code.replace(/"/g, '\'')
  }
  return noWrapper ? code : wrapper(code, reverse)
}

fxy060608's avatar
fxy060608 已提交
105
function getForIndexIdentifier (id) {
fxy060608's avatar
fxy060608 已提交
106 107 108
  return `__i${id}__`
}

fxy060608's avatar
fxy060608 已提交
109
function getForKey (forKey, forIndex, state) {
fxy060608's avatar
fxy060608 已提交
110 111 112
  if (forKey) {
    if (t.isIdentifier(forKey)) {
      if (forIndex !== forKey.name) { // 非 forIndex
113
        if (state.options.platform.name === 'mp-baidu') return getCode(forKey)
fxy060608's avatar
fxy060608 已提交
114 115 116 117 118 119 120
        return '*this'
      } else {
        // TODO
        // state.tips.add(`非 h5 平台 v-for 循环不支持使用索引值 ${forIndex} 作为 key,详情参考:https://uniapp.dcloud.io/use?id=key`)
        return forKey.name
      }
    } else if (t.isMemberExpression(forKey)) {
121
      if (state.options.platform.name === 'mp-baidu') return getCode(forKey)
fxy060608's avatar
fxy060608 已提交
122 123
      return forKey.property.name || forKey.property.value
    } else {
Q
qiang 已提交
124
      state.tips.add(uniI18n.__('templateCompiler.noH5KeyNoSupportExpression', { 0: getCode(forKey), 1: 'https://uniapp.dcloud.io/use?id=key' }))
fxy060608's avatar
fxy060608 已提交
125 126 127 128 129
    }
  }
  return ''
}

fxy060608's avatar
fxy060608 已提交
130
function processMemberProperty (node, state) {
fxy060608's avatar
fxy060608 已提交
131 132 133 134 135
  if (node.computed) {
    const property = node.property
    if (t.isNumericLiteral(property)) {
      node.property = t.identifier('__$n' + property.value)
    } else if (!t.isStringLiteral(property)) {
fxy060608's avatar
fxy060608 已提交
136
      if (!hasOwn(state.options, '__m__')) {
fxy060608's avatar
fxy060608 已提交
137 138 139 140 141
        state.options.__m__ = 0
        state.options.replaceCodes = {}
      }
      const identifier = '__$m' + (state.options.__m__++) + '__'
      state.options.replaceCodes[identifier] = `'+${genCode(property, true)}+'`
fxy060608's avatar
fxy060608 已提交
142 143 144
      if (state.computedProperty) {
        state.computedProperty[identifier] = property
      }
fxy060608's avatar
fxy060608 已提交
145 146 147 148 149 150
      node.property = t.identifier(identifier)
    }
    node.computed = false
  }
}

fxy060608's avatar
fxy060608 已提交
151
function processMemberExpression (element, state) {
fxy060608's avatar
fxy060608 已提交
152 153 154 155 156 157 158 159 160 161 162 163
  // item['order']=>item.order
  if (t.isMemberExpression(element)) {
    element = t.cloneDeep(element)
    if (t.isStringLiteral(element.property)) {
      element.computed = false
    }
    // item[itemIndex[0]] = item[__$0__]
    // item[1]=item['1']
    processMemberProperty(element, state)

    babelTraverse(element, {
      noScope: true,
fxy060608's avatar
fxy060608 已提交
164
      MemberExpression (path) {
fxy060608's avatar
fxy060608 已提交
165 166 167 168 169 170
        processMemberProperty(path.node, state)
      }
    })

    babelTraverse(element, {
      noScope: true,
fxy060608's avatar
fxy060608 已提交
171
      MemberExpression (path) {
fxy060608's avatar
fxy060608 已提交
172 173 174 175
        if (t.isStringLiteral(path.node.property)) {
          path.node.computed = false
        }
      },
fxy060608's avatar
fxy060608 已提交
176
      StringLiteral (path) {
fxy060608's avatar
fxy060608 已提交
177 178 179 180 181
        path.replaceWith(t.identifier(path.node.value))
      }
    })
  }
  return element
182
}
fxy060608's avatar
fxy060608 已提交
183

fxy060608's avatar
fxy060608 已提交
184
function hasOwn (obj, key) {
fxy060608's avatar
fxy060608 已提交
185
  return Object.prototype.hasOwnProperty.call(obj, key)
fxy060608's avatar
fxy060608 已提交
186 187
}

fxy060608's avatar
fxy060608 已提交
188
const tags = require('@dcloudio/uni-cli-shared/lib/tags')
fxy060608's avatar
fxy060608 已提交
189

fxy060608's avatar
fxy060608 已提交
190 191
const {
  isBuiltInComponent
192
} = require('@dcloudio/uni-cli-shared/lib/pages')
fxy060608's avatar
fxy060608 已提交
193

fxy060608's avatar
fxy060608 已提交
194 195 196 197
const {
  getTagName
} = require('./h5')

fxy060608's avatar
fxy060608 已提交
198
function isComponent (tagName) {
fxy060608's avatar
fxy060608 已提交
199 200
  if (
    tagName === 'block' ||
fxy060608's avatar
fxy060608 已提交
201
    tagName === 'component' ||
fxy060608's avatar
fxy060608 已提交
202 203 204
    tagName === 'template' ||
    tagName === 'keep-alive'
  ) {
fxy060608's avatar
fxy060608 已提交
205
    return false
fxy060608's avatar
fxy060608 已提交
206
  }
207 208
  // mp-weixin 底层支持 page-meta,navigation-bar
  if (process.env.UNI_PLATFORM === 'mp-weixin') {
fxy060608's avatar
fxy060608 已提交
209
    if (isBuiltInComponent(tagName)) {
210 211 212
      return false
    }
  }
213
  return !hasOwn(tags, getTagName(tagName.replace(/^(v-)?uni-/, '')))
fxy060608's avatar
fxy060608 已提交
214 215
}

fxy060608's avatar
fxy060608 已提交
216
function makeMap (str, expectsLowerCase) {
fxy060608's avatar
fxy060608 已提交
217 218 219 220 221
  const map = Object.create(null)
  const list = str.split(',')
  for (let i = 0; i < list.length; i++) {
    map[list[i]] = true
  }
fxy060608's avatar
fxy060608 已提交
222 223 224
  return expectsLowerCase
    ? val => map[val.toLowerCase()]
    : val => map[val]
fxy060608's avatar
fxy060608 已提交
225
}
226 227 228 229 230 231

/**
 * 微信、QQ小程序模板支持的简单类型
 * @param {*} node
 */
function isSimpleObjectExpression (node) {
232
  return t.isObjectExpression(node) && node.properties.length && !node.properties.find(({
fxy060608's avatar
fxy060608 已提交
233 234 235 236
    key,
    value
  }) => !t.isIdentifier(key) || !(t.isIdentifier(value) || t.isStringLiteral(value) || t.isBooleanLiteral(value) ||
    t.isNumericLiteral(value) || t.isNullLiteral(value)))
237
}
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
/**
 * 是否包含转义引号
 * @param {*} path
 * @returns {boolean}
 */
function hasEscapeQuote (path) {
  let has = false
  function hasEscapeQuote (node) {
    const quote = node.extra ? node.extra.raw[0] : '"'
    if (node.value.includes(quote)) {
      return true
    }
  }
  if (path.isStringLiteral()) {
    return hasEscapeQuote(path.node)
  } else {
    path.traverse({
      noScope: true,
      StringLiteral (path) {
        if (hasEscapeQuote(path.node)) {
          has = true
          path.stop()
        }
Q
qiang 已提交
261 262 263 264 265 266
      },
      TemplateElement (path) {
        if (path.node.value.cooked.includes('\'')) {
          has = true
          path.stop()
        }
267 268 269 270 271
      }
    })
  }
  return has
}
272

fxy060608's avatar
fxy060608 已提交
273
module.exports = {
fxy060608's avatar
fxy060608 已提交
274
  hasOwn,
fxy060608's avatar
fxy060608 已提交
275 276 277 278
  isUnaryTag: makeMap(
    'image,area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +
    'link,meta,param,source,track,wbr'
  ),
fxy060608's avatar
fxy060608 已提交
279
  isComponent,
fxy060608's avatar
fxy060608 已提交
280 281 282 283 284 285 286 287 288 289 290 291 292 293
  genCode,
  getCode,
  camelize,
  customize: cached((str) => {
    return camelize(str.replace(customizeRE, '-'))
  }),
  capitalize: cached(str => {
    return str.charAt(0).toUpperCase() + str.slice(1)
  }),
  hyphenate: cached((str) => {
    return str.replace(hyphenateRE, '-$1').toLowerCase()
  }),
  getForKey,
  traverseKey,
294
  traverseFilter,
fxy060608's avatar
fxy060608 已提交
295 296 297 298 299 300 301
  getComponentName: cached((str) => {
    if (str.indexOf('wx-') === 0) {
      return str.replace('wx-', 'weixin-')
    }
    return str
  }),
  processMemberExpression,
302
  getForIndexIdentifier,
303 304
  isSimpleObjectExpression,
  hasEscapeQuote
305
}