刮刮前端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

387 lines
9.8 KiB

const t = require('@babel/types')
const babelTraverse = require('@babel/traverse').default
const babelGenerate = require('@babel/generator').default
const babelTemplate = require('@babel/template').default
const uniI18n = require('@dcloudio/uni-cli-i18n')
const {
METHOD_RENDER_LIST,
METHOD_RESOLVE_SCOPED_SLOTS,
METHOD_CREATE_ELEMENT
} = require('./constants')
function cached (fn) {
const cache = Object.create(null)
return function cachedFn (str) {
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() : '')
})
function getCode (node) {
return babelGenerate(t.cloneDeep(node), {
compact: true,
jsescOption: {
quotes: 'single',
minimal: true
}
}).code
}
function traverseKey (ast, state) {
let forKey = false
babelTraverse(ast, {
noScope: true,
ObjectProperty (path) {
if (forKey) {
return
}
if (path.node.key.name === 'key') {
forKey = path.node.value
path.stop()
}
},
CallExpression (path) {
if (path.node.callee.name === METHOD_RENDER_LIST) {
path.stop()
} else if (path.node.callee.name === METHOD_RESOLVE_SCOPED_SLOTS) {
path.skip()
}
}
})
return forKey
}
function traverseFilter (ast, state) {
const filterModules = state.options.filterModules
if (!filterModules.length) {
return false
}
let isFilter = false
babelTraverse(ast, {
noScope: true,
Identifier (path) {
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
}
function wrapper (code, reverse = false) {
return reverse ? `{{!(${code})}}` : `{{${code}}}`
}
function genCode (node, noWrapper = false, reverse = false, quotes = true) {
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)
}
function getForIndexIdentifier (id) {
return `__i${id}__`
}
function getForKey (forKey, forIndex, state) {
if (forKey) {
if (t.isIdentifier(forKey)) {
if (forIndex !== forKey.name) { // 非 forIndex
if (state.options.platform.name === 'mp-baidu') return getCode(forKey)
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)) {
if (state.options.platform.name === 'mp-baidu') return getCode(forKey)
return forKey.property.name || forKey.property.value
} else {
state.tips.add(uniI18n.__('templateCompiler.noH5KeyNoSupportExpression', { 0: getCode(forKey), 1: 'https://uniapp.dcloud.io/use?id=key' }))
}
}
return ''
}
function processMemberProperty (node, state) {
if (node.computed) {
const property = node.property
if (t.isNumericLiteral(property)) {
node.property = t.identifier('__$n' + property.value)
} else if (!t.isStringLiteral(property)) {
if (!hasOwn(state.options, '__m__')) {
state.options.__m__ = 0
state.options.replaceCodes = {}
}
const identifier = '__$m' + (state.options.__m__++) + '__'
const code = { property }
code.toString = function () {
return `'+${genCode(this.property, true)}+'`
}
state.options.replaceCodes[identifier] = code
if (state.computedProperty) {
state.computedProperty[identifier] = property
}
node.property = t.identifier(identifier)
}
node.computed = false
}
}
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
}
})
const buildRequire = babelTemplate(code, { syntacticPlaceholders: true })
if (Object.keys(options).length) {
const ast = buildRequire(options)
return ast.expression
}
}
return stringLiteral
}
function processMemberExpression (element, state) {
// 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,
MemberExpression (path) {
processMemberProperty(path.node, state)
}
})
babelTraverse(element, {
noScope: true,
MemberExpression (path) {
if (t.isStringLiteral(path.node.property)) {
path.node.computed = false
}
},
StringLiteral (path) {
path.replaceWith(t.identifier(path.node.value))
}
})
}
return element
}
function hasOwn (obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key)
}
const tags = require('@dcloudio/uni-cli-shared/lib/tags')
const {
isBuiltInComponent
} = require('@dcloudio/uni-cli-shared/lib/pages')
const {
getTagName
} = require('./h5')
function isComponent (tagName) {
if (
tagName === 'block' ||
tagName === 'component' ||
tagName === 'template' ||
tagName === 'keep-alive'
) {
return false
}
// mp-weixin 底层支持 page-meta,navigation-bar
if (process.env.UNI_PLATFORM === 'mp-weixin') {
if (isBuiltInComponent(tagName)) {
return false
}
}
return !hasOwn(tags, getTagName(tagName.replace(/^v-uni-/, '')))
}
function makeMap (str, expectsLowerCase) {
const map = Object.create(null)
const list = str.split(',')
for (let i = 0; i < list.length; i++) {
map[list[i]] = true
}
return expectsLowerCase
? val => map[val.toLowerCase()]
: val => map[val]
}
/**
* 微信、QQ小程序模板支持的简单类型
* @param {*} node
*/
function isSimpleObjectExpression (node) {
return t.isObjectExpression(node) && node.properties.length && !node.properties.find(({
key,
value
}) => !t.isIdentifier(key) || !(t.isIdentifier(value) || t.isStringLiteral(value) || t.isBooleanLiteral(value) ||
t.isNumericLiteral(value) || t.isNullLiteral(value)))
}
/**
* 是否包含转义引号
* @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()
}
},
TemplateElement (path) {
if (path.node.value.cooked.includes('\'')) {
has = true
path.stop()
}
}
})
}
return has
}
/**
* 是否包含属性 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
}
function isRootElement (path) {
const result = path.findParent(path => (path.isCallExpression() && path.get('callee').isIdentifier({ name: METHOD_CREATE_ELEMENT })) || path.isReturnStatement())
return result.isReturnStatement()
}
/**
* 事件绑定是否存在成员表达式 => 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
}
module.exports = {
hasOwn,
isUnaryTag: makeMap(
'image,area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +
'link,meta,param,source,track,wbr'
),
isComponent,
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,
traverseFilter,
getComponentName: cached((str) => {
if (str.indexOf('wx-') === 0) {
return str.replace('wx-', 'weixin-')
}
return str
}),
processMemberExpression,
replaceMemberExpression,
getForIndexIdentifier,
isSimpleObjectExpression,
hasEscapeQuote,
hasLengthProperty,
isRootElement,
hasMemberExpression
}