提交 c7bdda25 编写于 作者: Q qiang

feat(mp): mergeVirtualHostAttributes

上级 026c9591
......@@ -57,6 +57,10 @@ export interface MiniProgramCompilerOptions {
* 格式化组件名称,比如 wx-btn => weixin-btn (微信不允许以 wx 命名自定义组件)
*/
normalizeName?: (name: string) => string
/**
* 合并虚拟化节点属性(class、style)
*/
mergeVirtualHostAttributes?: boolean
}
directive: string
emitFile?: (emittedFile: EmittedAsset) => string
......
......@@ -30,7 +30,11 @@ import { ForElementNode, isForElementNode } from '../transforms/vFor'
import { IfElementNode, isIfElementNode } from '../transforms/vIf'
import { findSlotName } from '../transforms/vSlot'
import { TransformContext } from '../transform'
import { ATTR_VUE_PROPS } from '../transforms/utils'
import {
ATTR_VUE_PROPS,
VIRTUAL_HOST_STYLE,
VIRTUAL_HOST_CLASS,
} from '../transforms/utils'
interface TemplateCodegenContext {
code: string
......@@ -342,11 +346,15 @@ function genElement(node: ElementNode, context: TemplateCodegenContext) {
genNode(node, context)
})
}
let virtualHost: boolean = false
if (isUserComponent(node, context)) {
tag = hyphenate(tag)
if (context.component?.normalizeName) {
tag = context.component?.normalizeName(tag)
}
if (context.component?.mergeVirtualHostAttributes) {
virtualHost = true
}
}
const { push } = context
......@@ -369,7 +377,7 @@ function genElement(node: ElementNode, context: TemplateCodegenContext) {
genVFor(node, context)
}
if (props.length) {
genElementProps(node, context)
genElementProps(node, virtualHost, context)
}
if (isSelfClosing) {
......@@ -386,15 +394,34 @@ function genElement(node: ElementNode, context: TemplateCodegenContext) {
}
}
function checkVirtualHostProps(name: string, virtualHost: boolean): string[] {
const names: string[] = [name]
if (virtualHost) {
const obj: { [key: string]: string } = {
style: VIRTUAL_HOST_STYLE,
class: VIRTUAL_HOST_CLASS,
}
if (name in obj) {
// TODO 支付宝平台移除原有属性(支付宝小程序自定义组件外部属性始终无效)
names.push(obj[name])
}
return names
}
return names
}
export function genElementProps(
node: ElementNode,
virtualHost: boolean,
context: TemplateCodegenContext
) {
node.props.forEach((prop) => {
if (prop.type === NodeTypes.ATTRIBUTE) {
const { value } = prop
if (value) {
context.push(` ${prop.name}="${value.content}"`)
checkVirtualHostProps(prop.name, virtualHost).forEach((name) => {
context.push(` ${name}="${value.content}"`)
})
} else {
context.push(` ${prop.name}`)
}
......@@ -403,7 +430,7 @@ export function genElementProps(
if (name === 'on') {
genOn(prop, node, context)
} else {
genDirectiveNode(prop, node, context)
genDirectiveNode(prop, node, virtualHost, context)
}
}
})
......@@ -431,6 +458,7 @@ function genOn(
function genDirectiveNode(
prop: DirectiveNode,
node: ElementNode,
virtualHost: boolean,
context: TemplateCodegenContext
) {
const { push, component } = context
......@@ -458,7 +486,9 @@ function genDirectiveNode(
} else if (prop.arg && prop.exp) {
const arg = (prop.arg as SimpleExpressionNode).content
const exp = (prop.exp as SimpleExpressionNode).content
push(` ${arg}="{{${exp}}}"`)
checkVirtualHostProps(arg, virtualHost).forEach((arg) => {
push(` ${arg}="{{${exp}}}"`)
})
} else {
if (prop.name !== 'bind') {
throw new Error(`unknown directive ` + JSON.stringify(prop))
......
......@@ -124,6 +124,7 @@ export interface TransformContext
addVForScope(initScope: CodegenVForScopeInit): CodegenVForScope
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
isMiniProgramComponent(name: string): 'plugin' | 'component' | undefined
rootNode: TemplateChildNode | null
}
export function isRootScope(scope: CodegenScope): scope is CodegenRootScope {
......@@ -154,6 +155,7 @@ export function isScopedSlotVFor({ source }: CodegenVForScope) {
export function transform(root: CodegenRootNode, options: TransformOptions) {
const context = createTransformContext(root, options)
findRootNode(root, context)
traverseNode(root, context)
root.renderData = createRenderDataExpr(context.scope.properties, context)
// finalize meta information
......@@ -164,6 +166,15 @@ export function transform(root: CodegenRootNode, options: TransformOptions) {
return context
}
function findRootNode(root: RootNode, context: TransformContext) {
const children = root.children.filter(
(node) => node.type === NodeTypes.ELEMENT && node.tag !== 'template'
)
if (children.length === 1) {
context.rootNode = children[0]
}
}
export function traverseNode(
node: RootNode | TemplateChildNode,
context: TransformContext
......@@ -476,6 +487,7 @@ export function createTransformContext(
isMiniProgramComponent(name) {
return miniProgramComponents[name]
},
rootNode: null,
}
function addId(id: string) {
......
......@@ -31,6 +31,7 @@ import {
createCompoundExpression,
SourceLocation,
} from '@vue/compiler-core'
import { createBindDirectiveNode } from '@dcloudio/uni-cli-shared'
import { parseExpr, isTrueExpr, isUndefined, parseStringLiteral } from '../ast'
import { genBabelExpr } from '../codegen'
import { NORMALIZE_CLASS } from '../runtimeHelpers'
......@@ -41,6 +42,7 @@ import {
parseExprWithRewriteClass,
rewriteExpression,
rewriteSpreadElement,
VIRTUAL_HOST_CLASS,
} from './utils'
export function isClassBinding({ arg }: DirectiveNode) {
......@@ -57,29 +59,33 @@ export function rewriteClass(
index: number,
classBindingProp: DirectiveNode,
props: (AttributeNode | DirectiveNode)[],
virtualHost: boolean,
context: TransformContext
) {
if (!classBindingProp.exp) {
return
}
const expr = parseExpr(classBindingProp.exp, context)
if (!expr) {
return
}
let classBindingExpr: Expression = expr
if (isObjectExpression(expr)) {
classBindingExpr = createClassBindingByObjectExpression(
rewriteClassObjectExpression(expr, classBindingProp.loc, context)
)
} else if (isArrayExpression(expr)) {
classBindingExpr = createClassBindingByArrayExpression(
rewriteClassArrayExpression(expr, context)
)
const expr = classBindingProp.exp
? parseExpr(classBindingProp.exp, context)
: undefined
let classBindingExpr: Expression
if (expr) {
classBindingExpr = expr
if (isObjectExpression(expr)) {
classBindingExpr = createClassBindingByObjectExpression(
rewriteClassObjectExpression(expr, classBindingProp.loc, context)
)
} else if (isArrayExpression(expr)) {
classBindingExpr = createClassBindingByArrayExpression(
rewriteClassArrayExpression(expr, context)
)
} else {
classBindingExpr = parseExpr(
rewriteClassExpression(classBindingProp.exp!, context).content,
context
) as Expression
}
} else if (virtualHost) {
classBindingExpr = arrayExpression([])
} else {
classBindingExpr = parseExpr(
rewriteClassExpression(classBindingProp.exp, context).content,
context
) as Expression
return
}
const staticClassPropIndex = findStaticClassIndex(props)
if (staticClassPropIndex > -1) {
......@@ -97,12 +103,29 @@ export function rewriteClass(
}
}
}
if (virtualHost) {
if (!isArrayExpression(classBindingExpr)) {
classBindingExpr = arrayExpression([classBindingExpr])
}
classBindingExpr.elements.push(identifier(VIRTUAL_HOST_CLASS))
}
if (!context.miniProgram.class.array) {
classBindingExpr = parseClassBindingArrayExpr(classBindingExpr)
}
classBindingProp.exp = createSimpleExpression(genBabelExpr(classBindingExpr))
}
export function createVirtualHostClass(
props: (AttributeNode | DirectiveNode)[],
context: TransformContext
) {
const classBindingProp = createBindDirectiveNode('class', '')
delete classBindingProp.exp
rewriteClass(0, classBindingProp, props, true, context)
return classBindingProp
}
/**
* 目前 mp-toutiao, mp-alipay, mp-lark 不支持数组绑定class,故统一转换为字符串相加
* @param classBindingExpr
......
......@@ -11,11 +11,13 @@ import {
findStaticClassIndex,
isClassBinding,
rewriteClass,
createVirtualHostClass,
} from './transformClass'
import {
findStaticStyleIndex,
isStyleBinding,
rewriteStyle,
createVirtualHostStyle,
} from './transformStyle'
import { TO_DISPLAY_STRING } from '../runtimeHelpers'
import { rewriteSlot } from './transformSlot'
......@@ -65,6 +67,10 @@ export const transformIdentifier: NodeTransform = (node, context) => {
}
const { props } = node
const virtualHost = !!(
context.miniProgram.component?.mergeVirtualHostAttributes &&
context.rootNode === node
)
for (let i = 0; i < props.length; i++) {
const dir = props[i]
......@@ -89,10 +95,10 @@ export const transformIdentifier: NodeTransform = (node, context) => {
// noop
} else if (isClassBinding(dir)) {
hasClassBinding = true
rewriteClass(i, dir, props, context)
rewriteClass(i, dir, props, virtualHost, context)
} else if (isStyleBinding(dir)) {
hasStyleBinding = true
rewriteStyle(i, dir, props, context)
rewriteStyle(i, dir, props, virtualHost, context)
} else if (isPropsBinding(dir)) {
rewritePropsBinding(dir, node, context)
} else {
......@@ -101,6 +107,16 @@ export const transformIdentifier: NodeTransform = (node, context) => {
}
}
}
if (virtualHost) {
if (!hasClassBinding) {
hasClassBinding = true
props.push(createVirtualHostClass(props, context))
}
if (!hasStyleBinding) {
hasStyleBinding = true
props.push(createVirtualHostStyle(props, context))
}
}
if (hasClassBinding) {
const staticClassIndex = findStaticClassIndex(props)
if (staticClassIndex > -1) {
......
......@@ -24,6 +24,7 @@ import {
SourceLocation,
} from '@vue/compiler-core'
import { hyphenate } from '@vue/shared'
import { createBindDirectiveNode } from '@dcloudio/uni-cli-shared'
import { HYPHENATE, STRINGIFY_STYLE } from '../runtimeHelpers'
import { parseExpr, parseStringLiteral } from '../ast'
import { genBabelExpr } from '../codegen'
......@@ -34,6 +35,7 @@ import {
rewirteWithHelper,
rewriteExpression,
rewriteSpreadElement,
VIRTUAL_HOST_STYLE,
} from './utils'
export function isStyleBinding({ arg, exp }: DirectiveNode) {
......@@ -49,31 +51,32 @@ export function rewriteStyle(
index: number,
styleBindingProp: DirectiveNode,
props: (AttributeNode | DirectiveNode)[],
virtualHost: boolean,
context: TransformContext
) {
if (!styleBindingProp.exp) {
return
}
const expr = parseExpr(styleBindingProp.exp, context)
if (!expr) {
return
}
const expr = styleBindingProp.exp
? parseExpr(styleBindingProp.exp, context)
: undefined
let styleBidingExpr: Expression | undefined = expr
if (isObjectExpression(expr)) {
styleBidingExpr = createStyleBindingByObjectExpression(
rewriteStyleObjectExpression(expr, styleBindingProp.loc, context)
)
} else if (isArrayExpression(expr)) {
styleBidingExpr = createStyleBindingByArrayExpression(
rewriteStyleArrayExpression(expr, context)
)
} else {
styleBidingExpr = parseExpr(
rewriteStyleExpression(styleBindingProp.exp, context).content,
context
) as Expression
}
if (!styleBidingExpr) {
if (expr) {
if (isObjectExpression(expr)) {
styleBidingExpr = createStyleBindingByObjectExpression(
rewriteStyleObjectExpression(expr, styleBindingProp.loc, context)
)
} else if (isArrayExpression(expr)) {
styleBidingExpr = createStyleBindingByArrayExpression(
rewriteStyleArrayExpression(expr, context)
)
} else {
styleBidingExpr = parseExpr(
rewriteStyleExpression(styleBindingProp.exp!, context).content,
context
) as Expression
}
if (!styleBidingExpr) {
return
}
} else if (!virtualHost) {
return
}
const staticStylePropIndex = findStaticStyleIndex(props)
......@@ -81,22 +84,45 @@ export function rewriteStyle(
const staticStyle = (props[staticStylePropIndex] as AttributeNode).value!
.content
if (staticStyle.trim()) {
if (index > staticStylePropIndex) {
styleBidingExpr = binaryExpression(
'+',
addSemicolon(stringLiteral(staticStyle)),
styleBidingExpr
)
if (styleBidingExpr) {
if (index > staticStylePropIndex) {
styleBidingExpr = binaryExpression(
'+',
addSemicolon(stringLiteral(staticStyle)),
styleBidingExpr
)
} else {
styleBidingExpr = binaryExpression(
'+',
addSemicolon(styleBidingExpr),
stringLiteral(staticStyle)
)
}
} else {
styleBidingExpr = binaryExpression(
styleBidingExpr = stringLiteral(staticStyle)
}
}
}
if (virtualHost) {
styleBidingExpr = styleBidingExpr
? binaryExpression(
'+',
addSemicolon(styleBidingExpr),
stringLiteral(staticStyle)
identifier(VIRTUAL_HOST_STYLE)
)
}
}
: identifier(VIRTUAL_HOST_STYLE)
}
styleBindingProp.exp = createSimpleExpression(genBabelExpr(styleBidingExpr))
styleBindingProp.exp = createSimpleExpression(genBabelExpr(styleBidingExpr!))
}
export function createVirtualHostStyle(
props: (AttributeNode | DirectiveNode)[],
context: TransformContext
) {
const styleBindingProp = createBindDirectiveNode('style', '')
delete styleBindingProp.exp
rewriteStyle(0, styleBindingProp, props, true, context)
return styleBindingProp
}
function rewriteStyleExpression(
......
......@@ -38,6 +38,9 @@ export const ATTR_COM_TYPE = 'u-t'
export const SCOPED_SLOT_IDENTIFIER = '__SCOPED_SLOT__'
export const VIRTUAL_HOST_STYLE = 'virtualHostStyle'
export const VIRTUAL_HOST_CLASS = 'virtualHostClass'
export function rewriteSpreadElement(
name: symbol,
expr: SpreadElement,
......
......@@ -53,6 +53,23 @@ function initDefaultProps(isBehavior: boolean = false) {
return properties
}
function initVirtualHostProps(options?: Component.ComponentOptions) {
const properties: Component.PropertyOption = {}
if (__PLATFORM__ === 'mp-weixin' || __PLATFORM__ === 'mp-alipay') {
if (__PLATFORM__ === 'mp-alipay' || (options && options.virtualHost)) {
properties.virtualHostStyle = {
type: null,
value: '',
}
properties.virtualHostClass = {
type: null,
value: '',
}
}
}
return properties
}
/**
*
* @param mpComponentOptions
......@@ -62,7 +79,11 @@ export function initProps(mpComponentOptions: MPComponentOptions) {
if (!mpComponentOptions.properties) {
mpComponentOptions.properties = {}
}
extend(mpComponentOptions.properties, initDefaultProps())
extend(
mpComponentOptions.properties,
initDefaultProps(),
initVirtualHostProps(mpComponentOptions.options)
)
}
const PROP_TYPES = [String, Number, Boolean, Object, Array, null]
......
import { extend } from '@vue/shared'
import {
isMiniProgramNativeTag as isNativeTag,
createIsCustomElement,
......@@ -8,6 +9,7 @@ import {
UniVitePlugin,
MiniProgramCompilerOptions,
transformPageHead,
parseManifestJsonOnce,
} from '@dcloudio/uni-cli-shared'
import type { TemplateCompiler } from '@vue/compiler-sfc'
import type { CompilerOptions } from '@dcloudio/uni-mp-compiler'
......@@ -24,12 +26,20 @@ export function uniOptions({
miniProgram: MiniProgramCompilerOptions
compilerOptions?: CompilerOptions
}): UniVitePlugin['uni'] {
const manifest = parseManifestJsonOnce(process.env.UNI_INPUT_DIR)
const platformOptions = manifest[process.env.UNI_PLATFORM] || {}
return {
copyOptions,
compiler: compiler as TemplateCompiler,
compilerOptions: {
root: process.env.UNI_INPUT_DIR,
miniProgram,
miniProgram: extend({}, miniProgram, {
component: extend({}, miniProgram.component, {
mergeVirtualHostAttributes:
platformOptions.mergeVirtualHostAttributes,
}),
}),
isNativeTag,
isCustomElement: createIsCustomElement(customElements),
...compilerOptions,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册