fix(compiler-core/compat): fix is prop usage on components
also fix v-bind:is usage on plain element in compat mode fix #3934
This commit is contained in:
parent
4de5d24aa7
commit
08e93220f1
@ -57,8 +57,9 @@ function parseWithElementTransform(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseWithBind(template: string) {
|
function parseWithBind(template: string, options?: CompilerOptions) {
|
||||||
return parseWithElementTransform(template, {
|
return parseWithElementTransform(template, {
|
||||||
|
...options,
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
bind: transformBind
|
bind: transformBind
|
||||||
}
|
}
|
||||||
@ -914,6 +915,18 @@ describe('compiler: element transform', () => {
|
|||||||
directives: undefined
|
directives: undefined
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #3934
|
||||||
|
test('normal component with is prop', () => {
|
||||||
|
const { node, root } = parseWithBind(`<custom-input is="foo" />`, {
|
||||||
|
isNativeTag: () => false
|
||||||
|
})
|
||||||
|
expect(root.helpers).toContain(RESOLVE_COMPONENT)
|
||||||
|
expect(root.helpers).not.toContain(RESOLVE_DYNAMIC_COMPONENT)
|
||||||
|
expect(node).toMatchObject({
|
||||||
|
tag: '_component_custom_input'
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('<svg> should be forced into blocks', () => {
|
test('<svg> should be forced into blocks', () => {
|
||||||
|
@ -10,7 +10,8 @@ import {
|
|||||||
assert,
|
assert,
|
||||||
advancePositionWithMutation,
|
advancePositionWithMutation,
|
||||||
advancePositionWithClone,
|
advancePositionWithClone,
|
||||||
isCoreComponent
|
isCoreComponent,
|
||||||
|
isBindKey
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import {
|
import {
|
||||||
Namespaces,
|
Namespaces,
|
||||||
@ -596,46 +597,11 @@ function parseTag(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let tagType = ElementTypes.ELEMENT
|
let tagType = ElementTypes.ELEMENT
|
||||||
const options = context.options
|
if (!context.inVPre) {
|
||||||
if (!context.inVPre && !options.isCustomElement(tag)) {
|
|
||||||
const hasVIs = props.some(p => {
|
|
||||||
if (p.name !== 'is') return
|
|
||||||
// v-is="xxx" (TODO: deprecate)
|
|
||||||
if (p.type === NodeTypes.DIRECTIVE) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// is="vue:xxx"
|
|
||||||
if (p.value && p.value.content.startsWith('vue:')) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// in compat mode, any is usage is considered a component
|
|
||||||
if (
|
|
||||||
__COMPAT__ &&
|
|
||||||
checkCompatEnabled(
|
|
||||||
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
|
|
||||||
context,
|
|
||||||
p.loc
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (options.isNativeTag && !hasVIs) {
|
|
||||||
if (!options.isNativeTag(tag)) tagType = ElementTypes.COMPONENT
|
|
||||||
} else if (
|
|
||||||
hasVIs ||
|
|
||||||
isCoreComponent(tag) ||
|
|
||||||
(options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
|
|
||||||
/^[A-Z]/.test(tag) ||
|
|
||||||
tag === 'component'
|
|
||||||
) {
|
|
||||||
tagType = ElementTypes.COMPONENT
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag === 'slot') {
|
if (tag === 'slot') {
|
||||||
tagType = ElementTypes.SLOT
|
tagType = ElementTypes.SLOT
|
||||||
} else if (
|
} else if (tag === 'template') {
|
||||||
tag === 'template' &&
|
if (
|
||||||
props.some(
|
props.some(
|
||||||
p =>
|
p =>
|
||||||
p.type === NodeTypes.DIRECTIVE && isSpecialTemplateDirective(p.name)
|
p.type === NodeTypes.DIRECTIVE && isSpecialTemplateDirective(p.name)
|
||||||
@ -643,6 +609,9 @@ function parseTag(
|
|||||||
) {
|
) {
|
||||||
tagType = ElementTypes.TEMPLATE
|
tagType = ElementTypes.TEMPLATE
|
||||||
}
|
}
|
||||||
|
} else if (isComponent(tag, props, context)) {
|
||||||
|
tagType = ElementTypes.COMPONENT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -658,6 +627,65 @@ function parseTag(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isComponent(
|
||||||
|
tag: string,
|
||||||
|
props: (AttributeNode | DirectiveNode)[],
|
||||||
|
context: ParserContext
|
||||||
|
) {
|
||||||
|
const options = context.options
|
||||||
|
if (options.isCustomElement(tag)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
tag === 'component' ||
|
||||||
|
/^[A-Z]/.test(tag) ||
|
||||||
|
isCoreComponent(tag) ||
|
||||||
|
(options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
|
||||||
|
(options.isNativeTag && !options.isNativeTag(tag))
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// at this point the tag should be a native tag, but check for potential "is"
|
||||||
|
// casting
|
||||||
|
for (let i = 0; i < props.length; i++) {
|
||||||
|
const p = props[i]
|
||||||
|
if (p.type === NodeTypes.ATTRIBUTE) {
|
||||||
|
if (p.name === 'is' && p.value) {
|
||||||
|
if (p.value.content.startsWith('vue:')) {
|
||||||
|
return true
|
||||||
|
} else if (
|
||||||
|
__COMPAT__ &&
|
||||||
|
checkCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
|
||||||
|
context,
|
||||||
|
p.loc
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// directive
|
||||||
|
// v-is (TODO Deprecate)
|
||||||
|
if (p.name === 'is') {
|
||||||
|
return true
|
||||||
|
} else if (
|
||||||
|
// :is on plain element - only treat as component in compat mode
|
||||||
|
p.name === 'bind' &&
|
||||||
|
isBindKey(p.arg, 'is') &&
|
||||||
|
__COMPAT__ &&
|
||||||
|
checkCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
|
||||||
|
context,
|
||||||
|
p.loc
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function parseAttributes(
|
function parseAttributes(
|
||||||
context: ParserContext,
|
context: ParserContext,
|
||||||
type: TagType
|
type: TagType
|
||||||
|
@ -240,16 +240,16 @@ export function resolveComponentType(
|
|||||||
|
|
||||||
// 1. dynamic component
|
// 1. dynamic component
|
||||||
const isExplicitDynamic = isComponentTag(tag)
|
const isExplicitDynamic = isComponentTag(tag)
|
||||||
const isProp =
|
const isProp = findProp(node, 'is')
|
||||||
findProp(node, 'is') || (!isExplicitDynamic && findDir(node, 'is'))
|
|
||||||
if (isProp) {
|
if (isProp) {
|
||||||
if (!isExplicitDynamic && isProp.type === NodeTypes.ATTRIBUTE) {
|
if (
|
||||||
// <button is="vue:xxx">
|
isExplicitDynamic ||
|
||||||
// if not <component>, only is value that starts with "vue:" will be
|
(__COMPAT__ &&
|
||||||
// treated as component by the parse phase and reach here, unless it's
|
isCompatEnabled(
|
||||||
// compat mode where all is values are considered components
|
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
|
||||||
tag = isProp.value!.content.replace(/^vue:/, '')
|
context
|
||||||
} else {
|
))
|
||||||
|
) {
|
||||||
const exp =
|
const exp =
|
||||||
isProp.type === NodeTypes.ATTRIBUTE
|
isProp.type === NodeTypes.ATTRIBUTE
|
||||||
? isProp.value && createSimpleExpression(isProp.value.content, true)
|
? isProp.value && createSimpleExpression(isProp.value.content, true)
|
||||||
@ -259,9 +259,26 @@ export function resolveComponentType(
|
|||||||
exp
|
exp
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
isProp.type === NodeTypes.ATTRIBUTE &&
|
||||||
|
isProp.value!.content.startsWith('vue:')
|
||||||
|
) {
|
||||||
|
// <button is="vue:xxx">
|
||||||
|
// if not <component>, only is value that starts with "vue:" will be
|
||||||
|
// treated as component by the parse phase and reach here, unless it's
|
||||||
|
// compat mode where all is values are considered components
|
||||||
|
tag = isProp.value!.content.slice(4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1.5 v-is (TODO: Deprecate)
|
||||||
|
const isDir = !isExplicitDynamic && findDir(node, 'is')
|
||||||
|
if (isDir && isDir.exp) {
|
||||||
|
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
|
||||||
|
isDir.exp
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
// 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)
|
// 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)
|
||||||
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
|
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
|
||||||
if (builtIn) {
|
if (builtIn) {
|
||||||
@ -433,7 +450,13 @@ export function buildProps(
|
|||||||
// skip is on <component>, or is="vue:xxx"
|
// skip is on <component>, or is="vue:xxx"
|
||||||
if (
|
if (
|
||||||
name === 'is' &&
|
name === 'is' &&
|
||||||
(isComponentTag(tag) || (value && value.content.startsWith('vue:')))
|
(isComponentTag(tag) ||
|
||||||
|
(value && value.content.startsWith('vue:')) ||
|
||||||
|
(__COMPAT__ &&
|
||||||
|
isCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
|
||||||
|
context
|
||||||
|
)))
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -473,7 +496,14 @@ export function buildProps(
|
|||||||
// skip v-is and :is on <component>
|
// skip v-is and :is on <component>
|
||||||
if (
|
if (
|
||||||
name === 'is' ||
|
name === 'is' ||
|
||||||
(isVBind && isComponentTag(tag) && isBindKey(arg, 'is'))
|
(isVBind &&
|
||||||
|
isBindKey(arg, 'is') &&
|
||||||
|
(isComponentTag(tag) ||
|
||||||
|
(__COMPAT__ &&
|
||||||
|
isCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
|
||||||
|
context
|
||||||
|
))))
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,22 @@ test('COMPILER_IS_ON_ELEMENT', () => {
|
|||||||
expect(CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT).toHaveBeenWarned()
|
expect(CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('COMPILER_IS_ON_ELEMENT (dynamic)', () => {
|
||||||
|
const MyButton = {
|
||||||
|
template: `<div><slot/></div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
const vm = new Vue({
|
||||||
|
template: `<button :is="'MyButton'">text</button>`,
|
||||||
|
components: {
|
||||||
|
MyButton
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
|
||||||
|
expect(vm.$el.outerHTML).toBe(`<div>text</div>`)
|
||||||
|
expect(CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
test('COMPILER_V_BIND_SYNC', async () => {
|
test('COMPILER_V_BIND_SYNC', async () => {
|
||||||
const MyButton = {
|
const MyButton = {
|
||||||
props: ['foo'],
|
props: ['foo'],
|
||||||
|
Loading…
Reference in New Issue
Block a user