}
}
-function parseWithBind(template: string) {
+function parseWithBind(template: string, options?: CompilerOptions) {
return parseWithElementTransform(template, {
+ ...options,
directiveTransforms: {
bind: transformBind
}
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', () => {
assert,
advancePositionWithMutation,
advancePositionWithClone,
- isCoreComponent
+ isCoreComponent,
+ isBindKey
} from './utils'
import {
Namespaces,
}
let tagType = ElementTypes.ELEMENT
- const options = context.options
- 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 (!context.inVPre) {
+ if (tag === 'slot') {
+ tagType = ElementTypes.SLOT
+ } else if (tag === 'template') {
if (
- __COMPAT__ &&
- checkCompatEnabled(
- CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
- context,
- p.loc
+ props.some(
+ p =>
+ p.type === NodeTypes.DIRECTIVE && isSpecialTemplateDirective(p.name)
)
) {
- return true
+ tagType = ElementTypes.TEMPLATE
}
- })
- 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'
- ) {
+ } else if (isComponent(tag, props, context)) {
tagType = ElementTypes.COMPONENT
}
-
- if (tag === 'slot') {
- tagType = ElementTypes.SLOT
- } else if (
- tag === 'template' &&
- props.some(
- p =>
- p.type === NodeTypes.DIRECTIVE && isSpecialTemplateDirective(p.name)
- )
- ) {
- tagType = ElementTypes.TEMPLATE
- }
}
return {
}
}
+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(
context: ParserContext,
type: TagType
// 1. dynamic component
const isExplicitDynamic = isComponentTag(tag)
- const isProp =
- findProp(node, 'is') || (!isExplicitDynamic && findDir(node, 'is'))
+ const isProp = findProp(node, 'is')
if (isProp) {
- if (!isExplicitDynamic && isProp.type === NodeTypes.ATTRIBUTE) {
- // <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.replace(/^vue:/, '')
- } else {
+ if (
+ isExplicitDynamic ||
+ (__COMPAT__ &&
+ isCompatEnabled(
+ CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
+ context
+ ))
+ ) {
const exp =
isProp.type === NodeTypes.ATTRIBUTE
? isProp.value && createSimpleExpression(isProp.value.content, true)
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...)
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
if (builtIn) {
// skip is on <component>, or is="vue:xxx"
if (
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
}
// skip v-is and :is on <component>
if (
name === 'is' ||
- (isVBind && isComponentTag(tag) && isBindKey(arg, 'is'))
+ (isVBind &&
+ isBindKey(arg, 'is') &&
+ (isComponentTag(tag) ||
+ (__COMPAT__ &&
+ isCompatEnabled(
+ CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
+ context
+ ))))
) {
continue
}
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 () => {
const MyButton = {
props: ['foo'],