util.js 10.3 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
4
const babelTemplate = require('@babel/template').default
d-u-a's avatar
d-u-a 已提交
5
const uniI18n = require('@dcloudio/uni-cli-i18n')
fxy060608's avatar
fxy060608 已提交
6 7

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

fxy060608's avatar
fxy060608 已提交
13
function cached (fn) {
fxy060608's avatar
fxy060608 已提交
14
  const cache = Object.create(null)
fxy060608's avatar
fxy060608 已提交
15
  return function cachedFn (str) {
fxy060608's avatar
fxy060608 已提交
16 17 18 19 20 21 22 23 24 25 26 27
    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 已提交
28
function getCode (node) {
fxy060608's avatar
fxy060608 已提交
29 30 31 32 33 34 35 36 37
  return babelGenerate(t.cloneDeep(node), {
    compact: true,
    jsescOption: {
      quotes: 'single',
      minimal: true
    }
  }).code
}

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

fxy060608's avatar
fxy060608 已提交
62
function traverseFilter (ast, state) {
63 64 65 66 67 68 69
  const filterModules = state.options.filterModules
  if (!filterModules.length) {
    return false
  }
  let isFilter = false
  babelTraverse(ast, {
    noScope: true,
fxy060608's avatar
fxy060608 已提交
70
    Identifier (path) {
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
      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 已提交
90
function wrapper (code, reverse = false) {
fxy060608's avatar
fxy060608 已提交
91 92 93
  return reverse ? `{{!(${code})}}` : `{{${code}}}`
}

fxy060608's avatar
fxy060608 已提交
94
function genCode (node, noWrapper = false, reverse = false, quotes = true) {
fxy060608's avatar
fxy060608 已提交
95 96 97 98 99 100 101 102 103 104 105 106
  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 已提交
107
function getForIndexIdentifier (id) {
fxy060608's avatar
fxy060608 已提交
108 109 110
  return `__i${id}__`
}

fxy060608's avatar
fxy060608 已提交
111
function getForKey (forKey, forIndex, state) {
fxy060608's avatar
fxy060608 已提交
112 113 114
  if (forKey) {
    if (t.isIdentifier(forKey)) {
      if (forIndex !== forKey.name) { // 非 forIndex
115
        if (state.options.platform.name === 'mp-baidu') return getCode(forKey)
fxy060608's avatar
fxy060608 已提交
116 117 118 119 120 121 122
        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)) {
123
      if (state.options.platform.name === 'mp-baidu') return getCode(forKey)
fxy060608's avatar
fxy060608 已提交
124 125
      return forKey.property.name || forKey.property.value
    } else {
Q
qiang 已提交
126
      state.tips.add(uniI18n.__('templateCompiler.noH5KeyNoSupportExpression', { 0: getCode(forKey), 1: 'https://uniapp.dcloud.io/use?id=key' }))
fxy060608's avatar
fxy060608 已提交
127 128 129 130 131
    }
  }
  return ''
}

fxy060608's avatar
fxy060608 已提交
132
function processMemberProperty (node, state) {
fxy060608's avatar
fxy060608 已提交
133 134 135 136 137
  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 已提交
138
      if (!hasOwn(state.options, '__m__')) {
fxy060608's avatar
fxy060608 已提交
139 140 141 142
        state.options.__m__ = 0
        state.options.replaceCodes = {}
      }
      const identifier = '__$m' + (state.options.__m__++) + '__'
143 144 145 146 147
      const code = { property }
      code.toString = function () {
        return `'+${genCode(this.property, true)}+'`
      }
      state.options.replaceCodes[identifier] = code
fxy060608's avatar
fxy060608 已提交
148 149 150
      if (state.computedProperty) {
        state.computedProperty[identifier] = property
      }
fxy060608's avatar
fxy060608 已提交
151 152 153 154 155 156
      node.property = t.identifier(identifier)
    }
    node.computed = false
  }
}

157 158 159 160 161 162 163 164 165 166 167 168
function replaceMemberExpression (stringLiteral, state) {
  let code = `'${stringLiteral.value}'`
  const replaceCodes = state.options.replaceCodes
  if (replaceCodes) {
    const options = {}
    Object.keys(replaceCodes).forEach(key => {
      const newCode = code.replace(new RegExp(key.replace('$', '\\$'), 'g'), `'+%%${key}%%+'`)
      if (newCode !== code) {
        options[key] = replaceCodes[key].property
        code = newCode
      }
    })
169
    const buildRequire = babelTemplate(code, { syntacticPlaceholders: true })
170 171 172 173 174 175 176 177
    if (Object.keys(options).length) {
      const ast = buildRequire(options)
      return ast.expression
    }
  }
  return stringLiteral
}

fxy060608's avatar
fxy060608 已提交
178
function processMemberExpression (element, state) {
fxy060608's avatar
fxy060608 已提交
179 180 181 182 183 184 185 186 187 188 189 190
  // 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 已提交
191
      MemberExpression (path) {
fxy060608's avatar
fxy060608 已提交
192 193 194 195 196 197
        processMemberProperty(path.node, state)
      }
    })

    babelTraverse(element, {
      noScope: true,
fxy060608's avatar
fxy060608 已提交
198
      MemberExpression (path) {
fxy060608's avatar
fxy060608 已提交
199 200 201 202
        if (t.isStringLiteral(path.node.property)) {
          path.node.computed = false
        }
      },
fxy060608's avatar
fxy060608 已提交
203
      StringLiteral (path) {
fxy060608's avatar
fxy060608 已提交
204 205 206 207 208
        path.replaceWith(t.identifier(path.node.value))
      }
    })
  }
  return element
209
}
fxy060608's avatar
fxy060608 已提交
210

fxy060608's avatar
fxy060608 已提交
211
function hasOwn (obj, key) {
fxy060608's avatar
fxy060608 已提交
212
  return Object.prototype.hasOwnProperty.call(obj, key)
fxy060608's avatar
fxy060608 已提交
213 214
}

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

fxy060608's avatar
fxy060608 已提交
217 218
const {
  isBuiltInComponent
219
} = require('@dcloudio/uni-cli-shared/lib/pages')
fxy060608's avatar
fxy060608 已提交
220

fxy060608's avatar
fxy060608 已提交
221 222 223 224
const {
  getTagName
} = require('./h5')

225 226 227 228 229 230 231
/**
 * isComponent
 * @param {string} tagName
 * @param {string} [platform]
 * @returns {boolean}
 */
function isComponent (tagName, platform) {
fxy060608's avatar
fxy060608 已提交
232 233
  if (
    tagName === 'block' ||
fxy060608's avatar
fxy060608 已提交
234
    tagName === 'component' ||
fxy060608's avatar
fxy060608 已提交
235 236 237
    tagName === 'template' ||
    tagName === 'keep-alive'
  ) {
fxy060608's avatar
fxy060608 已提交
238
    return false
fxy060608's avatar
fxy060608 已提交
239
  }
240 241
  // mp-weixin 底层支持 page-meta,navigation-bar
  if (process.env.UNI_PLATFORM === 'mp-weixin') {
fxy060608's avatar
fxy060608 已提交
242
    if (isBuiltInComponent(tagName)) {
243 244 245
      return false
    }
  }
246
  return !hasOwn(tags, getTagName(tagName.replace(/^v-uni-/, ''), platform))
fxy060608's avatar
fxy060608 已提交
247 248
}

fxy060608's avatar
fxy060608 已提交
249
function makeMap (str, expectsLowerCase) {
fxy060608's avatar
fxy060608 已提交
250 251 252 253 254
  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 已提交
255 256 257
  return expectsLowerCase
    ? val => map[val.toLowerCase()]
    : val => map[val]
fxy060608's avatar
fxy060608 已提交
258
}
259 260 261 262 263 264

/**
 * 微信、QQ小程序模板支持的简单类型
 * @param {*} node
 */
function isSimpleObjectExpression (node) {
265
  return t.isObjectExpression(node) && node.properties.length && !node.properties.find(({
fxy060608's avatar
fxy060608 已提交
266 267 268 269
    key,
    value
  }) => !t.isIdentifier(key) || !(t.isIdentifier(value) || t.isStringLiteral(value) || t.isBooleanLiteral(value) ||
    t.isNumericLiteral(value) || t.isNullLiteral(value)))
270
}
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
/**
 * 是否包含转义引号
 * @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 已提交
294 295 296 297 298 299
      },
      TemplateElement (path) {
        if (path.node.value.cooked.includes('\'')) {
          has = true
          path.stop()
        }
300 301 302 303 304
      }
    })
  }
  return has
}
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
/**
 * 是否包含属性 length 访问
 * @param {*} path
 * @returns {boolean}
 */
function hasLengthProperty (path) {
  let has = false
  function hasLengthProperty (node) {
    const property = node.property
    // 暂不考虑动态拼接和模板字符串
    return t.isIdentifier(property, { name: 'length' }) || t.isStringLiteral(property, { value: 'length' })
  }
  if (path.isMemberExpression()) {
    return hasLengthProperty(path.node)
  } else {
    path.traverse({
      noScope: true,
      MemberExpression (path) {
        if (hasLengthProperty(path.node)) {
          has = true
          path.stop()
        }
      }
    })
  }
  return has
}
332

333 334 335 336 337
function isRootElement (path) {
  const result = path.findParent(path => (path.isCallExpression() && path.get('callee').isIdentifier({ name: METHOD_CREATE_ELEMENT })) || path.isReturnStatement())
  return result.isReturnStatement()
}

338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
/**
 * 事件绑定是否存在成员表达式 => obj.click2()
 * @param {*} path
 * @returns {boolean}
*/
const hasMemberExpression = (funcPath) => {
  let result = false

  funcPath.get('body').traverse({
    CallExpression (path) {
      if (t.isMemberExpression(path.node.callee)) {
        result = true
        path.stop()
      }
    }
  })
  return result
}

fxy060608's avatar
fxy060608 已提交
357
module.exports = {
fxy060608's avatar
fxy060608 已提交
358
  hasOwn,
fxy060608's avatar
fxy060608 已提交
359 360 361 362
  isUnaryTag: makeMap(
    'image,area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +
    'link,meta,param,source,track,wbr'
  ),
fxy060608's avatar
fxy060608 已提交
363
  isComponent,
fxy060608's avatar
fxy060608 已提交
364 365 366 367 368 369 370 371 372 373 374 375 376 377
  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,
378
  traverseFilter,
fxy060608's avatar
fxy060608 已提交
379 380 381 382 383 384 385
  getComponentName: cached((str) => {
    if (str.indexOf('wx-') === 0) {
      return str.replace('wx-', 'weixin-')
    }
    return str
  }),
  processMemberExpression,
386
  replaceMemberExpression,
387
  getForIndexIdentifier,
388
  isSimpleObjectExpression,
389
  hasEscapeQuote,
390
  hasLengthProperty,
391 392
  isRootElement,
  hasMemberExpression
393
}