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.
270 lines
6.4 KiB
270 lines
6.4 KiB
const {
|
|
parse
|
|
} = require('mustache')
|
|
const recast = require('recast')
|
|
|
|
const TAGS = [
|
|
'ad',
|
|
'audio',
|
|
'button',
|
|
'camera',
|
|
'canvas',
|
|
'checkbox',
|
|
'checkbox-group',
|
|
'cover-image',
|
|
'cover-view',
|
|
'editor',
|
|
'form',
|
|
'functional-page-navigator',
|
|
'icon',
|
|
'image',
|
|
'input',
|
|
'label',
|
|
'live-player',
|
|
'live-pusher',
|
|
'map',
|
|
'movable-area',
|
|
'movable-view',
|
|
'navigator',
|
|
'official-account',
|
|
'open-data',
|
|
'picker',
|
|
'picker-view',
|
|
'picker-view-column',
|
|
'progress',
|
|
'radio',
|
|
'radio-group',
|
|
'rich-text',
|
|
'scroll-view',
|
|
'slider',
|
|
'swiper',
|
|
'swiper-item',
|
|
'switch',
|
|
'text',
|
|
'textarea',
|
|
'video',
|
|
'view',
|
|
'web-view'
|
|
]
|
|
|
|
const EVENTS = {
|
|
'touchstart': 'touchstart',
|
|
'touchmove': 'touchmove',
|
|
'touchcancel': 'touchcancel',
|
|
'touchend': 'touchend',
|
|
'tap': 'click',
|
|
'longpress': 'longpress',
|
|
'longtap': 'longpress',
|
|
'transitionend': 'transitionend',
|
|
'animationstart': 'animationstart',
|
|
'animationiteration': 'animationiteration',
|
|
'animationend': 'animationend',
|
|
'touchforcechange': 'touchforcechange'
|
|
}
|
|
|
|
const ATTRS = {
|
|
'wx:if': 'v-if',
|
|
'wx:elif': 'v-else-if',
|
|
'wx:else': 'v-else'
|
|
}
|
|
|
|
const FOR = {
|
|
for: 'wx:for',
|
|
item: 'wx:for-item',
|
|
index: 'wx:for-index',
|
|
key: 'wx:key'
|
|
}
|
|
|
|
const FOR_DEFAULT = {
|
|
item: 'item',
|
|
index: 'index',
|
|
index_fallback: '___i___'
|
|
}
|
|
|
|
function parseMustache(expr, identifier = false) {
|
|
if (!expr) {
|
|
return ''
|
|
}
|
|
const tokens = parse(expr)
|
|
const isIdentifier = tokens.length === 1
|
|
return tokens.map(token => {
|
|
if (token[0] === 'text') {
|
|
if (identifier) {
|
|
return token[1]
|
|
}
|
|
return `'${token[1]}'`
|
|
} else if (token[0] === '!') { // {{ !loading }}
|
|
return `(!${token[1]})`
|
|
} else if (token[0] === 'name') {
|
|
if (isIdentifier) {
|
|
return token[1]
|
|
}
|
|
return `(${token[1]})`
|
|
}
|
|
}).join('+')
|
|
}
|
|
|
|
|
|
function transformDirective(name, value, attribs) {
|
|
if (ATTRS[name]) {
|
|
attribs[ATTRS[name]] = parseMustache(value)
|
|
return true
|
|
}
|
|
}
|
|
|
|
function transformFor(attribs) {
|
|
const vFor = attribs[FOR.for]
|
|
if (!vFor) {
|
|
return
|
|
}
|
|
let vKey = parseMustache(attribs[FOR.key], true)
|
|
const vItem = parseMustache(attribs[FOR.item], true) || FOR_DEFAULT.item
|
|
const vIndex = parseMustache(attribs[FOR.index], true) || (
|
|
FOR_DEFAULT.index === vItem ? FOR_DEFAULT.index_fallback : FOR_DEFAULT.index
|
|
//处理 wx:for-item="index"
|
|
)
|
|
|
|
attribs['v-for'] = `(${vItem},${vIndex}) in (${parseMustache(vFor)})`
|
|
if (vKey) {
|
|
if (vKey === '*this') {
|
|
vKey = vItem
|
|
} else if (vKey !== vItem && vKey.indexOf('.') === -1) { // wx:for-key="{{item.value}}"
|
|
vKey = vItem + '.' + vKey
|
|
}
|
|
attribs[':key'] = vKey
|
|
}
|
|
|
|
delete attribs[FOR.for]
|
|
delete attribs[FOR.item]
|
|
delete attribs[FOR.index]
|
|
delete attribs[FOR.key]
|
|
}
|
|
|
|
const bindRE = /bind:?/
|
|
const catchRE = /catch:?/
|
|
const captureBindRE = /capture-bind:?/
|
|
const captureCatchRE = /capture-catch:?/
|
|
|
|
function transformEventName(name, state) {
|
|
if (state.isComponent) {
|
|
return '@' + (EVENTS[name] ? (EVENTS[name] + '.native') : name)
|
|
}
|
|
return '@' + (EVENTS[name] || name)
|
|
}
|
|
|
|
function transformEvent(name, value, attribs, state) {
|
|
let event = name
|
|
if (name.indexOf('bind') === 0) {
|
|
event = transformEventName(name.replace(bindRE, ''), state)
|
|
} else if (name.indexOf('catch') === 0) {
|
|
event = transformEventName(name.replace(catchRE, ''), state) + '.stop.prevent'
|
|
} else if (name.indexOf('capture-bind') === 0) {
|
|
event = transformEventName(name.replace(captureBindRE, ''), state) + '.capture'
|
|
} else if (name.indexOf('capture-catch') === 0) {
|
|
event = transformEventName(name.replace(captureCatchRE, ''), state) + '.stop.prevent.capture'
|
|
}
|
|
if (event !== name) {
|
|
// 模板 <template name> 中用到的方法在其父组件
|
|
let newValue = parseMustache(value, !state.isTemplate)
|
|
if (state.isTemplate) {
|
|
// TODO 改为运行时判断
|
|
newValue = `_$self.$parent${process.env.UNI_PLATFORM === 'h5' ? '.$parent' : ''}[(${newValue})]($event)`
|
|
} else if (newValue !== value) {
|
|
newValue = `_$self[(${newValue})||'_$noop']($event)`
|
|
}
|
|
attribs[event] = newValue
|
|
return true
|
|
}
|
|
}
|
|
|
|
|
|
function transformAttr(name, value, attribs, state) {
|
|
if (
|
|
name.indexOf('v-') === 0 ||
|
|
name.indexOf(':') === 0
|
|
) { // 已提前处理
|
|
return
|
|
}
|
|
delete attribs[name]
|
|
if (transformDirective(name, value, attribs)) {
|
|
return
|
|
}
|
|
if (transformEvent(name, value, attribs, state)) {
|
|
return
|
|
}
|
|
if (value.indexOf('{{') === -1) {
|
|
attribs[name] = value
|
|
return
|
|
}
|
|
attribs[':' + name] = parseMustache(value)
|
|
}
|
|
|
|
function transformAttrs(node, state) {
|
|
const attribs = node.attribs
|
|
if (!attribs) {
|
|
return
|
|
}
|
|
transformFor(attribs)
|
|
const isComponent = !TAGS.includes(node.name)
|
|
const isTemplate = state.templates.length
|
|
Object.keys(attribs).forEach(name => {
|
|
transformAttr(name, attribs[name], attribs, {
|
|
isComponent,
|
|
isTemplate
|
|
})
|
|
})
|
|
}
|
|
|
|
function transformChildren(node, state) {
|
|
node.children = node.children.filter(childNode => transformNode(childNode, state))
|
|
}
|
|
|
|
function transformTemplate(node, state) {
|
|
const attribs = node.attribs
|
|
if (attribs.name) {
|
|
const name = attribs.name
|
|
// 用于处理一个 wxml 文件内包含多个 template
|
|
attribs['v-if'] = `wxTemplateName === '${name}'`
|
|
delete attribs.name
|
|
state.templates.push(name)
|
|
} else if (attribs.is) {
|
|
const name = attribs.is
|
|
delete attribs.is
|
|
node.name = name
|
|
attribs['wx-template-name'] = name
|
|
const data = attribs.data
|
|
if (data && data.indexOf('{{') !== -1) {
|
|
const object = `{${parseMustache(data)}}`
|
|
attribs['v-bind'] = object
|
|
const ast = recast.parse(`const object = ${object}`)
|
|
const props = state.props[name] || ['wxTemplateName']
|
|
ast.program.body[0].declarations[0].init.properties.forEach(property => props.push(property.key.name))
|
|
state.props[name] = [...new Set(props)]
|
|
delete attribs.data
|
|
}
|
|
}
|
|
}
|
|
|
|
function transformNode(node, state) {
|
|
if (node.name === 'import') {
|
|
state.components.push(node)
|
|
return false
|
|
}
|
|
if (node.name === 'template') {
|
|
transformTemplate(node, state)
|
|
}
|
|
if (node.name === 'wxs') {
|
|
state.wxs.push(node)
|
|
return false
|
|
}
|
|
if (node.type === 'tag') {
|
|
transformAttrs(node, state)
|
|
transformChildren(node, state)
|
|
}
|
|
return true
|
|
}
|
|
|
|
module.exports = function traverse(node, state) {
|
|
transformNode(node, state)
|
|
return node
|
|
}
|
|
|