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.
435 lines
14 KiB
435 lines
14 KiB
"use strict"; |
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
exports.genElementProps = exports.genNode = exports.generate = void 0; |
|
const shared_1 = require("@vue/shared"); |
|
const uni_shared_1 = require("@dcloudio/uni-shared"); |
|
const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared"); |
|
const compiler_core_1 = require("@vue/compiler-core"); |
|
const codegen_1 = require("../codegen"); |
|
const vFor_1 = require("../transforms/vFor"); |
|
const vIf_1 = require("../transforms/vIf"); |
|
const vSlot_1 = require("../transforms/vSlot"); |
|
const utils_1 = require("../transforms/utils"); |
|
function generate({ children }, { slot, event, scopeId, emitFile, filename, directive, lazyElement, isBuiltInComponent, isMiniProgramComponent, component, }) { |
|
const context = { |
|
slot, |
|
event, |
|
code: '', |
|
scopeId, |
|
directive, |
|
lazyElement, |
|
component, |
|
isBuiltInComponent, |
|
isMiniProgramComponent, |
|
push(code) { |
|
context.code += code; |
|
}, |
|
}; |
|
children.forEach((node) => { |
|
genNode(node, context); |
|
}); |
|
emitFile({ type: 'asset', fileName: filename, source: context.code }); |
|
} |
|
exports.generate = generate; |
|
function genNode(node, context) { |
|
switch (node.type) { |
|
case 9 /* NodeTypes.IF */: |
|
return node.branches.forEach((node) => { |
|
genNode(node, context); |
|
}); |
|
case 2 /* NodeTypes.TEXT */: |
|
return genText(node, context); |
|
case 5 /* NodeTypes.INTERPOLATION */: |
|
return genExpression(node.content, context); |
|
case 1 /* NodeTypes.ELEMENT */: |
|
if (node.tagType === 2 /* ElementTypes.SLOT */) { |
|
return genSlot(node, context); |
|
} |
|
else if (node.tagType === 1 /* ElementTypes.COMPONENT */) { |
|
return genComponent(node, context); |
|
} |
|
else if (node.tagType === 3 /* ElementTypes.TEMPLATE */) { |
|
return genTemplate(node, context); |
|
} |
|
else if (isLazyElement(node, context)) { |
|
return genLazyElement(node, context); |
|
} |
|
return genElement(node, context); |
|
} |
|
} |
|
exports.genNode = genNode; |
|
function genText(node, { push }) { |
|
push(node.content); |
|
} |
|
function genExpression(node, { push }) { |
|
push(`{{${(0, codegen_1.genExpr)(node)}}}`); |
|
} |
|
function genVIf(exp, { push, directive }) { |
|
push(` ${directive}if="{{${exp}}}"`); |
|
} |
|
function genVElseIf(exp, { push, directive }) { |
|
push(` ${directive}elif="{{${exp}}}"`); |
|
} |
|
function genVElse({ push, directive }) { |
|
push(` ${directive}else`); |
|
} |
|
function genVFor(node, { push, directive }) { |
|
const { sourceCode, valueAlias, indexAlias } = node.vFor; |
|
push(` ${directive}for="${sourceCode}"`); |
|
if (valueAlias) { |
|
push(` ${directive}for-item="${valueAlias}"`); |
|
} |
|
if (valueAlias === 'index') { |
|
push(` ${directive}for-index="${indexAlias}"`); |
|
} |
|
const keyProp = (0, compiler_core_1.findProp)(node, 'key', true); |
|
if (keyProp) { |
|
const key = keyProp.exp.content; |
|
push(` ${directive}key="${key.includes('.') ? key.split('.')[1] : key}"`); |
|
node.props.splice(node.props.indexOf(keyProp), 1); |
|
} |
|
} |
|
function genSlot(node, context) { |
|
// 移除掉所有非name属性,即移除作用域插槽的绑定指令 |
|
node.props = node.props.filter((prop) => { |
|
if (prop.type === 6 /* NodeTypes.ATTRIBUTE */) { |
|
return prop.name === 'name'; |
|
} |
|
else if (prop.arg?.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */) { |
|
return prop.arg.content === 'name'; |
|
} |
|
}); |
|
if (!node.children.length || context.slot.fallbackContent) { |
|
// 无后备内容或支持后备内容 |
|
return genElement(node, context); |
|
} |
|
const { push } = context; |
|
const isVIfSlot = (0, vIf_1.isIfElementNode)(node); |
|
if (isVIfSlot) { |
|
push(`<block`); |
|
genVIfCode(node, context); |
|
push(`>`); |
|
delete node.vIf; |
|
} |
|
const children = node.children.slice(); |
|
node.children.length = 0; |
|
push(`<block`); |
|
const nameProp = (0, compiler_core_1.findProp)(node, 'name'); |
|
let name = uni_shared_1.SLOT_DEFAULT_NAME; |
|
if (nameProp) { |
|
if ((0, uni_cli_shared_1.isAttributeNode)(nameProp)) { |
|
if (nameProp.value?.content) { |
|
name = nameProp.value.content; |
|
} |
|
} |
|
else { |
|
if (nameProp.slotName) { |
|
name = nameProp.slotName; |
|
} |
|
} |
|
} |
|
genVIf(`$slots.${name}`, context); |
|
push(`>`); |
|
genElement(node, context); |
|
push(`</block>`); |
|
push(`<block`); |
|
genVElse(context); |
|
push(`>`); |
|
children.forEach((node) => { |
|
genNode(node, context); |
|
}); |
|
push(`</block>`); |
|
if (isVIfSlot) { |
|
push(`</block>`); |
|
} |
|
} |
|
function genTemplate(node, context) { |
|
const slotProp = node.props.find((prop) => prop.type === 7 /* NodeTypes.DIRECTIVE */ && |
|
(prop.name === 'slot' || |
|
(prop.name === 'bind' && |
|
prop.arg?.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */ && |
|
prop.arg.content === 'slot'))); |
|
// 为 bind 时,通常是作用域插槽生成的 vSlot.ts:197 createBindDirectiveNode('slot',...) |
|
if (slotProp && (slotProp.name === 'bind' || (0, vSlot_1.findSlotName)(slotProp))) { |
|
/** |
|
* 仅百度、字节支持使用 block 作为命名插槽根节点 |
|
* 此处为了统一仅默认替换为view |
|
* <template v-slot/> => <view slot=""> |
|
*/ |
|
node.tag = 'view'; |
|
} |
|
else { |
|
// <template/> => <block/> |
|
node.tag = 'block'; |
|
} |
|
// @ts-ignore |
|
node.tagType = 0 /* ElementTypes.ELEMENT */; |
|
// 仅单个子节点的命名插槽(非作用域),直接使用子节点作为插槽使用,避免多增加的 view 节点影响 flex 排版 |
|
if (slotProp && |
|
node.tag === 'view' && |
|
!(0, vFor_1.isForElementNode)(node) && |
|
node.children.length === 1) { |
|
const child = node.children[0]; |
|
if ((0, uni_cli_shared_1.isElementNode)(child) && |
|
!(0, vFor_1.isForElementNode)(child) && |
|
!(0, compiler_core_1.isSlotOutlet)(child)) { |
|
child.props.push(slotProp); |
|
return genElement(child, context); |
|
} |
|
} |
|
return genElement(node, context); |
|
} |
|
function genComponent(node, context) { |
|
if (context.component?.getPropertySync) { |
|
return genElement(node, context); |
|
} |
|
if ((0, vIf_1.isIfElementNode)(node) || (0, vFor_1.isForElementNode)(node)) { |
|
return genElement(node, context); |
|
} |
|
// 小程序原生组件,补充 if(r0) |
|
if (context.isMiniProgramComponent(node.tag)) { |
|
; |
|
node.vIf = { |
|
name: 'if', |
|
condition: 'r0', |
|
}; |
|
return genElement(node, context); |
|
} |
|
const prop = (0, compiler_core_1.findProp)(node, utils_1.ATTR_VUE_PROPS); |
|
if (!prop) { |
|
return genElement(node, context); |
|
} |
|
; |
|
node.vIf = { |
|
name: 'if', |
|
condition: prop.exp.content, |
|
}; |
|
return genElement(node, context); |
|
} |
|
function isLazyElement(node, context) { |
|
if (!context.lazyElement) { |
|
return false; |
|
} |
|
let lazyProps; |
|
if ((0, shared_1.isFunction)(context.lazyElement)) { |
|
const res = context.lazyElement(node, context); |
|
if (!(0, shared_1.isPlainObject)(res)) { |
|
return res; |
|
} |
|
lazyProps = res[node.tag]; |
|
} |
|
else { |
|
lazyProps = context.lazyElement[node.tag]; |
|
} |
|
if (lazyProps === true) { |
|
return true; |
|
} |
|
if (!lazyProps) { |
|
return; |
|
} |
|
return node.props.some((prop) => prop.type === 7 /* NodeTypes.DIRECTIVE */ && |
|
lazyProps.find((lazyProp) => { |
|
return (prop.name === lazyProp.name && |
|
prop.arg?.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */ && |
|
lazyProp.arg.includes(prop.arg.content)); |
|
})); |
|
} |
|
/** |
|
* 部分内置组件的部分事件在初始化时会立刻触发,但标准事件需要等首次渲染才能确认事件函数,故增加wx:if="{{r0}}" |
|
* @param node |
|
* @param context |
|
*/ |
|
function genLazyElement(node, context) { |
|
const { push } = context; |
|
if (!(0, vIf_1.isIfElementNode)(node)) { |
|
push(`<block`); |
|
// r0 => ready 首次渲染 |
|
genVIf(`r0`, context); |
|
push(`>`); |
|
genElement(node, context); |
|
push(`</block>`); |
|
return; |
|
} |
|
// v-if,v-else-if 无需处理 |
|
if (node.vIf.name !== 'else') { |
|
return genElement(node, context); |
|
} |
|
push(`<block`); |
|
genVElse(context); |
|
push(`>`); |
|
node.vIf.name = 'if'; |
|
node.vIf.condition = 'r0'; |
|
genElement(node, context); |
|
push(`</block>`); |
|
} |
|
function genVIfCode(node, context) { |
|
const { name, condition } = node.vIf; |
|
if (name === 'if') { |
|
genVIf(condition, context); |
|
} |
|
else if (name === 'else-if') { |
|
genVElseIf(condition, context); |
|
} |
|
else if (name === 'else') { |
|
genVElse(context); |
|
} |
|
} |
|
function genElement(node, context) { |
|
const { children, isSelfClosing, props } = node; |
|
let tag = node.tag; |
|
// <template slot="left"/> => <block slot="left"/> |
|
if (tag === 'template') { |
|
if ((0, compiler_core_1.findProp)(node, 'slot')) { |
|
tag = 'view'; |
|
} |
|
else { |
|
tag = 'block'; |
|
} |
|
} |
|
// 无用的 block |
|
if (tag === 'block' && |
|
props.length === 0 && |
|
!(0, vIf_1.isIfElementNode)(node) && |
|
!(0, vFor_1.isForElementNode)(node)) { |
|
return children.forEach((node) => { |
|
genNode(node, context); |
|
}); |
|
} |
|
let virtualHost = false; |
|
if ((0, uni_cli_shared_1.isUserComponent)(node, context)) { |
|
tag = (0, shared_1.hyphenate)(tag); |
|
if (context.component?.normalizeName) { |
|
tag = context.component?.normalizeName(tag); |
|
} |
|
if (context.component?.mergeVirtualHostAttributes) { |
|
virtualHost = true; |
|
} |
|
} |
|
const { push } = context; |
|
const hasVIf = (0, vIf_1.isIfElementNode)(node); |
|
const hasVFor = (0, vFor_1.isForElementNode)(node); |
|
const hasVIfAndVFor = hasVIf && hasVFor; |
|
// 小程序中 wx:else wx:elif 不支持与 wx:for 同时使用 |
|
// 故 if 需要补充一层 block |
|
if (hasVIfAndVFor) { |
|
push(`<block`); |
|
genVIfCode(node, context); |
|
push(`>`); |
|
} |
|
push(`<${tag}`); |
|
if (!hasVIfAndVFor && hasVIf) { |
|
genVIfCode(node, context); |
|
} |
|
if (hasVFor) { |
|
genVFor(node, context); |
|
} |
|
if (props.length) { |
|
genElementProps(node, virtualHost, context); |
|
} |
|
if (isSelfClosing) { |
|
push(`/>`); |
|
} |
|
else { |
|
push(`>`); |
|
children.forEach((node) => { |
|
genNode(node, context); |
|
}); |
|
push(`</${tag}>`); |
|
} |
|
if (hasVIfAndVFor) { |
|
push(`</block>`); |
|
} |
|
} |
|
function checkVirtualHostProps(name, virtualHost) { |
|
const names = [name]; |
|
if (virtualHost) { |
|
const obj = { |
|
style: utils_1.VIRTUAL_HOST_STYLE, |
|
class: utils_1.VIRTUAL_HOST_CLASS, |
|
}; |
|
if (name in obj) { |
|
// TODO 支付宝平台移除原有属性(支付宝小程序自定义组件外部属性始终无效) |
|
names.push(obj[name]); |
|
} |
|
return names; |
|
} |
|
return names; |
|
} |
|
function genElementProps(node, virtualHost, context) { |
|
node.props.forEach((prop) => { |
|
if (prop.type === 6 /* NodeTypes.ATTRIBUTE */) { |
|
const { value } = prop; |
|
if (value) { |
|
checkVirtualHostProps(prop.name, virtualHost).forEach((name) => { |
|
context.push(` ${name}="${value.content}"`); |
|
}); |
|
} |
|
else { |
|
context.push(` ${prop.name}`); |
|
} |
|
} |
|
else { |
|
const { name } = prop; |
|
if (name === 'on') { |
|
genOn(prop, node, context); |
|
} |
|
else { |
|
genDirectiveNode(prop, node, virtualHost, context); |
|
} |
|
} |
|
}); |
|
} |
|
exports.genElementProps = genElementProps; |
|
function genOn(prop, node, { push, event, isBuiltInComponent }) { |
|
const arg = prop.arg.content; |
|
const exp = prop.exp; |
|
const modifiers = prop.modifiers; |
|
const name = (event?.format || uni_cli_shared_1.formatMiniProgramEvent)(arg, { |
|
isCatch: modifiers.includes('stop') || modifiers.includes('prevent'), |
|
isCapture: modifiers.includes('capture'), |
|
isComponent: (0, uni_cli_shared_1.isUserComponent)(node, { isBuiltInComponent }), |
|
}); |
|
if (exp.isStatic) { |
|
push(` ${name}="${exp.content}"`); |
|
} |
|
else { |
|
push(` ${name}="{{${exp.content}}}"`); |
|
} |
|
} |
|
function genDirectiveNode(prop, node, virtualHost, context) { |
|
const { push, component } = context; |
|
if (prop.name === 'slot') { |
|
if (prop.arg) { |
|
const arg = prop.arg; |
|
if (arg.isStatic) { |
|
const slotName = (0, uni_shared_1.dynamicSlotName)(arg.content); |
|
// 非作用域默认插槽不生成 slot 属性 |
|
if (slotName !== uni_shared_1.SLOT_DEFAULT_NAME) { |
|
push(` slot="${slotName}"`); |
|
} |
|
} |
|
else { |
|
push(` slot="{{${arg.content}}}"`); |
|
} |
|
} |
|
} |
|
else if (prop.name === 'show') { |
|
let hiddenPropName = 'hidden'; |
|
if ((0, uni_cli_shared_1.isUserComponent)(node, context) && component && component.vShow) { |
|
hiddenPropName = component.vShow; |
|
} |
|
push(` ${hiddenPropName}="{{!${prop.exp.content}}}"`); |
|
} |
|
else if (prop.arg && prop.exp) { |
|
const arg = prop.arg.content; |
|
const exp = prop.exp.content; |
|
checkVirtualHostProps(arg, virtualHost).forEach((arg) => { |
|
push(` ${arg}="{{${exp}}}"`); |
|
}); |
|
} |
|
else { |
|
if (prop.name !== 'bind') { |
|
throw new Error(`unknown directive ` + JSON.stringify(prop)); |
|
} |
|
} |
|
}
|
|
|