first commit

This commit is contained in:
2026-09-08 20:28:54 +08:00
commit 2ada8d3d5a
37380 changed files with 4886169 additions and 0 deletions
@@ -0,0 +1,29 @@
const {
IDENTIFIER_ATTR
} = require('../../../constants')
const {
isSimpleObjectExpression,
hasEscapeQuote
} = require('../../../util')
const getMemberExpr = require('../member-expr')
function checkObjectExpression (path) {
return path.isObjectExpression() && !isSimpleObjectExpression(path.node)
}
module.exports = function processAttrs (paths, path, state, isComponent, tagName) {
const attrsPath = paths.attrs
if (attrsPath) {
attrsPath.get('value.properties').forEach(propertyPath => {
const valuePath = propertyPath.get('value')
// 对于简单的ObjectExpression不再单独处理,改为在转换temlplte时用()包裹(微信、QQ
// 属性中包含转义引号时部分小程序平台报错或显示异常
if (checkObjectExpression(valuePath) || hasEscapeQuote(valuePath)) {
valuePath.replaceWith(getMemberExpr(path, IDENTIFIER_ATTR, valuePath.node, state))
}
})
}
return []
}
@@ -0,0 +1,143 @@
const t = require('@babel/types')
const uniI18n = require('@dcloudio/uni-cli-i18n')
const {
VIRTUAL_HOST_CLASS
} = require('../../../constants')
const {
getCode,
isRootElement
} = require('../../../util')
function processClassArrayExpressionElements (classArrayExpression) {
let binaryExpression
classArrayExpression.elements.forEach(expr => {
if (t.isArrayExpression(expr)) {
expr = processClassArrayExpressionElements(expr)
}
if (!binaryExpression) {
binaryExpression = t.parenthesizedExpression(expr)
} else {
binaryExpression = t.parenthesizedExpression(t.binaryExpression(
'+',
t.binaryExpression(
'+',
binaryExpression,
t.stringLiteral(' ')
),
expr
))
}
})
return binaryExpression
}
function processStaticClass (classArrayExpression, staticClassPath, state) {
if (staticClassPath) {
const staticClassPathArr = staticClassPath.node.value.value.split(' ')
for (let len = staticClassPathArr.length, index = len - 1; index >= 0; index--) {
classArrayExpression.elements.unshift(t.stringLiteral(staticClassPathArr[index]))
}
staticClassPath.remove()
}
const transPlatform = ['mp-toutiao', 'mp-alipay', 'mp-lark']
if (transPlatform.includes(state.options.platform.name)) {
// classArrayExpression => binaryExpression
return processClassArrayExpressionElements(classArrayExpression)
}
return classArrayExpression
}
function processClassObjectExpression (classValuePath) {
const elements = []
const propertyPaths = classValuePath.get('properties')
propertyPaths.forEach(propertyPath => {
const key = propertyPath.node.key
elements.push(
t.conditionalExpression(
t.parenthesizedExpression(propertyPath.node.value),
propertyPath.node.computed ? t.parenthesizedExpression(key) : t.stringLiteral(key.name || key.value),
t.stringLiteral('')
)
)
})
return t.arrayExpression(elements)
}
function processClassArrayExpression (classValuePath) {
const elementPaths = classValuePath.get('elements')
elementPaths.forEach(elementPath => {
if (elementPath.isObjectExpression()) {
elementPath.replaceWith(processClassObjectExpression(elementPath))
}
})
return classValuePath.node
}
module.exports = function processClass (paths, path, state) {
const classPath = paths.class
const staticClassPath = paths.staticClass
const mergeVirtualHostAttributes = state.options.mergeVirtualHostAttributes
let classArrayExpression
if (classPath) {
const classValuePath = classPath.get('value')
if (classValuePath.isObjectExpression()) { // object
classArrayExpression = processClassObjectExpression(classValuePath)
} else if (classValuePath.isArrayExpression()) { // array
classArrayExpression = processClassArrayExpression(classValuePath)
} else if (
classValuePath.isStringLiteral() || // :class="'a'"
classValuePath.isIdentifier() || // TODO 需要优化到下一个条件,:class="classObject"
classValuePath.isMemberExpression() || // 需要优化到下一个条件,:class="item.classObject"
classValuePath.isConditionalExpression() ||
classValuePath.isLogicalExpression() ||
classValuePath.isBinaryExpression()
) {
// 理论上 ConditionalExpression,LogicalExpression 可能存在 classObject,应该__get_class,还是先不考虑这种情况吧
// ConditionalExpression :class="index === currentIndex ? activeStyle : itemStyle"
// BinaryExpression :class="'m-content-head-'+message.user"
classArrayExpression = t.arrayExpression([classValuePath.node])
} else if (
classValuePath.isIdentifier() ||
classValuePath.isMemberExpression()
) { // classObject :class="classObject" :class="vm.classObject"
// TODO 目前先不考虑 classObject,styleObject
// const args = [classPath.node.value]
// if (staticClassPath) {
// args.push(staticClassPath.node.value)
// staticClassPath.remove()
// }
// classValuePath.replaceWith(
// getMemberExpr(
// classPath,
// IDENTIFIER_CLASS,
// t.callExpression(t.identifier(INTERNAL_GET_CLASS), args),
// state
// )
// )
} else {
state.errors.add(':class' + uniI18n.__('templateCompiler.noSupportSyntax', { 0: getCode(classValuePath.node) }))
}
}
if (mergeVirtualHostAttributes && isRootElement(path.parentPath)) {
const virtualHostClass = t.identifier(VIRTUAL_HOST_CLASS)
if (classArrayExpression) {
classArrayExpression.elements.push(virtualHostClass)
} else {
classArrayExpression = t.arrayExpression([virtualHostClass])
const property = t.objectProperty(t.identifier('class'), processStaticClass(classArrayExpression, staticClassPath, state))
path.node.properties.push(property)
return []
}
}
if (classArrayExpression) {
const classValuePath = classPath.get('value')
classValuePath.replaceWith(processStaticClass(classArrayExpression, staticClassPath, state))
}
return []
}
@@ -0,0 +1,71 @@
const t = require('@babel/types')
const {
getModelEventFunctionExpr
} = require('./util')
module.exports = function processDir (paths, path, state) {
const directivesPath = paths.directives
if (directivesPath) {
/**
* directives: [{
* name: "model",
* rawName: "v-model",
* value: (aaa.cart_amount),
* expression: "aaa.cart_amount"
*}],
*/
const modelObjectExpr = directivesPath.node.value.elements.find(
objectExpression => {
return objectExpression.properties.find(property => {
return property.key.name === 'name' && property.value.value === 'model'
})
}
)
if (modelObjectExpr) {
const exprProperty = modelObjectExpr.properties.find(property => {
return property.key.name === 'expression'
})
const modifiersProperty = modelObjectExpr.properties.find(property => {
return property.key.name === 'modifiers'
})
if (exprProperty) {
const onPath = paths.on
const existingInput = onPath.node.value.properties.find(
property => property.key.value === 'input'
)
if (existingInput) {
let existingInputFuncExpr
// remove old model input event
if (!t.isArrayExpression(existingInput.value)) {
existingInputFuncExpr = existingInput.value
existingInput.value = t.arrayExpression([])
} else {
existingInputFuncExpr = existingInput.value.elements.shift()
}
if (existingInputFuncExpr) {
const modifiers = []
if (modifiersProperty) {
const properties = modifiersProperty.value.properties
if (properties.find(property => property.key.value === 'number')) {
modifiers.push(t.stringLiteral('number'))
}
if (properties.find(property => property.key.value === 'trim')) {
modifiers.push(t.stringLiteral('trim'))
}
}
existingInput.value.elements.unshift(
getModelEventFunctionExpr(
existingInputFuncExpr,
exprProperty.value.value.trim(),
modifiers
)
)
}
}
}
}
}
return []
}
@@ -0,0 +1,581 @@
const t = require('@babel/types')
const template = require('@babel/template').default
const {
IDENTIFIER_EVENT,
VUE_EVENT_MODIFIERS,
INTERNAL_EVENT_PROXY,
ATTR_DATA_EVENT_OPTS,
ATTR_DATA_EVENT_PARAMS,
INTERNAL_SET_SYNC,
INTERNAL_SET_MODEL,
ALLOWED_GLOBAL_OBJECT
} = require('../../../constants')
const {
getCode,
customize,
processMemberExpression,
replaceMemberExpression,
hasMemberExpression
} = require('../../../util')
const {
getEventExpressionStatement
} = require('../statements')
const defaultArgs = t.arrayExpression([t.stringLiteral('$event')])
function addEventExpressionStatement (funcPath, state, isCustom) {
const identifier = t.identifier(IDENTIFIER_EVENT)
const stringLiteral = t.stringLiteral(IDENTIFIER_EVENT)
state.identifierArray.push([identifier, stringLiteral])
state.initExpressionStatementArray.push(getEventExpressionStatement(identifier, funcPath.node))
const arrayExpression = [
stringLiteral
]
const args = []
if (!isCustom) { // native events
args.push(t.stringLiteral('$event'))
arrayExpression.push(t.arrayExpression(args))
} else { // custom events
}
// if (state.scoped) { // add forItem,forIndex
// const scopedArgs = []
// state.scoped.forEach(scoped => {
// if (scoped.forIndex && scoped.forIndex !== scoped.forItem) {
// scopedArgs.push(t.identifier(scoped.forIndex))
// }
// scopedArgs.push(t.identifier(scoped.forItem))
// })
// scopedArgs.reverse().forEach(arg => {
// args.push(arg)
// })
// }
return t.arrayExpression(arrayExpression)
}
function getIdentifierName (element) {
if (t.isMemberExpression(element)) {
return getIdentifierName(element.object)
}
return element.name.split('.')[0]
}
function getScoped (scopedArray, element, methodName, state) {
const identifierName = getIdentifierName(element)
const scoped = scopedArray.find(scoped => {
if (scoped.forItem === identifierName && !scoped.forKey) {
return true
}
})
if (scoped) {
const forExtra = t.cloneDeep(t.arrayExpression(scoped.forExtra))
if (t.isMemberExpression(element)) {
// 简单处理
// item['order']=>item.order
element = processMemberExpression(element, state)
// v-for="item in data.items" :key="item.data.id"
// v-for="meta in item.metas" :key="meta.id" @tap="change(meta,meta.b,true)"
// ['data.items','data.id',item.data.id]
// ['metas','id',meta.id]=>['metas','id',meta.id,'b']
forExtra.elements[forExtra.elements.length - 1].elements.push(
t.stringLiteral(
getExtraDataPath(
getCode(element).replace(scoped.forItem + '.', ''), methodName
)
)
)
}
return forExtra
}
}
function isForIndex (scopedArray, element) {
if (t.isIdentifier(element)) {
return scopedArray.find(scoped => {
if (scoped.forIndex === element.name) {
return true
}
})
}
return false
}
function isForItem (scopedArray, element) {
if (t.isIdentifier(element)) {
return scopedArray.find(scoped => {
if (scoped.forItem === element.name) {
return true
}
})
}
return false
}
function isForKey (scopedArray, element) {
if (t.isIdentifier(element)) {
return scopedArray.find(scoped => {
if (scoped.forKey === element.name) {
return true
}
})
}
return false
}
function getExtraDataPath (dataPath, methodName) {
if (methodName === INTERNAL_SET_SYNC) {
const dataPaths = dataPath.split('.')
dataPaths.pop()
return dataPaths.join('.')
}
return dataPath
}
function parseMethod (method, state) {
const elements = method.elements
const methodName = elements[0].value
const argsArrayExpr = elements[1]
if (argsArrayExpr) {
const extraArrayElements = []
argsArrayExpr.elements = argsArrayExpr.elements.map((element) => {
if (t.isIdentifier(element) || t.isMemberExpression(element)) { // item or item.b
if (state.scoped.length) {
const forExtra = getScoped(state.scoped, element, methodName, state)
if (!forExtra) {
if (isForIndex(state.scoped, element) || isForItem(state.scoped, element) || isForKey(state.scoped, element)) {
return element
} else {
extraArrayElements.push(replaceMemberExpression(t.stringLiteral(
getExtraDataPath(getCode(processMemberExpression(element, state)),
methodName)
), state))
}
} else {
extraArrayElements.push(forExtra)
}
} else {
extraArrayElements.push(replaceMemberExpression(t.stringLiteral(
getExtraDataPath(getCode(processMemberExpression(element, state)), methodName)
), state))
}
return t.stringLiteral('$' + (extraArrayElements.length - 1))
} else if ( // +1=>1
t.isUnaryExpression(element) &&
element.operator === '+' &&
t.isNumericLiteral(element.argument)
) {
element = t.numericLiteral(element.argument.value)
} else if (t.isObjectExpression(element)) {
// {name:'a',b:'c',d:123}=>[['name','a'],['b','c'],['d',123]]
const objectExprElements = [
t.stringLiteral('o')
]
element.properties.forEach(property => {
objectExprElements.push(t.arrayExpression([
t.stringLiteral(property.key.name || property.key.value),
t.cloneDeep(property.value)
]))
})
element = t.arrayExpression(objectExprElements)
}
return element
})
if (extraArrayElements.length) {
elements.push(t.arrayExpression(extraArrayElements))
}
}
}
function getMethodName (methodName) {
return methodName === '__HOLDER__' ? '' : methodName
}
function parseEventByCallExpression (callExpr, methods) {
let methodName = callExpr.callee.name
if (methodName === '$set') {
methodName = INTERNAL_SET_SYNC
}
const arrayExpression = [t.stringLiteral(getMethodName(methodName))]
const args = callExpr.arguments
if (methodName === INTERNAL_SET_SYNC) {
// v-bind:title.sync="doc.title"
// ['$set',['doc.a','title','$event']]
const argsExpression = []
argsExpression.push(
t.memberExpression(args[0], t.identifier(args[1].value))
)
argsExpression.push(t.stringLiteral(args[1].value))
argsExpression.push(t.stringLiteral('$event'))
arrayExpression.push(t.arrayExpression(argsExpression))
} else {
if (args.length) {
const argsExpression = []
args.forEach(arg => {
if (t.isIdentifier(arg) && arg.name === '$event') {
argsExpression.push(t.stringLiteral('$event'))
} else {
argsExpression.push(arg)
}
})
arrayExpression.push(t.arrayExpression(argsExpression))
}
}
methods.push(t.arrayExpression(arrayExpression))
}
function isValuePath (path) {
return path.key !== 'key' && path.key !== 'id' && (path.key !== 'property' || path.parent.computed) && !(path.key === 'value' && path.parentPath.parentPath.isObjectPattern()) && !(path.key === 'left' && path.parentPath.parentPath.parentPath.isObjectPattern())
}
const isSafeScoped = (state) => {
const scopedArray = state.scoped
let checkForIndex = false
for (let index = 0; index < scopedArray.length; index++) {
const scoped = scopedArray[index]
const arrayExtra = scoped.forExtra[0].elements[0].value
// 判断仅外层遍历对象是否包含了 index 参数
if (checkForIndex && scoped.forIndex && scoped.forKey) {
return false
}
if (index === 0 && !(scoped.forIndex && scoped.forKey)) {
checkForIndex = true
}
// 简易判断 v-for 中是否包含复杂表达式:数组、对象、方法
if (typeof arrayExtra === 'string' && (arrayExtra.startsWith('[') || arrayExtra.startsWith('{') || /\(.*\)/.test(arrayExtra))) {
return false
}
}
return true
}
function parseEvent (keyPath, valuePath, state, isComponent, isNativeOn = false, tagName, ret) {
const key = keyPath.node
let type = key.value || key.name || ''
const isCustom = isComponent && !isNativeOn
let isCatch = false
let isCapture = false
let isPassive = false
let isOnce = false
const methods = []
const params = []
if (type) {
isPassive = type.charAt(0) === VUE_EVENT_MODIFIERS.passive
type = isPassive ? type.slice(1) : type
isOnce = type.charAt(0) === VUE_EVENT_MODIFIERS.once // Prefixed last, checked first
type = isOnce ? type.slice(1) : type
isCapture = type.charAt(0) === VUE_EVENT_MODIFIERS.capture
type = isCapture ? type.slice(1) : type
const specialEvents = state.options.platform.specialEvents
const isSpecialEvent = specialEvents[tagName] && Object.keys(specialEvents[tagName]).includes(type)
if (!valuePath.isArrayExpression()) {
valuePath = [valuePath]
} else {
valuePath = valuePath.get('elements')
}
valuePath.forEach(funcPath => {
if ( // wxs event
funcPath.isMemberExpression() &&
t.isIdentifier(funcPath.node.object) &&
state.options.filterModules.includes(funcPath.node.object.name)
) {
const {
getEventType,
formatEventType
} = state.options.platform
const wxsEventType = formatEventType(getEventType(type))
if (key.value) {
key.value = wxsEventType
} else {
key.name = wxsEventType
}
} else if (funcPath.isIdentifier()) { // on:{click:handle}
if (!isSpecialEvent) {
const arrayExpression = [t.stringLiteral(getMethodName(funcPath.node.name))]
if (!isCustom) { // native events
arrayExpression.push(defaultArgs)
}
methods.push(t.arrayExpression(arrayExpression))
} else {
if (!state.options.specialMethods) {
state.options.specialMethods = new Set()
}
state.options.specialMethods.add(funcPath.node.name)
}
} else if (isSpecialEvent) {
state.errors.add(
`${tagName} 组件 ${type} 事件仅支持 @${type}="methodName" 方式绑定`
)
} else {
let anonymous = true
// "click":function($event) {click1(item);click2(item);}
const body = funcPath.node.body && funcPath.node.body.body
const funcParams = funcPath.node.params
if (body && body.length && funcParams && funcParams.length === 1 && !hasMemberExpression(funcPath) && isSafeScoped(state)) {
const exprStatements = body.filter(node => {
return t.isExpressionStatement(node) && t.isCallExpression(node.expression) && !node.expression.arguments.find(element => {
// click1(item().a)
if (t.isMemberExpression(element)) {
try {
getIdentifierName(element)
} catch {
return true
}
}
})
})
if (exprStatements.length === body.length) {
const paramPath = funcPath.get('params')[0]
const paramName = paramPath.node.name
if (paramName !== '$event') {
funcPath.get('body').traverse({
Identifier (path) {
const node = path.node
const binding = path.scope.getBinding(node.name)
if (binding && binding.identifier === paramPath.node && isValuePath(path)) {
path.replaceWith(t.identifier('$event'))
}
}
})
paramPath.replaceWith(t.identifier('$event'))
}
anonymous = false
exprStatements.forEach(exprStatement => {
parseEventByCallExpression(exprStatement.expression, methods)
})
}
}
const testCatch = function (stop) {
return function (path) {
// TODO 仅使用 name 容易误判
if (path.node.object.name === '$event' && path.node.property.name ===
'stopPropagation') {
isCatch = true
stop && path.stop()
}
}
}
// 如果 v-for 遍历的值为 数组、对象、方法 则进入底部匿名表达式处理
if (anonymous && isSafeScoped(state)) {
funcPath.traverse({
noScope: true,
MemberExpression: testCatch(),
AssignmentExpression (path) { // "update:title": function($event) {title = $event}
const left = path.node.left
const right = path.node.right
// v-bind:title.sync="title"
if (t.isIdentifier(left) &&
t.isIdentifier(right) &&
right.name === '$event' &&
type.indexOf('update:') === 0) {
methods.push(t.arrayExpression( // ['$set',['title','$event']]
[
t.stringLiteral(INTERNAL_SET_SYNC),
t.arrayExpression([
t.identifier(left.name),
t.stringLiteral(left.name),
t.stringLiteral('$event')
])
]
))
anonymous = false
path.stop()
}
},
ReturnStatement (path) {
const argument = path.node.argument
if (t.isCallExpression(argument)) {
if (t.isIdentifier(argument.callee)) { // || t.isMemberExpression(argument.callee)
anonymous = false
parseEventByCallExpression(argument, methods)
}
}
}
})
}
if (anonymous) {
// 处理复杂表达式中使用的局部变量(主要在v-for中定义)
funcPath.traverse({
MemberExpression: testCatch(),
Identifier (path) {
const scope = path.scope
const node = path.node
const name = node.name
if (!ALLOWED_GLOBAL_OBJECT.includes(name) && isValuePath(path) && scope && !scope.hasOwnBinding(name) && scope.hasBinding(name) && !params.includes(name) && name !== 'undefined') {
params.push(name)
}
}
})
params.forEach(name => {
funcPath.node.params.push(t.identifier(name))
})
if (params.length) {
if (!isCustom) {
const bodyStatements = funcPath.get('body.body')
const returnStatement = bodyStatements[0]
if (t.isReturnStatement(returnStatement) && t.isCallExpression(returnStatement.node.argument) && returnStatement.node.argument.callee.name === INTERNAL_SET_MODEL) {
funcPath.node.body.body.unshift(template('$event=$event.target.value')())
}
}
let argumentsName = 'arguments'
if (funcPath.isArrowFunctionExpression()) {
argumentsName = 'args'
funcPath.node.params.push(t.restElement(t.identifier(argumentsName)))
}
const datasetUid = funcPath.scope.generateDeclaredUidIdentifier().name
const paramsUid = funcPath.scope.generateDeclaredUidIdentifier().name
const dataset = ATTR_DATA_EVENT_PARAMS.substring(5)
const code = `var ${datasetUid}=${argumentsName}[${argumentsName}.length-1].currentTarget.dataset,${paramsUid}=${datasetUid}.${dataset.replace(/-([a-z])/, (_, str) => str.toUpperCase())}||${datasetUid}['${dataset}'],${params.map(item => `${item}=${paramsUid}.${item}`).join(',')}`
funcPath.node.body.body.unshift(template(code, { syntacticPlaceholders: true })())
}
methods.push(addEventExpressionStatement(funcPath, state, isComponent, isNativeOn))
}
}
})
}
return {
type,
params,
methods,
modifiers: {
isCatch,
isCapture,
isPassive,
isOnce,
isCustom
}
}
}
function _processEvent (path, state, isComponent, isNativeOn = false, tagName, ret) {
const opts = []
// remove invalid event
path.node.value.properties = path.node.value.properties.filter(property => {
return property.key.value || property.key.name
})
const len = path.node.value.properties.length
for (let i = 0; i < len; i++) {
const propertyPath = path.get(`value.properties.${i}`)
const keyPath = propertyPath.get('key')
const valuePath = propertyPath.get('value')
const {
type,
params,
methods,
modifiers: {
isCatch,
isCapture,
isOnce,
isCustom
}
} = parseEvent(
keyPath,
valuePath,
state,
isComponent,
isNativeOn,
tagName,
ret
)
if (!methods.length) {
continue
}
methods.forEach(method => {
parseMethod(method, state) // 解析参数
})
const getEventType = state.options.platform.getEventType
let optType = isCustom ? customize(type) : getEventType(type) // 比如自定义组件使用了 click 自定义事件
if (isOnce) {
optType = VUE_EVENT_MODIFIERS.once + optType
}
if (isCustom) {
optType = VUE_EVENT_MODIFIERS.custom + optType
}
opts.push({
opt: t.arrayExpression([
t.stringLiteral(optType),
t.arrayExpression(methods)
]),
params
})
keyPath.replaceWith(
t.stringLiteral(
state.options.platform.formatEventType(
isCustom ? customize(type) : getEventType(type), // 比如自定义组件使用了 click 自定义事件
isCatch,
isCapture,
isCustom
)
)
)
valuePath.replaceWith(t.stringLiteral(INTERNAL_EVENT_PROXY))
}
return opts
}
module.exports = function processEvent (paths, path, state, isComponent, tagName) {
const onPath = paths.on
const nativeOnPath = paths.nativeOn
const ret = []
const opts = []
const params = []
if (onPath) {
_processEvent(onPath, state, isComponent, false, tagName, ret).forEach(({ opt, params: array }) => {
opts.push(opt)
params.push(...array)
})
}
if (nativeOnPath) {
_processEvent(nativeOnPath, state, isComponent, true, tagName, ret).forEach(({ opt, params: array }) => {
opts.push(opt)
params.push(...array)
})
}
if (!opts.length) {
return ret
}
ret.push(
t.objectProperty(
t.stringLiteral(ATTR_DATA_EVENT_OPTS),
t.arrayExpression(opts)
)
)
if (params.length) {
ret.push(
t.objectProperty(
t.stringLiteral(ATTR_DATA_EVENT_PARAMS),
t.objectExpression(params.map(param => t.objectProperty(t.identifier(param), t.identifier(param), false, true)))
)
)
}
return ret
}
@@ -0,0 +1,156 @@
const t = require('@babel/types')
const {
METHOD_CREATE_ELEMENT,
ATTR_DATA_EVENT_OPTS,
ATTR_DATA_COM_TYPE,
ATTR_DATA_EVENT_LIST,
ATTR_DATA_EVENT_PARAMS,
ATTR_DATA_CUSTOM_HIDDEN,
INTERNAL_EVENT_WRAP
} = require('../../../constants')
const processRef = require('./ref')
const processAttrs = require('./attrs')
const processClass = require('./class')
const processEvent = require('./event')
const processStyle = require('./style')
const processModel = require('./model')
const processDir = require('./directives')
module.exports = function traverseData (path, state, tagName) {
if (path.node.$mpProcessed) {
return
}
path.node.$mpProcessed = true
const paths = {}
const propertyPaths = path.get('properties')
propertyPaths.forEach((propertyPath, index) => {
paths[propertyPath.node.key.name] = propertyPath
})
const addAttrProperties = []
const isComponent = state.options.platform.isComponent(tagName)
const processes = [processAttrs, processRef, processClass, processModel, processDir, processEvent, processStyle]
// ref(add staticClass) > class,model,dir(add input event)>event
processes.forEach(process => {
process(paths, path, state, isComponent, tagName).forEach((property) => {
addAttrProperties.push(property)
})
})
// 该组件是引入的小程序组件
const wxComponent = state.options.wxComponents[tagName]
if (wxComponent) {
addAttrProperties.push(
t.objectProperty(
t.stringLiteral(ATTR_DATA_COM_TYPE),
t.stringLiteral('wx')
)
)
if (state.options.platform.name === 'mp-alipay') {
if (!wxComponent.startsWith('plugin://')) {
addAttrProperties.push(
t.objectProperty(
t.stringLiteral('ref'),
t.stringLiteral('__r')
)
)
}
const on = path.node.properties.find(prop => prop.key.name === 'on')
if (on) {
const properties = on.value.properties
const list = []
for (let index = 0; index < properties.length; index++) {
const element = properties[index]
if (element.value.value === '__e') {
list.push(element.key.value)
}
}
if (list.length) {
addAttrProperties.push(
t.objectProperty(
t.stringLiteral(ATTR_DATA_EVENT_LIST),
t.stringLiteral(list.join(','))
)
)
}
}
if (wxComponent.startsWith('plugin://')) {
const wrapperTag = 'plugin-wrapper'
const orgPath = path.parentPath
const orgNode = orgPath.node
const args = orgNode.arguments
const orgTag = args[0]
orgTag.$mpPlugin = true
args[0] = t.stringLiteral(wrapperTag)
const orgOptions = args[1]
const orgOptionsProps = orgOptions.properties
const targetAttrs = []
const targetOptionsProps = [
t.objectProperty(t.identifier('attrs'), t.objectExpression(targetAttrs))
]
const uniAttrs = [
ATTR_DATA_EVENT_OPTS,
ATTR_DATA_COM_TYPE,
ATTR_DATA_EVENT_PARAMS,
ATTR_DATA_EVENT_LIST,
ATTR_DATA_CUSTOM_HIDDEN,
'vue-id'
]
for (let a = orgOptionsProps.length - 1; a >= 0; a--) {
const prop = orgOptionsProps[a]
if (prop.key.name === 'attrs') {
const attrs = prop.value.properties
for (let b = attrs.length - 1; b >= 0; b--) {
const element = attrs[b]
const key = element.key.value
if (!uniAttrs.includes(key)) {
attrs.splice(b, 1)
targetAttrs.push(element)
}
}
attrs.push(t.objectProperty(t.stringLiteral('onPluginWrap'), t.stringLiteral(INTERNAL_EVENT_WRAP)))
} else if (prop.key.name === 'on') {
const ons = prop.value.properties
ons.forEach(item => {
const attrs = path.node.properties.find(prop => prop.key.name === 'attrs').value.properties
const vueId = attrs.find(prop => prop.key.value === 'vue-id').value
const eventName = item.key.value
targetAttrs.push(t.objectProperty(t.stringLiteral(eventName), t.binaryExpression('+', t.stringLiteral(eventName), vueId)))
})
} else {
orgOptionsProps.splice(a, 1)
targetOptionsProps.push(prop)
}
}
const orgChild = args[2]
const targetOptions = t.objectExpression(targetOptionsProps)
targetOptions.$mpProcessed = true
const targetArguments = [
orgTag,
targetOptions
]
if (orgChild) {
targetArguments.push(orgChild)
}
const targetNode = t.callExpression(t.identifier(METHOD_CREATE_ELEMENT), targetArguments)
args[2] = targetNode
}
}
}
if (addAttrProperties.length) {
const attrsPath = paths.attrs
if (attrsPath) {
attrsPath.node.value.properties = attrsPath.node.value.properties.concat(addAttrProperties)
} else {
path.node.properties.unshift(
t.objectProperty(t.identifier('attrs'), t.objectExpression(addAttrProperties))
)
}
}
}
@@ -0,0 +1,74 @@
const t = require('@babel/types')
const {
getModelEventFunctionExpr
} = require('./util')
module.exports = function processRef (paths, path, state) {
const modelPath = paths.model
if (modelPath) {
const properties = modelPath.node.value.properties
const [callbackProperty] = properties.splice(properties.findIndex(property => {
return property.key.name === 'callback'
}), 1)
const valueProperty = properties.find(
property => property.key.name === 'value'
)
const exprProperty = properties.find(
property => property.key.name === 'expression'
)
const prop = exprProperty.value.value.trim()
const onPath = paths.on
// on:{'input':__m('msg',$event)}
if (!onPath) {
path.node.properties.unshift(
t.objectProperty(t.identifier('on'), t.objectExpression([
t.objectProperty(
t.stringLiteral('input'),
getModelEventFunctionExpr(
callbackProperty.value,
prop
)
)
]))
)
paths.on = path.get('properties').find(
propertyPath => propertyPath.node.key.name === 'on'
)
} else {
const existingInput = onPath.node.value.properties.find(
property => property.key.value === 'input'
)
if (existingInput) {
if (!t.isArrayExpression(existingInput.value)) {
existingInput.value = t.arrayExpression([existingInput.value])
}
existingInput.value.elements.unshift(getModelEventFunctionExpr(
callbackProperty.value,
prop
))
} else {
onPath.node.value.properties.push(
t.objectProperty(
t.stringLiteral('input'),
getModelEventFunctionExpr(
callbackProperty.value,
prop
)
)
)
}
}
return [ // attrs:{value:value}
t.objectProperty(
t.stringLiteral(process.env.UNI_USING_VUE3 ? 'modelValue' : 'value'),
valueProperty.value
)
]
}
return []
}
@@ -0,0 +1,43 @@
const t = require('@babel/types')
const {
CLASS_REF,
CLASS_REF_IN_FOR
} = require('../../../constants')
module.exports = function processRef (paths, path, state) {
const refPath = paths.ref
if (refPath) {
if (state.options.platform.name === 'mp-alipay') {
return [
t.objectProperty( // data-ref="" ,data-ref-in-for=""
t.stringLiteral('ref'),
t.stringLiteral('__r')
),
t.objectProperty( // data-ref="" ,data-ref-in-for=""
t.stringLiteral(state.inFor ? state.options.platform.refInFor : state.options.platform.ref),
refPath.node.value
)
]
}
const refClass = state.inFor ? CLASS_REF_IN_FOR : CLASS_REF
const staticClassPath = paths.staticClass
if (staticClassPath) { // append
staticClassPath.node.value.value = staticClassPath.node.value.value + ' ' + refClass
} else { // add staticClass
path.node.properties.unshift(
t.objectProperty(t.identifier('staticClass'), t.stringLiteral(refClass))
)
paths.staticClass = path.get('properties').find(
propertyPath => propertyPath.node.key.name === 'staticClass'
)
}
return [
t.objectProperty( // data-ref="" ,头条 vue-ref
t.stringLiteral(state.options.platform.ref),
refPath.node.value
)
]
}
return []
}
@@ -0,0 +1,203 @@
const t = require('@babel/types')
const {
IDENTIFIER_STYLE,
INTERNAL_GET_STYLE,
VIRTUAL_HOST_STYLE
} = require('../../../constants')
const {
getCode,
hyphenate,
isRootElement
} = require('../../../util')
const getMemberExpr = require('../member-expr')
const REGEX_PX = /(:|\s|\(|\/)[+-]?\d+(\.\d+)?u?px/g
const REGEX_UPX = /(:|\s|\(|\/)[+-]?\d+(\.\d+)?upx/g
function processStaticStyleUnit (styleStr, state) {
if (typeof styleStr === 'string') {
let matches = styleStr.match(REGEX_UPX)
if (matches && matches.length) {
matches.forEach(function (match) {
styleStr = styleStr.replace(match, match.substr(0, match.length - 3) + 'rpx')
})
}
// TODO 不应该再支持 px 转 rpx
if (state.options.transformPx) { // 需要转换 px
matches = styleStr.match(REGEX_PX)
if (matches && matches.length) {
matches.forEach(function (match) {
styleStr = styleStr.replace(match, match.substr(0, match.length - 2) + 'rpx')
})
}
}
}
return styleStr
}
function getStaticStyleStringLiteral (staticStylePath, state) {
const staticStyle = staticStylePath.node.value.properties
.map(property => {
return `${property.key.value}:${property.value.value}`
})
.join(';')
const staticStyleStr = processStaticStyleUnit(staticStyle, state).trim()
return t.stringLiteral(staticStyleStr + (!staticStyleStr.endsWith(';') ? ';' : ''))
}
function processStaticStyle (binaryExpressions, staticStylePath, state) {
let binaryExpression
binaryExpressions.forEach(binaryExpr => {
if (!binaryExpression) {
if (staticStylePath) {
binaryExpression = t.binaryExpression(
'+',
getStaticStyleStringLiteral(staticStylePath, state),
binaryExpr
)
staticStylePath.remove()
} else {
binaryExpression = binaryExpr
}
} else {
binaryExpression = t.binaryExpression(
'+',
binaryExpression,
binaryExpr
)
}
})
return binaryExpression
}
function processStyleObjectExpression (styleValuePath) {
const binaryExpressions = []
const propertyPaths = styleValuePath.get('properties')
propertyPaths.forEach(propertyPath => {
const key = propertyPath.node.key
binaryExpressions.push(
t.binaryExpression(
'+',
t.binaryExpression(
'+',
t.stringLiteral(hyphenate(key.name || key.value) + ':'),
t.parenthesizedExpression(propertyPath.node.value)
),
t.stringLiteral(';')
)
)
})
return binaryExpressions
}
function processStyleArrayExpression (elementPaths) {
let binaryExpressions = []
elementPaths.forEach(elementPath => {
binaryExpressions = binaryExpressions.concat(processStyleObjectExpression(elementPath))
})
return binaryExpressions
}
function generateGetStyle (stylePath, styleValuePath, staticStylePath, state) {
const args = [stylePath.node.value]
if (staticStylePath) {
args.push(staticStylePath.node.value)
staticStylePath.remove()
}
styleValuePath.replaceWith(
getMemberExpr(
stylePath,
IDENTIFIER_STYLE,
t.callExpression(t.identifier(INTERNAL_GET_STYLE), args),
state
)
)
}
module.exports = function processStyle (paths, path, state) {
const stylePath = paths.style
const staticStylePath = paths.staticStyle
const mergeVirtualHostAttributes = state.options.mergeVirtualHostAttributes
if (stylePath) {
const styleValuePath = stylePath.get('value')
if (styleValuePath.isObjectExpression()) {
// {} {...{}} {...{color}}
const hasDynamicContent = styleValuePath.node.properties.some(prop =>
!t.isObjectProperty(prop) && !t.isObjectExpression(prop.value)
)
const isEmptyObject = styleValuePath.node.properties.length === 0
if (hasDynamicContent || isEmptyObject) {
generateGetStyle(stylePath, styleValuePath, staticStylePath, state)
} else {
styleValuePath.replaceWith(
processStaticStyle(
processStyleObjectExpression(styleValuePath),
staticStylePath,
state
)
)
}
} else if (styleValuePath.isArrayExpression()) { // array
const elementPaths = styleValuePath.get('elements')
const dynamicStyle = elementPaths.find(elementPath => !elementPath.isObjectExpression())
if (dynamicStyle) {
generateGetStyle(stylePath, styleValuePath, staticStylePath, state)
} else {
styleValuePath.replaceWith(
processStaticStyle(
processStyleArrayExpression(elementPaths),
staticStylePath,
state
)
)
}
} else if (
styleValuePath.isStringLiteral() || // :style="'background:red'"
styleValuePath.isIdentifier() || // TODO 需要优化到下一个条件,:style="styleObject"
styleValuePath.isMemberExpression() || // TODO 需要优化到下一个条件,:style="item.styleObject"
styleValuePath.isConditionalExpression() ||
styleValuePath.isLogicalExpression() ||
styleValuePath.isBinaryExpression()
) {
// 理论上 ConditionalExpression,LogicalExpression 可能存在 styleObject,应该__get_style,还是先不考虑这种情况吧
// ConditionalExpression :style="index === currentIndex ? activeStyle : itemStyle"
// BinaryExpression :style="'m-content-head-'+message.user"
styleValuePath.replaceWith(
processStaticStyle(
[t.parenthesizedExpression(styleValuePath.node)],
staticStylePath,
state
)
)
} else if (
styleValuePath.isIdentifier() ||
styleValuePath.isMemberExpression()
) { // TODO 目前先不考虑 classObject,styleObject
// generateGetStyle(stylePath, styleValuePath, staticStylePath, state)
} else {
state.errors.add(`:style 不支持 ${getCode(styleValuePath.node)} 语法`)
}
if (mergeVirtualHostAttributes && isRootElement(path.parentPath)) {
styleValuePath.replaceWith(t.binaryExpression('+', styleValuePath.node, t.identifier(VIRTUAL_HOST_STYLE)))
}
} else if (staticStylePath) {
if (mergeVirtualHostAttributes && isRootElement(path.parentPath)) {
const styleNode = processStaticStyle([t.identifier(VIRTUAL_HOST_STYLE)], staticStylePath, state)
const property = t.objectProperty(t.identifier('style'), styleNode)
path.node.properties.push(property)
return []
}
staticStylePath.get('value').replaceWith(getStaticStyleStringLiteral(staticStylePath, state))
} else {
if (mergeVirtualHostAttributes && isRootElement(path.parentPath)) {
const property = t.objectProperty(t.identifier('style'), t.identifier(VIRTUAL_HOST_STYLE))
path.node.properties.push(property)
}
}
return []
}
@@ -0,0 +1 @@
// 需要转换 h5 标签至 staticClass 的样式名
@@ -0,0 +1,47 @@
const t = require('@babel/types')
const babelTraverse = require('@babel/traverse').default
const {
INTERNAL_SET_MODEL
} = require('../../../constants')
module.exports = {
getModelEventFunctionExpr (funcExpr, propPath, modifiers = []) {
let targetExpr
let keyExpr
babelTraverse(funcExpr, {
noScope: true,
CallExpression (path) {
if (path.node.callee.name === '$set') {
targetExpr = path.node.arguments[0]
keyExpr = path.node.arguments[1]
}
}
})
if (!targetExpr || !keyExpr) {
targetExpr = t.stringLiteral('')
keyExpr = t.stringLiteral(propPath)
}
return t.functionExpression(
null,
[t.identifier('$event')],
t.blockStatement(
[
t.returnStatement(
t.callExpression(
t.identifier(INTERNAL_SET_MODEL),
[
targetExpr,
keyExpr,
t.identifier('$event'),
t.arrayExpression(modifiers)
]
)
)
]
)
)
}
}
@@ -0,0 +1,179 @@
const t = require('@babel/types')
const {
VAR_FILTER
} = require('../../constants')
const GLOBAL_METHODS = [
'parseInt',
'parseFloat',
'isNaN',
'isFinite',
'decodeURI',
'decodeURIComponent',
'encodeURI',
'encodeURIComponent'
]
const GLOBAL_OBJECTS = {
Math: [
'abs',
'acos',
'asin',
'atan',
'atan2',
'ceil',
'cos',
'exp',
'floor',
'log',
'max',
'min',
'pow',
'random',
'round',
'sin',
'sqrt',
'tan'
],
JSON: [
'stringify',
'parse'
]
}
const BUILT_IN_METHODS = [
// number
'toString',
'toLocaleString',
'valueOf',
'toFixed',
'toExponential',
'toPrecision',
// string
// 'toString',
// 'valueOf',
'charAt',
'charCodeAt',
'concat',
'indexOf',
'lastIndexOf',
'localeCompare',
'match',
'replace',
'search',
'slice',
'split',
'substring',
'toLowerCase',
'toLocaleLowerCase',
'toUpperCase',
'toLocaleUpperCase',
'trim',
// boolean
// 'toString',
// 'valueOf',
// object
// 'toString',
// function
// 'toString',
// array
// 'toString',
// 'concat',
'join',
'pop',
'push',
'reverse',
'shift',
// 'slice',
'sort',
'splice',
'unshift',
// 'indexOf',
// 'lastIndexOf',
'every',
'some',
'forEach',
'map',
'filter',
'reduce',
'reduceRight'
]
function getGlobalMethodFilter (callExpr) {
const callee = callExpr.callee
if (callee) {
const name = callee.name
if (name && GLOBAL_METHODS.includes(name)) {
return t.callExpression(
t.memberExpression(
t.identifier(VAR_FILTER),
t.identifier(name)
),
callExpr.arguments
)
}
}
return false
}
function getGlobalObjectFilter (callExpr) {
const callee = callExpr.callee
if (t.isMemberExpression(callee)) {
const object = callee.object
const property = callee.property
const propertyName = property.name || property.value
const methods = GLOBAL_OBJECTS[object.name]
if (methods && methods.includes(propertyName)) {
return t.callExpression(
t.memberExpression(
t.identifier(VAR_FILTER),
t.identifier(propertyName)
),
callExpr.arguments
)
}
}
return false
}
function getMemberFilter (callExpr) {
const callee = callExpr.callee
if (t.isMemberExpression(callee)) {
const property = callee.property
const propertyName = property.name || property.value
if (BUILT_IN_METHODS.includes(propertyName)) {
return t.callExpression(
t.memberExpression(
t.identifier(VAR_FILTER),
t.identifier(propertyName)
),
[
callee.object,
...callExpr.arguments
]
)
}
}
}
function processFilter (callExpr, path) {
const globalMethodFilter = getGlobalMethodFilter(callExpr)
if (globalMethodFilter) {
path.replaceWith(globalMethodFilter)
return true
}
const globalObjectFilter = getGlobalObjectFilter(callExpr)
if (globalObjectFilter) {
path.replaceWith(globalObjectFilter)
return true
}
const memberFilter = getMemberFilter(callExpr)
if (memberFilter) {
path.replaceWith(memberFilter)
return true
}
return false
}
module.exports = processFilter
@@ -0,0 +1,139 @@
const t = require('@babel/types')
const babelTraverse = require('@babel/traverse').default
const {
VAR_ROOT,
IDENTIFIER_FOR,
IDENTIFIER_ATTR,
IDENTIFIER_METHOD,
IDENTIFIER_FILTER,
IDENTIFIER_CLASS,
IDENTIFIER_STYLE,
IDENTIFIER_EVENT,
IDENTIFIER_GLOBAL,
IDENTIFIER_TEXT,
PREFIX_ATTR,
PREFIX_GLOBAL,
PREFIX_METHOD,
PREFIX_FILTER,
PREFIX_FOR,
PREFIX_CLASS,
PREFIX_STYLE,
PREFIX_EVENT,
PREFIX_TEXT
} = require('../../constants')
const {
getInItIfStatement,
getDataExpressionStatement,
getRenderSlotStatement
} = require('./statements')
const visitor = require('./visitor')
function reIdentifier (identifierArray) {
const identifierOpts = {
[IDENTIFIER_FOR]: {
prefix: PREFIX_FOR,
id: 0
},
[IDENTIFIER_METHOD]: {
prefix: PREFIX_METHOD,
id: 0
},
[IDENTIFIER_FILTER]: {
prefix: PREFIX_FILTER,
id: 0
},
[IDENTIFIER_CLASS]: {
prefix: PREFIX_CLASS,
id: 0
},
[IDENTIFIER_STYLE]: {
prefix: PREFIX_STYLE,
id: 0
},
[IDENTIFIER_EVENT]: {
prefix: PREFIX_EVENT,
id: 0
},
[IDENTIFIER_GLOBAL]: {
prefix: PREFIX_GLOBAL,
id: 0
},
[IDENTIFIER_ATTR]: {
prefix: PREFIX_ATTR,
id: 0
},
[IDENTIFIER_TEXT]: {
prefix: PREFIX_TEXT,
id: 0
}
}
// TODO order
identifierArray.forEach(identifier => {
if (Array.isArray(identifier)) {
let opts = false
identifier.forEach(stringLiteral => {
const key = t.isStringLiteral(stringLiteral) ? 'value' : 'name'
if (opts === false) {
opts = identifierOpts[stringLiteral[key]]
stringLiteral[key] = `${opts.prefix + opts.id++}`
} else {
stringLiteral[key] = `${opts.prefix + (opts.id - 1)}`
}
})
} else {
const key = t.isStringLiteral(identifier) ? 'value' : 'name'
const opts = identifierOpts[identifier[key]]
identifier[key] = `${opts.prefix + opts.id++}`
}
})
}
module.exports = function traverse (ast, state) {
const identifierArray = []
const blockStatementBody = []
const objectPropertyArray = []
const initExpressionStatementArray = []
const renderSlotStatementArray = []
const resolveSlotStatementArray = []
// TODO 待重构,至少 filtermethod 等实现方式要调整
babelTraverse(ast, visitor, undefined, {
scoped: [],
context: VAR_ROOT,
options: state.options,
errors: state.errors,
tips: state.tips,
identifierArray: identifierArray,
propertyArray: objectPropertyArray,
declarationArray: blockStatementBody,
initExpressionStatementArray: initExpressionStatementArray,
renderSlotStatementArray,
resolveSlotStatementArray
})
if (initExpressionStatementArray.length) {
blockStatementBody.push(getInItIfStatement(initExpressionStatementArray))
}
if (objectPropertyArray.length) {
blockStatementBody.push(getDataExpressionStatement(objectPropertyArray))
}
if (renderSlotStatementArray.length) {
blockStatementBody.push(getRenderSlotStatement(state, renderSlotStatementArray))
}
if (resolveSlotStatementArray.length) {
blockStatementBody.push(...resolveSlotStatementArray)
}
reIdentifier(identifierArray)
return t.withStatement(
t.thisExpression(),
t.blockStatement(blockStatementBody)
)
}
@@ -0,0 +1,134 @@
const t = require('@babel/types')
const traverse = require('@babel/traverse').default
const {
VAR_ROOT,
IDENTIFIER_METHOD,
IDENTIFIER_FILTER,
IDENTIFIER_GLOBAL,
METHOD_RENDER_LIST
} = require('../../constants')
function isMatch (name, forItem, forIndex) {
return name === forItem || name === forIndex
}
function findScoped (path, test, state) {
if (!path) {
return state
}
const scoped = state.scoped.find(scoped => {
const {
forItem,
forIndex,
path: listPath
} = scoped
const funPath = path.findParent(path =>
path.isFunctionExpression() &&
t.isCallExpression(path.parentPath) &&
path.parentPath.node.callee.name === METHOD_RENDER_LIST
)
if (funPath && funPath.parentPath === listPath) {
// TODO 为兼容历史结构仅在当前 list 父级存在 v-if 返回
const parent = listPath.findParent(path => path.isFunctionExpression() || path.isConditionalExpression())
if (parent && parent.isConditionalExpression()) {
return true
}
} else {
return false
}
let match = false
path.traverse({
noScope: true,
Identifier (path) {
if (!match && path.key !== 'key' && (path.key !== 'property' || path.parent.computed)) {
match = isMatch(path.node.name, forItem, forIndex)
if (match) {
path.stop()
}
}
}
})
if (!match && test) {
traverse(t.arrayExpression([test]), {
noScope: true,
Identifier (path) {
if (!match && path.key !== 'key' && (path.key !== 'property' || path.parent.computed)) {
const node = path.node
match = isMatch(node.name, forItem, forIndex) || scoped.declarationArray.find(({ declarations }) => declarations.find(({ id }) => id === node))
if (match) {
path.stop()
}
}
}
})
}
return match
})
if (!scoped && state.scoped.length > 1) {
return state.scoped[1] // 取父
}
return scoped || state
}
function findTest (path, state) {
let tests
if (path) {
while (path.parentPath && path.key !== 'body') {
if (path.key === 'consequent' || path.key === 'alternate') {
const testOrig = path.container.test
let test = t.arrayExpression([t.cloneDeep(testOrig)])
traverse(test, {
noScope: true,
MemberExpression (memberExpressionPath) {
const names = state.scoped.map(scoped => scoped.forItem)
const node = memberExpressionPath.node
const objectName = node.object.name
const property = node.property
const propertyName = property.name
if (objectName === VAR_ROOT || (names.includes(objectName) && (propertyName === IDENTIFIER_METHOD || propertyName === IDENTIFIER_FILTER || propertyName === IDENTIFIER_GLOBAL))) {
const array = []
let tempPath = memberExpressionPath
while (tempPath.parentPath) {
const key = tempPath.key
array.unshift(typeof key === 'number' ? `[${key}]` : `.${key}`)
tempPath = tempPath.parentPath
}
memberExpressionPath.replaceWith(path.parentPath.get('test' + array.join('')).node.property)
}
}
})
test = test.elements[0]
if (path.key === 'alternate') {
test = t.unaryExpression('!', test)
}
tests = tests ? t.logicalExpression('&&', test, tests) : test
}
path = path.parentPath
}
}
return tests
}
module.exports = function getMemberExpr (path, name, init, state, variableDeclaration = true) {
const test = findTest(path, state)
const scoped = findScoped(path, test, state)
if (!variableDeclaration) {
scoped.declarationArray.push(t.expressionStatement(init))
return
}
const identifier = t.identifier(name)
scoped.propertyArray.push(t.objectProperty(identifier, identifier))
scoped.declarationArray.push(
t.variableDeclaration('var', [t.variableDeclarator(identifier, test ? t.conditionalExpression(test, init, t.nullLiteral()) : init)])
)
state.identifierArray.push(identifier)
const contextIdentifier = t.identifier(scoped.context)
contextIdentifier.$mpProcessed = true
return t.memberExpression(contextIdentifier, identifier)
}
@@ -0,0 +1,235 @@
const t = require('@babel/types')
const {
VAR_ORIGINAL,
VAR_INDEX,
IDENTIFIER_FOR,
METHOD_RENDER_LIST
} = require('../../constants')
const {
getMapCallExpression
} = require('./statements')
const {
hasOwn,
genCode,
traverseKey,
processMemberExpression,
getForIndexIdentifier,
isSimpleObjectExpression,
traverseFilter
} = require('../../util')
const getMemberExpr = require('./member-expr')
const origVisitor = {
noScope: true,
Identifier (path) {
if (
!path.node.$mpProcessed &&
path.node.name === this.forItem &&
path.isReferencedIdentifier()
) {
const forItemIdentifier = t.identifier(this.forItem)
forItemIdentifier.$mpProcessed = true
path.replaceWith(
t.memberExpression(forItemIdentifier, t.identifier(VAR_ORIGINAL))
)
}
},
FunctionExpression (path) {
const callee = path.parentPath.node.callee
if (t.isIdentifier(callee) && callee.name === METHOD_RENDER_LIST) {
path.traverse(origVisitor, {
forItem: this.forItem
})
path.skip()
}
}
}
function isRefrence (forItem, code) {
if (forItem === code) {
return true
}
return code.indexOf(forItem + '.') === 0
}
function replaceRefrence (forItem, code) {
if (forItem === code) {
return ''
}
return code.replace(forItem + '.', '')
}
function getForExtra (forItem, forIndex, path, state) {
const arg0 = path.node.arguments[0]
const isNumeric = t.isNumericLiteral(arg0)
const isString = t.isStringLiteral(arg0)
let forCode = genCode(processMemberExpression(arg0, state), true)
const forKey = traverseKey(path.node)
const origForKeyCode = t.isIdentifier(forKey) && forKey.name
let forKeyCode = ''
if (forKey) {
forKeyCode = genCode(processMemberExpression(forKey, state), true)
if (isRefrence(forItem, forKeyCode)) {
forKeyCode = replaceRefrence(forItem, forKeyCode)
}
}
const forExtraElements = []
if (state.scoped.length) {
const scoped = state.scoped.find(scoped => isRefrence(scoped.forItem, forCode))
if (scoped) {
forCode = replaceRefrence(scoped.forItem, forCode)
forExtraElements.push(...scoped.forExtra)
}
}
let forCodeElem = t.stringLiteral(forCode)
if (isNumeric) {
forCodeElem = t.numericLiteral(arg0.value)
} else if (isString) {
forCodeElem = t.stringLiteral('#s#' + forCode)
}
if (forItem === origForKeyCode) { // 以自身为 key,则依据 forIndex 查找 ['list','',__i0__],['list','',index]
forExtraElements.push(
t.arrayExpression(
[
forCodeElem,
t.stringLiteral(''),
t.identifier(forIndex)
]
)
)
} else {
forExtraElements.push(
t.arrayExpression(
[
forCodeElem,
t.stringLiteral(forIndex === forKeyCode ? '' : forKeyCode),
forKey || t.identifier(forIndex)
]
)
)
}
return forExtraElements
}
module.exports = function traverseRenderList (path, state) {
const functionExpression = path.get('arguments.1')
const params = functionExpression.node.params
const forItem = params[0].name
let forIndex = params.length > 1 && params[1].name
let forKey = params.length > 2 && params[2].name
if (forKey) {
[forKey, forIndex] = [forIndex, forKey]
}
if (!forIndex) {
if (!hasOwn(state.options, '$forIndexId')) {
state.options.$forIndexId = 0
}
forIndex = getForIndexIdentifier(state.options.$forIndexId++)
params.push(t.identifier(forIndex))
}
const forStateScoped = {
context: forItem,
forItem,
forKey,
forIndex,
forExtra: getForExtra(forItem, forIndex, path, state),
propertyArray: [],
declarationArray: [],
renderSlotStatementArray: [],
path
}
const forState = {
inFor: true,
context: state.context,
options: state.options,
errors: state.errors,
tips: state.tips,
scoped: [forStateScoped].concat(state.scoped),
identifierArray: state.identifierArray,
propertyArray: [],
declarationArray: [],
computedProperty: {},
initExpressionStatementArray: state.initExpressionStatementArray,
renderSlotStatementArray: state.renderSlotStatementArray,
resolveSlotStatementArray: state.resolveSlotStatementArray
}
functionExpression.traverse(require('./visitor'), forState)
const forPath = path.get('arguments.0')
if (forStateScoped.propertyArray.length || forStateScoped.renderSlotStatementArray.length || forKey) {
// for => map
forPath.replaceWith(
getMemberExpr(
forPath,
IDENTIFIER_FOR,
getMapCallExpression(
forPath.node,
forStateScoped.propertyArray,
forStateScoped.declarationArray,
forStateScoped.renderSlotStatementArray,
[], // eventPropertyArray
forItem,
forKey,
forIndex,
state
),
forState
)
)
functionExpression.traverse(origVisitor, {
forItem
})
if (forKey) {
functionExpression.traverse({
Identifier (path) {
if (
!path.node.$mpProcessed &&
path.node.name === this.forIndex &&
path.isReferencedIdentifier()
) {
const forItemIdentifier = t.identifier(VAR_INDEX)
forItemIdentifier.$mpProcessed = true
path.replaceWith(
t.memberExpression(t.identifier(this.forItem), forItemIdentifier)
)
}
}
}, {
forItem,
forIndex
})
}
const keys = Object.keys(forState.computedProperty)
if (keys.length) {
keys.forEach(key => {
const property = forState.computedProperty[key]
if (t.isMemberExpression(property) && property.object.name === forItem) {
property.object = t.memberExpression(t.identifier(forItem), t.identifier(VAR_ORIGINAL))
forState.options.replaceCodes[key] = `'+${genCode(property, true)}+'`
}
})
}
} else if ((forPath.isCallExpression() && !traverseFilter(forPath.node.callee, state)) || (forPath.isObjectExpression() && !isSimpleObjectExpression(forPath.node))) {
forPath.replaceWith(getMemberExpr(forPath, IDENTIFIER_FOR, forPath.node, forState))
} else {
forPath.traverse(require('./visitor'), forState)
}
forState.propertyArray.forEach(property => {
state.propertyArray.push(property)
})
forState.declarationArray.forEach(declaration => {
state.declarationArray.push(declaration)
})
}
@@ -0,0 +1,54 @@
const t = require('@babel/types')
const initStatement = t.expressionStatement(t.callExpression(t.identifier('$initSSP'), []))
const resolveStatement = t.expressionStatement(t.callExpression(t.identifier('$callSSP'), []))
module.exports = function getRenderSlot (path, state) {
const name = path.get('arguments.0')
const arg2 = path.get('arguments.2')
const arg3 = path.get('arguments.3')
let valueNode
if (arg3) {
// v-bind:object
valueNode = arg3.node
} else if (arg2 && !arg2.isNullLiteral()) {
if (arg2.isObjectExpression()) {
const propertiesPath = arg2.get('properties')
const oldProperties = []
const newProperties = []
propertiesPath.forEach(path => {
const properties = path.get('key').isStringLiteral({ value: 'SLOT_DEFAULT' }) ? oldProperties : newProperties
properties.push(state.options.scopedSlotsCompiler === 'auto' ? path.node : t.cloneNode(path.node, true))
})
if (!newProperties.length) {
return
}
valueNode = t.objectExpression(newProperties)
if (state.options.scopedSlotsCompiler !== 'auto') {
arg2.replaceWith(t.objectExpression(oldProperties))
}
} else {
valueNode = arg2.node
}
}
if (valueNode) {
if (!state.declarationArray.includes(initStatement)) {
state.declarationArray.push(initStatement)
}
const indexNode = t.callExpression(t.identifier('$setSSP'), [name.node, valueNode])
const slotMultipleInstance = state.options.scopedSlotsCompiler === 'augmented' && state.options.slotMultipleInstance
if (slotMultipleInstance) {
// 插槽名拼接 '.'+index
name.replaceWith(t.binaryExpression('+', name.node, t.binaryExpression('+', t.stringLiteral('.'), indexNode)))
} else {
const scoped = state.scoped
// TODO 判断是否包含作用域内变量
const renderSlotStatementArray = scoped && scoped.length ? scoped[scoped.length - 1].renderSlotStatementArray : state.renderSlotStatementArray
renderSlotStatementArray.push(t.expressionStatement(indexNode))
}
if (!state.resolveSlotStatementArray.includes(resolveStatement)) {
state.resolveSlotStatementArray.push(resolveStatement)
}
}
// TODO 组件嵌套
}
@@ -0,0 +1,159 @@
const t = require('@babel/types')
const template = require('@babel/template').default
const {
METHOD_BUILT_IN,
METHOD_CREATE_EMPTY_VNODE,
METHOD_CREATE_ELEMENT,
METHOD_RENDER_LIST
} = require('../../constants')
function findBinding (fnPath, idPath) {
const name = idPath.node.name
return fnPath.scope.bindings[name].referencePaths.find(refPath => refPath === idPath)
}
function needAugmentedSlotMode (path, ids, state) {
const platformName = state.options.platform.name
const fnPath = path.parentPath
let need
path.traverse({
noScope: false,
Property (path) {
// 跳过事件
if (path.node.key.name === 'on') {
const parentPath = path.parentPath.parentPath
if (t.isCallExpression(parentPath) && parentPath.node.callee.name === METHOD_CREATE_ELEMENT) {
path.skip()
}
}
},
Identifier (path) {
const name = path.node.name
if (path.key !== 'key' && (path.key !== 'property' || path.parent.computed)) {
// 使用作用域内方法或作用域外数据
if (name in ids) {
if (path.key === 'callee') {
need = true
}
} else if (!path.scope.hasBinding(name) && !METHOD_BUILT_IN.includes(name)) {
// 原生支持作用域插槽的平台允许使用作用域外数据,暂时只考虑作用内的数据作为方法参数的情况
if (['mp-baidu', 'mp-alipay'].includes(platformName)) {
if (path.key === 'callee') {
path.parentPath.traverse({
noScope: false,
Identifier (path) {
const name = path.node.name
if (name in ids && findBinding(fnPath, path)) {
need = true
path.stop()
}
}
})
}
} else {
need = true
}
}
} else if (platformName === 'mp-weixin' && path.key === 'property' && name === 'length') {
// 微信小程序平台无法观测 Array length 访问:https://developers.weixin.qq.com/community/develop/doc/000c8ee47d87a0d5b6685a8cb57000
need = true
}
if (need) {
path.stop()
}
}
})
return need
}
function replaceId (path, ids) {
let replaced
const fnPath = path.parentPath
path.traverse({
noScope: false,
Identifier (path) {
const name = path.node.name
if (name in ids && findBinding(fnPath, path)) {
path.replaceWith(t.cloneNode(ids[name], true))
replaced = true
}
}
})
return replaced
}
module.exports = function getResolveScopedSlots (parent, state) {
const elements0 = parent.get('arguments.0.elements.0')
let objectPath = elements0
// TODO v-else
if (objectPath.isConditionalExpression()) {
objectPath = objectPath.get('consequent')
}
if (objectPath.isCallExpression()) {
objectPath = objectPath.get('arguments.1.body.body.0.argument')
}
const properties = objectPath.get('properties')
const fn = properties.find(path => path.get('key').isIdentifier({ name: 'fn' }))
const params = fn.get('value.params.0')
if (!params) {
return
}
const vueId = parent.parentPath.parentPath.get('properties').find(path => path.get('key').isIdentifier({ name: 'attrs' })).get('value').get('properties').find(path => path.get('key').isStringLiteral({ value: 'vue-id' })).get('value').node
// TODO 多层 v-for 嵌套时,后续处理作用域可能发生变化,需安全重命名
const slotPath = properties.find(path => path.get('key').isIdentifier({ name: 'key' })).get('value')
const slotNode = slotPath.node
const slotMultipleInstance = state.options.scopedSlotsCompiler === 'augmented' && state.options.slotMultipleInstance
const scopedSlotsParams = {
item: elements0.scope.generateUidIdentifier('item'),
index: elements0.scope.generateUidIdentifier('index')
}
const ids = {}
function updateIds (vueId, slot, value, key) {
let node = slotMultipleInstance ? scopedSlotsParams.item : t.callExpression(t.identifier('$getSSP'), [vueId, slot])
if (key) {
node = t.memberExpression(node, t.stringLiteral(key), true)
}
ids[value] = node
}
if (params.isObjectPattern()) {
params.get('properties').forEach(prop => {
updateIds(vueId, slotNode, prop.get('value').node.name, prop.get('key').node.name)
})
} else if (params.isIdentifier()) {
updateIds(vueId, slotNode, params.node.name)
}
const fnBody = fn.get('value.body')
// 非原生支持作用域插槽的平台在含有动态 slotName 的情况下,scopedSlotsCompiler 指定使用增强编译模式
const isStaticSlotName = t.isStringLiteral(slotNode)
if (state.options.scopedSlotsCompiler === 'augmented' || needAugmentedSlotMode(fnBody, ids, state) || (!['mp-baidu', 'mp-alipay'].includes(state.options.platform.name) && !isStaticSlotName)) {
if (replaceId(fnBody, ids)) {
const test = t.callExpression(t.identifier('$hasSSP'), [vueId])
// scopedSlotsCompiler auto
objectPath.node.scopedSlotsCompiler = 'augmented'
if (slotMultipleInstance) {
// elements0 节点替换增加一层循环
let node = elements0.node
const builder = template(`${METHOD_RENDER_LIST}($getSSP(%%vueId%%, %%slot%%, true), function (%%item%%, %%index%%) {return %%node%%})`)
node = builder({
vueId,
slot: slotNode,
node,
item: scopedSlotsParams.item,
index: scopedSlotsParams.index
}).expression
node = t.conditionalExpression(test, node, t.callExpression(t.identifier(METHOD_CREATE_EMPTY_VNODE), []))
elements0.replaceWith(node)
// 插槽名拼接 '.'+index
// 百度、字节小程序不支持 v-for 嵌套 slot,且支持渲染多个实例,固定输出到第一个
const indexNode = ['mp-baidu', 'mp-toutiao'].includes(state.options.platform.name) ? t.numericLiteral(0) : scopedSlotsParams.index
slotPath.replaceWith(t.binaryExpression('+', slotNode, t.binaryExpression('+', t.stringLiteral('.'), indexNode)))
} else {
const orgin = fnBody.get('body.0.argument')
const elements = orgin.get('elements')
const node = (elements.length === 1 ? elements[0] : orgin).node
orgin.replaceWith(t.arrayExpression([t.conditionalExpression(test, node, t.callExpression(t.identifier(METHOD_CREATE_EMPTY_VNODE), []))]))
}
}
}
}
@@ -0,0 +1,188 @@
const t = require('@babel/types')
const {
VAR_MP,
VAR_ROOT,
VAR_ORIGINAL,
VAR_INDEX,
INTERNAL_GET_ORIG,
IDENTIFIER_METHOD,
IDENTIFIER_FILTER
} = require('../../constants')
/**
* e0=e=>count++
*/
function getEventExpressionStatement (left, right) {
return t.expressionStatement(
t.assignmentExpression(
'=',
left,
right
)
)
}
/**
* if(!_isMounted){}
*/
function getInItIfStatement (expressionStatementArray) {
return t.ifStatement(
t.unaryExpression(
'!',
t.identifier('_isMounted')
),
t.blockStatement(expressionStatementArray)
)
}
function getRenderSlotStatement (state, renderSlotStatementArray, forItem) {
function cloneNode (node) {
if (Array.isArray(node)) {
return node.map(function (item) {
return cloneNode(item)
})
} else if (typeof node === 'object') {
if (!node) {
return node
}
if (t.isMemberExpression(node)) { // 纠正被处理过的对象
const name = node.object.name
// identifier 使用原值以被后续修改
if ((name === VAR_ROOT || name === forItem) && t.isIdentifier(node.property) && [IDENTIFIER_METHOD, IDENTIFIER_FILTER].includes(node.property.name)) {
return node.property
}
} else if (t.isIdentifier(node, { name: forItem })) { // 预处理 forItem
return t.identifier(VAR_ORIGINAL)
}
const target = Object.create(node)
Object.keys(node).forEach(function (key) {
target[key] = cloneNode(node[key])
})
return target
} else {
return node
}
}
renderSlotStatementArray.forEach(renderSlotStatement => {
const argument = renderSlotStatement.expression.arguments[1]
if (t.isObjectExpression(argument)) {
// 克隆以避免影响模板
argument.properties = cloneNode(argument.properties)
}
})
const blockStatement = t.blockStatement(renderSlotStatementArray)
if (state.options.scopedSlotsCompiler === 'auto') {
return t.ifStatement(
t.binaryExpression('===',
t.memberExpression(t.memberExpression(t.identifier('$scope'), t.identifier(state.options.platform.name === 'mp-alipay' ? 'props' : 'data')), t.identifier('scopedSlotsCompiler')), t.stringLiteral('augmented')
),
blockStatement
)
}
return blockStatement
}
/**
* items.map(function(item,index){return {}})
*/
function getMapCallExpression (
object,
objectPropertyArray,
declarationArray,
renderSlotStatementArray,
eventPropertyArray,
forItem,
forKey,
forIndex,
state
) {
const blockStatement = []
// var $orgi = __get_orig(forItem)
blockStatement.push(t.variableDeclaration('var', [
t.variableDeclarator(t.identifier(VAR_ORIGINAL), t.callExpression(t.identifier(INTERNAL_GET_ORIG), [
t.identifier(forItem)
]))
]))
if (declarationArray.length) {
declarationArray.forEach(declaration => {
blockStatement.push(declaration)
})
}
if (renderSlotStatementArray.length) {
blockStatement.push(getRenderSlotStatement(state, renderSlotStatementArray, forItem))
}
blockStatement.push(t.returnStatement(
// return {$orgi:$orgi}
t.objectExpression(
[
t.objectProperty(
t.identifier(VAR_ORIGINAL),
t.identifier(VAR_ORIGINAL)
)
].concat(objectPropertyArray)
.concat(forKey && forIndex ? [t.objectProperty(
t.identifier(VAR_INDEX),
t.identifier(forIndex)
)] : [])
)
))
const params = [t.identifier(forItem)]
if (forKey) {
params.push(t.identifier(forKey))
}
if (forIndex) {
params.push(t.identifier(forIndex))
}
return t.callExpression(t.identifier('__map'), [
object,
t.functionExpression(
null,
params,
t.blockStatement(blockStatement)
)
])
}
/**
* $mp.data = Object.assign({},{$root:{}})
*/
function getDataExpressionStatement (objectPropertyArray) {
return t.expressionStatement(
t.assignmentExpression(
'=',
t.memberExpression(
// left
t.identifier(VAR_MP),
t.identifier('data')
),
t.callExpression(
// right
t.memberExpression(
// Object.assign
t.identifier('Object'),
t.identifier('assign')
),
[
t.objectExpression([]), // {}
t.objectExpression([
// {$root:{}}
t.objectProperty(
t.identifier(VAR_ROOT),
t.objectExpression(objectPropertyArray)
)
])
]
)
)
)
}
module.exports = {
getInItIfStatement,
getMapCallExpression,
getDataExpressionStatement,
getEventExpressionStatement,
getRenderSlotStatement
}
@@ -0,0 +1,325 @@
const t = require('@babel/types')
const uniI18n = require('@dcloudio/uni-cli-i18n')
const {
METHOD_CREATE_ELEMENT,
METHOD_TO_STRING,
METHOD_RENDER_LIST,
METHOD_BUILT_IN,
METHOD_RESOLVE_FILTER,
METHOD_RENDER_SLOT,
METHOD_RESOLVE_SCOPED_SLOTS,
IDENTIFIER_FILTER,
IDENTIFIER_METHOD,
IDENTIFIER_GLOBAL,
IDENTIFIER_TEXT
} = require('../../constants')
const {
getTagName
} = require('../../h5')
const {
hasOwn,
hyphenate,
traverseFilter,
getComponentName,
hasEscapeQuote,
hasLengthProperty,
isRootElement
} = require('../../util')
const traverseData = require('./data')
const traverseRenderList = require('./render-list')
const getMemberExpr = require('./member-expr')
const getRenderSlot = require('./render-slot')
const getResolveScopedSlots = require('./resolve-scoped-slots')
function addStaticClass (path, staticClass) {
const dataPath = path.get('arguments.1')
if (dataPath && dataPath.isObjectExpression()) {
const staticClassProperty = dataPath.node.properties.find(property => property.key.name === 'staticClass')
if (staticClassProperty) { // update
staticClassProperty.value.value = staticClassProperty.value.value + ' ' + staticClass
} else { // add
dataPath.node.properties.push(
t.objectProperty(t.identifier('staticClass'), t.stringLiteral(staticClass))
)
}
} else { // {staticClass:'data-v-aaa'}
const args = path.node.arguments
args.splice(1, 0, t.objectExpression(
[
t.objectProperty(t.identifier('staticClass'), t.stringLiteral(staticClass))
]
))
}
}
function addVueId (path, state) {
// const platformName = state.options.platform.name
// if ( // 暂不对 mp-weixin,app-plus 增加 vueId
// platformName === 'mp-weixin' ||
// platformName === 'app-plus'
// ) {
// return
// }
if (!hasOwn(state.options, '$vueId')) {
state.options.$vueId = 1
}
const hashId = state.options.hashId
const vueId = (hashId ? (hashId + '-') : '') + (state.options.$vueId++)
let value
if (state.scoped.length) {
const scopeds = state.scoped
const len = scopeds.length
if (len > 1) { // v-for 嵌套,forIndex 不允许重复
const forIndexSet = new Set()
for (let i = 0; i < len; i++) {
const scoped = scopeds[i]
forIndexSet.add(scoped.forIndex)
if (forIndexSet.size !== i + 1) {
state.errors.add(uniI18n.__('templateCompiler.forNestedIndexNameNoArrowRepeat', { 0: 'v-for', 1: scoped.forIndex }))
break
}
}
}
for (let i = len - 1; i >= 0; i--) {
const scoped = scopeds[i]
if (!value) {
value = t.binaryExpression('+', t.stringLiteral(vueId + '-'), t.identifier(scoped.forIndex))
} else {
value = t.binaryExpression('+',
t.binaryExpression('+', value, t.stringLiteral('-')),
t.identifier(scoped.forIndex)
)
}
}
} else {
value = t.stringLiteral(vueId)
}
const objectProperty = t.objectProperty(
t.stringLiteral('vue-id'),
value
)
const dataPath = path.get('arguments.1')
if (dataPath && dataPath.isObjectExpression()) {
const attrsProperty = dataPath.node.properties.find(property => property.key.name === 'attrs')
if (attrsProperty) {
attrsProperty.value.properties.unshift(objectProperty)
} else {
dataPath.node.properties.push(
t.objectProperty(t.identifier('attrs'), t.objectExpression([
objectProperty
]))
)
}
} else { // {attrs:{'vue-id':'2'}}
const args = path.node.arguments
args.splice(1, 0, t.objectExpression(
[
t.objectProperty(t.identifier('attrs'), t.objectExpression([
objectProperty
]))
]
))
}
}
function checkUsingGlobalComponents (name, globalUsingComponents, state) {
if (globalUsingComponents && globalUsingComponents[name]) {
if (!state.options.usingGlobalComponents) {
state.options.usingGlobalComponents = Object.create(null)
}
state.options.usingGlobalComponents[name] = globalUsingComponents[name]
}
}
module.exports = {
noScope: false,
MemberExpression (path) {
if ( // t.m(123)
t.isIdentifier(path.node.object) &&
this.options.filterModules.includes(path.node.object.name)
) {
path.skip()
}
// 微信小程序平台无法观测 Array length 访问:https://developers.weixin.qq.com/community/develop/doc/000c8ee47d87a0d5b6685a8cb57000
if (this.options.platform.name === 'mp-weixin' && hasLengthProperty(path)) {
let newPath = path
while (newPath) {
path = newPath
newPath = path.findParent((path) => path.isLogicalExpression())
}
path.skip()
if (path.findParent((path) => path.shouldSkip || (this.options.scopedSlotsCompiler === 'legacy' && path.isCallExpression() && path.node.callee.name === METHOD_RESOLVE_SCOPED_SLOTS))) {
return
}
path.replaceWith(getMemberExpr(path, IDENTIFIER_GLOBAL, path.node, this))
}
},
CallExpression (path) {
const callee = path.node.callee
if (traverseFilter(callee, this)) {
return path.skip()
} else if (t.isIdentifier(callee)) {
const methodName = callee.name
switch (methodName) {
case METHOD_CREATE_ELEMENT:
{
const tagNode = path.node.arguments[0]
if (t.isStringLiteral(tagNode)) {
// 需要把标签增加到 class 样式中
const tagName = getTagName(tagNode.value, this.options.platform.name)
if (tagName !== tagNode.value) {
addStaticClass(path, '_' + tagNode.value)
}
tagNode.value = getComponentName(hyphenate(tagName))
// 组件增加 vueId
// 跳过支付宝插件组件
if (this.options.platform.isComponent(tagNode.value) && !tagNode.$mpPlugin) {
addVueId(path, this)
}
// 查找全局组件
checkUsingGlobalComponents(
tagNode.value,
this.options.globalUsingComponents,
this
)
}
if (this.options.scopeId) {
addStaticClass(path, this.options.scopeId)
}
// 根节点无 attrs 时添加空对象,方便后续合并外层 attrs
if (this.options.mergeVirtualHostAttributes && !t.isObjectExpression(path.node.arguments[1]) && isRootElement(path)) {
path.node.arguments.splice(1, 0, t.objectExpression([]))
}
const dataPath = path.get('arguments.1')
dataPath && dataPath.isObjectExpression() && traverseData(dataPath, this, tagNode.value)
}
break
case METHOD_TO_STRING:
{
const stringPath = path.get('arguments.0')
if (hasEscapeQuote(stringPath)) {
// 属性中包含转义引号时部分小程序平台报错或显示异常
// TODO 简单情况翻转外层引号
stringPath.replaceWith(getMemberExpr(path, IDENTIFIER_TEXT, stringPath.node, this))
}
const stringNodes = stringPath.node
stringNodes.$toString = true
path.replaceWith(stringNodes)
}
break
case METHOD_RENDER_LIST:
traverseRenderList(path, this)
path.skip()
break
default:
// TODO 检测是否是 filterModules
if (!METHOD_BUILT_IN.includes(methodName)) {
if (
path.findParent(
path =>
path.isObjectProperty() && ['on', 'nativeOn'].includes(path.node.key.name)
)
// path is model.callback
// || path.findParent(path => path.isObjectProperty() && path.node.key.name === 'callback' && t.isFunctionExpression(path.node.value) && t.isObjectProperty(path.parentPath.parentPath) && path.parentPath.parentPath.node.key.name === 'model')
) {
// event
return path.skip()
}
let newPath = path
while (newPath) {
path = newPath
newPath = path.findParent((path) => path.isLogicalExpression())
}
path.skip()
if (path.findParent((path) => path.shouldSkip)) {
return
}
path.replaceWith(
getMemberExpr(
path,
methodName === METHOD_RESOLVE_FILTER
? IDENTIFIER_FILTER
: IDENTIFIER_METHOD,
path.node,
this
)
)
} else if (this.options.scopedSlotsCompiler === 'auto' || this.options.scopedSlotsCompiler === 'augmented') {
if (methodName === METHOD_RESOLVE_SCOPED_SLOTS) {
getResolveScopedSlots(path, this)
} else if (methodName === METHOD_RENDER_SLOT) {
getRenderSlot(path, this)
}
}
break
}
} else if (
t.isCallExpression(callee) &&
t.isIdentifier(callee.callee) &&
callee.callee.name === METHOD_RESOLVE_FILTER
) {
// multi filter
path.replaceWith(getMemberExpr(path, IDENTIFIER_FILTER, path.node, this))
} else if (
t.isMemberExpression(callee) // message.split('').reverse().join('')
) {
// Object.assign...
let newPath = path
while (newPath) {
path = newPath
newPath = path.findParent((path) => path.isLogicalExpression())
}
path.skip()
if (path.findParent((path) => path.shouldSkip)) {
return
}
path.replaceWith(getMemberExpr(path, IDENTIFIER_GLOBAL, path.node, this))
}
},
TemplateLiteral (path) {
const nodes = []
const expressions = path.get('expressions')
let index = 0
for (const elem of path.node.quasis) {
if (elem.value.cooked) {
nodes.push(t.stringLiteral(elem.value.cooked))
}
if (index < expressions.length) {
const expr = expressions[index++]
const node = expr.node
if (!t.isStringLiteral(node, {
value: ''
})) {
nodes.push(node)
}
}
}
// since `+` is left-to-right associative
// ensure the first node is a string if first/second isn't
const considerSecondNode = !t.isStringLiteral(nodes[1])
if (!t.isStringLiteral(nodes[0]) && considerSecondNode) {
nodes.unshift(t.stringLiteral(''))
}
let root = nodes[0]
for (let i = 1; i < nodes.length; i++) {
root = t.binaryExpression('+', root, nodes[i])
}
path.replaceWith(root)
}
}