chore: Merge branch 'v2-compat'
This commit is contained in:
commit
cd33714935
@ -41,7 +41,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
// Packages targeting DOM
|
// Packages targeting DOM
|
||||||
{
|
{
|
||||||
files: ['packages/{vue,runtime-dom}/**'],
|
files: ['packages/{vue,vue-compat,runtime-dom}/**'],
|
||||||
rules: {
|
rules: {
|
||||||
'no-restricted-globals': ['error', ...NodeGlobals]
|
'no-restricted-globals': ['error', ...NodeGlobals]
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@ module.exports = {
|
|||||||
__NODE_JS__: true,
|
__NODE_JS__: true,
|
||||||
__FEATURE_OPTIONS_API__: true,
|
__FEATURE_OPTIONS_API__: true,
|
||||||
__FEATURE_SUSPENSE__: true,
|
__FEATURE_SUSPENSE__: true,
|
||||||
__FEATURE_PROD_DEVTOOLS__: false
|
__FEATURE_PROD_DEVTOOLS__: false,
|
||||||
|
__COMPAT__: true
|
||||||
},
|
},
|
||||||
coverageDirectory: 'coverage',
|
coverageDirectory: 'coverage',
|
||||||
coverageReporters: ['html', 'lcov', 'text'],
|
coverageReporters: ['html', 'lcov', 'text'],
|
||||||
@ -34,6 +35,7 @@ module.exports = {
|
|||||||
watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'],
|
watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'],
|
||||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
|
'@vue/compat': '<rootDir>/packages/vue-compat/src',
|
||||||
'^@vue/(.*?)$': '<rootDir>/packages/$1/src',
|
'^@vue/(.*?)$': '<rootDir>/packages/$1/src',
|
||||||
vue: '<rootDir>/packages/vue/src'
|
vue: '<rootDir>/packages/vue/src'
|
||||||
},
|
},
|
||||||
|
@ -1736,20 +1736,26 @@ foo
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('whitespace management', () => {
|
describe('whitespace management when adopting strategy condense', () => {
|
||||||
|
const parse = (content: string, options?: ParserOptions) =>
|
||||||
|
baseParse(content, {
|
||||||
|
whitespace: 'condense',
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
|
||||||
it('should remove whitespaces at start/end inside an element', () => {
|
it('should remove whitespaces at start/end inside an element', () => {
|
||||||
const ast = baseParse(`<div> <span/> </div>`)
|
const ast = parse(`<div> <span/> </div>`)
|
||||||
expect((ast.children[0] as ElementNode).children.length).toBe(1)
|
expect((ast.children[0] as ElementNode).children.length).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should remove whitespaces w/ newline between elements', () => {
|
it('should remove whitespaces w/ newline between elements', () => {
|
||||||
const ast = baseParse(`<div/> \n <div/> \n <div/>`)
|
const ast = parse(`<div/> \n <div/> \n <div/>`)
|
||||||
expect(ast.children.length).toBe(3)
|
expect(ast.children.length).toBe(3)
|
||||||
expect(ast.children.every(c => c.type === NodeTypes.ELEMENT)).toBe(true)
|
expect(ast.children.every(c => c.type === NodeTypes.ELEMENT)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should remove whitespaces adjacent to comments', () => {
|
it('should remove whitespaces adjacent to comments', () => {
|
||||||
const ast = baseParse(`<div/> \n <!--foo--> <div/>`)
|
const ast = parse(`<div/> \n <!--foo--> <div/>`)
|
||||||
expect(ast.children.length).toBe(3)
|
expect(ast.children.length).toBe(3)
|
||||||
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
|
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
|
||||||
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
|
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
|
||||||
@ -1757,7 +1763,7 @@ foo
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should remove whitespaces w/ newline between comments and elements', () => {
|
it('should remove whitespaces w/ newline between comments and elements', () => {
|
||||||
const ast = baseParse(`<div/> \n <!--foo--> \n <div/>`)
|
const ast = parse(`<div/> \n <!--foo--> \n <div/>`)
|
||||||
expect(ast.children.length).toBe(3)
|
expect(ast.children.length).toBe(3)
|
||||||
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
|
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
|
||||||
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
|
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
|
||||||
@ -1765,7 +1771,7 @@ foo
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should NOT remove whitespaces w/ newline between interpolations', () => {
|
it('should NOT remove whitespaces w/ newline between interpolations', () => {
|
||||||
const ast = baseParse(`{{ foo }} \n {{ bar }}`)
|
const ast = parse(`{{ foo }} \n {{ bar }}`)
|
||||||
expect(ast.children.length).toBe(3)
|
expect(ast.children.length).toBe(3)
|
||||||
expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
|
expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
|
||||||
expect(ast.children[1]).toMatchObject({
|
expect(ast.children[1]).toMatchObject({
|
||||||
@ -1776,7 +1782,7 @@ foo
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should NOT remove whitespaces w/o newline between elements', () => {
|
it('should NOT remove whitespaces w/o newline between elements', () => {
|
||||||
const ast = baseParse(`<div/> <div/> <div/>`)
|
const ast = parse(`<div/> <div/> <div/>`)
|
||||||
expect(ast.children.length).toBe(5)
|
expect(ast.children.length).toBe(5)
|
||||||
expect(ast.children.map(c => c.type)).toMatchObject([
|
expect(ast.children.map(c => c.type)).toMatchObject([
|
||||||
NodeTypes.ELEMENT,
|
NodeTypes.ELEMENT,
|
||||||
@ -1788,7 +1794,7 @@ foo
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should condense consecutive whitespaces in text', () => {
|
it('should condense consecutive whitespaces in text', () => {
|
||||||
const ast = baseParse(` foo \n bar baz `)
|
const ast = parse(` foo \n bar baz `)
|
||||||
expect((ast.children[0] as TextNode).content).toBe(` foo bar baz `)
|
expect((ast.children[0] as TextNode).content).toBe(` foo bar baz `)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1824,6 +1830,84 @@ foo
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('whitespace management when adopting strategy preserve', () => {
|
||||||
|
const parse = (content: string, options?: ParserOptions) =>
|
||||||
|
baseParse(content, {
|
||||||
|
whitespace: 'preserve',
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should still remove whitespaces at start/end inside an element', () => {
|
||||||
|
const ast = parse(`<div> <span/> </div>`)
|
||||||
|
expect((ast.children[0] as ElementNode).children.length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should preserve whitespaces w/ newline between elements', () => {
|
||||||
|
const ast = parse(`<div/> \n <div/> \n <div/>`)
|
||||||
|
expect(ast.children.length).toBe(5)
|
||||||
|
expect(ast.children.map(c => c.type)).toMatchObject([
|
||||||
|
NodeTypes.ELEMENT,
|
||||||
|
NodeTypes.TEXT,
|
||||||
|
NodeTypes.ELEMENT,
|
||||||
|
NodeTypes.TEXT,
|
||||||
|
NodeTypes.ELEMENT
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should preserve whitespaces adjacent to comments', () => {
|
||||||
|
const ast = parse(`<div/> \n <!--foo--> <div/>`)
|
||||||
|
expect(ast.children.length).toBe(5)
|
||||||
|
expect(ast.children.map(c => c.type)).toMatchObject([
|
||||||
|
NodeTypes.ELEMENT,
|
||||||
|
NodeTypes.TEXT,
|
||||||
|
NodeTypes.COMMENT,
|
||||||
|
NodeTypes.TEXT,
|
||||||
|
NodeTypes.ELEMENT
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should preserve whitespaces w/ newline between comments and elements', () => {
|
||||||
|
const ast = parse(`<div/> \n <!--foo--> \n <div/>`)
|
||||||
|
expect(ast.children.length).toBe(5)
|
||||||
|
expect(ast.children.map(c => c.type)).toMatchObject([
|
||||||
|
NodeTypes.ELEMENT,
|
||||||
|
NodeTypes.TEXT,
|
||||||
|
NodeTypes.COMMENT,
|
||||||
|
NodeTypes.TEXT,
|
||||||
|
NodeTypes.ELEMENT
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should preserve whitespaces w/ newline between interpolations', () => {
|
||||||
|
const ast = parse(`{{ foo }} \n {{ bar }}`)
|
||||||
|
expect(ast.children.length).toBe(3)
|
||||||
|
expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
|
||||||
|
expect(ast.children[1]).toMatchObject({
|
||||||
|
type: NodeTypes.TEXT,
|
||||||
|
content: ' '
|
||||||
|
})
|
||||||
|
expect(ast.children[2].type).toBe(NodeTypes.INTERPOLATION)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should preserve whitespaces w/o newline between elements', () => {
|
||||||
|
const ast = parse(`<div/> <div/> <div/>`)
|
||||||
|
expect(ast.children.length).toBe(5)
|
||||||
|
expect(ast.children.map(c => c.type)).toMatchObject([
|
||||||
|
NodeTypes.ELEMENT,
|
||||||
|
NodeTypes.TEXT,
|
||||||
|
NodeTypes.ELEMENT,
|
||||||
|
NodeTypes.TEXT,
|
||||||
|
NodeTypes.ELEMENT
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should preserve consecutive whitespaces in text', () => {
|
||||||
|
const content = ` foo \n bar baz `
|
||||||
|
const ast = parse(content)
|
||||||
|
expect((ast.children[0] as TextNode).content).toBe(content)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Errors', () => {
|
describe('Errors', () => {
|
||||||
const patterns: {
|
const patterns: {
|
||||||
[key: string]: Array<{
|
[key: string]: Array<{
|
||||||
|
@ -306,6 +306,7 @@ describe('compiler: v-if', () => {
|
|||||||
code: ErrorCodes.X_V_IF_SAME_KEY
|
code: ErrorCodes.X_V_IF_SAME_KEY
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
expect('unnecessary key usage on v-if').toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
],
|
],
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
"name": "VueCompilerCore",
|
"name": "VueCompilerCore",
|
||||||
|
"compat": true,
|
||||||
"formats": [
|
"formats": [
|
||||||
"esm-bundler",
|
"esm-bundler",
|
||||||
"cjs"
|
"cjs"
|
||||||
|
@ -109,6 +109,9 @@ export interface RootNode extends Node {
|
|||||||
temps: number
|
temps: number
|
||||||
ssrHelpers?: symbol[]
|
ssrHelpers?: symbol[]
|
||||||
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
|
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
|
||||||
|
|
||||||
|
// v2 compat only
|
||||||
|
filters?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ElementNode =
|
export type ElementNode =
|
||||||
|
@ -50,7 +50,8 @@ import {
|
|||||||
CREATE_BLOCK,
|
CREATE_BLOCK,
|
||||||
OPEN_BLOCK,
|
OPEN_BLOCK,
|
||||||
CREATE_STATIC,
|
CREATE_STATIC,
|
||||||
WITH_CTX
|
WITH_CTX,
|
||||||
|
RESOLVE_FILTER
|
||||||
} from './runtimeHelpers'
|
} from './runtimeHelpers'
|
||||||
import { ImportItem } from './transform'
|
import { ImportItem } from './transform'
|
||||||
|
|
||||||
@ -274,6 +275,12 @@ export function generate(
|
|||||||
newline()
|
newline()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (__COMPAT__ && ast.filters && ast.filters.length) {
|
||||||
|
newline()
|
||||||
|
genAssets(ast.filters, 'filter', context)
|
||||||
|
newline()
|
||||||
|
}
|
||||||
|
|
||||||
if (ast.temps > 0) {
|
if (ast.temps > 0) {
|
||||||
push(`let `)
|
push(`let `)
|
||||||
for (let i = 0; i < ast.temps; i++) {
|
for (let i = 0; i < ast.temps; i++) {
|
||||||
@ -458,11 +465,15 @@ function genModulePreamble(
|
|||||||
|
|
||||||
function genAssets(
|
function genAssets(
|
||||||
assets: string[],
|
assets: string[],
|
||||||
type: 'component' | 'directive',
|
type: 'component' | 'directive' | 'filter',
|
||||||
{ helper, push, newline }: CodegenContext
|
{ helper, push, newline }: CodegenContext
|
||||||
) {
|
) {
|
||||||
const resolver = helper(
|
const resolver = helper(
|
||||||
type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
|
__COMPAT__ && type === 'filter'
|
||||||
|
? RESOLVE_FILTER
|
||||||
|
: type === 'component'
|
||||||
|
? RESOLVE_COMPONENT
|
||||||
|
: RESOLVE_DIRECTIVE
|
||||||
)
|
)
|
||||||
for (let i = 0; i < assets.length; i++) {
|
for (let i = 0; i < assets.length; i++) {
|
||||||
let id = assets[i]
|
let id = assets[i]
|
||||||
@ -727,13 +738,11 @@ function genExpressionAsPropertyKey(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function genComment(node: CommentNode, context: CodegenContext) {
|
function genComment(node: CommentNode, context: CodegenContext) {
|
||||||
if (__DEV__) {
|
const { push, helper, pure } = context
|
||||||
const { push, helper, pure } = context
|
if (pure) {
|
||||||
if (pure) {
|
push(PURE_ANNOTATION)
|
||||||
push(PURE_ANNOTATION)
|
|
||||||
}
|
|
||||||
push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)
|
|
||||||
}
|
}
|
||||||
|
push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
function genVNodeCall(node: VNodeCall, context: CodegenContext) {
|
function genVNodeCall(node: VNodeCall, context: CodegenContext) {
|
||||||
|
167
packages/compiler-core/src/compat/compatConfig.ts
Normal file
167
packages/compiler-core/src/compat/compatConfig.ts
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import { SourceLocation } from '../ast'
|
||||||
|
import { CompilerError } from '../errors'
|
||||||
|
import { ParserContext } from '../parse'
|
||||||
|
import { TransformContext } from '../transform'
|
||||||
|
|
||||||
|
export type CompilerCompatConfig = Partial<
|
||||||
|
Record<CompilerDeprecationTypes, boolean | 'suppress-warning'>
|
||||||
|
> & {
|
||||||
|
MODE?: 2 | 3
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompilerCompatOptions {
|
||||||
|
compatConfig?: CompilerCompatConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum CompilerDeprecationTypes {
|
||||||
|
COMPILER_IS_ON_ELEMENT = 'COMPILER_IS_ON_ELEMENT',
|
||||||
|
COMPILER_V_BIND_SYNC = 'COMPILER_V_BIND_SYNC',
|
||||||
|
COMPILER_V_BIND_PROP = 'COMPILER_V_BIND_PROP',
|
||||||
|
COMPILER_V_BIND_OBJECT_ORDER = 'COMPILER_V_BIND_OBJECT_ORDER',
|
||||||
|
COMPILER_V_ON_NATIVE = 'COMPILER_V_ON_NATIVE',
|
||||||
|
COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE',
|
||||||
|
COMPILER_V_FOR_REF = 'COMPILER_V_FOR_REF',
|
||||||
|
COMPILER_NATIVE_TEMPLATE = 'COMPILER_NATIVE_TEMPLATE',
|
||||||
|
COMPILER_INLINE_TEMPLATE = 'COMPILER_INLINE_TEMPLATE',
|
||||||
|
COMPILER_FILTERS = 'COMPILER_FILTER'
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeprecationData = {
|
||||||
|
message: string | ((...args: any[]) => string)
|
||||||
|
link?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
|
||||||
|
[CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT]: {
|
||||||
|
message:
|
||||||
|
`Platform-native elements with "is" prop will no longer be ` +
|
||||||
|
`treated as components in Vue 3 unless the "is" value is explicitly ` +
|
||||||
|
`prefixed with "vue:".`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/custom-elements-interop.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[CompilerDeprecationTypes.COMPILER_V_BIND_SYNC]: {
|
||||||
|
message: key =>
|
||||||
|
`.sync modifier for v-bind has been removed. Use v-model with ` +
|
||||||
|
`argument instead. \`v-bind:${key}.sync\` should be changed to ` +
|
||||||
|
`\`v-model:${key}\`.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/v-model.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[CompilerDeprecationTypes.COMPILER_V_BIND_PROP]: {
|
||||||
|
message:
|
||||||
|
`.prop modifier for v-bind has been removed and no longer necessary. ` +
|
||||||
|
`Vue 3 will automatically set a binding as DOM property when appropriate.`
|
||||||
|
},
|
||||||
|
|
||||||
|
[CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER]: {
|
||||||
|
message:
|
||||||
|
`v-bind="obj" usage is now order sensitive and behaves like JavaScript ` +
|
||||||
|
`object spread: it will now overwrite an existing non-mergeable attribute ` +
|
||||||
|
`that appears before v-bind in the case of conflict. ` +
|
||||||
|
`To retain 2.x behavior, move v-bind to make it the first attribute. ` +
|
||||||
|
`You can also suppress this warning if the usage is intended.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/v-bind.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[CompilerDeprecationTypes.COMPILER_V_ON_NATIVE]: {
|
||||||
|
message: `.native modifier for v-on has been removed as is no longer necessary.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/v-on-native-modifier-removed.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE]: {
|
||||||
|
message:
|
||||||
|
`v-if / v-for precedence when used on the same element has changed ` +
|
||||||
|
`in Vue 3: v-if now takes higher precedence and will no longer have ` +
|
||||||
|
`access to v-for scope variables. It is best to avoid the ambiguity ` +
|
||||||
|
`with <template> tags or use a computed property that filters v-for ` +
|
||||||
|
`data source.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/v-if-v-for.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[CompilerDeprecationTypes.COMPILER_V_FOR_REF]: {
|
||||||
|
message:
|
||||||
|
`Ref usage on v-for no longer creates array ref values in Vue 3. ` +
|
||||||
|
`Consider using function refs or refactor to avoid ref usage altogether.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/array-refs.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE]: {
|
||||||
|
message:
|
||||||
|
`<template> with no special directives will render as a native template ` +
|
||||||
|
`element instead of its inner content in Vue 3.`
|
||||||
|
},
|
||||||
|
|
||||||
|
[CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE]: {
|
||||||
|
message: `"inline-template" has been removed in Vue 3.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/inline-template-attribute.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[CompilerDeprecationTypes.COMPILER_FILTERS]: {
|
||||||
|
message:
|
||||||
|
`filters have been removed in Vue 3. ` +
|
||||||
|
`The "|" symbol will be treated as native JavaScript bitwise OR operator. ` +
|
||||||
|
`Use method calls or computed properties instead.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/filters.html`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCompatValue(
|
||||||
|
key: CompilerDeprecationTypes | 'MODE',
|
||||||
|
context: ParserContext | TransformContext
|
||||||
|
) {
|
||||||
|
const config = (context as ParserContext).options
|
||||||
|
? (context as ParserContext).options.compatConfig
|
||||||
|
: (context as TransformContext).compatConfig
|
||||||
|
const value = config && config[key]
|
||||||
|
if (key === 'MODE') {
|
||||||
|
return value || 3 // compiler defaults to v3 behavior
|
||||||
|
} else {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isCompatEnabled(
|
||||||
|
key: CompilerDeprecationTypes,
|
||||||
|
context: ParserContext | TransformContext
|
||||||
|
) {
|
||||||
|
const mode = getCompatValue('MODE', context)
|
||||||
|
const value = getCompatValue(key, context)
|
||||||
|
// in v3 mode, only enable if explicitly set to true
|
||||||
|
// otherwise enable for any non-false value
|
||||||
|
return mode === 3 ? value === true : value !== false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkCompatEnabled(
|
||||||
|
key: CompilerDeprecationTypes,
|
||||||
|
context: ParserContext | TransformContext,
|
||||||
|
loc: SourceLocation | null,
|
||||||
|
...args: any[]
|
||||||
|
): boolean {
|
||||||
|
const enabled = isCompatEnabled(key, context)
|
||||||
|
if (__DEV__ && enabled) {
|
||||||
|
warnDeprecation(key, context, loc, ...args)
|
||||||
|
}
|
||||||
|
return enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
export function warnDeprecation(
|
||||||
|
key: CompilerDeprecationTypes,
|
||||||
|
context: ParserContext | TransformContext,
|
||||||
|
loc: SourceLocation | null,
|
||||||
|
...args: any[]
|
||||||
|
) {
|
||||||
|
const val = getCompatValue(key, context)
|
||||||
|
if (val === 'suppress-warning') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { message, link } = deprecationData[key]
|
||||||
|
const msg = `(deprecation ${key}) ${
|
||||||
|
typeof message === 'function' ? message(...args) : message
|
||||||
|
}${link ? `\n Details: ${link}` : ``}`
|
||||||
|
|
||||||
|
const err = new SyntaxError(msg) as CompilerError
|
||||||
|
err.code = key
|
||||||
|
if (loc) err.loc = loc
|
||||||
|
context.onWarn(err)
|
||||||
|
}
|
194
packages/compiler-core/src/compat/transformFilter.ts
Normal file
194
packages/compiler-core/src/compat/transformFilter.ts
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import { RESOLVE_FILTER } from '../runtimeHelpers'
|
||||||
|
import {
|
||||||
|
AttributeNode,
|
||||||
|
DirectiveNode,
|
||||||
|
NodeTransform,
|
||||||
|
NodeTypes,
|
||||||
|
SimpleExpressionNode,
|
||||||
|
toValidAssetId,
|
||||||
|
TransformContext
|
||||||
|
} from '@vue/compiler-core'
|
||||||
|
import {
|
||||||
|
CompilerDeprecationTypes,
|
||||||
|
isCompatEnabled,
|
||||||
|
warnDeprecation
|
||||||
|
} from './compatConfig'
|
||||||
|
import { ExpressionNode } from '../ast'
|
||||||
|
|
||||||
|
const validDivisionCharRE = /[\w).+\-_$\]]/
|
||||||
|
|
||||||
|
export const transformFilter: NodeTransform = (node, context) => {
|
||||||
|
if (!isCompatEnabled(CompilerDeprecationTypes.COMPILER_FILTERS, context)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === NodeTypes.INTERPOLATION) {
|
||||||
|
// filter rewrite is applied before expression transform so only
|
||||||
|
// simple expressions are possible at this stage
|
||||||
|
rewriteFilter(node.content, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === NodeTypes.ELEMENT) {
|
||||||
|
node.props.forEach((prop: AttributeNode | DirectiveNode) => {
|
||||||
|
if (
|
||||||
|
prop.type === NodeTypes.DIRECTIVE &&
|
||||||
|
prop.name !== 'for' &&
|
||||||
|
prop.exp
|
||||||
|
) {
|
||||||
|
rewriteFilter(prop.exp, context)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewriteFilter(node: ExpressionNode, context: TransformContext) {
|
||||||
|
if (node.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||||
|
parseFilter(node, context)
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
|
const child = node.children[i]
|
||||||
|
if (typeof child !== 'object') continue
|
||||||
|
if (child.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||||
|
parseFilter(child, context)
|
||||||
|
} else if (child.type === NodeTypes.COMPOUND_EXPRESSION) {
|
||||||
|
rewriteFilter(node, context)
|
||||||
|
} else if (child.type === NodeTypes.INTERPOLATION) {
|
||||||
|
rewriteFilter(child.content, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFilter(node: SimpleExpressionNode, context: TransformContext) {
|
||||||
|
const exp = node.content
|
||||||
|
let inSingle = false
|
||||||
|
let inDouble = false
|
||||||
|
let inTemplateString = false
|
||||||
|
let inRegex = false
|
||||||
|
let curly = 0
|
||||||
|
let square = 0
|
||||||
|
let paren = 0
|
||||||
|
let lastFilterIndex = 0
|
||||||
|
let c,
|
||||||
|
prev,
|
||||||
|
i: number,
|
||||||
|
expression,
|
||||||
|
filters: string[] = []
|
||||||
|
|
||||||
|
for (i = 0; i < exp.length; i++) {
|
||||||
|
prev = c
|
||||||
|
c = exp.charCodeAt(i)
|
||||||
|
if (inSingle) {
|
||||||
|
if (c === 0x27 && prev !== 0x5c) inSingle = false
|
||||||
|
} else if (inDouble) {
|
||||||
|
if (c === 0x22 && prev !== 0x5c) inDouble = false
|
||||||
|
} else if (inTemplateString) {
|
||||||
|
if (c === 0x60 && prev !== 0x5c) inTemplateString = false
|
||||||
|
} else if (inRegex) {
|
||||||
|
if (c === 0x2f && prev !== 0x5c) inRegex = false
|
||||||
|
} else if (
|
||||||
|
c === 0x7c && // pipe
|
||||||
|
exp.charCodeAt(i + 1) !== 0x7c &&
|
||||||
|
exp.charCodeAt(i - 1) !== 0x7c &&
|
||||||
|
!curly &&
|
||||||
|
!square &&
|
||||||
|
!paren
|
||||||
|
) {
|
||||||
|
if (expression === undefined) {
|
||||||
|
// first filter, end of expression
|
||||||
|
lastFilterIndex = i + 1
|
||||||
|
expression = exp.slice(0, i).trim()
|
||||||
|
} else {
|
||||||
|
pushFilter()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (c) {
|
||||||
|
case 0x22:
|
||||||
|
inDouble = true
|
||||||
|
break // "
|
||||||
|
case 0x27:
|
||||||
|
inSingle = true
|
||||||
|
break // '
|
||||||
|
case 0x60:
|
||||||
|
inTemplateString = true
|
||||||
|
break // `
|
||||||
|
case 0x28:
|
||||||
|
paren++
|
||||||
|
break // (
|
||||||
|
case 0x29:
|
||||||
|
paren--
|
||||||
|
break // )
|
||||||
|
case 0x5b:
|
||||||
|
square++
|
||||||
|
break // [
|
||||||
|
case 0x5d:
|
||||||
|
square--
|
||||||
|
break // ]
|
||||||
|
case 0x7b:
|
||||||
|
curly++
|
||||||
|
break // {
|
||||||
|
case 0x7d:
|
||||||
|
curly--
|
||||||
|
break // }
|
||||||
|
}
|
||||||
|
if (c === 0x2f) {
|
||||||
|
// /
|
||||||
|
let j = i - 1
|
||||||
|
let p
|
||||||
|
// find first non-whitespace prev char
|
||||||
|
for (; j >= 0; j--) {
|
||||||
|
p = exp.charAt(j)
|
||||||
|
if (p !== ' ') break
|
||||||
|
}
|
||||||
|
if (!p || !validDivisionCharRE.test(p)) {
|
||||||
|
inRegex = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expression === undefined) {
|
||||||
|
expression = exp.slice(0, i).trim()
|
||||||
|
} else if (lastFilterIndex !== 0) {
|
||||||
|
pushFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushFilter() {
|
||||||
|
filters.push(exp.slice(lastFilterIndex, i).trim())
|
||||||
|
lastFilterIndex = i + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
filters.length &&
|
||||||
|
warnDeprecation(
|
||||||
|
CompilerDeprecationTypes.COMPILER_FILTERS,
|
||||||
|
context,
|
||||||
|
node.loc
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
for (i = 0; i < filters.length; i++) {
|
||||||
|
expression = wrapFilter(expression, filters[i], context)
|
||||||
|
}
|
||||||
|
node.content = expression
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapFilter(
|
||||||
|
exp: string,
|
||||||
|
filter: string,
|
||||||
|
context: TransformContext
|
||||||
|
): string {
|
||||||
|
context.helper(RESOLVE_FILTER)
|
||||||
|
const i = filter.indexOf('(')
|
||||||
|
if (i < 0) {
|
||||||
|
context.filters!.add(filter)
|
||||||
|
return `${toValidAssetId(filter, 'filter')}(${exp})`
|
||||||
|
} else {
|
||||||
|
const name = filter.slice(0, i)
|
||||||
|
const args = filter.slice(i + 1)
|
||||||
|
context.filters!.add(name)
|
||||||
|
return `${toValidAssetId(name, 'filter')}(${exp}${
|
||||||
|
args !== ')' ? ',' + args : args
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ import { trackSlotScopes, trackVForSlotScopes } from './transforms/vSlot'
|
|||||||
import { transformText } from './transforms/transformText'
|
import { transformText } from './transforms/transformText'
|
||||||
import { transformOnce } from './transforms/vOnce'
|
import { transformOnce } from './transforms/vOnce'
|
||||||
import { transformModel } from './transforms/vModel'
|
import { transformModel } from './transforms/vModel'
|
||||||
|
import { transformFilter } from './compat/transformFilter'
|
||||||
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
||||||
|
|
||||||
export type TransformPreset = [
|
export type TransformPreset = [
|
||||||
@ -30,6 +31,7 @@ export function getBaseTransformPreset(
|
|||||||
transformOnce,
|
transformOnce,
|
||||||
transformIf,
|
transformIf,
|
||||||
transformFor,
|
transformFor,
|
||||||
|
...(__COMPAT__ ? [transformFilter] : []),
|
||||||
...(!__BROWSER__ && prefixIdentifiers
|
...(!__BROWSER__ && prefixIdentifiers
|
||||||
? [
|
? [
|
||||||
// order is important
|
// order is important
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { SourceLocation } from './ast'
|
import { SourceLocation } from './ast'
|
||||||
|
|
||||||
export interface CompilerError extends SyntaxError {
|
export interface CompilerError extends SyntaxError {
|
||||||
code: number
|
code: number | string
|
||||||
loc?: SourceLocation
|
loc?: SourceLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,6 +13,10 @@ export function defaultOnError(error: CompilerError) {
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function defaultOnWarn(msg: CompilerError) {
|
||||||
|
__DEV__ && console.warn(`[Vue warn] ${msg.message}`)
|
||||||
|
}
|
||||||
|
|
||||||
export function createCompilerError<T extends number>(
|
export function createCompilerError<T extends number>(
|
||||||
code: T,
|
code: T,
|
||||||
loc?: SourceLocation,
|
loc?: SourceLocation,
|
||||||
@ -87,13 +91,16 @@ export const enum ErrorCodes {
|
|||||||
X_CACHE_HANDLER_NOT_SUPPORTED,
|
X_CACHE_HANDLER_NOT_SUPPORTED,
|
||||||
X_SCOPE_ID_NOT_SUPPORTED,
|
X_SCOPE_ID_NOT_SUPPORTED,
|
||||||
|
|
||||||
|
// warnings
|
||||||
|
X_V_IF_KEY,
|
||||||
|
|
||||||
// Special value for higher-order compilers to pick up the last code
|
// Special value for higher-order compilers to pick up the last code
|
||||||
// to avoid collision of error codes. This should always be kept as the last
|
// to avoid collision of error codes. This should always be kept as the last
|
||||||
// item.
|
// item.
|
||||||
__EXTEND_POINT__
|
__EXTEND_POINT__
|
||||||
}
|
}
|
||||||
|
|
||||||
export const errorMessages: { [code: number]: string } = {
|
export const errorMessages: Record<ErrorCodes, string> = {
|
||||||
// parse errors
|
// parse errors
|
||||||
[ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT]: 'Illegal comment.',
|
[ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT]: 'Illegal comment.',
|
||||||
[ErrorCodes.CDATA_IN_HTML_CONTENT]:
|
[ErrorCodes.CDATA_IN_HTML_CONTENT]:
|
||||||
@ -124,6 +131,7 @@ export const errorMessages: { [code: number]: string } = {
|
|||||||
"Attribute name cannot start with '='.",
|
"Attribute name cannot start with '='.",
|
||||||
[ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME]:
|
[ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME]:
|
||||||
"'<?' is allowed only in XML context.",
|
"'<?' is allowed only in XML context.",
|
||||||
|
[ErrorCodes.UNEXPECTED_NULL_CHARACTER]: `Unexpected null cahracter.`,
|
||||||
[ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG]: "Illegal '/' in tags.",
|
[ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG]: "Illegal '/' in tags.",
|
||||||
|
|
||||||
// Vue-specific parse errors
|
// Vue-specific parse errors
|
||||||
@ -164,5 +172,13 @@ export const errorMessages: { [code: number]: string } = {
|
|||||||
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
|
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
|
||||||
[ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`,
|
[ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`,
|
||||||
[ErrorCodes.X_CACHE_HANDLER_NOT_SUPPORTED]: `"cacheHandlers" option is only supported when the "prefixIdentifiers" option is enabled.`,
|
[ErrorCodes.X_CACHE_HANDLER_NOT_SUPPORTED]: `"cacheHandlers" option is only supported when the "prefixIdentifiers" option is enabled.`,
|
||||||
[ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED]: `"scopeId" option is only supported in module mode.`
|
[ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED]: `"scopeId" option is only supported in module mode.`,
|
||||||
|
|
||||||
|
// warnings
|
||||||
|
[ErrorCodes.X_V_IF_KEY]:
|
||||||
|
`unnecessary key usage on v-if/else branches. ` +
|
||||||
|
`Vue will automatically generate unique keys for each branch.`,
|
||||||
|
|
||||||
|
// just to fullfill types
|
||||||
|
[ErrorCodes.__EXTEND_POINT__]: ``
|
||||||
}
|
}
|
||||||
|
@ -57,3 +57,10 @@ export {
|
|||||||
} from './transforms/transformElement'
|
} from './transforms/transformElement'
|
||||||
export { processSlotOutlet } from './transforms/transformSlotOutlet'
|
export { processSlotOutlet } from './transforms/transformSlotOutlet'
|
||||||
export { generateCodeFrame } from '@vue/shared'
|
export { generateCodeFrame } from '@vue/shared'
|
||||||
|
|
||||||
|
// v2 compat only
|
||||||
|
export {
|
||||||
|
checkCompatEnabled,
|
||||||
|
warnDeprecation,
|
||||||
|
CompilerDeprecationTypes
|
||||||
|
} from './compat/compatConfig'
|
||||||
|
@ -6,9 +6,17 @@ import {
|
|||||||
DirectiveTransform,
|
DirectiveTransform,
|
||||||
TransformContext
|
TransformContext
|
||||||
} from './transform'
|
} from './transform'
|
||||||
|
import { CompilerCompatOptions } from './compat/compatConfig'
|
||||||
import { ParserPlugin } from '@babel/parser'
|
import { ParserPlugin } from '@babel/parser'
|
||||||
|
|
||||||
export interface ParserOptions {
|
export interface ErrorHandlingOptions {
|
||||||
|
onWarn?: (warning: CompilerError) => void
|
||||||
|
onError?: (error: CompilerError) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParserOptions
|
||||||
|
extends ErrorHandlingOptions,
|
||||||
|
CompilerCompatOptions {
|
||||||
/**
|
/**
|
||||||
* e.g. platform native elements, e.g. `<div>` for browsers
|
* e.g. platform native elements, e.g. `<div>` for browsers
|
||||||
*/
|
*/
|
||||||
@ -44,11 +52,14 @@ export interface ParserOptions {
|
|||||||
* @default ['{{', '}}']
|
* @default ['{{', '}}']
|
||||||
*/
|
*/
|
||||||
delimiters?: [string, string]
|
delimiters?: [string, string]
|
||||||
|
/**
|
||||||
|
* Whitespace handling strategy
|
||||||
|
*/
|
||||||
|
whitespace?: 'preserve' | 'condense'
|
||||||
/**
|
/**
|
||||||
* Only needed for DOM compilers
|
* Only needed for DOM compilers
|
||||||
*/
|
*/
|
||||||
decodeEntities?: (rawText: string, asAttr: boolean) => string
|
decodeEntities?: (rawText: string, asAttr: boolean) => string
|
||||||
onError?: (error: CompilerError) => void
|
|
||||||
/**
|
/**
|
||||||
* Keep comments in the templates AST, even in production
|
* Keep comments in the templates AST, even in production
|
||||||
*/
|
*/
|
||||||
@ -138,7 +149,10 @@ interface SharedTransformCodegenOptions {
|
|||||||
filename?: string
|
filename?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransformOptions extends SharedTransformCodegenOptions {
|
export interface TransformOptions
|
||||||
|
extends SharedTransformCodegenOptions,
|
||||||
|
ErrorHandlingOptions,
|
||||||
|
CompilerCompatOptions {
|
||||||
/**
|
/**
|
||||||
* An array of node transforms to be applied to every AST node.
|
* An array of node transforms to be applied to every AST node.
|
||||||
*/
|
*/
|
||||||
@ -213,7 +227,6 @@ export interface TransformOptions extends SharedTransformCodegenOptions {
|
|||||||
* needed to render inline CSS variables on component root
|
* needed to render inline CSS variables on component root
|
||||||
*/
|
*/
|
||||||
ssrCssVars?: string
|
ssrCssVars?: string
|
||||||
onError?: (error: CompilerError) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CodegenOptions extends SharedTransformCodegenOptions {
|
export interface CodegenOptions extends SharedTransformCodegenOptions {
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { ParserOptions } from './options'
|
import { ErrorHandlingOptions, ParserOptions } from './options'
|
||||||
import { NO, isArray, makeMap, extend } from '@vue/shared'
|
import { NO, isArray, makeMap, extend } from '@vue/shared'
|
||||||
import { ErrorCodes, createCompilerError, defaultOnError } from './errors'
|
import {
|
||||||
|
ErrorCodes,
|
||||||
|
createCompilerError,
|
||||||
|
defaultOnError,
|
||||||
|
defaultOnWarn
|
||||||
|
} from './errors'
|
||||||
import {
|
import {
|
||||||
assert,
|
assert,
|
||||||
advancePositionWithMutation,
|
advancePositionWithMutation,
|
||||||
@ -25,8 +30,18 @@ import {
|
|||||||
createRoot,
|
createRoot,
|
||||||
ConstantTypes
|
ConstantTypes
|
||||||
} from './ast'
|
} from './ast'
|
||||||
|
import {
|
||||||
|
checkCompatEnabled,
|
||||||
|
CompilerCompatOptions,
|
||||||
|
CompilerDeprecationTypes,
|
||||||
|
warnDeprecation
|
||||||
|
} from './compat/compatConfig'
|
||||||
|
|
||||||
type OptionalOptions = 'isNativeTag' | 'isBuiltInComponent'
|
type OptionalOptions =
|
||||||
|
| 'whitespace'
|
||||||
|
| 'isNativeTag'
|
||||||
|
| 'isBuiltInComponent'
|
||||||
|
| keyof CompilerCompatOptions
|
||||||
type MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &
|
type MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &
|
||||||
Pick<ParserOptions, OptionalOptions>
|
Pick<ParserOptions, OptionalOptions>
|
||||||
type AttributeValue =
|
type AttributeValue =
|
||||||
@ -59,6 +74,7 @@ export const defaultParserOptions: MergedParserOptions = {
|
|||||||
decodeEntities: (rawText: string): string =>
|
decodeEntities: (rawText: string): string =>
|
||||||
rawText.replace(decodeRE, (_, p1) => decodeMap[p1]),
|
rawText.replace(decodeRE, (_, p1) => decodeMap[p1]),
|
||||||
onError: defaultOnError,
|
onError: defaultOnError,
|
||||||
|
onWarn: defaultOnWarn,
|
||||||
comments: false
|
comments: false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +96,7 @@ export interface ParserContext {
|
|||||||
column: number
|
column: number
|
||||||
inPre: boolean // HTML <pre> tag, preserve whitespaces
|
inPre: boolean // HTML <pre> tag, preserve whitespaces
|
||||||
inVPre: boolean // v-pre, do not process directives and interpolations
|
inVPre: boolean // v-pre, do not process directives and interpolations
|
||||||
|
onWarn: NonNullable<ErrorHandlingOptions['onWarn']>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function baseParse(
|
export function baseParse(
|
||||||
@ -111,7 +128,8 @@ function createParserContext(
|
|||||||
originalSource: content,
|
originalSource: content,
|
||||||
source: content,
|
source: content,
|
||||||
inPre: false,
|
inPre: false,
|
||||||
inVPre: false
|
inVPre: false,
|
||||||
|
onWarn: options.onWarn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,38 +220,39 @@ function parseChildren(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whitespace management for more efficient output
|
// Whitespace handling strategy like v2
|
||||||
// (same as v2 whitespace: 'condense')
|
|
||||||
let removedWhitespace = false
|
let removedWhitespace = false
|
||||||
if (mode !== TextModes.RAWTEXT && mode !== TextModes.RCDATA) {
|
if (mode !== TextModes.RAWTEXT && mode !== TextModes.RCDATA) {
|
||||||
|
const preserve = context.options.whitespace === 'preserve'
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
const node = nodes[i]
|
const node = nodes[i]
|
||||||
if (!context.inPre && node.type === NodeTypes.TEXT) {
|
if (!context.inPre && node.type === NodeTypes.TEXT) {
|
||||||
if (!/[^\t\r\n\f ]/.test(node.content)) {
|
if (!/[^\t\r\n\f ]/.test(node.content)) {
|
||||||
const prev = nodes[i - 1]
|
const prev = nodes[i - 1]
|
||||||
const next = nodes[i + 1]
|
const next = nodes[i + 1]
|
||||||
// If:
|
// Remove if:
|
||||||
// - the whitespace is the first or last node, or:
|
// - the whitespace is the first or last node, or:
|
||||||
// - the whitespace is adjacent to a comment, or:
|
// - (condense mode) the whitespace is adjacent to a comment, or:
|
||||||
// - the whitespace is between two elements AND contains newline
|
// - (condense mode) the whitespace is between two elements AND contains newline
|
||||||
// Then the whitespace is ignored.
|
|
||||||
if (
|
if (
|
||||||
!prev ||
|
!prev ||
|
||||||
!next ||
|
!next ||
|
||||||
prev.type === NodeTypes.COMMENT ||
|
(!preserve &&
|
||||||
next.type === NodeTypes.COMMENT ||
|
(prev.type === NodeTypes.COMMENT ||
|
||||||
(prev.type === NodeTypes.ELEMENT &&
|
next.type === NodeTypes.COMMENT ||
|
||||||
next.type === NodeTypes.ELEMENT &&
|
(prev.type === NodeTypes.ELEMENT &&
|
||||||
/[\r\n]/.test(node.content))
|
next.type === NodeTypes.ELEMENT &&
|
||||||
|
/[\r\n]/.test(node.content))))
|
||||||
) {
|
) {
|
||||||
removedWhitespace = true
|
removedWhitespace = true
|
||||||
nodes[i] = null as any
|
nodes[i] = null as any
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, condensed consecutive whitespace inside the text
|
// Otherwise, the whitespace is condensed into a single space
|
||||||
// down to a single space
|
|
||||||
node.content = ' '
|
node.content = ' '
|
||||||
}
|
}
|
||||||
} else {
|
} else if (!preserve) {
|
||||||
|
// in condense mode, consecutive whitespaces in text are condensed
|
||||||
|
// down to a single space.
|
||||||
node.content = node.content.replace(/[\t\r\n\f ]+/g, ' ')
|
node.content = node.content.replace(/[\t\r\n\f ]+/g, ' ')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -389,6 +408,27 @@ function parseElement(
|
|||||||
const children = parseChildren(context, mode, ancestors)
|
const children = parseChildren(context, mode, ancestors)
|
||||||
ancestors.pop()
|
ancestors.pop()
|
||||||
|
|
||||||
|
// 2.x inline-template compat
|
||||||
|
if (__COMPAT__) {
|
||||||
|
const inlineTemplateProp = element.props.find(
|
||||||
|
p => p.type === NodeTypes.ATTRIBUTE && p.name === 'inline-template'
|
||||||
|
) as AttributeNode
|
||||||
|
if (
|
||||||
|
inlineTemplateProp &&
|
||||||
|
checkCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE,
|
||||||
|
context,
|
||||||
|
inlineTemplateProp.loc
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
inlineTemplateProp.value!.content = getSelection(
|
||||||
|
context,
|
||||||
|
element.loc.end
|
||||||
|
).source
|
||||||
|
console.log(inlineTemplateProp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
element.children = children
|
element.children = children
|
||||||
|
|
||||||
// End tag.
|
// End tag.
|
||||||
@ -427,11 +467,21 @@ const isSpecialTemplateDirective = /*#__PURE__*/ makeMap(
|
|||||||
/**
|
/**
|
||||||
* Parse a tag (E.g. `<div id=a>`) with that type (start tag or end tag).
|
* Parse a tag (E.g. `<div id=a>`) with that type (start tag or end tag).
|
||||||
*/
|
*/
|
||||||
|
function parseTag(
|
||||||
|
context: ParserContext,
|
||||||
|
type: TagType.Start,
|
||||||
|
parent: ElementNode | undefined
|
||||||
|
): ElementNode
|
||||||
|
function parseTag(
|
||||||
|
context: ParserContext,
|
||||||
|
type: TagType.End,
|
||||||
|
parent: ElementNode | undefined
|
||||||
|
): void
|
||||||
function parseTag(
|
function parseTag(
|
||||||
context: ParserContext,
|
context: ParserContext,
|
||||||
type: TagType,
|
type: TagType,
|
||||||
parent: ElementNode | undefined
|
parent: ElementNode | undefined
|
||||||
): ElementNode {
|
): ElementNode | undefined {
|
||||||
__TEST__ && assert(/^<\/?[a-z]/i.test(context.source))
|
__TEST__ && assert(/^<\/?[a-z]/i.test(context.source))
|
||||||
__TEST__ &&
|
__TEST__ &&
|
||||||
assert(
|
assert(
|
||||||
@ -461,6 +511,7 @@ function parseTag(
|
|||||||
|
|
||||||
// check v-pre
|
// check v-pre
|
||||||
if (
|
if (
|
||||||
|
type === TagType.Start &&
|
||||||
!context.inVPre &&
|
!context.inVPre &&
|
||||||
props.some(p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre')
|
props.some(p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre')
|
||||||
) {
|
) {
|
||||||
@ -484,12 +535,58 @@ function parseTag(
|
|||||||
advanceBy(context, isSelfClosing ? 2 : 1)
|
advanceBy(context, isSelfClosing ? 2 : 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === TagType.End) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.x deprecation checks
|
||||||
|
if (__COMPAT__ && __DEV__ && !__TEST__) {
|
||||||
|
let hasIf = false
|
||||||
|
let hasFor = false
|
||||||
|
for (let i = 0; i < props.length; i++) {
|
||||||
|
const p = props[i]
|
||||||
|
if (p.type === NodeTypes.DIRECTIVE) {
|
||||||
|
if (p.name === 'if') {
|
||||||
|
hasIf = true
|
||||||
|
} else if (p.name === 'for') {
|
||||||
|
hasFor = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasIf && hasFor) {
|
||||||
|
warnDeprecation(
|
||||||
|
CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
|
||||||
|
context,
|
||||||
|
getSelection(context, start)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let tagType = ElementTypes.ELEMENT
|
let tagType = ElementTypes.ELEMENT
|
||||||
const options = context.options
|
const options = context.options
|
||||||
if (!context.inVPre && !options.isCustomElement(tag)) {
|
if (!context.inVPre && !options.isCustomElement(tag)) {
|
||||||
const hasVIs = props.some(
|
const hasVIs = props.some(p => {
|
||||||
p => p.type === NodeTypes.DIRECTIVE && p.name === 'is'
|
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 && !hasVIs) {
|
||||||
if (!options.isNativeTag(tag)) tagType = ElementTypes.COMPONENT
|
if (!options.isNativeTag(tag)) tagType = ElementTypes.COMPONENT
|
||||||
} else if (
|
} else if (
|
||||||
@ -506,11 +603,10 @@ function parseTag(
|
|||||||
tagType = ElementTypes.SLOT
|
tagType = ElementTypes.SLOT
|
||||||
} else if (
|
} else if (
|
||||||
tag === 'template' &&
|
tag === 'template' &&
|
||||||
props.some(p => {
|
props.some(
|
||||||
return (
|
p =>
|
||||||
p.type === NodeTypes.DIRECTIVE && isSpecialTemplateDirective(p.name)
|
p.type === NodeTypes.DIRECTIVE && isSpecialTemplateDirective(p.name)
|
||||||
)
|
)
|
||||||
})
|
|
||||||
) {
|
) {
|
||||||
tagType = ElementTypes.TEMPLATE
|
tagType = ElementTypes.TEMPLATE
|
||||||
}
|
}
|
||||||
@ -615,10 +711,9 @@ function parseAttribute(
|
|||||||
name
|
name
|
||||||
)!
|
)!
|
||||||
|
|
||||||
const dirName =
|
let dirName =
|
||||||
match[1] ||
|
match[1] ||
|
||||||
(startsWith(name, ':') ? 'bind' : startsWith(name, '@') ? 'on' : 'slot')
|
(startsWith(name, ':') ? 'bind' : startsWith(name, '@') ? 'on' : 'slot')
|
||||||
|
|
||||||
let arg: ExpressionNode | undefined
|
let arg: ExpressionNode | undefined
|
||||||
|
|
||||||
if (match[2]) {
|
if (match[2]) {
|
||||||
@ -673,6 +768,32 @@ function parseAttribute(
|
|||||||
valueLoc.source = valueLoc.source.slice(1, -1)
|
valueLoc.source = valueLoc.source.slice(1, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const modifiers = match[3] ? match[3].substr(1).split('.') : []
|
||||||
|
|
||||||
|
// 2.x compat v-bind:foo.sync -> v-model:foo
|
||||||
|
if (__COMPAT__ && dirName === 'bind' && arg) {
|
||||||
|
if (
|
||||||
|
modifiers.includes('sync') &&
|
||||||
|
checkCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
|
||||||
|
context,
|
||||||
|
loc,
|
||||||
|
arg.loc.source
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
dirName = 'model'
|
||||||
|
modifiers.splice(modifiers.indexOf('sync'), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__DEV__ && modifiers.includes('prop')) {
|
||||||
|
checkCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_V_BIND_PROP,
|
||||||
|
context,
|
||||||
|
loc
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.DIRECTIVE,
|
type: NodeTypes.DIRECTIVE,
|
||||||
name: dirName,
|
name: dirName,
|
||||||
@ -686,7 +807,7 @@ function parseAttribute(
|
|||||||
loc: value.loc
|
loc: value.loc
|
||||||
},
|
},
|
||||||
arg,
|
arg,
|
||||||
modifiers: match[3] ? match[3].substr(1).split('.') : [],
|
modifiers,
|
||||||
loc
|
loc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ export const RESOLVE_DYNAMIC_COMPONENT = Symbol(
|
|||||||
__DEV__ ? `resolveDynamicComponent` : ``
|
__DEV__ ? `resolveDynamicComponent` : ``
|
||||||
)
|
)
|
||||||
export const RESOLVE_DIRECTIVE = Symbol(__DEV__ ? `resolveDirective` : ``)
|
export const RESOLVE_DIRECTIVE = Symbol(__DEV__ ? `resolveDirective` : ``)
|
||||||
|
export const RESOLVE_FILTER = Symbol(__DEV__ ? `resolveFilter` : ``)
|
||||||
export const WITH_DIRECTIVES = Symbol(__DEV__ ? `withDirectives` : ``)
|
export const WITH_DIRECTIVES = Symbol(__DEV__ ? `withDirectives` : ``)
|
||||||
export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``)
|
export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``)
|
||||||
export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``)
|
export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``)
|
||||||
@ -50,6 +51,7 @@ export const helperNameMap: any = {
|
|||||||
[RESOLVE_COMPONENT]: `resolveComponent`,
|
[RESOLVE_COMPONENT]: `resolveComponent`,
|
||||||
[RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
|
[RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
|
||||||
[RESOLVE_DIRECTIVE]: `resolveDirective`,
|
[RESOLVE_DIRECTIVE]: `resolveDirective`,
|
||||||
|
[RESOLVE_FILTER]: `resolveFilter`,
|
||||||
[WITH_DIRECTIVES]: `withDirectives`,
|
[WITH_DIRECTIVES]: `withDirectives`,
|
||||||
[RENDER_LIST]: `renderList`,
|
[RENDER_LIST]: `renderList`,
|
||||||
[RENDER_SLOT]: `renderSlot`,
|
[RENDER_SLOT]: `renderSlot`,
|
||||||
|
@ -28,7 +28,7 @@ import {
|
|||||||
capitalize,
|
capitalize,
|
||||||
camelize
|
camelize
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { defaultOnError } from './errors'
|
import { defaultOnError, defaultOnWarn } from './errors'
|
||||||
import {
|
import {
|
||||||
TO_DISPLAY_STRING,
|
TO_DISPLAY_STRING,
|
||||||
FRAGMENT,
|
FRAGMENT,
|
||||||
@ -40,6 +40,7 @@ import {
|
|||||||
} from './runtimeHelpers'
|
} from './runtimeHelpers'
|
||||||
import { isVSlot } from './utils'
|
import { isVSlot } from './utils'
|
||||||
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
|
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
|
||||||
|
import { CompilerCompatOptions } from './compat/compatConfig'
|
||||||
|
|
||||||
// There are two types of transforms:
|
// There are two types of transforms:
|
||||||
//
|
//
|
||||||
@ -83,7 +84,10 @@ export interface ImportItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TransformContext
|
export interface TransformContext
|
||||||
extends Required<Omit<TransformOptions, 'filename'>> {
|
extends Required<
|
||||||
|
Omit<TransformOptions, 'filename' | keyof CompilerCompatOptions>
|
||||||
|
>,
|
||||||
|
CompilerCompatOptions {
|
||||||
selfName: string | null
|
selfName: string | null
|
||||||
root: RootNode
|
root: RootNode
|
||||||
helpers: Map<symbol, number>
|
helpers: Map<symbol, number>
|
||||||
@ -114,6 +118,9 @@ export interface TransformContext
|
|||||||
hoist(exp: JSChildNode): SimpleExpressionNode
|
hoist(exp: JSChildNode): SimpleExpressionNode
|
||||||
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
|
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
|
||||||
constantCache: Map<TemplateChildNode, ConstantTypes>
|
constantCache: Map<TemplateChildNode, ConstantTypes>
|
||||||
|
|
||||||
|
// 2.x Compat only
|
||||||
|
filters?: Set<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTransformContext(
|
export function createTransformContext(
|
||||||
@ -136,7 +143,9 @@ export function createTransformContext(
|
|||||||
bindingMetadata = EMPTY_OBJ,
|
bindingMetadata = EMPTY_OBJ,
|
||||||
inline = false,
|
inline = false,
|
||||||
isTS = false,
|
isTS = false,
|
||||||
onError = defaultOnError
|
onError = defaultOnError,
|
||||||
|
onWarn = defaultOnWarn,
|
||||||
|
compatConfig
|
||||||
}: TransformOptions
|
}: TransformOptions
|
||||||
): TransformContext {
|
): TransformContext {
|
||||||
const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
|
const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
|
||||||
@ -160,6 +169,8 @@ export function createTransformContext(
|
|||||||
inline,
|
inline,
|
||||||
isTS,
|
isTS,
|
||||||
onError,
|
onError,
|
||||||
|
onWarn,
|
||||||
|
compatConfig,
|
||||||
|
|
||||||
// state
|
// state
|
||||||
root,
|
root,
|
||||||
@ -281,6 +292,10 @@ export function createTransformContext(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
context.filters = new Set()
|
||||||
|
}
|
||||||
|
|
||||||
function addId(id: string) {
|
function addId(id: string) {
|
||||||
const { identifiers } = context
|
const { identifiers } = context
|
||||||
if (identifiers[id] === undefined) {
|
if (identifiers[id] === undefined) {
|
||||||
@ -313,6 +328,10 @@ export function transform(root: RootNode, options: TransformOptions) {
|
|||||||
root.hoists = context.hoists
|
root.hoists = context.hoists
|
||||||
root.temps = context.temps
|
root.temps = context.temps
|
||||||
root.cached = context.cached
|
root.cached = context.cached
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
root.filters = [...context.filters!]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRootCodegen(root: RootNode, context: TransformContext) {
|
function createRootCodegen(root: RootNode, context: TransformContext) {
|
||||||
|
@ -41,7 +41,8 @@ import {
|
|||||||
TELEPORT,
|
TELEPORT,
|
||||||
KEEP_ALIVE,
|
KEEP_ALIVE,
|
||||||
SUSPENSE,
|
SUSPENSE,
|
||||||
UNREF
|
UNREF,
|
||||||
|
FRAGMENT
|
||||||
} from '../runtimeHelpers'
|
} from '../runtimeHelpers'
|
||||||
import {
|
import {
|
||||||
getInnerRange,
|
getInnerRange,
|
||||||
@ -55,6 +56,11 @@ import {
|
|||||||
import { buildSlots } from './vSlot'
|
import { buildSlots } from './vSlot'
|
||||||
import { getConstantType } from './hoistStatic'
|
import { getConstantType } from './hoistStatic'
|
||||||
import { BindingTypes } from '../options'
|
import { BindingTypes } from '../options'
|
||||||
|
import {
|
||||||
|
checkCompatEnabled,
|
||||||
|
CompilerDeprecationTypes,
|
||||||
|
isCompatEnabled
|
||||||
|
} from '../compat/compatConfig'
|
||||||
|
|
||||||
// some directive transforms (e.g. v-model) may return a symbol for runtime
|
// some directive transforms (e.g. v-model) may return a symbol for runtime
|
||||||
// import, which should be used instead of a resolveDirective call.
|
// import, which should be used instead of a resolveDirective call.
|
||||||
@ -82,9 +88,23 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
|
|
||||||
// The goal of the transform is to create a codegenNode implementing the
|
// The goal of the transform is to create a codegenNode implementing the
|
||||||
// VNodeCall interface.
|
// VNodeCall interface.
|
||||||
const vnodeTag = isComponent
|
let vnodeTag = isComponent
|
||||||
? resolveComponentType(node as ComponentNode, context)
|
? resolveComponentType(node as ComponentNode, context)
|
||||||
: `"${tag}"`
|
: `"${tag}"`
|
||||||
|
|
||||||
|
// 2.x <template> with no directives compat
|
||||||
|
if (
|
||||||
|
__COMPAT__ &&
|
||||||
|
tag === 'template' &&
|
||||||
|
checkCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
|
||||||
|
context,
|
||||||
|
node.loc
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
vnodeTag = context.helper(FRAGMENT)
|
||||||
|
}
|
||||||
|
|
||||||
const isDynamicComponent =
|
const isDynamicComponent =
|
||||||
isObject(vnodeTag) && vnodeTag.callee === RESOLVE_DYNAMIC_COMPONENT
|
isObject(vnodeTag) && vnodeTag.callee === RESOLVE_DYNAMIC_COMPONENT
|
||||||
|
|
||||||
@ -230,21 +250,29 @@ export function resolveComponentType(
|
|||||||
context: TransformContext,
|
context: TransformContext,
|
||||||
ssr = false
|
ssr = false
|
||||||
) {
|
) {
|
||||||
const { tag } = node
|
let { tag } = node
|
||||||
|
|
||||||
// 1. dynamic component
|
// 1. dynamic component
|
||||||
const isProp = isComponentTag(tag)
|
const isExplicitDynamic = isComponentTag(tag)
|
||||||
? findProp(node, 'is')
|
const isProp =
|
||||||
: findDir(node, 'is')
|
findProp(node, 'is') || (!isExplicitDynamic && findDir(node, 'is'))
|
||||||
if (isProp) {
|
if (isProp) {
|
||||||
const exp =
|
if (!isExplicitDynamic && isProp.type === NodeTypes.ATTRIBUTE) {
|
||||||
isProp.type === NodeTypes.ATTRIBUTE
|
// <button is="vue:xxx">
|
||||||
? isProp.value && createSimpleExpression(isProp.value.content, true)
|
// if not <component>, only is value that starts with "vue:" will be
|
||||||
: isProp.exp
|
// treated as component by the parse phase and reach here, unless it's
|
||||||
if (exp) {
|
// compat mode where all is values are considered components
|
||||||
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
|
tag = isProp.value!.content.replace(/^vue:/, '')
|
||||||
exp
|
} else {
|
||||||
])
|
const exp =
|
||||||
|
isProp.type === NodeTypes.ATTRIBUTE
|
||||||
|
? isProp.value && createSimpleExpression(isProp.value.content, true)
|
||||||
|
: isProp.exp
|
||||||
|
if (exp) {
|
||||||
|
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
|
||||||
|
exp
|
||||||
|
])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,8 +444,11 @@ export function buildProps(
|
|||||||
isStatic = false
|
isStatic = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// skip :is on <component>
|
// skip is on <component>, or is="vue:xxx"
|
||||||
if (name === 'is' && isComponentTag(tag)) {
|
if (
|
||||||
|
name === 'is' &&
|
||||||
|
(isComponentTag(tag) || (value && value.content.startsWith('vue:')))
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
properties.push(
|
properties.push(
|
||||||
@ -437,8 +468,8 @@ export function buildProps(
|
|||||||
} else {
|
} else {
|
||||||
// directives
|
// directives
|
||||||
const { name, arg, exp, loc } = prop
|
const { name, arg, exp, loc } = prop
|
||||||
const isBind = name === 'bind'
|
const isVBind = name === 'bind'
|
||||||
const isOn = name === 'on'
|
const isVOn = name === 'on'
|
||||||
|
|
||||||
// skip v-slot - it is handled by its dedicated transform.
|
// skip v-slot - it is handled by its dedicated transform.
|
||||||
if (name === 'slot') {
|
if (name === 'slot') {
|
||||||
@ -456,17 +487,17 @@ export function buildProps(
|
|||||||
// skip v-is and :is on <component>
|
// skip v-is and :is on <component>
|
||||||
if (
|
if (
|
||||||
name === 'is' ||
|
name === 'is' ||
|
||||||
(isBind && isComponentTag(tag) && isBindKey(arg, 'is'))
|
(isVBind && isComponentTag(tag) && isBindKey(arg, 'is'))
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// skip v-on in SSR compilation
|
// skip v-on in SSR compilation
|
||||||
if (isOn && ssr) {
|
if (isVOn && ssr) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// special case for v-bind and v-on with no argument
|
// special case for v-bind and v-on with no argument
|
||||||
if (!arg && (isBind || isOn)) {
|
if (!arg && (isVBind || isVOn)) {
|
||||||
hasDynamicKeys = true
|
hasDynamicKeys = true
|
||||||
if (exp) {
|
if (exp) {
|
||||||
if (properties.length) {
|
if (properties.length) {
|
||||||
@ -475,7 +506,50 @@ export function buildProps(
|
|||||||
)
|
)
|
||||||
properties = []
|
properties = []
|
||||||
}
|
}
|
||||||
if (isBind) {
|
if (isVBind) {
|
||||||
|
if (__COMPAT__) {
|
||||||
|
// 2.x v-bind object order compat
|
||||||
|
if (__DEV__) {
|
||||||
|
const hasOverridableKeys = mergeArgs.some(arg => {
|
||||||
|
if (arg.type === NodeTypes.JS_OBJECT_EXPRESSION) {
|
||||||
|
return arg.properties.some(({ key }) => {
|
||||||
|
if (
|
||||||
|
key.type !== NodeTypes.SIMPLE_EXPRESSION ||
|
||||||
|
!key.isStatic
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
key.content !== 'class' &&
|
||||||
|
key.content !== 'style' &&
|
||||||
|
!isOn(key.content)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// dynamic expression
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (hasOverridableKeys) {
|
||||||
|
checkCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
|
||||||
|
context,
|
||||||
|
loc
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
mergeArgs.unshift(exp)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mergeArgs.push(exp)
|
mergeArgs.push(exp)
|
||||||
} else {
|
} else {
|
||||||
// v-on="obj" -> toHandlers(obj)
|
// v-on="obj" -> toHandlers(obj)
|
||||||
@ -489,7 +563,7 @@ export function buildProps(
|
|||||||
} else {
|
} else {
|
||||||
context.onError(
|
context.onError(
|
||||||
createCompilerError(
|
createCompilerError(
|
||||||
isBind
|
isVBind
|
||||||
? ErrorCodes.X_V_BIND_NO_EXPRESSION
|
? ErrorCodes.X_V_BIND_NO_EXPRESSION
|
||||||
: ErrorCodes.X_V_ON_NO_EXPRESSION,
|
: ErrorCodes.X_V_ON_NO_EXPRESSION,
|
||||||
loc
|
loc
|
||||||
@ -516,6 +590,25 @@ export function buildProps(
|
|||||||
runtimeDirectives.push(prop)
|
runtimeDirectives.push(prop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
__COMPAT__ &&
|
||||||
|
prop.type === NodeTypes.ATTRIBUTE &&
|
||||||
|
prop.name === 'ref' &&
|
||||||
|
context.scopes.vFor > 0 &&
|
||||||
|
checkCompatEnabled(
|
||||||
|
CompilerDeprecationTypes.COMPILER_V_FOR_REF,
|
||||||
|
context,
|
||||||
|
prop.loc
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
properties.push(
|
||||||
|
createObjectProperty(
|
||||||
|
createSimpleExpression('refInFor', true),
|
||||||
|
createSimpleExpression('true', false)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let propsExpression: PropsExpression | undefined = undefined
|
let propsExpression: PropsExpression | undefined = undefined
|
||||||
|
@ -254,6 +254,11 @@ export function processExpression(
|
|||||||
parent && parentStack.push(parent)
|
parent && parentStack.push(parent)
|
||||||
if (node.type === 'Identifier') {
|
if (node.type === 'Identifier') {
|
||||||
if (!isDuplicate(node)) {
|
if (!isDuplicate(node)) {
|
||||||
|
// v2 wrapped filter call
|
||||||
|
if (__COMPAT__ && node.name.startsWith('_filter_')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const needPrefix = shouldPrefix(node, parent!, parentStack)
|
const needPrefix = shouldPrefix(node, parent!, parentStack)
|
||||||
if (!knownIds[node.name] && needPrefix) {
|
if (!knownIds[node.name] && needPrefix) {
|
||||||
if (isStaticProperty(parent!) && parent.shorthand) {
|
if (isStaticProperty(parent!) && parent.shorthand) {
|
||||||
|
@ -63,7 +63,11 @@ export const transformText: NodeTransform = (node, context) => {
|
|||||||
(children.length === 1 &&
|
(children.length === 1 &&
|
||||||
(node.type === NodeTypes.ROOT ||
|
(node.type === NodeTypes.ROOT ||
|
||||||
(node.type === NodeTypes.ELEMENT &&
|
(node.type === NodeTypes.ELEMENT &&
|
||||||
node.tagType === ElementTypes.ELEMENT)))
|
node.tagType === ElementTypes.ELEMENT &&
|
||||||
|
// in compat mode, <template> tags with no special directives
|
||||||
|
// will be rendered as a fragment so its children must be
|
||||||
|
// converted into vnodes.
|
||||||
|
!(__COMPAT__ && node.tag === 'template'))))
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import { CAMELIZE } from '../runtimeHelpers'
|
|||||||
// v-bind without arg is handled directly in ./transformElements.ts due to it affecting
|
// v-bind without arg is handled directly in ./transformElements.ts due to it affecting
|
||||||
// codegen for the entire props object. This transform here is only for v-bind
|
// codegen for the entire props object. This transform here is only for v-bind
|
||||||
// *with* args.
|
// *with* args.
|
||||||
export const transformBind: DirectiveTransform = (dir, node, context) => {
|
export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
||||||
const { exp, modifiers, loc } = dir
|
const { exp, modifiers, loc } = dir
|
||||||
const arg = dir.arg!
|
const arg = dir.arg!
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ export function processIf(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dir.name === 'if') {
|
if (dir.name === 'if') {
|
||||||
const branch = createIfBranch(node, dir)
|
const branch = createIfBranch(node, dir, context)
|
||||||
const ifNode: IfNode = {
|
const ifNode: IfNode = {
|
||||||
type: NodeTypes.IF,
|
type: NodeTypes.IF,
|
||||||
loc: node.loc,
|
loc: node.loc,
|
||||||
@ -145,7 +145,7 @@ export function processIf(
|
|||||||
if (sibling && sibling.type === NodeTypes.IF) {
|
if (sibling && sibling.type === NodeTypes.IF) {
|
||||||
// move the node to the if node's branches
|
// move the node to the if node's branches
|
||||||
context.removeNode()
|
context.removeNode()
|
||||||
const branch = createIfBranch(node, dir)
|
const branch = createIfBranch(node, dir, context)
|
||||||
if (__DEV__ && comments.length) {
|
if (__DEV__ && comments.length) {
|
||||||
branch.children = [...comments, ...branch.children]
|
branch.children = [...comments, ...branch.children]
|
||||||
}
|
}
|
||||||
@ -187,7 +187,16 @@ export function processIf(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
|
function createIfBranch(
|
||||||
|
node: ElementNode,
|
||||||
|
dir: DirectiveNode,
|
||||||
|
context: TransformContext
|
||||||
|
): IfBranchNode {
|
||||||
|
const userKey = findProp(node, `key`)
|
||||||
|
if (__DEV__ && userKey) {
|
||||||
|
context.onWarn(createCompilerError(ErrorCodes.X_V_IF_KEY, userKey.loc))
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.IF_BRANCH,
|
type: NodeTypes.IF_BRANCH,
|
||||||
loc: node.loc,
|
loc: node.loc,
|
||||||
@ -196,7 +205,7 @@ function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
|
|||||||
node.tagType === ElementTypes.TEMPLATE && !findDir(node, 'for')
|
node.tagType === ElementTypes.TEMPLATE && !findDir(node, 'for')
|
||||||
? node.children
|
? node.children
|
||||||
: [node],
|
: [node],
|
||||||
userKey: findProp(node, `key`)
|
userKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +271,7 @@ export function injectProp(
|
|||||||
|
|
||||||
export function toValidAssetId(
|
export function toValidAssetId(
|
||||||
name: string,
|
name: string,
|
||||||
type: 'component' | 'directive'
|
type: 'component' | 'directive' | 'filter'
|
||||||
): string {
|
): string {
|
||||||
return `_${type}_${name.replace(/[^\w]/g, '_')}`
|
return `_${type}_${name.replace(/[^\w]/g, '_')}`
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
"name": "VueCompilerDOM",
|
"name": "VueCompilerDOM",
|
||||||
|
"compat": true,
|
||||||
"formats": [
|
"formats": [
|
||||||
"esm-bundler",
|
"esm-bundler",
|
||||||
"esm-browser",
|
"esm-browser",
|
||||||
|
@ -12,12 +12,12 @@ export interface DOMCompilerError extends CompilerError {
|
|||||||
export function createDOMCompilerError(
|
export function createDOMCompilerError(
|
||||||
code: DOMErrorCodes,
|
code: DOMErrorCodes,
|
||||||
loc?: SourceLocation
|
loc?: SourceLocation
|
||||||
): DOMCompilerError {
|
) {
|
||||||
return createCompilerError(
|
return createCompilerError(
|
||||||
code,
|
code,
|
||||||
loc,
|
loc,
|
||||||
__DEV__ || !__BROWSER__ ? DOMErrorMessages : undefined
|
__DEV__ || !__BROWSER__ ? DOMErrorMessages : undefined
|
||||||
)
|
) as DOMCompilerError
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum DOMErrorCodes {
|
export const enum DOMErrorCodes {
|
||||||
|
@ -8,7 +8,9 @@ import {
|
|||||||
createCompoundExpression,
|
createCompoundExpression,
|
||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
isStaticExp
|
isStaticExp,
|
||||||
|
warnDeprecation,
|
||||||
|
CompilerDeprecationTypes
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../runtimeHelpers'
|
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../runtimeHelpers'
|
||||||
import { makeMap, capitalize } from '@vue/shared'
|
import { makeMap, capitalize } from '@vue/shared'
|
||||||
@ -92,6 +94,14 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
|
|||||||
const { modifiers } = dir
|
const { modifiers } = dir
|
||||||
if (!modifiers.length) return baseResult
|
if (!modifiers.length) return baseResult
|
||||||
|
|
||||||
|
if (__COMPAT__ && __DEV__ && modifiers.includes('native')) {
|
||||||
|
warnDeprecation(
|
||||||
|
CompilerDeprecationTypes.COMPILER_V_ON_NATIVE,
|
||||||
|
context,
|
||||||
|
dir.loc
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let { key, value: handlerExp } = baseResult.props[0]
|
let { key, value: handlerExp } = baseResult.props[0]
|
||||||
const {
|
const {
|
||||||
keyModifiers,
|
keyModifiers,
|
||||||
|
@ -155,6 +155,18 @@ export function parse(
|
|||||||
false
|
false
|
||||||
) as SFCTemplateBlock)
|
) as SFCTemplateBlock)
|
||||||
templateBlock.ast = node
|
templateBlock.ast = node
|
||||||
|
|
||||||
|
// warn against 2.x <template functional>
|
||||||
|
if (templateBlock.attrs.functional) {
|
||||||
|
const err = new SyntaxError(
|
||||||
|
`<template functional> is no longer supported in Vue 3, since ` +
|
||||||
|
`functional components no longer have significant performance ` +
|
||||||
|
`difference from stateful ones. Just use a normal <template> ` +
|
||||||
|
`instead.`
|
||||||
|
) as CompilerError
|
||||||
|
err.loc = node.props.find(p => p.name === 'functional')!.loc
|
||||||
|
errors.push(err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.push(createDuplicateBlockError(node))
|
errors.push(createDuplicateBlockError(node))
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,8 @@ export interface SSRCompilerError extends CompilerError {
|
|||||||
export function createSSRCompilerError(
|
export function createSSRCompilerError(
|
||||||
code: SSRErrorCodes,
|
code: SSRErrorCodes,
|
||||||
loc?: SourceLocation
|
loc?: SourceLocation
|
||||||
): SSRCompilerError {
|
) {
|
||||||
return createCompilerError(code, loc, SSRErrorMessages)
|
return createCompilerError(code, loc, SSRErrorMessages) as SSRCompilerError
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum SSRErrorCodes {
|
export const enum SSRErrorCodes {
|
||||||
|
1
packages/global.d.ts
vendored
1
packages/global.d.ts
vendored
@ -8,6 +8,7 @@ declare var __ESM_BROWSER__: boolean
|
|||||||
declare var __NODE_JS__: boolean
|
declare var __NODE_JS__: boolean
|
||||||
declare var __COMMIT__: string
|
declare var __COMMIT__: string
|
||||||
declare var __VERSION__: string
|
declare var __VERSION__: string
|
||||||
|
declare var __COMPAT__: boolean
|
||||||
|
|
||||||
// Feature flags
|
// Feature flags
|
||||||
declare var __FEATURE_OPTIONS_API__: boolean
|
declare var __FEATURE_OPTIONS_API__: boolean
|
||||||
|
@ -481,4 +481,7 @@ describe('api: createApp', () => {
|
|||||||
app.mount(root)
|
app.mount(root)
|
||||||
expect(serializeInner(root)).toBe('hello')
|
expect(serializeInner(root)).toBe('hello')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// config.compilerOptions is tested in packages/vue since it is only
|
||||||
|
// supported in the full build.
|
||||||
})
|
})
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
createApp,
|
createApp,
|
||||||
shallowReadonly
|
shallowReadonly
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import { ComponentInternalInstance } from '../src/component'
|
import { ComponentInternalInstance, ComponentOptions } from '../src/component'
|
||||||
|
|
||||||
describe('component: proxy', () => {
|
describe('component: proxy', () => {
|
||||||
test('data', () => {
|
test('data', () => {
|
||||||
@ -93,7 +93,7 @@ describe('component: proxy', () => {
|
|||||||
expect(instanceProxy.$root).toBe(instance!.root.proxy)
|
expect(instanceProxy.$root).toBe(instance!.root.proxy)
|
||||||
expect(instanceProxy.$emit).toBe(instance!.emit)
|
expect(instanceProxy.$emit).toBe(instance!.emit)
|
||||||
expect(instanceProxy.$el).toBe(instance!.vnode.el)
|
expect(instanceProxy.$el).toBe(instance!.vnode.el)
|
||||||
expect(instanceProxy.$options).toBe(instance!.type)
|
expect(instanceProxy.$options).toBe(instance!.type as ComponentOptions)
|
||||||
expect(() => (instanceProxy.$data = {})).toThrow(TypeError)
|
expect(() => (instanceProxy.$data = {})).toThrow(TypeError)
|
||||||
expect(`Attempting to mutate public property "$data"`).toHaveBeenWarned()
|
expect(`Attempting to mutate public property "$data"`).toHaveBeenWarned()
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ describe('component: slots', () => {
|
|||||||
expect(slots.default()).toMatchObject([normalizeVNode(h('span'))])
|
expect(slots.default()).toMatchObject([normalizeVNode(h('span'))])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('updateSlots: instance.slots should be update correctly (when slotType is number)', async () => {
|
test('updateSlots: instance.slots should be updated correctly (when slotType is number)', async () => {
|
||||||
const flag1 = ref(true)
|
const flag1 = ref(true)
|
||||||
|
|
||||||
let instance: any
|
let instance: any
|
||||||
@ -124,7 +124,7 @@ describe('component: slots', () => {
|
|||||||
expect(instance.slots).toHaveProperty('two')
|
expect(instance.slots).toHaveProperty('two')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('updateSlots: instance.slots should be update correctly (when slotType is null)', async () => {
|
test('updateSlots: instance.slots should be updated correctly (when slotType is null)', async () => {
|
||||||
const flag1 = ref(true)
|
const flag1 = ref(true)
|
||||||
|
|
||||||
let instance: any
|
let instance: any
|
||||||
|
@ -4,17 +4,20 @@ import {
|
|||||||
validateComponentName,
|
validateComponentName,
|
||||||
Component
|
Component
|
||||||
} from './component'
|
} from './component'
|
||||||
import { ComponentOptions } from './componentOptions'
|
import { ComponentOptions, RuntimeCompilerOptions } from './componentOptions'
|
||||||
import { ComponentPublicInstance } from './componentPublicInstance'
|
import { ComponentPublicInstance } from './componentPublicInstance'
|
||||||
import { Directive, validateDirectiveName } from './directives'
|
import { Directive, validateDirectiveName } from './directives'
|
||||||
import { RootRenderFunction } from './renderer'
|
import { RootRenderFunction } from './renderer'
|
||||||
import { InjectionKey } from './apiInject'
|
import { InjectionKey } from './apiInject'
|
||||||
import { isFunction, NO, isObject } from '@vue/shared'
|
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { createVNode, cloneVNode, VNode } from './vnode'
|
import { createVNode, cloneVNode, VNode } from './vnode'
|
||||||
import { RootHydrateFunction } from './hydration'
|
import { RootHydrateFunction } from './hydration'
|
||||||
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
|
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
|
||||||
|
import { isFunction, NO, isObject } from '@vue/shared'
|
||||||
import { version } from '.'
|
import { version } from '.'
|
||||||
|
import { installCompatMount } from './compat/global'
|
||||||
|
import { installLegacyConfigProperties } from './compat/globalConfig'
|
||||||
|
import { installGlobalFilterMethod } from './compat/filter'
|
||||||
|
|
||||||
export interface App<HostElement = any> {
|
export interface App<HostElement = any> {
|
||||||
version: string
|
version: string
|
||||||
@ -39,6 +42,17 @@ export interface App<HostElement = any> {
|
|||||||
_props: Data | null
|
_props: Data | null
|
||||||
_container: HostElement | null
|
_container: HostElement | null
|
||||||
_context: AppContext
|
_context: AppContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v2 compat only
|
||||||
|
*/
|
||||||
|
filter?(name: string): Function | undefined
|
||||||
|
filter?(name: string, filter: Function): this
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal v3 compat only
|
||||||
|
*/
|
||||||
|
_createRoot?(options: ComponentOptions): ComponentPublicInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OptionMergeFunction = (
|
export type OptionMergeFunction = (
|
||||||
@ -55,7 +69,6 @@ export interface AppConfig {
|
|||||||
performance: boolean
|
performance: boolean
|
||||||
optionMergeStrategies: Record<string, OptionMergeFunction>
|
optionMergeStrategies: Record<string, OptionMergeFunction>
|
||||||
globalProperties: Record<string, any>
|
globalProperties: Record<string, any>
|
||||||
isCustomElement: (tag: string) => boolean
|
|
||||||
errorHandler?: (
|
errorHandler?: (
|
||||||
err: unknown,
|
err: unknown,
|
||||||
instance: ComponentPublicInstance | null,
|
instance: ComponentPublicInstance | null,
|
||||||
@ -66,6 +79,17 @@ export interface AppConfig {
|
|||||||
instance: ComponentPublicInstance | null,
|
instance: ComponentPublicInstance | null,
|
||||||
trace: string
|
trace: string
|
||||||
) => void
|
) => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use config.compilerOptions.isCustomElement
|
||||||
|
*/
|
||||||
|
isCustomElement?: (tag: string) => boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options to pass to @vue/compiler-dom.
|
||||||
|
* Only supported in runtime compiler build.
|
||||||
|
*/
|
||||||
|
compilerOptions: RuntimeCompilerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppContext {
|
export interface AppContext {
|
||||||
@ -85,6 +109,11 @@ export interface AppContext {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
reload?: () => void
|
reload?: () => void
|
||||||
|
/**
|
||||||
|
* v2 compat only
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
filters?: Record<string, Function>
|
||||||
}
|
}
|
||||||
|
|
||||||
type PluginInstallFunction = (app: App, ...options: any[]) => any
|
type PluginInstallFunction = (app: App, ...options: any[]) => any
|
||||||
@ -103,9 +132,11 @@ export function createAppContext(): AppContext {
|
|||||||
performance: false,
|
performance: false,
|
||||||
globalProperties: {},
|
globalProperties: {},
|
||||||
optionMergeStrategies: {},
|
optionMergeStrategies: {},
|
||||||
isCustomElement: NO,
|
|
||||||
errorHandler: undefined,
|
errorHandler: undefined,
|
||||||
warnHandler: undefined
|
warnHandler: undefined,
|
||||||
|
compilerOptions: {
|
||||||
|
isCustomElement: NO
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mixins: [],
|
mixins: [],
|
||||||
components: {},
|
components: {},
|
||||||
@ -298,6 +329,12 @@ export function createAppAPI<HostElement>(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
installCompatMount(app, context, render, hydrate)
|
||||||
|
installGlobalFilterMethod(app, context)
|
||||||
|
if (__DEV__) installLegacyConfigProperties(app.config)
|
||||||
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,8 @@ import {
|
|||||||
NOOP,
|
NOOP,
|
||||||
remove,
|
remove,
|
||||||
isMap,
|
isMap,
|
||||||
isSet
|
isSet,
|
||||||
|
isPlainObject
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
currentInstance,
|
currentInstance,
|
||||||
@ -33,6 +34,9 @@ import {
|
|||||||
} from './errorHandling'
|
} from './errorHandling'
|
||||||
import { queuePostRenderEffect } from './renderer'
|
import { queuePostRenderEffect } from './renderer'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
import { DeprecationTypes } from './compat/compatConfig'
|
||||||
|
import { checkCompatEnabled, isCompatEnabled } from './compat/compatConfig'
|
||||||
|
import { ObjectWatchOptionItem } from './componentOptions'
|
||||||
|
|
||||||
export type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void
|
export type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void
|
||||||
|
|
||||||
@ -217,6 +221,21 @@ function doWatch(
|
|||||||
__DEV__ && warnInvalidSource(source)
|
__DEV__ && warnInvalidSource(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2.x array mutation watch compat
|
||||||
|
if (__COMPAT__ && cb && !deep) {
|
||||||
|
const baseGetter = getter
|
||||||
|
getter = () => {
|
||||||
|
const val = baseGetter()
|
||||||
|
if (
|
||||||
|
isArray(val) &&
|
||||||
|
checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)
|
||||||
|
) {
|
||||||
|
traverse(val)
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (cb && deep) {
|
if (cb && deep) {
|
||||||
const baseGetter = getter
|
const baseGetter = getter
|
||||||
getter = () => traverse(baseGetter())
|
getter = () => traverse(baseGetter())
|
||||||
@ -254,7 +273,14 @@ function doWatch(
|
|||||||
if (cb) {
|
if (cb) {
|
||||||
// watch(source, cb)
|
// watch(source, cb)
|
||||||
const newValue = runner()
|
const newValue = runner()
|
||||||
if (deep || forceTrigger || hasChanged(newValue, oldValue)) {
|
if (
|
||||||
|
deep ||
|
||||||
|
forceTrigger ||
|
||||||
|
hasChanged(newValue, oldValue) ||
|
||||||
|
(__COMPAT__ &&
|
||||||
|
isArray(newValue) &&
|
||||||
|
isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))
|
||||||
|
) {
|
||||||
// cleanup before running cb again
|
// cleanup before running cb again
|
||||||
if (cleanup) {
|
if (cleanup) {
|
||||||
cleanup()
|
cleanup()
|
||||||
@ -329,7 +355,7 @@ function doWatch(
|
|||||||
export function instanceWatch(
|
export function instanceWatch(
|
||||||
this: ComponentInternalInstance,
|
this: ComponentInternalInstance,
|
||||||
source: string | Function,
|
source: string | Function,
|
||||||
cb: WatchCallback,
|
value: WatchCallback | ObjectWatchOptionItem,
|
||||||
options?: WatchOptions
|
options?: WatchOptions
|
||||||
): WatchStopHandle {
|
): WatchStopHandle {
|
||||||
const publicThis = this.proxy as any
|
const publicThis = this.proxy as any
|
||||||
@ -338,6 +364,13 @@ export function instanceWatch(
|
|||||||
? createPathGetter(publicThis, source)
|
? createPathGetter(publicThis, source)
|
||||||
: () => publicThis[source]
|
: () => publicThis[source]
|
||||||
: source.bind(publicThis)
|
: source.bind(publicThis)
|
||||||
|
let cb
|
||||||
|
if (isFunction(value)) {
|
||||||
|
cb = value
|
||||||
|
} else {
|
||||||
|
cb = value.handler as Function
|
||||||
|
options = value
|
||||||
|
}
|
||||||
return doWatch(getter, cb.bind(publicThis), options, this)
|
return doWatch(getter, cb.bind(publicThis), options, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,9 +400,9 @@ function traverse(value: unknown, seen: Set<unknown> = new Set()) {
|
|||||||
value.forEach((v: any) => {
|
value.forEach((v: any) => {
|
||||||
traverse(v, seen)
|
traverse(v, seen)
|
||||||
})
|
})
|
||||||
} else {
|
} else if (isPlainObject(value)) {
|
||||||
for (const key in value) {
|
for (const key in value) {
|
||||||
traverse(value[key], seen)
|
traverse((value as any)[key], seen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
|
336
packages/runtime-core/src/compat/__tests__/global.spec.ts
Normal file
336
packages/runtime-core/src/compat/__tests__/global.spec.ts
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
import Vue from '@vue/compat'
|
||||||
|
import { effect, isReactive } from '@vue/reactivity'
|
||||||
|
import {
|
||||||
|
DeprecationTypes,
|
||||||
|
deprecationData,
|
||||||
|
toggleDeprecationWarning
|
||||||
|
} from '../compatConfig'
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
toggleDeprecationWarning(false)
|
||||||
|
Vue.configureCompat({ MODE: 2 })
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Vue.configureCompat({ MODE: 3 })
|
||||||
|
toggleDeprecationWarning(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GLOBAL_MOUNT', () => {
|
||||||
|
test('new Vue() with el', () => {
|
||||||
|
toggleDeprecationWarning(true)
|
||||||
|
|
||||||
|
const el = document.createElement('div')
|
||||||
|
el.innerHTML = `{{ msg }}`
|
||||||
|
new Vue({
|
||||||
|
el,
|
||||||
|
compatConfig: { GLOBAL_MOUNT: true },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
msg: 'hello'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(
|
||||||
|
deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
expect(el.innerHTML).toBe('hello')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('new Vue() + $mount', () => {
|
||||||
|
const el = document.createElement('div')
|
||||||
|
el.innerHTML = `{{ msg }}`
|
||||||
|
new Vue({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
msg: 'hello'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).$mount(el)
|
||||||
|
expect(el.innerHTML).toBe('hello')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GLOBAL_MOUNT_CONTAINER', () => {
|
||||||
|
test('should warn', () => {
|
||||||
|
toggleDeprecationWarning(true)
|
||||||
|
|
||||||
|
const el = document.createElement('div')
|
||||||
|
el.innerHTML = `test`
|
||||||
|
el.setAttribute('v-bind:id', 'foo')
|
||||||
|
new Vue().$mount(el)
|
||||||
|
// warning only
|
||||||
|
expect(
|
||||||
|
deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
expect(
|
||||||
|
deprecationData[DeprecationTypes.GLOBAL_MOUNT_CONTAINER].message
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GLOBAL_EXTEND', () => {
|
||||||
|
// https://github.com/vuejs/vue/blob/dev/test/unit/features/global-api/extend.spec.js
|
||||||
|
it('should correctly merge options', () => {
|
||||||
|
toggleDeprecationWarning(true)
|
||||||
|
|
||||||
|
const Test = Vue.extend({
|
||||||
|
name: 'test',
|
||||||
|
a: 1,
|
||||||
|
b: 2
|
||||||
|
})
|
||||||
|
expect(Test.options.a).toBe(1)
|
||||||
|
expect(Test.options.b).toBe(2)
|
||||||
|
expect(Test.super).toBe(Vue)
|
||||||
|
const t = new Test({
|
||||||
|
a: 2
|
||||||
|
})
|
||||||
|
expect(t.$options.a).toBe(2)
|
||||||
|
expect(t.$options.b).toBe(2)
|
||||||
|
// inheritance
|
||||||
|
const Test2 = Test.extend({
|
||||||
|
a: 2
|
||||||
|
})
|
||||||
|
expect(Test2.options.a).toBe(2)
|
||||||
|
expect(Test2.options.b).toBe(2)
|
||||||
|
const t2 = new Test2({
|
||||||
|
a: 3
|
||||||
|
})
|
||||||
|
expect(t2.$options.a).toBe(3)
|
||||||
|
expect(t2.$options.b).toBe(2)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
expect(
|
||||||
|
deprecationData[DeprecationTypes.GLOBAL_EXTEND].message
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should work when used as components', () => {
|
||||||
|
const foo = Vue.extend({
|
||||||
|
template: '<span>foo</span>'
|
||||||
|
})
|
||||||
|
const bar = Vue.extend({
|
||||||
|
template: '<span>bar</span>'
|
||||||
|
})
|
||||||
|
const vm = new Vue({
|
||||||
|
template: '<div><foo></foo><bar></bar></div>',
|
||||||
|
components: { foo, bar }
|
||||||
|
}).$mount()
|
||||||
|
expect(vm.$el.innerHTML).toBe('<span>foo</span><span>bar</span>')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should merge lifecycle hooks', () => {
|
||||||
|
const calls: number[] = []
|
||||||
|
const A = Vue.extend({
|
||||||
|
created() {
|
||||||
|
calls.push(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const B = A.extend({
|
||||||
|
created() {
|
||||||
|
calls.push(2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
new B({
|
||||||
|
created() {
|
||||||
|
calls.push(3)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(calls).toEqual([1, 2, 3])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not merge nested mixins created with Vue.extend', () => {
|
||||||
|
const A = Vue.extend({
|
||||||
|
created: () => {}
|
||||||
|
})
|
||||||
|
const B = Vue.extend({
|
||||||
|
mixins: [A],
|
||||||
|
created: () => {}
|
||||||
|
})
|
||||||
|
const C = Vue.extend({
|
||||||
|
extends: B,
|
||||||
|
created: () => {}
|
||||||
|
})
|
||||||
|
const D = Vue.extend({
|
||||||
|
mixins: [C],
|
||||||
|
created: () => {}
|
||||||
|
})
|
||||||
|
expect(D.options.created!.length).toBe(4)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should merge methods', () => {
|
||||||
|
const A = Vue.extend({
|
||||||
|
methods: {
|
||||||
|
a() {
|
||||||
|
return this.n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const B = A.extend({
|
||||||
|
methods: {
|
||||||
|
b() {
|
||||||
|
return this.n + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const b = new B({
|
||||||
|
data: () => ({ n: 0 }),
|
||||||
|
methods: {
|
||||||
|
c() {
|
||||||
|
return this.n + 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) as any
|
||||||
|
expect(b.a()).toBe(0)
|
||||||
|
expect(b.b()).toBe(1)
|
||||||
|
expect(b.c()).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should merge assets', () => {
|
||||||
|
const A = Vue.extend({
|
||||||
|
components: {
|
||||||
|
aa: {
|
||||||
|
template: '<div>A</div>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const B = A.extend({
|
||||||
|
components: {
|
||||||
|
bb: {
|
||||||
|
template: '<div>B</div>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const b = new B({
|
||||||
|
template: '<div><aa></aa><bb></bb></div>'
|
||||||
|
}).$mount()
|
||||||
|
expect(b.$el.innerHTML).toBe('<div>A</div><div>B</div>')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('caching', () => {
|
||||||
|
const options = {
|
||||||
|
template: '<div></div>'
|
||||||
|
}
|
||||||
|
const A = Vue.extend(options)
|
||||||
|
const B = Vue.extend(options)
|
||||||
|
expect(A).toBe(B)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('extended options should use different identify from parent', () => {
|
||||||
|
const A = Vue.extend({ computed: {} })
|
||||||
|
const B = A.extend()
|
||||||
|
B.options.computed.b = () => 'foo'
|
||||||
|
expect(B.options.computed).not.toBe(A.options.computed)
|
||||||
|
expect(A.options.computed.b).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GLOBAL_PROTOTYPE', () => {
|
||||||
|
test('plain properties', () => {
|
||||||
|
toggleDeprecationWarning(true)
|
||||||
|
Vue.prototype.$test = 1
|
||||||
|
const vm = new Vue() as any
|
||||||
|
expect(vm.$test).toBe(1)
|
||||||
|
delete Vue.prototype.$test
|
||||||
|
expect(
|
||||||
|
deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
expect(
|
||||||
|
deprecationData[DeprecationTypes.GLOBAL_PROTOTYPE].message
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('method this context', () => {
|
||||||
|
Vue.prototype.$test = function() {
|
||||||
|
return this.msg
|
||||||
|
}
|
||||||
|
const vm = new Vue({
|
||||||
|
data() {
|
||||||
|
return { msg: 'method' }
|
||||||
|
}
|
||||||
|
}) as any
|
||||||
|
expect(vm.$test()).toBe('method')
|
||||||
|
delete Vue.prototype.$test
|
||||||
|
})
|
||||||
|
|
||||||
|
test('defined properties', () => {
|
||||||
|
Object.defineProperty(Vue.prototype, '$test', {
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
return this.msg
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const vm = new Vue({
|
||||||
|
data() {
|
||||||
|
return { msg: 'getter' }
|
||||||
|
}
|
||||||
|
}) as any
|
||||||
|
expect(vm.$test).toBe('getter')
|
||||||
|
delete Vue.prototype.$test
|
||||||
|
})
|
||||||
|
|
||||||
|
test('extended prototype', async () => {
|
||||||
|
const Foo = Vue.extend()
|
||||||
|
Foo.prototype.$test = 1
|
||||||
|
const vm = new Foo() as any
|
||||||
|
expect(vm.$test).toBe(1)
|
||||||
|
const plain = new Vue() as any
|
||||||
|
expect(plain.$test).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GLOBAL_SET/DELETE', () => {
|
||||||
|
test('set', () => {
|
||||||
|
toggleDeprecationWarning(true)
|
||||||
|
const obj: any = {}
|
||||||
|
Vue.set(obj, 'foo', 1)
|
||||||
|
expect(obj.foo).toBe(1)
|
||||||
|
expect(
|
||||||
|
deprecationData[DeprecationTypes.GLOBAL_SET].message
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('delete', () => {
|
||||||
|
toggleDeprecationWarning(true)
|
||||||
|
const obj: any = { foo: 1 }
|
||||||
|
Vue.delete(obj, 'foo')
|
||||||
|
expect('foo' in obj).toBe(false)
|
||||||
|
expect(
|
||||||
|
deprecationData[DeprecationTypes.GLOBAL_DELETE].message
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GLOBAL_OBSERVABLE', () => {
|
||||||
|
test('should work', () => {
|
||||||
|
toggleDeprecationWarning(true)
|
||||||
|
const obj = Vue.observable({})
|
||||||
|
expect(isReactive(obj)).toBe(true)
|
||||||
|
expect(
|
||||||
|
deprecationData[DeprecationTypes.GLOBAL_OBSERVABLE].message
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GLOBAL_PRIVATE_UTIL', () => {
|
||||||
|
test('defineReactive', () => {
|
||||||
|
toggleDeprecationWarning(true)
|
||||||
|
const obj: any = {}
|
||||||
|
// @ts-ignore
|
||||||
|
Vue.util.defineReactive(obj, 'test', 1)
|
||||||
|
|
||||||
|
let n
|
||||||
|
effect(() => {
|
||||||
|
n = obj.test
|
||||||
|
})
|
||||||
|
expect(n).toBe(1)
|
||||||
|
obj.test++
|
||||||
|
expect(n).toBe(2)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
deprecationData[DeprecationTypes.GLOBAL_PRIVATE_UTIL].message
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,77 @@
|
|||||||
|
import Vue from '@vue/compat'
|
||||||
|
import { toggleDeprecationWarning } from '../compatConfig'
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
toggleDeprecationWarning(false)
|
||||||
|
Vue.configureCompat({ MODE: 2 })
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Vue.configureCompat({ MODE: 3 })
|
||||||
|
toggleDeprecationWarning(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
function triggerEvent(
|
||||||
|
target: Element,
|
||||||
|
event: string,
|
||||||
|
process?: (e: any) => any
|
||||||
|
) {
|
||||||
|
const e = document.createEvent('HTMLEvents')
|
||||||
|
e.initEvent(event, true, true)
|
||||||
|
if (process) process(e)
|
||||||
|
target.dispatchEvent(e)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// only testing config options that affect runtime behavior.
|
||||||
|
|
||||||
|
test('GLOBAL_KEY_CODES', () => {
|
||||||
|
Vue.config.keyCodes = {
|
||||||
|
foo: 86,
|
||||||
|
bar: [38, 87]
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFoo = jest.fn()
|
||||||
|
const onBar = jest.fn()
|
||||||
|
|
||||||
|
const el = document.createElement('div')
|
||||||
|
new Vue({
|
||||||
|
el,
|
||||||
|
template: `<input type="text" @keyup.foo="onFoo" @keyup.bar="onBar">`,
|
||||||
|
methods: {
|
||||||
|
onFoo,
|
||||||
|
onBar
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
triggerEvent(el.children[0], 'keyup', e => {
|
||||||
|
e.key = '_'
|
||||||
|
e.keyCode = 86
|
||||||
|
})
|
||||||
|
expect(onFoo).toHaveBeenCalledTimes(1)
|
||||||
|
expect(onBar).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
triggerEvent(el.children[0], 'keyup', e => {
|
||||||
|
e.key = '_'
|
||||||
|
e.keyCode = 38
|
||||||
|
})
|
||||||
|
expect(onFoo).toHaveBeenCalledTimes(1)
|
||||||
|
expect(onBar).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
triggerEvent(el.children[0], 'keyup', e => {
|
||||||
|
e.key = '_'
|
||||||
|
e.keyCode = 87
|
||||||
|
})
|
||||||
|
expect(onFoo).toHaveBeenCalledTimes(1)
|
||||||
|
expect(onBar).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('GLOBAL_IGNORED_ELEMENTS', () => {
|
||||||
|
Vue.config.ignoredElements = [/^v-/, 'foo']
|
||||||
|
const el = document.createElement('div')
|
||||||
|
new Vue({
|
||||||
|
el,
|
||||||
|
template: `<v-foo/><foo/>`
|
||||||
|
})
|
||||||
|
expect(el.innerHTML).toBe(`<v-foo></v-foo><foo></foo>`)
|
||||||
|
})
|
178
packages/runtime-core/src/compat/__tests__/renderFn.spec.ts
Normal file
178
packages/runtime-core/src/compat/__tests__/renderFn.spec.ts
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import { ShapeFlags } from '@vue/shared/src'
|
||||||
|
import { createComponentInstance } from '../../component'
|
||||||
|
import { setCurrentRenderingInstance } from '../../componentRenderContext'
|
||||||
|
import { DirectiveBinding } from '../../directives'
|
||||||
|
import { createVNode } from '../../vnode'
|
||||||
|
import { compatH as h } from '../renderFn'
|
||||||
|
|
||||||
|
describe('compat: render function', () => {
|
||||||
|
const mockDir = {}
|
||||||
|
const mockChildComp = {}
|
||||||
|
const mockComponent = {
|
||||||
|
directives: {
|
||||||
|
mockDir
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
foo: mockChildComp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mockInstance = createComponentInstance(
|
||||||
|
createVNode(mockComponent),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
beforeEach(() => {
|
||||||
|
setCurrentRenderingInstance(mockInstance)
|
||||||
|
})
|
||||||
|
afterEach(() => {
|
||||||
|
setCurrentRenderingInstance(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('string component lookup', () => {
|
||||||
|
expect(h('foo')).toMatchObject({
|
||||||
|
type: mockChildComp
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('class / style / attrs / domProps / props', () => {
|
||||||
|
expect(
|
||||||
|
h('div', {
|
||||||
|
class: 'foo',
|
||||||
|
style: { color: 'red' },
|
||||||
|
attrs: {
|
||||||
|
id: 'foo'
|
||||||
|
},
|
||||||
|
domProps: {
|
||||||
|
innerHTML: 'hi'
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
myProp: 'foo'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
props: {
|
||||||
|
class: 'foo',
|
||||||
|
style: { color: 'red' },
|
||||||
|
id: 'foo',
|
||||||
|
innerHTML: 'hi',
|
||||||
|
myProp: 'foo'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('staticClass + class', () => {
|
||||||
|
expect(
|
||||||
|
h('div', {
|
||||||
|
class: { foo: true },
|
||||||
|
staticClass: 'bar'
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
props: {
|
||||||
|
class: 'bar foo'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('staticStyle + style', () => {
|
||||||
|
expect(
|
||||||
|
h('div', {
|
||||||
|
style: { color: 'red' },
|
||||||
|
staticStyle: { fontSize: '14px' }
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
props: {
|
||||||
|
style: {
|
||||||
|
color: 'red',
|
||||||
|
fontSize: '14px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('on / nativeOn', () => {
|
||||||
|
const fn = () => {}
|
||||||
|
expect(
|
||||||
|
h('div', {
|
||||||
|
on: {
|
||||||
|
click: fn,
|
||||||
|
fooBar: fn
|
||||||
|
},
|
||||||
|
nativeOn: {
|
||||||
|
click: fn,
|
||||||
|
'bar-baz': fn
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
props: {
|
||||||
|
onClick: fn, // should dedupe
|
||||||
|
onFooBar: fn,
|
||||||
|
'onBar-baz': fn
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('directives', () => {
|
||||||
|
expect(
|
||||||
|
h('div', {
|
||||||
|
directives: [
|
||||||
|
{
|
||||||
|
name: 'mock-dir',
|
||||||
|
value: '2',
|
||||||
|
// expression: '1 + 1',
|
||||||
|
arg: 'foo',
|
||||||
|
modifiers: {
|
||||||
|
bar: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
dirs: [
|
||||||
|
{
|
||||||
|
dir: mockDir,
|
||||||
|
instance: mockInstance.proxy,
|
||||||
|
value: '2',
|
||||||
|
oldValue: void 0,
|
||||||
|
arg: 'foo',
|
||||||
|
modifiers: {
|
||||||
|
bar: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] as DirectiveBinding[]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('scopedSlots', () => {
|
||||||
|
const scopedSlots = {
|
||||||
|
default() {}
|
||||||
|
}
|
||||||
|
const vnode = h(mockComponent, {
|
||||||
|
scopedSlots
|
||||||
|
})
|
||||||
|
expect(vnode).toMatchObject({
|
||||||
|
children: scopedSlots
|
||||||
|
})
|
||||||
|
expect('scopedSlots' in vnode.props!).toBe(false)
|
||||||
|
expect(vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('legacy named slot', () => {
|
||||||
|
const vnode = h(mockComponent, [
|
||||||
|
'text',
|
||||||
|
h('div', { slot: 'foo' }, 'one'),
|
||||||
|
h('div', { slot: 'bar' }, 'two'),
|
||||||
|
h('div', { slot: 'foo' }, 'three'),
|
||||||
|
h('div', 'four')
|
||||||
|
])
|
||||||
|
expect(vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN).toBeTruthy()
|
||||||
|
const slots = vnode.children as any
|
||||||
|
|
||||||
|
// default
|
||||||
|
expect(slots.default()).toMatchObject(['text', { children: 'four' }])
|
||||||
|
expect(slots.foo()).toMatchObject([
|
||||||
|
{ children: 'one' },
|
||||||
|
{ children: 'three' }
|
||||||
|
])
|
||||||
|
expect(slots.bar()).toMatchObject([{ children: 'two' }])
|
||||||
|
})
|
||||||
|
})
|
27
packages/runtime-core/src/compat/attrsFallthrough.ts
Normal file
27
packages/runtime-core/src/compat/attrsFallthrough.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { isOn } from '@vue/shared'
|
||||||
|
import { ComponentInternalInstance } from '../component'
|
||||||
|
import { DeprecationTypes, isCompatEnabled } from './compatConfig'
|
||||||
|
|
||||||
|
export function shouldSkipAttr(
|
||||||
|
key: string,
|
||||||
|
value: any,
|
||||||
|
instance: ComponentInternalInstance
|
||||||
|
): boolean {
|
||||||
|
if (
|
||||||
|
(key === 'class' || key === 'style') &&
|
||||||
|
isCompatEnabled(DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE, instance)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isOn(key) &&
|
||||||
|
isCompatEnabled(DeprecationTypes.INSTANCE_LISTENERS, instance)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// vue-router
|
||||||
|
if (key.startsWith('routerView') || key === 'registerRouteInstance') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
636
packages/runtime-core/src/compat/compatConfig.ts
Normal file
636
packages/runtime-core/src/compat/compatConfig.ts
Normal file
@ -0,0 +1,636 @@
|
|||||||
|
import { extend, hasOwn, isArray } from '@vue/shared'
|
||||||
|
import {
|
||||||
|
ComponentInternalInstance,
|
||||||
|
ComponentOptions,
|
||||||
|
formatComponentName,
|
||||||
|
getComponentName,
|
||||||
|
getCurrentInstance,
|
||||||
|
isRuntimeOnly
|
||||||
|
} from '../component'
|
||||||
|
import { warn } from '../warning'
|
||||||
|
|
||||||
|
export const enum DeprecationTypes {
|
||||||
|
GLOBAL_MOUNT = 'GLOBAL_MOUNT',
|
||||||
|
GLOBAL_MOUNT_CONTAINER = 'GLOBAL_MOUNT_CONTAINER',
|
||||||
|
GLOBAL_EXTEND = 'GLOBAL_EXTEND',
|
||||||
|
GLOBAL_PROTOTYPE = 'GLOBAL_PROTOTYPE',
|
||||||
|
GLOBAL_SET = 'GLOBAL_SET',
|
||||||
|
GLOBAL_DELETE = 'GLOBAL_DELETE',
|
||||||
|
GLOBAL_OBSERVABLE = 'GLOBAL_OBSERVABLE',
|
||||||
|
GLOBAL_PRIVATE_UTIL = 'GLOBAL_PRIVATE_UTIL',
|
||||||
|
|
||||||
|
CONFIG_SILENT = 'CONFIG_SILENT',
|
||||||
|
CONFIG_DEVTOOLS = 'CONFIG_DEVTOOLS',
|
||||||
|
CONFIG_KEY_CODES = 'CONFIG_KEY_CODES',
|
||||||
|
CONFIG_PRODUCTION_TIP = 'CONFIG_PRODUCTION_TIP',
|
||||||
|
CONFIG_IGNORED_ELEMENTS = 'CONFIG_IGNORED_ELEMENTS',
|
||||||
|
CONFIG_WHITESPACE = 'CONFIG_WHITESPACE',
|
||||||
|
|
||||||
|
INSTANCE_SET = 'INSTANCE_SET',
|
||||||
|
INSTANCE_DELETE = 'INSTANCE_DELETE',
|
||||||
|
INSTANCE_DESTROY = 'INSTANCE_DESTROY',
|
||||||
|
INSTANCE_EVENT_EMITTER = 'INSTANCE_EVENT_EMITTER',
|
||||||
|
INSTANCE_EVENT_HOOKS = 'INSTANCE_EVENT_HOOKS',
|
||||||
|
INSTANCE_CHILDREN = 'INSTANCE_CHILDREN',
|
||||||
|
INSTANCE_LISTENERS = 'INSTANCE_LISTENERS',
|
||||||
|
INSTANCE_SCOPED_SLOTS = 'INSTANCE_SCOPED_SLOTS',
|
||||||
|
INSTANCE_ATTRS_CLASS_STYLE = 'INSTANCE_ATTRS_CLASS_STYLE',
|
||||||
|
|
||||||
|
OPTIONS_DATA_FN = 'OPTIONS_DATA_FN',
|
||||||
|
OPTIONS_DATA_MERGE = 'OPTIONS_DATA_MERGE',
|
||||||
|
OPTIONS_BEFORE_DESTROY = 'OPTIONS_BEFORE_DESTROY',
|
||||||
|
OPTIONS_DESTROYED = 'OPTIONS_DESTROYED',
|
||||||
|
|
||||||
|
WATCH_ARRAY = 'WATCH_ARRAY',
|
||||||
|
PROPS_DEFAULT_THIS = 'PROPS_DEFAULT_THIS',
|
||||||
|
|
||||||
|
V_FOR_REF = 'V_FOR_REF',
|
||||||
|
V_ON_KEYCODE_MODIFIER = 'V_ON_KEYCODE_MODIFIER',
|
||||||
|
CUSTOM_DIR = 'CUSTOM_DIR',
|
||||||
|
|
||||||
|
ATTR_FALSE_VALUE = 'ATTR_FALSE_VALUE',
|
||||||
|
ATTR_ENUMERATED_COERSION = 'ATTR_ENUMERATED_COERSION',
|
||||||
|
|
||||||
|
TRANSITION_CLASSES = 'TRANSITION_CLASSES',
|
||||||
|
TRANSITION_GROUP_ROOT = 'TRANSITION_GROUP_ROOT',
|
||||||
|
|
||||||
|
COMPONENT_ASYNC = 'COMPONENT_ASYNC',
|
||||||
|
COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL',
|
||||||
|
COMPONENT_V_MODEL = 'COMPONENT_V_MODEL',
|
||||||
|
|
||||||
|
RENDER_FUNCTION = 'RENDER_FUNCTION',
|
||||||
|
|
||||||
|
FILTERS = 'FILTERS',
|
||||||
|
|
||||||
|
PRIVATE_APIS = 'PRIVATE_APIS'
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeprecationData = {
|
||||||
|
message: string | ((...args: any[]) => string)
|
||||||
|
link?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deprecationData: Record<DeprecationTypes, DeprecationData> = {
|
||||||
|
[DeprecationTypes.GLOBAL_MOUNT]: {
|
||||||
|
message:
|
||||||
|
`The global app bootstrapping API has changed: vm.$mount() and the "el" ` +
|
||||||
|
`option have been removed. Use createApp(RootComponent).mount() instead.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/global-api.html#mounting-app-instance`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.GLOBAL_MOUNT_CONTAINER]: {
|
||||||
|
message:
|
||||||
|
`Vue detected directives on the mount container. ` +
|
||||||
|
`In Vue 3, the container is no longer considered part of the template ` +
|
||||||
|
`and will not be processed/replaced.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/mount-changes.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.GLOBAL_EXTEND]: {
|
||||||
|
message:
|
||||||
|
`Vue.extend() has been removed in Vue 3. ` +
|
||||||
|
`Use defineComponent() instead.`,
|
||||||
|
link: `https://v3.vuejs.org/api/global-api.html#definecomponent`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.GLOBAL_PROTOTYPE]: {
|
||||||
|
message:
|
||||||
|
`Vue.prototype is no longer available in Vue 3. ` +
|
||||||
|
`Use config.globalProperties instead.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/global-api.html#vue-prototype-replaced-by-config-globalproperties`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.GLOBAL_SET]: {
|
||||||
|
message:
|
||||||
|
`Vue.set() has been removed as it is no longer needed in Vue 3. ` +
|
||||||
|
`Simply use native JavaScript mutations.`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.GLOBAL_DELETE]: {
|
||||||
|
message:
|
||||||
|
`Vue.delete() has been removed as it is no longer needed in Vue 3. ` +
|
||||||
|
`Simply use native JavaScript mutations.`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.GLOBAL_OBSERVABLE]: {
|
||||||
|
message:
|
||||||
|
`Vue.observable() has been removed. ` +
|
||||||
|
`Use \`import { reactive } from "vue"\` from Composition API instead.`,
|
||||||
|
link: `https://v3.vuejs.org/api/basic-reactivity.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.GLOBAL_PRIVATE_UTIL]: {
|
||||||
|
message:
|
||||||
|
`Vue.util has been removed. Please refactor to avoid its usage ` +
|
||||||
|
`since it was an internal API even in Vue 2.`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.CONFIG_SILENT]: {
|
||||||
|
message:
|
||||||
|
`config.silent has been removed because it is not good practice to ` +
|
||||||
|
`intentionally suppress warnings. You can use your browser console's ` +
|
||||||
|
`filter features to focus on relevant messages.`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.CONFIG_DEVTOOLS]: {
|
||||||
|
message:
|
||||||
|
`config.devtools has been removed. To enable devtools for ` +
|
||||||
|
`production, configure the __VUE_PROD_DEVTOOLS__ compile-time flag.`,
|
||||||
|
link: `https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.CONFIG_KEY_CODES]: {
|
||||||
|
message:
|
||||||
|
`config.keyCodes has been removed. ` +
|
||||||
|
`In Vue 3, you can directly use the kebab-case key names as v-on modifiers.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/keycode-modifiers.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.CONFIG_PRODUCTION_TIP]: {
|
||||||
|
message: `config.productionTip has been removed.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/global-api.html#config-productiontip-removed`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.CONFIG_IGNORED_ELEMENTS]: {
|
||||||
|
message: () => {
|
||||||
|
let msg = `config.ignoredElements has been removed.`
|
||||||
|
if (isRuntimeOnly()) {
|
||||||
|
msg += ` Pass the "isCustomElement" option to @vue/compiler-dom instead.`
|
||||||
|
} else {
|
||||||
|
msg += ` Use config.isCustomElement instead.`
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
},
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/global-api.html#config-ignoredelements-is-now-config-iscustomelement`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.CONFIG_WHITESPACE]: {
|
||||||
|
// this warning is only relevant in the full build when using runtime
|
||||||
|
// compilation, so it's put in the runtime compatConfig list.
|
||||||
|
message:
|
||||||
|
`Vue 3 compiler's whitespace option will default to "condense" instead of ` +
|
||||||
|
`"preserve". To suppress this warning, provide an explicit value for ` +
|
||||||
|
`\`config.compilerOptions.whitespace\`.`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.INSTANCE_SET]: {
|
||||||
|
message:
|
||||||
|
`vm.$set() has been removed as it is no longer needed in Vue 3. ` +
|
||||||
|
`Simply use native JavaScript mutations.`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.INSTANCE_DELETE]: {
|
||||||
|
message:
|
||||||
|
`vm.$delete() has been removed as it is no longer needed in Vue 3. ` +
|
||||||
|
`Simply use native JavaScript mutations.`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.INSTANCE_DESTROY]: {
|
||||||
|
message: `vm.$destroy() has been removed. Use app.unmount() instead.`,
|
||||||
|
link: `https://v3.vuejs.org/api/application-api.html#unmount`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.INSTANCE_EVENT_EMITTER]: {
|
||||||
|
message:
|
||||||
|
`vm.$on/$once/$off() have been removed. ` +
|
||||||
|
`Use an external event emitter library instead.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/events-api.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.INSTANCE_EVENT_HOOKS]: {
|
||||||
|
message: event =>
|
||||||
|
`"${event}" lifecycle events are no longer supported. From templates, ` +
|
||||||
|
`use the "vnode" prefix instead of "hook:". For example, @${event} ` +
|
||||||
|
`should be changed to @vnode-${event.slice(5)}. ` +
|
||||||
|
`From JavaScript, use Composition API to dynamically register lifecycle ` +
|
||||||
|
`hooks.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/vnode-lifecycle-events.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.INSTANCE_CHILDREN]: {
|
||||||
|
message:
|
||||||
|
`vm.$children has been removed. Consider refactoring your logic ` +
|
||||||
|
`to avoid relying on direct access to child components.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/children.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.INSTANCE_LISTENERS]: {
|
||||||
|
message:
|
||||||
|
`vm.$listeners has been removed. In Vue 3, parent v-on listeners are ` +
|
||||||
|
`included in vm.$attrs and it is no longer necessary to separately use ` +
|
||||||
|
`v-on="$listeners" if you are already using v-bind="$attrs". ` +
|
||||||
|
`(Note: the Vue 3 behavior only applies if this compat config is disabled)`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/listeners-removed.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.INSTANCE_SCOPED_SLOTS]: {
|
||||||
|
message: `vm.$scopedSlots has been removed. Use vm.$slots instead.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/slots-unification.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE]: {
|
||||||
|
message: componentName =>
|
||||||
|
`Component <${componentName}> has \`inheritAttrs: false\` but is ` +
|
||||||
|
`relying on class/style fallthrough from parent. In Vue 3, class/style ` +
|
||||||
|
`are now included in $attrs and will no longer fallthrough when ` +
|
||||||
|
`inheritAttrs is false. If you are already using v-bind="$attrs" on ` +
|
||||||
|
`component root it should render the same end result. ` +
|
||||||
|
`If you are binding $attrs to a non-root element and expecting ` +
|
||||||
|
`class/style to fallthrough on root, you will need to now manually bind ` +
|
||||||
|
`them on root via :class="$attrs.class".`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/attrs-includes-class-style.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.OPTIONS_DATA_FN]: {
|
||||||
|
message:
|
||||||
|
`The "data" option can no longer be a plain object. ` +
|
||||||
|
`Always use a function.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/data-option.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.OPTIONS_DATA_MERGE]: {
|
||||||
|
message: (key: string) =>
|
||||||
|
`Detected conflicting key "${key}" when merging data option values. ` +
|
||||||
|
`In Vue 3, data keys are merged shallowly and will override one another.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/data-option.html#mixin-merge-behavior-change`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.OPTIONS_BEFORE_DESTROY]: {
|
||||||
|
message: `\`beforeDestroy\` has been renamed to \`beforeUnmount\`.`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.OPTIONS_DESTROYED]: {
|
||||||
|
message: `\`destroyed\` has been renamed to \`unmounted\`.`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.WATCH_ARRAY]: {
|
||||||
|
message:
|
||||||
|
`"watch" option or vm.$watch on an array value will no longer ` +
|
||||||
|
`trigger on array mutation unless the "deep" option is specified. ` +
|
||||||
|
`If current usage is intended, you can disable the compat behavior and ` +
|
||||||
|
`suppress this warning with:` +
|
||||||
|
`\n\n configureCompat({ ${DeprecationTypes.WATCH_ARRAY}: false })\n`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/watch.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.PROPS_DEFAULT_THIS]: {
|
||||||
|
message: (key: string) =>
|
||||||
|
`props default value function no longer has access to "this". The compat ` +
|
||||||
|
`build only offers access to this.$options.` +
|
||||||
|
`(found in prop "${key}")`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/props-default-this.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.CUSTOM_DIR]: {
|
||||||
|
message: (legacyHook: string, newHook: string) =>
|
||||||
|
`Custom directive hook "${legacyHook}" has been removed. ` +
|
||||||
|
`Use "${newHook}" instead.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/custom-directives.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.V_FOR_REF]: {
|
||||||
|
message:
|
||||||
|
`Ref usage on v-for no longer creates array ref values in Vue 3. ` +
|
||||||
|
`Consider using function refs or refactor to avoid ref usage altogether.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/array-refs.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.V_ON_KEYCODE_MODIFIER]: {
|
||||||
|
message:
|
||||||
|
`Using keyCode as v-on modifier is no longer supported. ` +
|
||||||
|
`Use kebab-case key name modifiers instead.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/keycode-modifiers.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.ATTR_FALSE_VALUE]: {
|
||||||
|
message: (name: string) =>
|
||||||
|
`Attribute "${name}" with v-bind value \`false\` will render ` +
|
||||||
|
`${name}="false" instead of removing it in Vue 3. To remove the attribute, ` +
|
||||||
|
`use \`null\` or \`undefined\` instead. If the usage is intended, ` +
|
||||||
|
`you can disable the compat behavior and suppress this warning with:` +
|
||||||
|
`\n\n configureCompat({ ${
|
||||||
|
DeprecationTypes.ATTR_FALSE_VALUE
|
||||||
|
}: false })\n`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/attribute-coercion.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.ATTR_ENUMERATED_COERSION]: {
|
||||||
|
message: (name: string, value: any, coerced: string) =>
|
||||||
|
`Enumerated attribute "${name}" with v-bind value \`${value}\` will ` +
|
||||||
|
`${
|
||||||
|
value === null ? `be removed` : `render the value as-is`
|
||||||
|
} instead of coercing the value to "${coerced}" in Vue 3. ` +
|
||||||
|
`Always use explicit "true" or "false" values for enumerated attributes. ` +
|
||||||
|
`If the usage is intended, ` +
|
||||||
|
`you can disable the compat behavior and suppress this warning with:` +
|
||||||
|
`\n\n configureCompat({ ${
|
||||||
|
DeprecationTypes.ATTR_ENUMERATED_COERSION
|
||||||
|
}: false })\n`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/attribute-coercion.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.TRANSITION_CLASSES]: {
|
||||||
|
message: `` // this feature cannot be runtime-detected
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.TRANSITION_GROUP_ROOT]: {
|
||||||
|
message:
|
||||||
|
`<TransitionGroup> no longer renders a root <span> element by ` +
|
||||||
|
`default if no "tag" prop is specified. If you do not rely on the span ` +
|
||||||
|
`for styling, you can disable the compat behavior and suppress this ` +
|
||||||
|
`warning with:` +
|
||||||
|
`\n\n configureCompat({ ${
|
||||||
|
DeprecationTypes.TRANSITION_GROUP_ROOT
|
||||||
|
}: false })\n`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/transition-group.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.COMPONENT_ASYNC]: {
|
||||||
|
message: (comp: any) => {
|
||||||
|
const name = getComponentName(comp)
|
||||||
|
return (
|
||||||
|
`Async component${
|
||||||
|
name ? ` <${name}>` : `s`
|
||||||
|
} should be explicitly created via \`defineAsyncComponent()\` ` +
|
||||||
|
`in Vue 3. Plain functions will be treated as functional components in ` +
|
||||||
|
`non-compat build. If you have already migrated all async component ` +
|
||||||
|
`usage and intend to use plain functions for functional components, ` +
|
||||||
|
`you can disable the compat behavior and suppress this ` +
|
||||||
|
`warning with:` +
|
||||||
|
`\n\n configureCompat({ ${
|
||||||
|
DeprecationTypes.COMPONENT_ASYNC
|
||||||
|
}: false })\n`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/async-components.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.COMPONENT_FUNCTIONAL]: {
|
||||||
|
message: (comp: any) => {
|
||||||
|
const name = getComponentName(comp)
|
||||||
|
return (
|
||||||
|
`Functional component${
|
||||||
|
name ? ` <${name}>` : `s`
|
||||||
|
} should be defined as a plain function in Vue 3. The "functional" ` +
|
||||||
|
`option has been removed. NOTE: Before migrating to use plain ` +
|
||||||
|
`functions for functional components, first make sure that all async ` +
|
||||||
|
`components usage have been migrated and its compat behavior has ` +
|
||||||
|
`been disabled.`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/functional-components.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.COMPONENT_V_MODEL]: {
|
||||||
|
message: (comp: ComponentOptions) => {
|
||||||
|
const configMsg =
|
||||||
|
`opt-in to ` +
|
||||||
|
`Vue 3 behavior on a per-component basis with \`compatConfig: { ${
|
||||||
|
DeprecationTypes.COMPONENT_V_MODEL
|
||||||
|
}: false }\`.`
|
||||||
|
if (
|
||||||
|
comp.props && isArray(comp.props)
|
||||||
|
? comp.props.includes('modelValue')
|
||||||
|
: hasOwn(comp.props, 'modelValue')
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
`Component delcares "modelValue" prop, which is Vue 3 usage, but ` +
|
||||||
|
`is running under Vue 2 compat v-model behavior. You can ${configMsg}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
`v-model usage on component has changed in Vue 3. Component that expects ` +
|
||||||
|
`to work with v-model should now use the "modelValue" prop and emit the ` +
|
||||||
|
`"update:modelValue" event. You can update the usage and then ${configMsg}`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/v-model.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.RENDER_FUNCTION]: {
|
||||||
|
message:
|
||||||
|
`Vue 3's render function API has changed. ` +
|
||||||
|
`You can opt-in to the new API with:` +
|
||||||
|
`\n\n configureCompat({ ${
|
||||||
|
DeprecationTypes.RENDER_FUNCTION
|
||||||
|
}: false })\n` +
|
||||||
|
`\n (This can also be done per-component via the "compatConfig" option.)`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/render-function-api.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.FILTERS]: {
|
||||||
|
message:
|
||||||
|
`filters have been removed in Vue 3. ` +
|
||||||
|
`The "|" symbol will be treated as native JavaScript bitwise OR operator. ` +
|
||||||
|
`Use method calls or computed properties instead.`,
|
||||||
|
link: `https://v3.vuejs.org/guide/migration/filters.html`
|
||||||
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.PRIVATE_APIS]: {
|
||||||
|
message: name =>
|
||||||
|
`"${name}" is a Vue 2 private API that no longer exists in Vue 3. ` +
|
||||||
|
`If you are seeing this warning only due to a dependency, you can ` +
|
||||||
|
`suppress this warning via { PRIVATE_APIS: 'supress-warning' }.`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const instanceWarned: Record<string, true> = Object.create(null)
|
||||||
|
const warnCount: Record<string, number> = Object.create(null)
|
||||||
|
|
||||||
|
// test only
|
||||||
|
let warningEnabled = true
|
||||||
|
|
||||||
|
export function toggleDeprecationWarning(flag: boolean) {
|
||||||
|
warningEnabled = flag
|
||||||
|
}
|
||||||
|
|
||||||
|
export function warnDeprecation(
|
||||||
|
key: DeprecationTypes,
|
||||||
|
instance: ComponentInternalInstance | null,
|
||||||
|
...args: any[]
|
||||||
|
) {
|
||||||
|
if (!__DEV__) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (__TEST__ && !warningEnabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
instance = instance || getCurrentInstance()
|
||||||
|
|
||||||
|
// check user config
|
||||||
|
const config = getCompatConfigForKey(key, instance)
|
||||||
|
if (config === 'suppress-warning') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const dupKey = key + args.join('')
|
||||||
|
let compId: string | number | null =
|
||||||
|
instance && formatComponentName(instance, instance.type)
|
||||||
|
if (compId === 'Anonymous' && instance) {
|
||||||
|
compId = instance.uid
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip if the same warning is emitted for the same component type
|
||||||
|
const componentDupKey = dupKey + compId
|
||||||
|
if (!__TEST__ && componentDupKey in instanceWarned) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
instanceWarned[componentDupKey] = true
|
||||||
|
|
||||||
|
// same warning, but different component. skip the long message and just
|
||||||
|
// log the key and count.
|
||||||
|
if (!__TEST__ && dupKey in warnCount) {
|
||||||
|
warn(`(deprecation ${key}) (${++warnCount[dupKey] + 1})`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
warnCount[dupKey] = 0
|
||||||
|
|
||||||
|
const { message, link } = deprecationData[key]
|
||||||
|
warn(
|
||||||
|
`(deprecation ${key}) ${
|
||||||
|
typeof message === 'function' ? message(...args) : message
|
||||||
|
}${link ? `\n Details: ${link}` : ``}`
|
||||||
|
)
|
||||||
|
if (!isCompatEnabled(key, instance)) {
|
||||||
|
console.error(
|
||||||
|
`^ The above deprecation's compat behavior is disabled and will likely ` +
|
||||||
|
`lead to runtime errors.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompatConfig = Partial<
|
||||||
|
Record<DeprecationTypes, boolean | 'suppress-warning'>
|
||||||
|
> & {
|
||||||
|
MODE?: 2 | 3
|
||||||
|
}
|
||||||
|
|
||||||
|
export const globalCompatConfig: CompatConfig = {
|
||||||
|
MODE: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export function configureCompat(config: CompatConfig) {
|
||||||
|
if (__DEV__) {
|
||||||
|
validateCompatConfig(config)
|
||||||
|
}
|
||||||
|
extend(globalCompatConfig, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
const seenConfigObjects = /*#__PURE__*/ new WeakSet<CompatConfig>()
|
||||||
|
const warnedInvalidKeys: Record<string, boolean> = {}
|
||||||
|
|
||||||
|
// dev only
|
||||||
|
export function validateCompatConfig(config: CompatConfig) {
|
||||||
|
if (seenConfigObjects.has(config)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seenConfigObjects.add(config)
|
||||||
|
|
||||||
|
for (const key of Object.keys(config)) {
|
||||||
|
if (
|
||||||
|
key !== 'MODE' &&
|
||||||
|
!(key in deprecationData) &&
|
||||||
|
!(key in warnedInvalidKeys)
|
||||||
|
) {
|
||||||
|
if (key.startsWith('COMPILER_')) {
|
||||||
|
if (isRuntimeOnly()) {
|
||||||
|
warn(
|
||||||
|
`Depreaction config "${key}" is compiler-specific and you are ` +
|
||||||
|
`running a runtime-only build of Vue. This deprecation should be ` +
|
||||||
|
`configured via compiler options in your build setup instead.`
|
||||||
|
// TODO link to migration build docs on build setup
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn(`Invalid deprecation config "${key}".`)
|
||||||
|
}
|
||||||
|
warnedInvalidKeys[key] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCompatConfigForKey(
|
||||||
|
key: DeprecationTypes | 'MODE',
|
||||||
|
instance: ComponentInternalInstance | null
|
||||||
|
) {
|
||||||
|
const instanceConfig =
|
||||||
|
instance && (instance.type as ComponentOptions).compatConfig
|
||||||
|
if (instanceConfig && key in instanceConfig) {
|
||||||
|
return instanceConfig[key]
|
||||||
|
}
|
||||||
|
return globalCompatConfig[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isCompatEnabled(
|
||||||
|
key: DeprecationTypes,
|
||||||
|
instance: ComponentInternalInstance | null
|
||||||
|
): boolean {
|
||||||
|
// skip compat for built-in components
|
||||||
|
if (instance && instance.type.__isBuiltIn) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const mode = getCompatConfigForKey('MODE', instance) || 2
|
||||||
|
const val = getCompatConfigForKey(key, instance)
|
||||||
|
if (mode === 2) {
|
||||||
|
return val !== false
|
||||||
|
} else {
|
||||||
|
return val === true || val === 'suppress-warning'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this for features that are completely removed in non-compat build.
|
||||||
|
*/
|
||||||
|
export function assertCompatEnabled(
|
||||||
|
key: DeprecationTypes,
|
||||||
|
instance: ComponentInternalInstance | null,
|
||||||
|
...args: any[]
|
||||||
|
) {
|
||||||
|
if (!isCompatEnabled(key, instance)) {
|
||||||
|
throw new Error(`${key} compat has been disabled.`)
|
||||||
|
} else if (__DEV__) {
|
||||||
|
warnDeprecation(key, instance, ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this for features where legacy usage is still possible, but will likely
|
||||||
|
* lead to runtime error if compat is disabled. (warn in all cases)
|
||||||
|
*/
|
||||||
|
export function softAssertCompatEnabled(
|
||||||
|
key: DeprecationTypes,
|
||||||
|
instance: ComponentInternalInstance | null,
|
||||||
|
...args: any[]
|
||||||
|
) {
|
||||||
|
if (__DEV__) {
|
||||||
|
warnDeprecation(key, instance, ...args)
|
||||||
|
}
|
||||||
|
return isCompatEnabled(key, instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this for features with the same syntax but with mutually exclusive
|
||||||
|
* behavior in 2 vs 3. Only warn if compat is enabled.
|
||||||
|
* e.g. render function
|
||||||
|
*/
|
||||||
|
export function checkCompatEnabled(
|
||||||
|
key: DeprecationTypes,
|
||||||
|
instance: ComponentInternalInstance | null,
|
||||||
|
...args: any[]
|
||||||
|
) {
|
||||||
|
const enabled = isCompatEnabled(key, instance)
|
||||||
|
if (__DEV__ && enabled) {
|
||||||
|
warnDeprecation(key, instance, ...args)
|
||||||
|
}
|
||||||
|
return enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// run tests in v3 mode by default
|
||||||
|
if (__TEST__) {
|
||||||
|
configureCompat({
|
||||||
|
MODE: 3
|
||||||
|
})
|
||||||
|
}
|
164
packages/runtime-core/src/compat/component.ts
Normal file
164
packages/runtime-core/src/compat/component.ts
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import { isArray, isFunction, isObject, isPromise } from '@vue/shared'
|
||||||
|
import { defineAsyncComponent } from '../apiAsyncComponent'
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
ComponentInternalInstance,
|
||||||
|
ComponentOptions,
|
||||||
|
FunctionalComponent,
|
||||||
|
getCurrentInstance
|
||||||
|
} from '../component'
|
||||||
|
import { resolveInjections } from '../componentOptions'
|
||||||
|
import { InternalSlots } from '../componentSlots'
|
||||||
|
import { isVNode } from '../vnode'
|
||||||
|
import {
|
||||||
|
checkCompatEnabled,
|
||||||
|
softAssertCompatEnabled,
|
||||||
|
DeprecationTypes
|
||||||
|
} from './compatConfig'
|
||||||
|
import { getCompatListeners } from './instanceListeners'
|
||||||
|
import { compatH } from './renderFn'
|
||||||
|
|
||||||
|
export function convertLegacyComponent(
|
||||||
|
comp: any,
|
||||||
|
instance: ComponentInternalInstance | null
|
||||||
|
): Component {
|
||||||
|
if (comp.__isBuiltIn) {
|
||||||
|
return comp
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.x constructor
|
||||||
|
if (isFunction(comp) && comp.cid) {
|
||||||
|
comp = comp.options
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.x async component
|
||||||
|
if (
|
||||||
|
isFunction(comp) &&
|
||||||
|
checkCompatEnabled(DeprecationTypes.COMPONENT_ASYNC, instance, comp)
|
||||||
|
) {
|
||||||
|
// since after disabling this, plain functions are still valid usage, do not
|
||||||
|
// use softAssert here.
|
||||||
|
return convertLegacyAsyncComponent(comp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.x functional component
|
||||||
|
if (
|
||||||
|
isObject(comp) &&
|
||||||
|
comp.functional &&
|
||||||
|
softAssertCompatEnabled(
|
||||||
|
DeprecationTypes.COMPONENT_FUNCTIONAL,
|
||||||
|
instance,
|
||||||
|
comp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return convertLegacyFunctionalComponent(comp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return comp
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LegacyAsyncOptions {
|
||||||
|
component: Promise<Component>
|
||||||
|
loading?: Component
|
||||||
|
error?: Component
|
||||||
|
delay?: number
|
||||||
|
timeout?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type LegacyAsyncReturnValue = Promise<Component> | LegacyAsyncOptions
|
||||||
|
|
||||||
|
type LegacyAsyncComponent = (
|
||||||
|
resolve?: (res: LegacyAsyncReturnValue) => void,
|
||||||
|
reject?: (reason?: any) => void
|
||||||
|
) => LegacyAsyncReturnValue | undefined
|
||||||
|
|
||||||
|
const normalizedAsyncComponentMap = new Map<LegacyAsyncComponent, Component>()
|
||||||
|
|
||||||
|
function convertLegacyAsyncComponent(comp: LegacyAsyncComponent) {
|
||||||
|
if (normalizedAsyncComponentMap.has(comp)) {
|
||||||
|
return normalizedAsyncComponentMap.get(comp)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have to call the function here due to how v2's API won't expose the
|
||||||
|
// options until we call it
|
||||||
|
let resolve: (res: LegacyAsyncReturnValue) => void
|
||||||
|
let reject: (reason?: any) => void
|
||||||
|
const fallbackPromise = new Promise<Component>((r, rj) => {
|
||||||
|
;(resolve = r), (reject = rj)
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = comp(resolve!, reject!)
|
||||||
|
|
||||||
|
let converted: Component
|
||||||
|
if (isPromise(res)) {
|
||||||
|
converted = defineAsyncComponent(() => res)
|
||||||
|
} else if (isObject(res) && !isVNode(res) && !isArray(res)) {
|
||||||
|
converted = defineAsyncComponent({
|
||||||
|
loader: () => res.component,
|
||||||
|
loadingComponent: res.loading,
|
||||||
|
errorComponent: res.error,
|
||||||
|
delay: res.delay,
|
||||||
|
timeout: res.timeout
|
||||||
|
})
|
||||||
|
} else if (res == null) {
|
||||||
|
converted = defineAsyncComponent(() => fallbackPromise)
|
||||||
|
} else {
|
||||||
|
converted = comp as any // probably a v3 functional comp
|
||||||
|
}
|
||||||
|
normalizedAsyncComponentMap.set(comp, converted)
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedFunctionalComponentMap = new Map<
|
||||||
|
ComponentOptions,
|
||||||
|
FunctionalComponent
|
||||||
|
>()
|
||||||
|
|
||||||
|
export const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = {
|
||||||
|
get(target, key: string) {
|
||||||
|
const slot = target[key]
|
||||||
|
return slot && slot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertLegacyFunctionalComponent(comp: ComponentOptions) {
|
||||||
|
if (normalizedFunctionalComponentMap.has(comp)) {
|
||||||
|
return normalizedFunctionalComponentMap.get(comp)!
|
||||||
|
}
|
||||||
|
|
||||||
|
const legacyFn = comp.render as any
|
||||||
|
|
||||||
|
const Func: FunctionalComponent = (props, ctx) => {
|
||||||
|
const instance = getCurrentInstance()!
|
||||||
|
|
||||||
|
const legacyCtx = {
|
||||||
|
props,
|
||||||
|
children: instance.vnode.children || [],
|
||||||
|
data: instance.vnode.props || {},
|
||||||
|
scopedSlots: ctx.slots,
|
||||||
|
parent: instance.parent && instance.parent.proxy,
|
||||||
|
slots() {
|
||||||
|
return new Proxy(ctx.slots, legacySlotProxyHandlers)
|
||||||
|
},
|
||||||
|
get listeners() {
|
||||||
|
return getCompatListeners(instance)
|
||||||
|
},
|
||||||
|
get injections() {
|
||||||
|
if (comp.inject) {
|
||||||
|
const injections = {}
|
||||||
|
resolveInjections(comp.inject, {})
|
||||||
|
return injections
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return legacyFn(compatH, legacyCtx)
|
||||||
|
}
|
||||||
|
Func.props = comp.props
|
||||||
|
Func.displayName = comp.name
|
||||||
|
// v2 functional components do not inherit attrs
|
||||||
|
Func.inheritAttrs = false
|
||||||
|
|
||||||
|
normalizedFunctionalComponentMap.set(comp, Func)
|
||||||
|
return Func
|
||||||
|
}
|
60
packages/runtime-core/src/compat/customDirective.ts
Normal file
60
packages/runtime-core/src/compat/customDirective.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { isArray } from '@vue/shared'
|
||||||
|
import { ComponentInternalInstance } from '../component'
|
||||||
|
import { ObjectDirective, DirectiveHook } from '../directives'
|
||||||
|
import { softAssertCompatEnabled, DeprecationTypes } from './compatConfig'
|
||||||
|
|
||||||
|
export interface LegacyDirective {
|
||||||
|
bind?: DirectiveHook
|
||||||
|
inserted?: DirectiveHook
|
||||||
|
update?: DirectiveHook
|
||||||
|
componentUpdated?: DirectiveHook
|
||||||
|
unbind?: DirectiveHook
|
||||||
|
}
|
||||||
|
|
||||||
|
const legacyDirectiveHookMap: Partial<
|
||||||
|
Record<
|
||||||
|
keyof ObjectDirective,
|
||||||
|
keyof LegacyDirective | (keyof LegacyDirective)[]
|
||||||
|
>
|
||||||
|
> = {
|
||||||
|
beforeMount: 'bind',
|
||||||
|
mounted: 'inserted',
|
||||||
|
updated: ['update', 'componentUpdated'],
|
||||||
|
unmounted: 'unbind'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapCompatDirectiveHook(
|
||||||
|
name: keyof ObjectDirective,
|
||||||
|
dir: ObjectDirective & LegacyDirective,
|
||||||
|
instance: ComponentInternalInstance | null
|
||||||
|
): DirectiveHook | DirectiveHook[] | undefined {
|
||||||
|
const mappedName = legacyDirectiveHookMap[name]
|
||||||
|
if (mappedName) {
|
||||||
|
if (isArray(mappedName)) {
|
||||||
|
const hook: DirectiveHook[] = []
|
||||||
|
mappedName.forEach(name => {
|
||||||
|
const mappedHook = dir[name]
|
||||||
|
if (mappedHook) {
|
||||||
|
softAssertCompatEnabled(
|
||||||
|
DeprecationTypes.CUSTOM_DIR,
|
||||||
|
instance,
|
||||||
|
mappedName,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
hook.push(mappedHook)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return hook.length ? hook : undefined
|
||||||
|
} else {
|
||||||
|
if (dir[mappedName]) {
|
||||||
|
softAssertCompatEnabled(
|
||||||
|
DeprecationTypes.CUSTOM_DIR,
|
||||||
|
instance,
|
||||||
|
mappedName,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return dir[mappedName]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
packages/runtime-core/src/compat/data.ts
Normal file
38
packages/runtime-core/src/compat/data.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { isFunction, isPlainObject } from '@vue/shared'
|
||||||
|
import { ComponentInternalInstance } from '../component'
|
||||||
|
import { ComponentPublicInstance } from '../componentPublicInstance'
|
||||||
|
import { DeprecationTypes, warnDeprecation } from './compatConfig'
|
||||||
|
|
||||||
|
export function deepMergeData(
|
||||||
|
to: any,
|
||||||
|
from: any,
|
||||||
|
instance: ComponentInternalInstance
|
||||||
|
) {
|
||||||
|
for (const key in from) {
|
||||||
|
const toVal = to[key]
|
||||||
|
const fromVal = from[key]
|
||||||
|
if (key in to && isPlainObject(toVal) && isPlainObject(fromVal)) {
|
||||||
|
__DEV__ &&
|
||||||
|
warnDeprecation(DeprecationTypes.OPTIONS_DATA_MERGE, instance, key)
|
||||||
|
deepMergeData(toVal, fromVal, instance)
|
||||||
|
} else {
|
||||||
|
to[key] = fromVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeDataOption(to: any, from: any) {
|
||||||
|
if (!from) {
|
||||||
|
return to
|
||||||
|
}
|
||||||
|
if (!to) {
|
||||||
|
return from
|
||||||
|
}
|
||||||
|
return function mergedDataFn(this: ComponentPublicInstance) {
|
||||||
|
return deepMergeData(
|
||||||
|
isFunction(to) ? to.call(this, this) : to,
|
||||||
|
isFunction(from) ? from.call(this, this) : from,
|
||||||
|
this.$
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
18
packages/runtime-core/src/compat/filter.ts
Normal file
18
packages/runtime-core/src/compat/filter.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { App, AppContext } from '../apiCreateApp'
|
||||||
|
import { warn } from '../warning'
|
||||||
|
import { assertCompatEnabled, DeprecationTypes } from './compatConfig'
|
||||||
|
|
||||||
|
export function installGlobalFilterMethod(app: App, context: AppContext) {
|
||||||
|
context.filters = {}
|
||||||
|
app.filter = (name: string, filter?: Function): any => {
|
||||||
|
assertCompatEnabled(DeprecationTypes.FILTERS, null)
|
||||||
|
if (!filter) {
|
||||||
|
return context.filters![name]
|
||||||
|
}
|
||||||
|
if (__DEV__ && context.filters![name]) {
|
||||||
|
warn(`Filter "${name}" has already been registered.`)
|
||||||
|
}
|
||||||
|
context.filters![name] = filter
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
}
|
529
packages/runtime-core/src/compat/global.ts
Normal file
529
packages/runtime-core/src/compat/global.ts
Normal file
@ -0,0 +1,529 @@
|
|||||||
|
import {
|
||||||
|
isReactive,
|
||||||
|
reactive,
|
||||||
|
track,
|
||||||
|
TrackOpTypes,
|
||||||
|
trigger,
|
||||||
|
TriggerOpTypes
|
||||||
|
} from '@vue/reactivity'
|
||||||
|
import {
|
||||||
|
isFunction,
|
||||||
|
extend,
|
||||||
|
NOOP,
|
||||||
|
EMPTY_OBJ,
|
||||||
|
isArray,
|
||||||
|
isObject,
|
||||||
|
isString
|
||||||
|
} from '@vue/shared'
|
||||||
|
import { warn } from '../warning'
|
||||||
|
import { cloneVNode, createVNode } from '../vnode'
|
||||||
|
import { RootRenderFunction } from '../renderer'
|
||||||
|
import { RootHydrateFunction } from '../hydration'
|
||||||
|
import {
|
||||||
|
App,
|
||||||
|
AppConfig,
|
||||||
|
AppContext,
|
||||||
|
CreateAppFunction,
|
||||||
|
Plugin
|
||||||
|
} from '../apiCreateApp'
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
ComponentOptions,
|
||||||
|
createComponentInstance,
|
||||||
|
finishComponentSetup,
|
||||||
|
isRuntimeOnly,
|
||||||
|
setupComponent
|
||||||
|
} from '../component'
|
||||||
|
import { RenderFunction, mergeOptions } from '../componentOptions'
|
||||||
|
import { ComponentPublicInstance } from '../componentPublicInstance'
|
||||||
|
import { devtoolsInitApp } from '../devtools'
|
||||||
|
import { Directive } from '../directives'
|
||||||
|
import { nextTick } from '../scheduler'
|
||||||
|
import { version } from '..'
|
||||||
|
import { LegacyConfig, legacyOptionMergeStrats } from './globalConfig'
|
||||||
|
import { LegacyDirective } from './customDirective'
|
||||||
|
import {
|
||||||
|
warnDeprecation,
|
||||||
|
DeprecationTypes,
|
||||||
|
assertCompatEnabled,
|
||||||
|
configureCompat,
|
||||||
|
isCompatEnabled,
|
||||||
|
softAssertCompatEnabled
|
||||||
|
} from './compatConfig'
|
||||||
|
import { LegacyPublicInstance } from './instance'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated the default `Vue` export has been removed in Vue 3. The type for
|
||||||
|
* the default export is provided only for migration purposes. Please use
|
||||||
|
* named imports instead - e.g. `import { createApp } from 'vue'`.
|
||||||
|
*/
|
||||||
|
export type CompatVue = Pick<App, 'version' | 'component' | 'directive'> & {
|
||||||
|
configureCompat: typeof configureCompat
|
||||||
|
|
||||||
|
// no inference here since these types are not meant for actual use - they
|
||||||
|
// are merely here to provide type checks for internal implementation and
|
||||||
|
// information for migration.
|
||||||
|
new (options?: ComponentOptions): LegacyPublicInstance
|
||||||
|
|
||||||
|
version: string
|
||||||
|
config: AppConfig & LegacyConfig
|
||||||
|
|
||||||
|
extend: (options?: ComponentOptions) => CompatVue
|
||||||
|
nextTick: typeof nextTick
|
||||||
|
|
||||||
|
use(plugin: Plugin, ...options: any[]): CompatVue
|
||||||
|
mixin(mixin: ComponentOptions): CompatVue
|
||||||
|
|
||||||
|
component(name: string): Component | undefined
|
||||||
|
component(name: string, component: Component): CompatVue
|
||||||
|
directive(name: string): Directive | undefined
|
||||||
|
directive(name: string, directive: Directive): CompatVue
|
||||||
|
|
||||||
|
compile(template: string): RenderFunction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Vue 3 no longer needs set() for adding new properties.
|
||||||
|
*/
|
||||||
|
set(target: any, key: string | number | symbol, value: any): void
|
||||||
|
/**
|
||||||
|
* @deprecated Vue 3 no longer needs delete() for property deletions.
|
||||||
|
*/
|
||||||
|
delete(target: any, key: string | number | symbol): void
|
||||||
|
/**
|
||||||
|
* @deprecated use `reactive` instead.
|
||||||
|
*/
|
||||||
|
observable: typeof reactive
|
||||||
|
/**
|
||||||
|
* @deprecated filters have been removed from Vue 3.
|
||||||
|
*/
|
||||||
|
filter(name: string, arg: any): null
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
cid: number
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
options: ComponentOptions
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
super: CompatVue
|
||||||
|
}
|
||||||
|
|
||||||
|
export let isCopyingConfig = false
|
||||||
|
|
||||||
|
// Legacy global Vue constructor
|
||||||
|
export function createCompatVue(
|
||||||
|
createApp: CreateAppFunction<Element>
|
||||||
|
): CompatVue {
|
||||||
|
const Vue: CompatVue = function Vue(options: ComponentOptions = {}) {
|
||||||
|
return createCompatApp(options, Vue)
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const singletonApp = createApp({})
|
||||||
|
|
||||||
|
function createCompatApp(options: ComponentOptions = {}, Ctor: any) {
|
||||||
|
assertCompatEnabled(DeprecationTypes.GLOBAL_MOUNT, null)
|
||||||
|
|
||||||
|
const { data } = options
|
||||||
|
if (
|
||||||
|
data &&
|
||||||
|
!isFunction(data) &&
|
||||||
|
softAssertCompatEnabled(DeprecationTypes.OPTIONS_DATA_FN, null)
|
||||||
|
) {
|
||||||
|
options.data = () => data
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = createApp(options)
|
||||||
|
|
||||||
|
// copy over asset registries and deopt flag
|
||||||
|
;['mixins', 'components', 'directives', 'deopt'].forEach(key => {
|
||||||
|
// @ts-ignore
|
||||||
|
app._context[key] = singletonApp._context[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
// copy over global config mutations
|
||||||
|
isCopyingConfig = true
|
||||||
|
for (const key in singletonApp.config) {
|
||||||
|
if (key === 'isNativeTag') continue
|
||||||
|
if (
|
||||||
|
isRuntimeOnly() &&
|
||||||
|
(key === 'isCustomElement' || key === 'compilerOptions')
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const val = singletonApp.config[key as keyof AppConfig]
|
||||||
|
// @ts-ignore
|
||||||
|
app.config[key] = val
|
||||||
|
|
||||||
|
// compat for runtime ignoredElements -> isCustomElement
|
||||||
|
if (
|
||||||
|
key === 'ignoredElements' &&
|
||||||
|
isCompatEnabled(DeprecationTypes.CONFIG_IGNORED_ELEMENTS, null) &&
|
||||||
|
!isRuntimeOnly() &&
|
||||||
|
isArray(val)
|
||||||
|
) {
|
||||||
|
app.config.compilerOptions.isCustomElement = tag => {
|
||||||
|
return val.some(v => (isString(v) ? v === tag : v.test(tag)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isCopyingConfig = false
|
||||||
|
|
||||||
|
// copy prototype augmentations as config.globalProperties
|
||||||
|
if (isCompatEnabled(DeprecationTypes.GLOBAL_PROTOTYPE, null)) {
|
||||||
|
app.config.globalProperties = Ctor.prototype
|
||||||
|
}
|
||||||
|
let hasPrototypeAugmentations = false
|
||||||
|
for (const key in Ctor.prototype) {
|
||||||
|
if (key !== 'constructor') {
|
||||||
|
hasPrototypeAugmentations = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (__DEV__ && hasPrototypeAugmentations) {
|
||||||
|
warnDeprecation(DeprecationTypes.GLOBAL_PROTOTYPE, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const vm = app._createRoot!(options)
|
||||||
|
if (options.el) {
|
||||||
|
return (vm as any).$mount(options.el)
|
||||||
|
} else {
|
||||||
|
return vm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.version = __VERSION__
|
||||||
|
Vue.config = singletonApp.config
|
||||||
|
Vue.nextTick = nextTick
|
||||||
|
Vue.options = { _base: Vue }
|
||||||
|
|
||||||
|
let cid = 1
|
||||||
|
Vue.cid = cid
|
||||||
|
|
||||||
|
const extendCache = new WeakMap()
|
||||||
|
|
||||||
|
function extendCtor(this: any, extendOptions: ComponentOptions = {}) {
|
||||||
|
assertCompatEnabled(DeprecationTypes.GLOBAL_EXTEND, null)
|
||||||
|
if (isFunction(extendOptions)) {
|
||||||
|
extendOptions = extendOptions.options
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extendCache.has(extendOptions)) {
|
||||||
|
return extendCache.get(extendOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Super = this
|
||||||
|
function SubVue(inlineOptions?: ComponentOptions) {
|
||||||
|
if (!inlineOptions) {
|
||||||
|
return createCompatApp(SubVue.options, SubVue)
|
||||||
|
} else {
|
||||||
|
return createCompatApp(
|
||||||
|
mergeOptions(
|
||||||
|
extend({}, SubVue.options),
|
||||||
|
inlineOptions,
|
||||||
|
null,
|
||||||
|
legacyOptionMergeStrats as any
|
||||||
|
),
|
||||||
|
SubVue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SubVue.super = Super
|
||||||
|
SubVue.prototype = Object.create(Vue.prototype)
|
||||||
|
SubVue.prototype.constructor = SubVue
|
||||||
|
|
||||||
|
// clone non-primitive base option values for edge case of mutating
|
||||||
|
// extended options
|
||||||
|
const mergeBase: any = {}
|
||||||
|
for (const key in Super.options) {
|
||||||
|
const superValue = Super.options[key]
|
||||||
|
mergeBase[key] = isArray(superValue)
|
||||||
|
? superValue.slice()
|
||||||
|
: isObject(superValue)
|
||||||
|
? extend(Object.create(null), superValue)
|
||||||
|
: superValue
|
||||||
|
}
|
||||||
|
|
||||||
|
SubVue.options = mergeOptions(
|
||||||
|
mergeBase,
|
||||||
|
extendOptions,
|
||||||
|
null,
|
||||||
|
legacyOptionMergeStrats as any
|
||||||
|
)
|
||||||
|
|
||||||
|
SubVue.options._base = SubVue
|
||||||
|
SubVue.extend = extendCtor.bind(SubVue)
|
||||||
|
SubVue.mixin = Super.mixin
|
||||||
|
SubVue.use = Super.use
|
||||||
|
SubVue.cid = ++cid
|
||||||
|
|
||||||
|
extendCache.set(extendOptions, SubVue)
|
||||||
|
return SubVue
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.extend = extendCtor.bind(Vue) as any
|
||||||
|
|
||||||
|
Vue.set = (target, key, value) => {
|
||||||
|
assertCompatEnabled(DeprecationTypes.GLOBAL_SET, null)
|
||||||
|
target[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.delete = (target, key) => {
|
||||||
|
assertCompatEnabled(DeprecationTypes.GLOBAL_DELETE, null)
|
||||||
|
delete target[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.observable = (target: any) => {
|
||||||
|
assertCompatEnabled(DeprecationTypes.GLOBAL_OBSERVABLE, null)
|
||||||
|
return reactive(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.use = (p, ...options) => {
|
||||||
|
if (p && isFunction(p.install)) {
|
||||||
|
p.install(Vue as any, ...options)
|
||||||
|
} else if (isFunction(p)) {
|
||||||
|
p(Vue as any, ...options)
|
||||||
|
}
|
||||||
|
return Vue
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.mixin = m => {
|
||||||
|
singletonApp.mixin(m)
|
||||||
|
return Vue
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.component = ((name: string, comp: Component) => {
|
||||||
|
if (comp) {
|
||||||
|
singletonApp.component(name, comp)
|
||||||
|
return Vue
|
||||||
|
} else {
|
||||||
|
return singletonApp.component(name)
|
||||||
|
}
|
||||||
|
}) as any
|
||||||
|
|
||||||
|
Vue.directive = ((name: string, dir: Directive | LegacyDirective) => {
|
||||||
|
if (dir) {
|
||||||
|
singletonApp.directive(name, dir as Directive)
|
||||||
|
return Vue
|
||||||
|
} else {
|
||||||
|
return singletonApp.directive(name)
|
||||||
|
}
|
||||||
|
}) as any
|
||||||
|
|
||||||
|
Vue.filter = ((name: string, filter: any) => {
|
||||||
|
// TODO deprecation warning
|
||||||
|
// TODO compiler warning for filters (maybe behavior compat?)
|
||||||
|
}) as any
|
||||||
|
|
||||||
|
// internal utils - these are technically internal but some plugins use it.
|
||||||
|
const util = {
|
||||||
|
warn: __DEV__ ? warn : NOOP,
|
||||||
|
extend,
|
||||||
|
mergeOptions: (parent: any, child: any, vm?: ComponentPublicInstance) =>
|
||||||
|
mergeOptions(
|
||||||
|
parent,
|
||||||
|
child,
|
||||||
|
vm && vm.$,
|
||||||
|
vm ? undefined : (legacyOptionMergeStrats as any)
|
||||||
|
),
|
||||||
|
defineReactive
|
||||||
|
}
|
||||||
|
Object.defineProperty(Vue, 'util', {
|
||||||
|
get() {
|
||||||
|
assertCompatEnabled(DeprecationTypes.GLOBAL_PRIVATE_UTIL, null)
|
||||||
|
return util
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Vue.configureCompat = configureCompat
|
||||||
|
|
||||||
|
return Vue
|
||||||
|
}
|
||||||
|
|
||||||
|
export function installCompatMount(
|
||||||
|
app: App,
|
||||||
|
context: AppContext,
|
||||||
|
render: RootRenderFunction,
|
||||||
|
hydrate?: RootHydrateFunction
|
||||||
|
) {
|
||||||
|
let isMounted = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vue 2 supports the behavior of creating a component instance but not
|
||||||
|
* mounting it, which is no longer possible in Vue 3 - this internal
|
||||||
|
* function simulates that behavior.
|
||||||
|
*/
|
||||||
|
app._createRoot = options => {
|
||||||
|
const component = app._component
|
||||||
|
const vnode = createVNode(component, options.propsData || null)
|
||||||
|
vnode.appContext = context
|
||||||
|
|
||||||
|
const hasNoRender =
|
||||||
|
!isFunction(component) && !component.render && !component.template
|
||||||
|
const emptyRender = () => {}
|
||||||
|
|
||||||
|
// create root instance
|
||||||
|
const instance = createComponentInstance(vnode, null, null)
|
||||||
|
// suppress "missing render fn" warning since it can't be determined
|
||||||
|
// until $mount is called
|
||||||
|
if (hasNoRender) {
|
||||||
|
instance.render = emptyRender
|
||||||
|
}
|
||||||
|
setupComponent(instance)
|
||||||
|
vnode.component = instance
|
||||||
|
|
||||||
|
// $mount & $destroy
|
||||||
|
// these are defined on ctx and picked up by the $mount/$destroy
|
||||||
|
// public property getters on the instance proxy.
|
||||||
|
// Note: the following assumes DOM environment since the compat build
|
||||||
|
// only targets web. It essentially includes logic for app.mount from
|
||||||
|
// both runtime-core AND runtime-dom.
|
||||||
|
instance.ctx._compat_mount = (selectorOrEl?: string | Element) => {
|
||||||
|
if (isMounted) {
|
||||||
|
__DEV__ && warn(`Root instance is already mounted.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let container: Element
|
||||||
|
if (typeof selectorOrEl === 'string') {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const result = document.querySelector(selectorOrEl)
|
||||||
|
if (!result) {
|
||||||
|
__DEV__ &&
|
||||||
|
warn(
|
||||||
|
`Failed to mount root instance: selector "${selectorOrEl}" returned null.`
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
container = result
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
container = selectorOrEl || document.createElement('div')
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSVG = container instanceof SVGElement
|
||||||
|
|
||||||
|
// HMR root reload
|
||||||
|
if (__DEV__) {
|
||||||
|
context.reload = () => {
|
||||||
|
const cloned = cloneVNode(vnode)
|
||||||
|
// compat mode will use instance if not reset to null
|
||||||
|
cloned.component = null
|
||||||
|
render(cloned, container, isSVG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve in-DOM template if component did not provide render
|
||||||
|
// and no setup/mixin render functions are provided (by checking
|
||||||
|
// that the instance is still using the placeholder render fn)
|
||||||
|
if (hasNoRender && instance.render === emptyRender) {
|
||||||
|
// root directives check
|
||||||
|
if (__DEV__) {
|
||||||
|
for (let i = 0; i < container.attributes.length; i++) {
|
||||||
|
const attr = container.attributes[i]
|
||||||
|
if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
|
||||||
|
warnDeprecation(DeprecationTypes.GLOBAL_MOUNT_CONTAINER, null)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
instance.render = null
|
||||||
|
;(component as ComponentOptions).template = container.innerHTML
|
||||||
|
finishComponentSetup(instance, false, true /* skip options */)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear content before mounting
|
||||||
|
container.innerHTML = ''
|
||||||
|
|
||||||
|
// TODO hydration
|
||||||
|
render(vnode, container, isSVG)
|
||||||
|
|
||||||
|
if (container instanceof Element) {
|
||||||
|
container.removeAttribute('v-cloak')
|
||||||
|
container.setAttribute('data-v-app', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
isMounted = true
|
||||||
|
app._container = container
|
||||||
|
// for devtools and telemetry
|
||||||
|
;(container as any).__vue_app__ = app
|
||||||
|
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||||
|
devtoolsInitApp(app, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance.proxy!
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.ctx._compat_destroy = app.unmount
|
||||||
|
|
||||||
|
return instance.proxy!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const methodsToPatch = [
|
||||||
|
'push',
|
||||||
|
'pop',
|
||||||
|
'shift',
|
||||||
|
'unshift',
|
||||||
|
'splice',
|
||||||
|
'sort',
|
||||||
|
'reverse'
|
||||||
|
]
|
||||||
|
|
||||||
|
const patched = new WeakSet<object>()
|
||||||
|
|
||||||
|
function defineReactive(obj: any, key: string, val: any) {
|
||||||
|
// it's possible for the orignial object to be mutated after being defined
|
||||||
|
// and expecting reactivity... we are covering it here because this seems to
|
||||||
|
// be a bit more common.
|
||||||
|
if (isObject(val) && !isReactive(val) && !patched.has(val)) {
|
||||||
|
const reactiveVal = reactive(val)
|
||||||
|
if (isArray(val)) {
|
||||||
|
methodsToPatch.forEach(m => {
|
||||||
|
// @ts-ignore
|
||||||
|
val[m] = (...args: any[]) => {
|
||||||
|
// @ts-ignore
|
||||||
|
Array.prototype[m].call(reactiveVal, ...args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Object.keys(val).forEach(key => {
|
||||||
|
try {
|
||||||
|
defineReactiveSimple(val, key, val[key])
|
||||||
|
} catch (e) {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const i = obj.$
|
||||||
|
if (i && obj === i.proxy) {
|
||||||
|
// Vue instance, add it to data
|
||||||
|
if (i.data === EMPTY_OBJ) {
|
||||||
|
i.data = reactive({})
|
||||||
|
}
|
||||||
|
i.data[key] = val
|
||||||
|
i.accessCache = Object.create(null)
|
||||||
|
} else if (isReactive(obj)) {
|
||||||
|
obj[key] = val
|
||||||
|
} else {
|
||||||
|
defineReactiveSimple(obj, key, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function defineReactiveSimple(obj: any, key: string, val: any) {
|
||||||
|
val = isObject(val) ? reactive(val) : val
|
||||||
|
Object.defineProperty(obj, key, {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
track(obj, TrackOpTypes.GET, key)
|
||||||
|
return val
|
||||||
|
},
|
||||||
|
set(newVal) {
|
||||||
|
val = isObject(newVal) ? reactive(newVal) : newVal
|
||||||
|
trigger(obj, TriggerOpTypes.SET, key, newVal)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
105
packages/runtime-core/src/compat/globalConfig.ts
Normal file
105
packages/runtime-core/src/compat/globalConfig.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { extend, isArray } from '@vue/shared'
|
||||||
|
import { AppConfig } from '../apiCreateApp'
|
||||||
|
import { mergeDataOption } from './data'
|
||||||
|
import { DeprecationTypes, warnDeprecation } from './compatConfig'
|
||||||
|
import { isCopyingConfig } from './global'
|
||||||
|
|
||||||
|
// legacy config warnings
|
||||||
|
export type LegacyConfig = {
|
||||||
|
/**
|
||||||
|
* @deprecated `config.silent` option has been removed
|
||||||
|
*/
|
||||||
|
silent?: boolean
|
||||||
|
/**
|
||||||
|
* @deprecated use __VUE_PROD_DEVTOOLS__ compile-time feature flag instead
|
||||||
|
* https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags
|
||||||
|
*/
|
||||||
|
devtools?: boolean
|
||||||
|
/**
|
||||||
|
* @deprecated use `config.isCustomElement` instead
|
||||||
|
* https://v3.vuejs.org/guide/migration/global-api.html#config-ignoredelements-is-now-config-iscustomelement
|
||||||
|
*/
|
||||||
|
ignoredElements?: (string | RegExp)[]
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* https://v3.vuejs.org/guide/migration/keycode-modifiers.html
|
||||||
|
*/
|
||||||
|
keyCodes?: Record<string, number | number[]>
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* https://v3.vuejs.org/guide/migration/global-api.html#config-productiontip-removed
|
||||||
|
*/
|
||||||
|
productionTip?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// dev only
|
||||||
|
export function installLegacyConfigProperties(config: AppConfig) {
|
||||||
|
const legacyConfigOptions: Record<string, DeprecationTypes> = {
|
||||||
|
silent: DeprecationTypes.CONFIG_SILENT,
|
||||||
|
devtools: DeprecationTypes.CONFIG_DEVTOOLS,
|
||||||
|
ignoredElements: DeprecationTypes.CONFIG_IGNORED_ELEMENTS,
|
||||||
|
keyCodes: DeprecationTypes.CONFIG_KEY_CODES,
|
||||||
|
productionTip: DeprecationTypes.CONFIG_PRODUCTION_TIP
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(legacyConfigOptions).forEach(key => {
|
||||||
|
let val = (config as any)[key]
|
||||||
|
Object.defineProperty(config, key, {
|
||||||
|
enumerable: true,
|
||||||
|
get() {
|
||||||
|
return val
|
||||||
|
},
|
||||||
|
set(newVal) {
|
||||||
|
if (!isCopyingConfig) {
|
||||||
|
warnDeprecation(legacyConfigOptions[key], null)
|
||||||
|
}
|
||||||
|
val = newVal
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Internal merge strats which are no longer needed in v3, but we need to
|
||||||
|
// expose them because some v2 plugins will reuse these internal strats to
|
||||||
|
// merge their custom options.
|
||||||
|
extend(config.optionMergeStrategies, legacyOptionMergeStrats)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const legacyOptionMergeStrats = {
|
||||||
|
data: mergeDataOption,
|
||||||
|
beforeCreate: mergeHook,
|
||||||
|
created: mergeHook,
|
||||||
|
beforeMount: mergeHook,
|
||||||
|
mounted: mergeHook,
|
||||||
|
beforeUpdate: mergeHook,
|
||||||
|
updated: mergeHook,
|
||||||
|
beforeDestroy: mergeHook,
|
||||||
|
destroyed: mergeHook,
|
||||||
|
activated: mergeHook,
|
||||||
|
deactivated: mergeHook,
|
||||||
|
errorCaptured: mergeHook,
|
||||||
|
serverPrefetch: mergeHook,
|
||||||
|
// assets
|
||||||
|
components: mergeObjectOptions,
|
||||||
|
directives: mergeObjectOptions,
|
||||||
|
filters: mergeObjectOptions,
|
||||||
|
// objects
|
||||||
|
props: mergeObjectOptions,
|
||||||
|
methods: mergeObjectOptions,
|
||||||
|
inject: mergeObjectOptions,
|
||||||
|
computed: mergeObjectOptions,
|
||||||
|
// watch has special merge behavior in v2, but isn't actually needed in v3.
|
||||||
|
// since we are only exposing these for compat and nobody should be relying
|
||||||
|
// on the watch-specific behavior, just expose the object merge strat.
|
||||||
|
watch: mergeObjectOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeHook(
|
||||||
|
to: Function[] | Function | undefined,
|
||||||
|
from: Function | Function[]
|
||||||
|
) {
|
||||||
|
return Array.from(new Set([...(isArray(to) ? to : to ? [to] : []), from]))
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeObjectOptions(to: Object | undefined, from: Object | undefined) {
|
||||||
|
return to ? extend(extend(Object.create(null), to), from) : from
|
||||||
|
}
|
154
packages/runtime-core/src/compat/instance.ts
Normal file
154
packages/runtime-core/src/compat/instance.ts
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import {
|
||||||
|
extend,
|
||||||
|
looseEqual,
|
||||||
|
looseIndexOf,
|
||||||
|
NOOP,
|
||||||
|
toDisplayString,
|
||||||
|
toNumber
|
||||||
|
} from '@vue/shared'
|
||||||
|
import {
|
||||||
|
ComponentPublicInstance,
|
||||||
|
PublicPropertiesMap
|
||||||
|
} from '../componentPublicInstance'
|
||||||
|
import { getCompatChildren } from './instanceChildren'
|
||||||
|
import {
|
||||||
|
DeprecationTypes,
|
||||||
|
assertCompatEnabled,
|
||||||
|
isCompatEnabled
|
||||||
|
} from './compatConfig'
|
||||||
|
import { off, on, once } from './instanceEventEmitter'
|
||||||
|
import { getCompatListeners } from './instanceListeners'
|
||||||
|
import { shallowReadonly } from '@vue/reactivity'
|
||||||
|
import { legacySlotProxyHandlers } from './component'
|
||||||
|
import { compatH } from './renderFn'
|
||||||
|
import { createCommentVNode, createTextVNode } from '../vnode'
|
||||||
|
import { renderList } from '../helpers/renderList'
|
||||||
|
import {
|
||||||
|
legacyBindDynamicKeys,
|
||||||
|
legacyBindObjectListeners,
|
||||||
|
legacyBindObjectProps,
|
||||||
|
legacyCheckKeyCodes,
|
||||||
|
legacyMarkOnce,
|
||||||
|
legacyPrependModifier,
|
||||||
|
legacyRenderSlot,
|
||||||
|
legacyRenderStatic,
|
||||||
|
legacyresolveScopedSlots
|
||||||
|
} from './renderHelpers'
|
||||||
|
import { resolveFilter } from '../helpers/resolveAssets'
|
||||||
|
import { resolveMergedOptions } from '../componentOptions'
|
||||||
|
import { Slots } from '../componentSlots'
|
||||||
|
|
||||||
|
export type LegacyPublicInstance = ComponentPublicInstance &
|
||||||
|
LegacyPublicProperties
|
||||||
|
|
||||||
|
export interface LegacyPublicProperties {
|
||||||
|
$set(target: object, key: string, value: any): void
|
||||||
|
$delete(target: object, key: string): void
|
||||||
|
$mount(el?: string | Element): this
|
||||||
|
$destroy(): void
|
||||||
|
$scopedSlots: Slots
|
||||||
|
$on(event: string | string[], fn: Function): this
|
||||||
|
$once(event: string, fn: Function): this
|
||||||
|
$off(event?: string, fn?: Function): this
|
||||||
|
$children: LegacyPublicProperties[]
|
||||||
|
$listeners: Record<string, Function | Function[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function installCompatInstanceProperties(map: PublicPropertiesMap) {
|
||||||
|
const set = (target: any, key: any, val: any) => {
|
||||||
|
target[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
const del = (target: any, key: any) => {
|
||||||
|
delete target[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
extend(map, {
|
||||||
|
$set: i => {
|
||||||
|
assertCompatEnabled(DeprecationTypes.INSTANCE_SET, i)
|
||||||
|
return set
|
||||||
|
},
|
||||||
|
|
||||||
|
$delete: i => {
|
||||||
|
assertCompatEnabled(DeprecationTypes.INSTANCE_DELETE, i)
|
||||||
|
return del
|
||||||
|
},
|
||||||
|
|
||||||
|
$mount: i => {
|
||||||
|
assertCompatEnabled(
|
||||||
|
DeprecationTypes.GLOBAL_MOUNT,
|
||||||
|
null /* this warning is global */
|
||||||
|
)
|
||||||
|
// root mount override from ./global.ts in installCompatMount
|
||||||
|
return i.ctx._compat_mount || NOOP
|
||||||
|
},
|
||||||
|
|
||||||
|
$destroy: i => {
|
||||||
|
assertCompatEnabled(DeprecationTypes.INSTANCE_DESTROY, i)
|
||||||
|
// root destroy override from ./global.ts in installCompatMount
|
||||||
|
return i.ctx._compat_destroy || NOOP
|
||||||
|
},
|
||||||
|
|
||||||
|
// overrides existing accessor
|
||||||
|
$slots: i => {
|
||||||
|
if (
|
||||||
|
isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, i) &&
|
||||||
|
i.render &&
|
||||||
|
i.render._compatWrapped
|
||||||
|
) {
|
||||||
|
return new Proxy(i.slots, legacySlotProxyHandlers)
|
||||||
|
}
|
||||||
|
return __DEV__ ? shallowReadonly(i.slots) : i.slots
|
||||||
|
},
|
||||||
|
|
||||||
|
$scopedSlots: i => {
|
||||||
|
assertCompatEnabled(DeprecationTypes.INSTANCE_SCOPED_SLOTS, i)
|
||||||
|
return __DEV__ ? shallowReadonly(i.slots) : i.slots
|
||||||
|
},
|
||||||
|
|
||||||
|
$on: i => on.bind(null, i),
|
||||||
|
$once: i => once.bind(null, i),
|
||||||
|
$off: i => off.bind(null, i),
|
||||||
|
|
||||||
|
$children: getCompatChildren,
|
||||||
|
$listeners: getCompatListeners
|
||||||
|
} as PublicPropertiesMap)
|
||||||
|
|
||||||
|
if (isCompatEnabled(DeprecationTypes.PRIVATE_APIS, null)) {
|
||||||
|
extend(map, {
|
||||||
|
$vnode: i => i.vnode,
|
||||||
|
|
||||||
|
// inject addtional properties into $options for compat
|
||||||
|
$options: i => {
|
||||||
|
let res = resolveMergedOptions(i)
|
||||||
|
if (res === i.type) res = i.type.__merged = extend({}, res)
|
||||||
|
res.parent = i.proxy!.$parent
|
||||||
|
res.propsData = i.vnode.props
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
|
||||||
|
// v2 render helpers
|
||||||
|
$createElement: () => compatH,
|
||||||
|
_self: i => i.proxy,
|
||||||
|
_uid: i => i.uid,
|
||||||
|
_c: () => compatH,
|
||||||
|
_o: () => legacyMarkOnce,
|
||||||
|
_n: () => toNumber,
|
||||||
|
_s: () => toDisplayString,
|
||||||
|
_l: () => renderList,
|
||||||
|
_t: i => legacyRenderSlot.bind(null, i),
|
||||||
|
_q: () => looseEqual,
|
||||||
|
_i: () => looseIndexOf,
|
||||||
|
_m: i => legacyRenderStatic.bind(null, i),
|
||||||
|
_f: () => resolveFilter,
|
||||||
|
_k: i => legacyCheckKeyCodes.bind(null, i),
|
||||||
|
_b: () => legacyBindObjectProps,
|
||||||
|
_v: () => createTextVNode,
|
||||||
|
_e: () => createCommentVNode,
|
||||||
|
_u: () => legacyresolveScopedSlots,
|
||||||
|
_g: () => legacyBindObjectListeners,
|
||||||
|
_d: () => legacyBindDynamicKeys,
|
||||||
|
_p: () => legacyPrependModifier
|
||||||
|
} as PublicPropertiesMap)
|
||||||
|
}
|
||||||
|
}
|
28
packages/runtime-core/src/compat/instanceChildren.ts
Normal file
28
packages/runtime-core/src/compat/instanceChildren.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { ShapeFlags } from '@vue/shared/src'
|
||||||
|
import { ComponentInternalInstance } from '../component'
|
||||||
|
import { ComponentPublicInstance } from '../componentPublicInstance'
|
||||||
|
import { VNode } from '../vnode'
|
||||||
|
import { assertCompatEnabled, DeprecationTypes } from './compatConfig'
|
||||||
|
|
||||||
|
export function getCompatChildren(
|
||||||
|
instance: ComponentInternalInstance
|
||||||
|
): ComponentPublicInstance[] {
|
||||||
|
assertCompatEnabled(DeprecationTypes.INSTANCE_CHILDREN, instance)
|
||||||
|
const root = instance.subTree
|
||||||
|
const children: ComponentPublicInstance[] = []
|
||||||
|
if (root) {
|
||||||
|
walk(root, children)
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
||||||
|
function walk(vnode: VNode, children: ComponentPublicInstance[]) {
|
||||||
|
if (vnode.component) {
|
||||||
|
children.push(vnode.component.proxy!)
|
||||||
|
} else if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
||||||
|
const vnodes = vnode.children as VNode[]
|
||||||
|
for (let i = 0; i < vnodes.length; i++) {
|
||||||
|
walk(vnodes[i], children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
108
packages/runtime-core/src/compat/instanceEventEmitter.ts
Normal file
108
packages/runtime-core/src/compat/instanceEventEmitter.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { isArray } from '@vue/shared'
|
||||||
|
import { ComponentInternalInstance } from '../component'
|
||||||
|
import { callWithAsyncErrorHandling, ErrorCodes } from '../errorHandling'
|
||||||
|
import { assertCompatEnabled, DeprecationTypes } from './compatConfig'
|
||||||
|
|
||||||
|
interface EventRegistry {
|
||||||
|
[event: string]: Function[] | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventRegistryMap = /*#__PURE__*/ new WeakMap<
|
||||||
|
ComponentInternalInstance,
|
||||||
|
EventRegistry
|
||||||
|
>()
|
||||||
|
|
||||||
|
export function getRegistry(
|
||||||
|
instance: ComponentInternalInstance
|
||||||
|
): EventRegistry {
|
||||||
|
let events = eventRegistryMap.get(instance)
|
||||||
|
if (!events) {
|
||||||
|
eventRegistryMap.set(instance, (events = Object.create(null)))
|
||||||
|
}
|
||||||
|
return events!
|
||||||
|
}
|
||||||
|
|
||||||
|
export function on(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
event: string | string[],
|
||||||
|
fn: Function
|
||||||
|
) {
|
||||||
|
if (isArray(event)) {
|
||||||
|
event.forEach(e => on(instance, e, fn))
|
||||||
|
} else {
|
||||||
|
if (event.startsWith('hook:')) {
|
||||||
|
assertCompatEnabled(
|
||||||
|
DeprecationTypes.INSTANCE_EVENT_HOOKS,
|
||||||
|
instance,
|
||||||
|
event
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
assertCompatEnabled(DeprecationTypes.INSTANCE_EVENT_EMITTER, instance)
|
||||||
|
}
|
||||||
|
const events = getRegistry(instance)
|
||||||
|
;(events[event] || (events[event] = [])).push(fn)
|
||||||
|
}
|
||||||
|
return instance.proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
export function once(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
event: string,
|
||||||
|
fn: Function
|
||||||
|
) {
|
||||||
|
const wrapped = (...args: any[]) => {
|
||||||
|
off(instance, event, wrapped)
|
||||||
|
fn.call(instance.proxy, ...args)
|
||||||
|
}
|
||||||
|
wrapped.fn = fn
|
||||||
|
on(instance, event, wrapped)
|
||||||
|
return instance.proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
export function off(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
event?: string,
|
||||||
|
fn?: Function
|
||||||
|
) {
|
||||||
|
assertCompatEnabled(DeprecationTypes.INSTANCE_EVENT_EMITTER, instance)
|
||||||
|
const vm = instance.proxy
|
||||||
|
// all
|
||||||
|
if (!arguments.length) {
|
||||||
|
eventRegistryMap.set(instance, Object.create(null))
|
||||||
|
return vm
|
||||||
|
}
|
||||||
|
// array of events
|
||||||
|
if (isArray(event)) {
|
||||||
|
event.forEach(e => off(instance, e, fn))
|
||||||
|
return vm
|
||||||
|
}
|
||||||
|
// specific event
|
||||||
|
const events = getRegistry(instance)
|
||||||
|
const cbs = events[event!]
|
||||||
|
if (!cbs) {
|
||||||
|
return vm
|
||||||
|
}
|
||||||
|
if (!fn) {
|
||||||
|
events[event!] = undefined
|
||||||
|
return vm
|
||||||
|
}
|
||||||
|
events[event!] = cbs.filter(cb => !(cb === fn || (cb as any).fn === fn))
|
||||||
|
return vm
|
||||||
|
}
|
||||||
|
|
||||||
|
export function emit(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
event: string,
|
||||||
|
...args: any[]
|
||||||
|
) {
|
||||||
|
const cbs = getRegistry(instance)[event]
|
||||||
|
if (cbs) {
|
||||||
|
callWithAsyncErrorHandling(
|
||||||
|
cbs,
|
||||||
|
instance,
|
||||||
|
ErrorCodes.COMPONENT_EVENT_HANDLER,
|
||||||
|
args
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return instance.proxy
|
||||||
|
}
|
19
packages/runtime-core/src/compat/instanceListeners.ts
Normal file
19
packages/runtime-core/src/compat/instanceListeners.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { isOn } from '@vue/shared'
|
||||||
|
import { ComponentInternalInstance } from '../component'
|
||||||
|
import { assertCompatEnabled, DeprecationTypes } from './compatConfig'
|
||||||
|
|
||||||
|
export function getCompatListeners(instance: ComponentInternalInstance) {
|
||||||
|
assertCompatEnabled(DeprecationTypes.INSTANCE_LISTENERS, instance)
|
||||||
|
|
||||||
|
const listeners: Record<string, Function | Function[]> = {}
|
||||||
|
const rawProps = instance.vnode.props
|
||||||
|
if (!rawProps) {
|
||||||
|
return listeners
|
||||||
|
}
|
||||||
|
for (const key in rawProps) {
|
||||||
|
if (isOn(key)) {
|
||||||
|
listeners[key[2].toLowerCase() + key.slice(3)] = rawProps[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return listeners
|
||||||
|
}
|
40
packages/runtime-core/src/compat/props.ts
Normal file
40
packages/runtime-core/src/compat/props.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { isArray } from '@vue/shared'
|
||||||
|
import { inject } from '../apiInject'
|
||||||
|
import { ComponentInternalInstance, Data } from '../component'
|
||||||
|
import { ComponentOptions, resolveMergedOptions } from '../componentOptions'
|
||||||
|
import { DeprecationTypes, warnDeprecation } from './compatConfig'
|
||||||
|
|
||||||
|
export function createPropsDefaultThis(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
rawProps: Data,
|
||||||
|
propKey: string
|
||||||
|
) {
|
||||||
|
return new Proxy(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get(_, key: string) {
|
||||||
|
__DEV__ &&
|
||||||
|
warnDeprecation(DeprecationTypes.PROPS_DEFAULT_THIS, null, propKey)
|
||||||
|
// $options
|
||||||
|
if (key === '$options') {
|
||||||
|
return resolveMergedOptions(instance)
|
||||||
|
}
|
||||||
|
// props
|
||||||
|
if (key in rawProps) {
|
||||||
|
return rawProps[key]
|
||||||
|
}
|
||||||
|
// injections
|
||||||
|
const injections = (instance.type as ComponentOptions).inject
|
||||||
|
if (injections) {
|
||||||
|
if (isArray(injections)) {
|
||||||
|
if (injections.includes(key)) {
|
||||||
|
return inject(key)
|
||||||
|
}
|
||||||
|
} else if (key in injections) {
|
||||||
|
return inject(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
45
packages/runtime-core/src/compat/ref.ts
Normal file
45
packages/runtime-core/src/compat/ref.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { isArray, remove } from '@vue/shared'
|
||||||
|
import { ComponentInternalInstance, Data } from '../component'
|
||||||
|
import { VNode } from '../vnode'
|
||||||
|
import { DeprecationTypes, warnDeprecation } from './compatConfig'
|
||||||
|
|
||||||
|
export function convertLegacyRefInFor(vnode: VNode) {
|
||||||
|
// refInFor
|
||||||
|
if (vnode.props && vnode.props.refInFor) {
|
||||||
|
delete vnode.props.refInFor
|
||||||
|
if (vnode.ref) {
|
||||||
|
if (isArray(vnode.ref)) {
|
||||||
|
vnode.ref.forEach(r => (r.f = true))
|
||||||
|
} else {
|
||||||
|
vnode.ref.f = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerLegacyRef(
|
||||||
|
refs: Data,
|
||||||
|
key: string,
|
||||||
|
value: any,
|
||||||
|
owner: ComponentInternalInstance,
|
||||||
|
isInFor: boolean | undefined,
|
||||||
|
isUnmount: boolean
|
||||||
|
) {
|
||||||
|
const existing = refs[key]
|
||||||
|
if (isUnmount) {
|
||||||
|
if (isArray(existing)) {
|
||||||
|
remove(existing, value)
|
||||||
|
} else {
|
||||||
|
refs[key] = null
|
||||||
|
}
|
||||||
|
} else if (isInFor) {
|
||||||
|
__DEV__ && warnDeprecation(DeprecationTypes.V_FOR_REF, owner)
|
||||||
|
if (!isArray(existing)) {
|
||||||
|
refs[key] = [value]
|
||||||
|
} else if (!existing.includes(value)) {
|
||||||
|
existing.push(value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
refs[key] = value
|
||||||
|
}
|
||||||
|
}
|
344
packages/runtime-core/src/compat/renderFn.ts
Normal file
344
packages/runtime-core/src/compat/renderFn.ts
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
import {
|
||||||
|
extend,
|
||||||
|
hyphenate,
|
||||||
|
isArray,
|
||||||
|
isObject,
|
||||||
|
isString,
|
||||||
|
makeMap,
|
||||||
|
normalizeClass,
|
||||||
|
normalizeStyle,
|
||||||
|
ShapeFlags,
|
||||||
|
toHandlerKey
|
||||||
|
} from '@vue/shared'
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
ComponentInternalInstance,
|
||||||
|
ComponentOptions,
|
||||||
|
Data,
|
||||||
|
InternalRenderFunction
|
||||||
|
} from '../component'
|
||||||
|
import { currentRenderingInstance } from '../componentRenderContext'
|
||||||
|
import { DirectiveArguments, withDirectives } from '../directives'
|
||||||
|
import {
|
||||||
|
resolveDirective,
|
||||||
|
resolveDynamicComponent
|
||||||
|
} from '../helpers/resolveAssets'
|
||||||
|
import {
|
||||||
|
Comment,
|
||||||
|
createVNode,
|
||||||
|
isVNode,
|
||||||
|
normalizeChildren,
|
||||||
|
VNode,
|
||||||
|
VNodeArrayChildren,
|
||||||
|
VNodeProps
|
||||||
|
} from '../vnode'
|
||||||
|
import {
|
||||||
|
checkCompatEnabled,
|
||||||
|
DeprecationTypes,
|
||||||
|
isCompatEnabled
|
||||||
|
} from './compatConfig'
|
||||||
|
import { compatModelEventPrefix } from './vModel'
|
||||||
|
|
||||||
|
const v3CompiledRenderFnRE = /^(?:function \w+)?\(_ctx, _cache/
|
||||||
|
|
||||||
|
export function convertLegacyRenderFn(instance: ComponentInternalInstance) {
|
||||||
|
const Component = instance.type as ComponentOptions
|
||||||
|
const render = Component.render as InternalRenderFunction | undefined
|
||||||
|
|
||||||
|
// v3 runtime compiled, or already checked / wrapped
|
||||||
|
if (!render || render._rc || render._compatChecked || render._compatWrapped) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v3CompiledRenderFnRE.test(render.toString())) {
|
||||||
|
// v3 pre-compiled function
|
||||||
|
render._compatChecked = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// v2 render function, try to provide compat
|
||||||
|
if (checkCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)) {
|
||||||
|
const wrapped = (Component.render = function compatRender() {
|
||||||
|
// @ts-ignore
|
||||||
|
return render.call(this, compatH)
|
||||||
|
})
|
||||||
|
// @ts-ignore
|
||||||
|
wrapped._compatWrapped = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LegacyVNodeProps {
|
||||||
|
key?: string | number
|
||||||
|
ref?: string
|
||||||
|
refInFor?: boolean
|
||||||
|
|
||||||
|
staticClass?: string
|
||||||
|
class?: unknown
|
||||||
|
staticStyle?: Record<string, unknown>
|
||||||
|
style?: Record<string, unknown>
|
||||||
|
attrs?: Record<string, unknown>
|
||||||
|
domProps?: Record<string, unknown>
|
||||||
|
on?: Record<string, Function | Function[]>
|
||||||
|
nativeOn?: Record<string, Function | Function[]>
|
||||||
|
directives?: LegacyVNodeDirective[]
|
||||||
|
|
||||||
|
// component only
|
||||||
|
props?: Record<string, unknown>
|
||||||
|
slot?: string
|
||||||
|
scopedSlots?: Record<string, Function>
|
||||||
|
model?: {
|
||||||
|
value: any
|
||||||
|
callback: (v: any) => void
|
||||||
|
expression: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LegacyVNodeDirective {
|
||||||
|
name: string
|
||||||
|
value: unknown
|
||||||
|
arg?: string
|
||||||
|
modifiers?: Record<string, boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
type LegacyVNodeChildren =
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| VNode
|
||||||
|
| VNodeArrayChildren
|
||||||
|
|
||||||
|
export function compatH(
|
||||||
|
type: string | Component,
|
||||||
|
children?: LegacyVNodeChildren
|
||||||
|
): VNode
|
||||||
|
export function compatH(
|
||||||
|
type: string | Component,
|
||||||
|
props?: LegacyVNodeProps,
|
||||||
|
children?: LegacyVNodeChildren
|
||||||
|
): VNode
|
||||||
|
|
||||||
|
export function compatH(
|
||||||
|
type: any,
|
||||||
|
propsOrChildren?: any,
|
||||||
|
children?: any
|
||||||
|
): VNode {
|
||||||
|
if (!type) {
|
||||||
|
type = Comment
|
||||||
|
}
|
||||||
|
|
||||||
|
// to support v2 string component name look!up
|
||||||
|
if (typeof type === 'string') {
|
||||||
|
const t = hyphenate(type)
|
||||||
|
if (t === 'transition' || t === 'transition-group' || t === 'keep-alive') {
|
||||||
|
// since transition and transition-group are runtime-dom-specific,
|
||||||
|
// we cannot import them directly here. Instead they are registered using
|
||||||
|
// special keys in @vue/compat entry.
|
||||||
|
type = `__compat__${t}`
|
||||||
|
}
|
||||||
|
type = resolveDynamicComponent(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
const l = arguments.length
|
||||||
|
const is2ndArgArrayChildren = isArray(propsOrChildren)
|
||||||
|
if (l === 2 || is2ndArgArrayChildren) {
|
||||||
|
if (isObject(propsOrChildren) && !is2ndArgArrayChildren) {
|
||||||
|
// single vnode without props
|
||||||
|
if (isVNode(propsOrChildren)) {
|
||||||
|
return convertLegacySlots(createVNode(type, null, [propsOrChildren]))
|
||||||
|
}
|
||||||
|
// props without children
|
||||||
|
return convertLegacySlots(
|
||||||
|
convertLegacyDirectives(
|
||||||
|
createVNode(type, convertLegacyProps(propsOrChildren, type)),
|
||||||
|
propsOrChildren
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// omit props
|
||||||
|
return convertLegacySlots(createVNode(type, null, propsOrChildren))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isVNode(children)) {
|
||||||
|
children = [children]
|
||||||
|
}
|
||||||
|
return convertLegacySlots(
|
||||||
|
convertLegacyDirectives(
|
||||||
|
createVNode(type, convertLegacyProps(propsOrChildren, type), children),
|
||||||
|
propsOrChildren
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const skipLegacyRootLevelProps = /*#__PURE__*/ makeMap(
|
||||||
|
'staticStyle,staticClass,directives,model,hook'
|
||||||
|
)
|
||||||
|
|
||||||
|
function convertLegacyProps(
|
||||||
|
legacyProps: LegacyVNodeProps | undefined,
|
||||||
|
type: any
|
||||||
|
): Data & VNodeProps | null {
|
||||||
|
if (!legacyProps) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const converted: Data & VNodeProps = {}
|
||||||
|
|
||||||
|
for (const key in legacyProps) {
|
||||||
|
if (key === 'attrs' || key === 'domProps' || key === 'props') {
|
||||||
|
extend(converted, legacyProps[key])
|
||||||
|
} else if (key === 'on' || key === 'nativeOn') {
|
||||||
|
const listeners = legacyProps[key]
|
||||||
|
for (const event in listeners) {
|
||||||
|
const handlerKey = convertLegacyEventKey(event)
|
||||||
|
const existing = converted[handlerKey]
|
||||||
|
const incoming = listeners[event]
|
||||||
|
if (existing !== incoming) {
|
||||||
|
if (existing) {
|
||||||
|
// for the rare case where the same handler is attached
|
||||||
|
// twice with/without .native modifier...
|
||||||
|
if (key === 'nativeOn' && String(existing) === String(incoming)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
converted[handlerKey] = [].concat(existing as any, incoming as any)
|
||||||
|
} else {
|
||||||
|
converted[handlerKey] = incoming
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!skipLegacyRootLevelProps(key)) {
|
||||||
|
converted[key] = legacyProps[key as keyof LegacyVNodeProps]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (legacyProps.staticClass) {
|
||||||
|
converted.class = normalizeClass([legacyProps.staticClass, converted.class])
|
||||||
|
}
|
||||||
|
if (legacyProps.staticStyle) {
|
||||||
|
converted.style = normalizeStyle([legacyProps.staticStyle, converted.style])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (legacyProps.model && isObject(type)) {
|
||||||
|
// v2 compiled component v-model
|
||||||
|
const { prop = 'value', event = 'input' } = (type as any).model || {}
|
||||||
|
converted[prop] = legacyProps.model.value
|
||||||
|
converted[compatModelEventPrefix + event] = legacyProps.model.callback
|
||||||
|
}
|
||||||
|
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertLegacyEventKey(event: string): string {
|
||||||
|
// normalize v2 event prefixes
|
||||||
|
if (event[0] === '&') {
|
||||||
|
event = event.slice(1) + 'Passive'
|
||||||
|
}
|
||||||
|
if (event[0] === '~') {
|
||||||
|
event = event.slice(1) + 'Once'
|
||||||
|
}
|
||||||
|
if (event[0] === '!') {
|
||||||
|
event = event.slice(1) + 'Capture'
|
||||||
|
}
|
||||||
|
return toHandlerKey(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertLegacyDirectives(
|
||||||
|
vnode: VNode,
|
||||||
|
props?: LegacyVNodeProps
|
||||||
|
): VNode {
|
||||||
|
if (props && props.directives) {
|
||||||
|
return withDirectives(
|
||||||
|
vnode,
|
||||||
|
props.directives.map(({ name, value, arg, modifiers }) => {
|
||||||
|
return [
|
||||||
|
resolveDirective(name)!,
|
||||||
|
value,
|
||||||
|
arg,
|
||||||
|
modifiers
|
||||||
|
] as DirectiveArguments[number]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return vnode
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertLegacySlots(vnode: VNode): VNode {
|
||||||
|
const { props, children } = vnode
|
||||||
|
|
||||||
|
let slots: Record<string, any> | undefined
|
||||||
|
|
||||||
|
if (vnode.shapeFlag & ShapeFlags.COMPONENT && isArray(children)) {
|
||||||
|
slots = {}
|
||||||
|
// check "slot" property on vnodes and turn them into v3 function slots
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const child = children[i]
|
||||||
|
const slotName =
|
||||||
|
(isVNode(child) && child.props && child.props.slot) || 'default'
|
||||||
|
const slot = slots[slotName] || (slots[slotName] = [] as any[])
|
||||||
|
if (isVNode(child) && child.type === 'template') {
|
||||||
|
slot.push(child.children)
|
||||||
|
} else {
|
||||||
|
slot.push(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (slots) {
|
||||||
|
for (const key in slots) {
|
||||||
|
const slotChildren = slots[key]
|
||||||
|
slots[key] = () => slotChildren
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const scopedSlots = props && props.scopedSlots
|
||||||
|
if (scopedSlots) {
|
||||||
|
delete props!.scopedSlots
|
||||||
|
if (slots) {
|
||||||
|
extend(slots, scopedSlots)
|
||||||
|
} else {
|
||||||
|
slots = scopedSlots
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slots) {
|
||||||
|
normalizeChildren(vnode, slots)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vnode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defineLegacyVNodeProperties(vnode: VNode) {
|
||||||
|
if (
|
||||||
|
isCompatEnabled(
|
||||||
|
DeprecationTypes.RENDER_FUNCTION,
|
||||||
|
currentRenderingInstance
|
||||||
|
) &&
|
||||||
|
isCompatEnabled(DeprecationTypes.PRIVATE_APIS, currentRenderingInstance)
|
||||||
|
) {
|
||||||
|
const context = currentRenderingInstance
|
||||||
|
const getInstance = () => vnode.component && vnode.component.proxy
|
||||||
|
let componentOptions: any
|
||||||
|
Object.defineProperties(vnode, {
|
||||||
|
tag: { get: () => vnode.type },
|
||||||
|
data: { get: () => vnode.props, set: p => (vnode.props = p) },
|
||||||
|
elm: { get: () => vnode.el },
|
||||||
|
componentInstance: { get: getInstance },
|
||||||
|
child: { get: getInstance },
|
||||||
|
text: { get: () => (isString(vnode.children) ? vnode.children : null) },
|
||||||
|
context: { get: () => context && context.proxy },
|
||||||
|
componentOptions: {
|
||||||
|
get: () => {
|
||||||
|
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
|
||||||
|
if (componentOptions) {
|
||||||
|
return componentOptions
|
||||||
|
}
|
||||||
|
return (componentOptions = {
|
||||||
|
Ctor: vnode.type,
|
||||||
|
propsData: vnode.props,
|
||||||
|
children: vnode.children
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
182
packages/runtime-core/src/compat/renderHelpers.ts
Normal file
182
packages/runtime-core/src/compat/renderHelpers.ts
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import {
|
||||||
|
camelize,
|
||||||
|
extend,
|
||||||
|
hyphenate,
|
||||||
|
isArray,
|
||||||
|
isObject,
|
||||||
|
isReservedProp,
|
||||||
|
normalizeClass
|
||||||
|
} from '@vue/shared'
|
||||||
|
import { ComponentInternalInstance } from '../component'
|
||||||
|
import { Slot } from '../componentSlots'
|
||||||
|
import { createSlots } from '../helpers/createSlots'
|
||||||
|
import { renderSlot } from '../helpers/renderSlot'
|
||||||
|
import { toHandlers } from '../helpers/toHandlers'
|
||||||
|
import { mergeProps, VNode } from '../vnode'
|
||||||
|
|
||||||
|
function toObject(arr: Array<any>): Object {
|
||||||
|
const res = {}
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
if (arr[i]) {
|
||||||
|
extend(res, arr[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
export function legacyBindObjectProps(
|
||||||
|
data: any,
|
||||||
|
_tag: string,
|
||||||
|
value: any,
|
||||||
|
_asProp: boolean,
|
||||||
|
isSync?: boolean
|
||||||
|
) {
|
||||||
|
if (value && isObject(value)) {
|
||||||
|
if (isArray(value)) {
|
||||||
|
value = toObject(value)
|
||||||
|
}
|
||||||
|
for (const key in value) {
|
||||||
|
if (isReservedProp(key)) {
|
||||||
|
data[key] = value[key]
|
||||||
|
} else if (key === 'class') {
|
||||||
|
data.class = normalizeClass([data.class, value.class])
|
||||||
|
} else if (key === 'style') {
|
||||||
|
data.style = normalizeClass([data.style, value.style])
|
||||||
|
} else {
|
||||||
|
const attrs = data.attrs || (data.attrs = {})
|
||||||
|
const camelizedKey = camelize(key)
|
||||||
|
const hyphenatedKey = hyphenate(key)
|
||||||
|
if (!(camelizedKey in attrs) && !(hyphenatedKey in attrs)) {
|
||||||
|
attrs[key] = value[key]
|
||||||
|
|
||||||
|
if (isSync) {
|
||||||
|
const on = data.on || (data.on = {})
|
||||||
|
on[`update:${key}`] = function($event: any) {
|
||||||
|
value[key] = $event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
export function legacyBindObjectListeners(props: any, listeners: any) {
|
||||||
|
return mergeProps(props, toHandlers(listeners))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function legacyRenderSlot(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
name: string,
|
||||||
|
fallback?: VNode[],
|
||||||
|
props?: any,
|
||||||
|
bindObject?: any
|
||||||
|
) {
|
||||||
|
if (bindObject) {
|
||||||
|
props = mergeProps(props, bindObject)
|
||||||
|
}
|
||||||
|
return renderSlot(instance.slots, name, props, fallback && (() => fallback))
|
||||||
|
}
|
||||||
|
|
||||||
|
type LegacyScopedSlotsData = Array<
|
||||||
|
| {
|
||||||
|
key: string
|
||||||
|
fn: Function
|
||||||
|
}
|
||||||
|
| LegacyScopedSlotsData
|
||||||
|
>
|
||||||
|
|
||||||
|
export function legacyresolveScopedSlots(
|
||||||
|
fns: LegacyScopedSlotsData,
|
||||||
|
raw?: Record<string, Slot>,
|
||||||
|
// the following are added in 2.6
|
||||||
|
hasDynamicKeys?: boolean
|
||||||
|
) {
|
||||||
|
// v2 default slot doesn't have name
|
||||||
|
return createSlots(
|
||||||
|
raw || ({ $stable: !hasDynamicKeys } as any),
|
||||||
|
mapKeyToName(fns)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapKeyToName(slots: LegacyScopedSlotsData) {
|
||||||
|
for (let i = 0; i < slots.length; i++) {
|
||||||
|
const fn = slots[i]
|
||||||
|
if (fn) {
|
||||||
|
if (isArray(fn)) {
|
||||||
|
mapKeyToName(fn)
|
||||||
|
} else {
|
||||||
|
;(fn as any).name = fn.key || 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return slots as any
|
||||||
|
}
|
||||||
|
|
||||||
|
const staticCacheMap = /*#__PURE__*/ new WeakMap<
|
||||||
|
ComponentInternalInstance,
|
||||||
|
any[]
|
||||||
|
>()
|
||||||
|
|
||||||
|
export function legacyRenderStatic(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
index: number
|
||||||
|
) {
|
||||||
|
let cache = staticCacheMap.get(instance)
|
||||||
|
if (!cache) {
|
||||||
|
staticCacheMap.set(instance, (cache = []))
|
||||||
|
}
|
||||||
|
if (cache[index]) {
|
||||||
|
return cache[index]
|
||||||
|
}
|
||||||
|
const fn = (instance.type as any).staticRenderFns[index]
|
||||||
|
const ctx = instance.proxy
|
||||||
|
return (cache[index] = fn.call(ctx, null, ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function legacyCheckKeyCodes(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
eventKeyCode: number,
|
||||||
|
key: string,
|
||||||
|
builtInKeyCode?: number | number[],
|
||||||
|
eventKeyName?: string,
|
||||||
|
builtInKeyName?: string | string[]
|
||||||
|
) {
|
||||||
|
const config = instance.appContext.config as any
|
||||||
|
const configKeyCodes = config.keyCodes || {}
|
||||||
|
const mappedKeyCode = configKeyCodes[key] || builtInKeyCode
|
||||||
|
if (builtInKeyName && eventKeyName && !configKeyCodes[key]) {
|
||||||
|
return isKeyNotMatch(builtInKeyName, eventKeyName)
|
||||||
|
} else if (mappedKeyCode) {
|
||||||
|
return isKeyNotMatch(mappedKeyCode, eventKeyCode)
|
||||||
|
} else if (eventKeyName) {
|
||||||
|
return hyphenate(eventKeyName) !== key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isKeyNotMatch<T>(expect: T | T[], actual: T): boolean {
|
||||||
|
if (isArray(expect)) {
|
||||||
|
return expect.indexOf(actual) === -1
|
||||||
|
} else {
|
||||||
|
return expect !== actual
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function legacyMarkOnce(tree: VNode) {
|
||||||
|
return tree
|
||||||
|
}
|
||||||
|
|
||||||
|
export function legacyBindDynamicKeys(props: any, values: any[]) {
|
||||||
|
for (let i = 0; i < values.length; i += 2) {
|
||||||
|
const key = values[i]
|
||||||
|
if (typeof key === 'string' && key) {
|
||||||
|
props[values[i]] = values[i + 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
||||||
|
export function legacyPrependModifier(value: any, symbol: string) {
|
||||||
|
return typeof value === 'string' ? symbol + value : value
|
||||||
|
}
|
71
packages/runtime-core/src/compat/vModel.ts
Normal file
71
packages/runtime-core/src/compat/vModel.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { ShapeFlags } from '@vue/shared'
|
||||||
|
import { ComponentInternalInstance, ComponentOptions } from '../component'
|
||||||
|
import { callWithErrorHandling, ErrorCodes } from '../errorHandling'
|
||||||
|
import { VNode } from '../vnode'
|
||||||
|
import { popWarningContext, pushWarningContext } from '../warning'
|
||||||
|
import {
|
||||||
|
DeprecationTypes,
|
||||||
|
warnDeprecation,
|
||||||
|
isCompatEnabled
|
||||||
|
} from './compatConfig'
|
||||||
|
|
||||||
|
export const compatModelEventPrefix = `onModelCompat:`
|
||||||
|
|
||||||
|
const warnedTypes = new WeakSet()
|
||||||
|
|
||||||
|
export function convertLegacyVModelProps(vnode: VNode) {
|
||||||
|
const { type, shapeFlag, props, dynamicProps } = vnode
|
||||||
|
if (shapeFlag & ShapeFlags.COMPONENT && props && 'modelValue' in props) {
|
||||||
|
if (
|
||||||
|
!isCompatEnabled(
|
||||||
|
DeprecationTypes.COMPONENT_V_MODEL,
|
||||||
|
// this is a special case where we want to use the vnode component's
|
||||||
|
// compat config instead of the current rendering instance (which is the
|
||||||
|
// parent of the component that exposes v-model)
|
||||||
|
{ type } as any
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__DEV__ && !warnedTypes.has(type as ComponentOptions)) {
|
||||||
|
pushWarningContext(vnode)
|
||||||
|
warnDeprecation(DeprecationTypes.COMPONENT_V_MODEL, { type } as any, type)
|
||||||
|
popWarningContext()
|
||||||
|
warnedTypes.add(type as ComponentOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// v3 compiled model code -> v2 compat props
|
||||||
|
// modelValue -> value
|
||||||
|
// onUpdate:modelValue -> onModelCompat:input
|
||||||
|
const { prop = 'value', event = 'input' } = (type as any).model || {}
|
||||||
|
props[prop] = props.modelValue
|
||||||
|
delete props.modelValue
|
||||||
|
// important: update dynamic props
|
||||||
|
if (dynamicProps) {
|
||||||
|
dynamicProps[dynamicProps.indexOf('modelValue')] = prop
|
||||||
|
}
|
||||||
|
props[compatModelEventPrefix + event] = props['onUpdate:modelValue']
|
||||||
|
delete props['onUpdate:modelValue']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compatModelEmit(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
event: string,
|
||||||
|
args: any[]
|
||||||
|
) {
|
||||||
|
if (!isCompatEnabled(DeprecationTypes.COMPONENT_V_MODEL, instance)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const props = instance.vnode.props
|
||||||
|
const modelHandler = props && props[compatModelEventPrefix + event]
|
||||||
|
if (modelHandler) {
|
||||||
|
callWithErrorHandling(
|
||||||
|
modelHandler,
|
||||||
|
instance,
|
||||||
|
ErrorCodes.COMPONENT_EVENT_HANDLER,
|
||||||
|
args
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -47,13 +47,16 @@ import {
|
|||||||
NO,
|
NO,
|
||||||
makeMap,
|
makeMap,
|
||||||
isPromise,
|
isPromise,
|
||||||
ShapeFlags
|
ShapeFlags,
|
||||||
|
extend
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { SuspenseBoundary } from './components/Suspense'
|
import { SuspenseBoundary } from './components/Suspense'
|
||||||
import { CompilerOptions } from '@vue/compiler-core'
|
import { CompilerOptions } from '@vue/compiler-core'
|
||||||
import { markAttrsAccessed } from './componentRenderUtils'
|
import { markAttrsAccessed } from './componentRenderUtils'
|
||||||
import { currentRenderingInstance } from './componentRenderContext'
|
import { currentRenderingInstance } from './componentRenderContext'
|
||||||
import { startMeasure, endMeasure } from './profiling'
|
import { startMeasure, endMeasure } from './profiling'
|
||||||
|
import { convertLegacyRenderFn } from './compat/renderFn'
|
||||||
|
import { globalCompatConfig, validateCompatConfig } from './compat/compatConfig'
|
||||||
|
|
||||||
export type Data = Record<string, unknown>
|
export type Data = Record<string, unknown>
|
||||||
|
|
||||||
@ -93,6 +96,10 @@ export interface ComponentInternalOptions {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
__hmrId?: string
|
__hmrId?: string
|
||||||
|
/**
|
||||||
|
* Compat build only, for bailing out of certain compatibility behavior
|
||||||
|
*/
|
||||||
|
__isBuiltIn?: boolean
|
||||||
/**
|
/**
|
||||||
* This one should be exposed so that devtools can make use of it
|
* This one should be exposed so that devtools can make use of it
|
||||||
*/
|
*/
|
||||||
@ -185,6 +192,10 @@ export type InternalRenderFunction = {
|
|||||||
$options: ComponentInternalInstance['ctx']
|
$options: ComponentInternalInstance['ctx']
|
||||||
): VNodeChild
|
): VNodeChild
|
||||||
_rc?: boolean // isRuntimeCompiled
|
_rc?: boolean // isRuntimeCompiled
|
||||||
|
|
||||||
|
// __COMPAT__ only
|
||||||
|
_compatChecked?: boolean // v3 and already checked for v2 compat
|
||||||
|
_compatWrapped?: boolean // is wrapped for v2 compat
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -257,6 +268,11 @@ export interface ComponentInternalInstance {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
directives: Record<string, Directive> | null
|
directives: Record<string, Directive> | null
|
||||||
|
/**
|
||||||
|
* Resolved filters registry, v2 compat only
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
filters?: Record<string, Function>
|
||||||
/**
|
/**
|
||||||
* resolved props options
|
* resolved props options
|
||||||
* @internal
|
* @internal
|
||||||
@ -562,6 +578,13 @@ function setupStatefulComponent(
|
|||||||
validateDirectiveName(names[i])
|
validateDirectiveName(names[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Component.compilerOptions && isRuntimeOnly()) {
|
||||||
|
warn(
|
||||||
|
`"compilerOptions" is only supported when using a build of Vue that ` +
|
||||||
|
`includes the runtime compiler. Since you are using a runtime-only ` +
|
||||||
|
`build, the options should be passed via your build tool config instead.`
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 0. create render proxy property access cache
|
// 0. create render proxy property access cache
|
||||||
instance.accessCache = Object.create(null)
|
instance.accessCache = Object.create(null)
|
||||||
@ -674,12 +697,21 @@ export function registerRuntimeCompiler(_compile: any) {
|
|||||||
compile = _compile
|
compile = _compile
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishComponentSetup(
|
export function finishComponentSetup(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
isSSR: boolean
|
isSSR: boolean,
|
||||||
|
skipOptions?: boolean
|
||||||
) {
|
) {
|
||||||
const Component = instance.type as ComponentOptions
|
const Component = instance.type as ComponentOptions
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
convertLegacyRenderFn(instance)
|
||||||
|
|
||||||
|
if (__DEV__ && Component.compatConfig) {
|
||||||
|
validateCompatConfig(Component.compatConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// template / render function normalization
|
// template / render function normalization
|
||||||
if (__NODE_JS__ && isSSR) {
|
if (__NODE_JS__ && isSSR) {
|
||||||
// 1. the render function may already exist, returned by `setup`
|
// 1. the render function may already exist, returned by `setup`
|
||||||
@ -692,16 +724,42 @@ function finishComponentSetup(
|
|||||||
NOOP) as InternalRenderFunction
|
NOOP) as InternalRenderFunction
|
||||||
} else if (!instance.render) {
|
} else if (!instance.render) {
|
||||||
// could be set from setup()
|
// could be set from setup()
|
||||||
if (compile && Component.template && !Component.render) {
|
if (compile && !Component.render) {
|
||||||
if (__DEV__) {
|
const template =
|
||||||
startMeasure(instance, `compile`)
|
(__COMPAT__ &&
|
||||||
}
|
instance.vnode.props &&
|
||||||
Component.render = compile(Component.template, {
|
instance.vnode.props['inline-template']) ||
|
||||||
isCustomElement: instance.appContext.config.isCustomElement,
|
Component.template
|
||||||
delimiters: Component.delimiters
|
if (template) {
|
||||||
})
|
if (__DEV__) {
|
||||||
if (__DEV__) {
|
startMeasure(instance, `compile`)
|
||||||
endMeasure(instance, `compile`)
|
}
|
||||||
|
const { isCustomElement, compilerOptions } = instance.appContext.config
|
||||||
|
const {
|
||||||
|
delimiters,
|
||||||
|
compilerOptions: componentCompilerOptions
|
||||||
|
} = Component
|
||||||
|
const finalCompilerOptions: CompilerOptions = extend(
|
||||||
|
extend(
|
||||||
|
{
|
||||||
|
isCustomElement,
|
||||||
|
delimiters
|
||||||
|
},
|
||||||
|
compilerOptions
|
||||||
|
),
|
||||||
|
componentCompilerOptions
|
||||||
|
)
|
||||||
|
if (__COMPAT__) {
|
||||||
|
// pass runtime compat config into the compiler
|
||||||
|
finalCompilerOptions.compatConfig = Object.create(globalCompatConfig)
|
||||||
|
if (Component.compatConfig) {
|
||||||
|
extend(finalCompilerOptions.compatConfig, Component.compatConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component.render = compile(template, finalCompilerOptions)
|
||||||
|
if (__DEV__) {
|
||||||
|
endMeasure(instance, `compile`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -719,7 +777,7 @@ function finishComponentSetup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// support for 2.x options
|
// support for 2.x options
|
||||||
if (__FEATURE_OPTIONS_API__) {
|
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
|
||||||
currentInstance = instance
|
currentInstance = instance
|
||||||
pauseTracking()
|
pauseTracking()
|
||||||
applyOptions(instance, Component)
|
applyOptions(instance, Component)
|
||||||
|
@ -21,6 +21,8 @@ import { warn } from './warning'
|
|||||||
import { UnionToIntersection } from './helpers/typeUtils'
|
import { UnionToIntersection } from './helpers/typeUtils'
|
||||||
import { devtoolsComponentEmit } from './devtools'
|
import { devtoolsComponentEmit } from './devtools'
|
||||||
import { AppContext } from './apiCreateApp'
|
import { AppContext } from './apiCreateApp'
|
||||||
|
import { emit as compatInstanceEmit } from './compat/instanceEventEmitter'
|
||||||
|
import { compatModelEventPrefix, compatModelEmit } from './compat/vModel'
|
||||||
|
|
||||||
export type ObjectEmitsOptions = Record<
|
export type ObjectEmitsOptions = Record<
|
||||||
string,
|
string,
|
||||||
@ -56,7 +58,14 @@ export function emit(
|
|||||||
propsOptions: [propsOptions]
|
propsOptions: [propsOptions]
|
||||||
} = instance
|
} = instance
|
||||||
if (emitsOptions) {
|
if (emitsOptions) {
|
||||||
if (!(event in emitsOptions)) {
|
if (
|
||||||
|
!(event in emitsOptions) &&
|
||||||
|
!(
|
||||||
|
__COMPAT__ &&
|
||||||
|
(event.startsWith('hook:') ||
|
||||||
|
event.startsWith(compatModelEventPrefix))
|
||||||
|
)
|
||||||
|
) {
|
||||||
if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {
|
if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {
|
||||||
warn(
|
warn(
|
||||||
`Component emitted event "${event}" but it is neither declared in ` +
|
`Component emitted event "${event}" but it is neither declared in ` +
|
||||||
@ -148,6 +157,11 @@ export function emit(
|
|||||||
args
|
args
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
compatModelEmit(instance, event, args)
|
||||||
|
return compatInstanceEmit(instance, event, args)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeEmitsOptions(
|
export function normalizeEmitsOptions(
|
||||||
@ -205,6 +219,11 @@ export function isEmitListener(
|
|||||||
if (!options || !isOn(key)) {
|
if (!options || !isOn(key)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__COMPAT__ && key.startsWith(compatModelEventPrefix)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
key = key.slice(2).replace(/Once$/, '')
|
key = key.slice(2).replace(/Once$/, '')
|
||||||
return (
|
return (
|
||||||
hasOwn(options, key[0].toLowerCase() + key.slice(1)) ||
|
hasOwn(options, key[0].toLowerCase() + key.slice(1)) ||
|
||||||
|
@ -65,6 +65,19 @@ import { warn } from './warning'
|
|||||||
import { VNodeChild } from './vnode'
|
import { VNodeChild } from './vnode'
|
||||||
import { callWithAsyncErrorHandling } from './errorHandling'
|
import { callWithAsyncErrorHandling } from './errorHandling'
|
||||||
import { UnionToIntersection } from './helpers/typeUtils'
|
import { UnionToIntersection } from './helpers/typeUtils'
|
||||||
|
import { deepMergeData } from './compat/data'
|
||||||
|
import { DeprecationTypes } from './compat/compatConfig'
|
||||||
|
import {
|
||||||
|
CompatConfig,
|
||||||
|
isCompatEnabled,
|
||||||
|
softAssertCompatEnabled
|
||||||
|
} from './compat/compatConfig'
|
||||||
|
import {
|
||||||
|
AssetTypes,
|
||||||
|
COMPONENTS,
|
||||||
|
DIRECTIVES,
|
||||||
|
FILTERS
|
||||||
|
} from './helpers/resolveAssets'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for declaring custom options.
|
* Interface for declaring custom options.
|
||||||
@ -137,6 +150,9 @@ export interface ComponentOptionsBase<
|
|||||||
expose?: string[]
|
expose?: string[]
|
||||||
serverPrefetch?(): Promise<any>
|
serverPrefetch?(): Promise<any>
|
||||||
|
|
||||||
|
// Runtime compiler only -----------------------------------------------------
|
||||||
|
compilerOptions?: RuntimeCompilerOptions
|
||||||
|
|
||||||
// Internal ------------------------------------------------------------------
|
// Internal ------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -190,6 +206,16 @@ export interface ComponentOptionsBase<
|
|||||||
__defaults?: Defaults
|
__defaults?: Defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subset of compiler options that makes sense for the runtime.
|
||||||
|
*/
|
||||||
|
export interface RuntimeCompilerOptions {
|
||||||
|
isCustomElement?: (tag: string) => boolean
|
||||||
|
whitespace?: 'preserve' | 'condense'
|
||||||
|
comments?: boolean
|
||||||
|
delimiters?: [string, string]
|
||||||
|
}
|
||||||
|
|
||||||
export type ComponentOptionsWithoutProps<
|
export type ComponentOptionsWithoutProps<
|
||||||
Props = {},
|
Props = {},
|
||||||
RawBindings = {},
|
RawBindings = {},
|
||||||
@ -347,10 +373,11 @@ export type ExtractComputedReturns<T extends any> = {
|
|||||||
: T[key] extends (...args: any[]) => infer TReturn ? TReturn : never
|
: T[key] extends (...args: any[]) => infer TReturn ? TReturn : never
|
||||||
}
|
}
|
||||||
|
|
||||||
type WatchOptionItem =
|
export type ObjectWatchOptionItem = {
|
||||||
| string
|
handler: WatchCallback | string
|
||||||
| WatchCallback
|
} & WatchOptions
|
||||||
| { handler: WatchCallback | string } & WatchOptions
|
|
||||||
|
type WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem
|
||||||
|
|
||||||
type ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[]
|
type ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[]
|
||||||
|
|
||||||
@ -371,6 +398,8 @@ interface LegacyOptions<
|
|||||||
Mixin extends ComponentOptionsMixin,
|
Mixin extends ComponentOptionsMixin,
|
||||||
Extends extends ComponentOptionsMixin
|
Extends extends ComponentOptionsMixin
|
||||||
> {
|
> {
|
||||||
|
compatConfig?: CompatConfig
|
||||||
|
|
||||||
// allow any custom options
|
// allow any custom options
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
|
|
||||||
@ -404,6 +433,9 @@ interface LegacyOptions<
|
|||||||
provide?: Data | Function
|
provide?: Data | Function
|
||||||
inject?: ComponentInjectOptions
|
inject?: ComponentInjectOptions
|
||||||
|
|
||||||
|
// assets
|
||||||
|
filters?: Record<string, Function>
|
||||||
|
|
||||||
// composition
|
// composition
|
||||||
mixins?: Mixin[]
|
mixins?: Mixin[]
|
||||||
extends?: Extends
|
extends?: Extends
|
||||||
@ -427,7 +459,10 @@ interface LegacyOptions<
|
|||||||
renderTriggered?: DebuggerHook
|
renderTriggered?: DebuggerHook
|
||||||
errorCaptured?: ErrorCapturedHook
|
errorCaptured?: ErrorCapturedHook
|
||||||
|
|
||||||
// runtime compile only
|
/**
|
||||||
|
* runtime compile only
|
||||||
|
* @deprecated use `compilerOptions.delimiters` instead.
|
||||||
|
*/
|
||||||
delimiters?: [string, string]
|
delimiters?: [string, string]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -490,6 +525,10 @@ export function applyOptions(
|
|||||||
deferredProvide: (Data | Function)[] = [],
|
deferredProvide: (Data | Function)[] = [],
|
||||||
asMixin: boolean = false
|
asMixin: boolean = false
|
||||||
) {
|
) {
|
||||||
|
if (__COMPAT__ && isFunction(options)) {
|
||||||
|
options = options.options
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
// composition
|
// composition
|
||||||
mixins,
|
mixins,
|
||||||
@ -501,9 +540,6 @@ export function applyOptions(
|
|||||||
watch: watchOptions,
|
watch: watchOptions,
|
||||||
provide: provideOptions,
|
provide: provideOptions,
|
||||||
inject: injectOptions,
|
inject: injectOptions,
|
||||||
// assets
|
|
||||||
components,
|
|
||||||
directives,
|
|
||||||
// lifecycle
|
// lifecycle
|
||||||
beforeMount,
|
beforeMount,
|
||||||
mounted,
|
mounted,
|
||||||
@ -588,31 +624,7 @@ export function applyOptions(
|
|||||||
// - watch (deferred since it relies on `this` access)
|
// - watch (deferred since it relies on `this` access)
|
||||||
|
|
||||||
if (injectOptions) {
|
if (injectOptions) {
|
||||||
if (isArray(injectOptions)) {
|
resolveInjections(injectOptions, ctx, checkDuplicateProperties)
|
||||||
for (let i = 0; i < injectOptions.length; i++) {
|
|
||||||
const key = injectOptions[i]
|
|
||||||
ctx[key] = inject(key)
|
|
||||||
if (__DEV__) {
|
|
||||||
checkDuplicateProperties!(OptionTypes.INJECT, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const key in injectOptions) {
|
|
||||||
const opt = injectOptions[key]
|
|
||||||
if (isObject(opt)) {
|
|
||||||
ctx[key] = inject(
|
|
||||||
opt.from || key,
|
|
||||||
opt.default,
|
|
||||||
true /* treat default function as factory */
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ctx[key] = inject(opt)
|
|
||||||
}
|
|
||||||
if (__DEV__) {
|
|
||||||
checkDuplicateProperties!(OptionTypes.INJECT, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (methods) {
|
if (methods) {
|
||||||
@ -736,25 +748,10 @@ export function applyOptions(
|
|||||||
// To reduce memory usage, only components with mixins or extends will have
|
// To reduce memory usage, only components with mixins or extends will have
|
||||||
// resolved asset registry attached to instance.
|
// resolved asset registry attached to instance.
|
||||||
if (asMixin) {
|
if (asMixin) {
|
||||||
if (components) {
|
resolveInstanceAssets(instance, options, COMPONENTS)
|
||||||
extend(
|
resolveInstanceAssets(instance, options, DIRECTIVES)
|
||||||
instance.components ||
|
if (__COMPAT__ && isCompatEnabled(DeprecationTypes.FILTERS, instance)) {
|
||||||
(instance.components = extend(
|
resolveInstanceAssets(instance, options, FILTERS)
|
||||||
{},
|
|
||||||
(instance.type as ComponentOptions).components
|
|
||||||
) as Record<string, ConcreteComponent>),
|
|
||||||
components
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (directives) {
|
|
||||||
extend(
|
|
||||||
instance.directives ||
|
|
||||||
(instance.directives = extend(
|
|
||||||
{},
|
|
||||||
(instance.type as ComponentOptions).directives
|
|
||||||
)),
|
|
||||||
directives
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -795,19 +792,28 @@ export function applyOptions(
|
|||||||
if (renderTriggered) {
|
if (renderTriggered) {
|
||||||
onRenderTriggered(renderTriggered.bind(publicThis))
|
onRenderTriggered(renderTriggered.bind(publicThis))
|
||||||
}
|
}
|
||||||
if (__DEV__ && beforeDestroy) {
|
|
||||||
warn(`\`beforeDestroy\` has been renamed to \`beforeUnmount\`.`)
|
|
||||||
}
|
|
||||||
if (beforeUnmount) {
|
if (beforeUnmount) {
|
||||||
onBeforeUnmount(beforeUnmount.bind(publicThis))
|
onBeforeUnmount(beforeUnmount.bind(publicThis))
|
||||||
}
|
}
|
||||||
if (__DEV__ && destroyed) {
|
|
||||||
warn(`\`destroyed\` has been renamed to \`unmounted\`.`)
|
|
||||||
}
|
|
||||||
if (unmounted) {
|
if (unmounted) {
|
||||||
onUnmounted(unmounted.bind(publicThis))
|
onUnmounted(unmounted.bind(publicThis))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
if (
|
||||||
|
beforeDestroy &&
|
||||||
|
softAssertCompatEnabled(DeprecationTypes.OPTIONS_BEFORE_DESTROY, instance)
|
||||||
|
) {
|
||||||
|
onBeforeUnmount(beforeDestroy.bind(publicThis))
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
destroyed &&
|
||||||
|
softAssertCompatEnabled(DeprecationTypes.OPTIONS_DESTROYED, instance)
|
||||||
|
) {
|
||||||
|
onUnmounted(destroyed.bind(publicThis))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isArray(expose)) {
|
if (isArray(expose)) {
|
||||||
if (!asMixin) {
|
if (!asMixin) {
|
||||||
if (expose.length) {
|
if (expose.length) {
|
||||||
@ -824,6 +830,55 @@ export function applyOptions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveInstanceAssets(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
mixin: ComponentOptions,
|
||||||
|
type: AssetTypes
|
||||||
|
) {
|
||||||
|
if (mixin[type]) {
|
||||||
|
extend(
|
||||||
|
instance[type] ||
|
||||||
|
(instance[type] = extend(
|
||||||
|
{},
|
||||||
|
(instance.type as ComponentOptions)[type]
|
||||||
|
) as any),
|
||||||
|
mixin[type]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveInjections(
|
||||||
|
injectOptions: ComponentInjectOptions,
|
||||||
|
ctx: any,
|
||||||
|
checkDuplicateProperties = NOOP as any
|
||||||
|
) {
|
||||||
|
if (isArray(injectOptions)) {
|
||||||
|
for (let i = 0; i < injectOptions.length; i++) {
|
||||||
|
const key = injectOptions[i]
|
||||||
|
ctx[key] = inject(key)
|
||||||
|
if (__DEV__) {
|
||||||
|
checkDuplicateProperties!(OptionTypes.INJECT, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const key in injectOptions) {
|
||||||
|
const opt = injectOptions[key]
|
||||||
|
if (isObject(opt)) {
|
||||||
|
ctx[key] = inject(
|
||||||
|
opt.from || key,
|
||||||
|
opt.default,
|
||||||
|
true /* treat default function as factory */
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ctx[key] = inject(opt)
|
||||||
|
}
|
||||||
|
if (__DEV__) {
|
||||||
|
checkDuplicateProperties!(OptionTypes.INJECT, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function callSyncHook(
|
function callSyncHook(
|
||||||
name: 'beforeCreate' | 'created',
|
name: 'beforeCreate' | 'created',
|
||||||
type: LifecycleHooks,
|
type: LifecycleHooks,
|
||||||
@ -854,7 +909,13 @@ function callHookWithMixinAndExtends(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (selfHook) {
|
if (selfHook) {
|
||||||
callWithAsyncErrorHandling(selfHook.bind(instance.proxy!), instance, type)
|
callWithAsyncErrorHandling(
|
||||||
|
__COMPAT__ && isArray(selfHook)
|
||||||
|
? selfHook.map(h => h.bind(instance.proxy!))
|
||||||
|
: selfHook.bind(instance.proxy!),
|
||||||
|
instance,
|
||||||
|
type
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -904,11 +965,18 @@ function resolveData(
|
|||||||
instance.data = reactive(data)
|
instance.data = reactive(data)
|
||||||
} else {
|
} else {
|
||||||
// existing data: this is a mixin or extends.
|
// existing data: this is a mixin or extends.
|
||||||
extend(instance.data, data)
|
if (
|
||||||
|
__COMPAT__ &&
|
||||||
|
isCompatEnabled(DeprecationTypes.OPTIONS_DATA_MERGE, instance)
|
||||||
|
) {
|
||||||
|
deepMergeData(instance.data, data, instance)
|
||||||
|
} else {
|
||||||
|
extend(instance.data, data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createWatcher(
|
export function createWatcher(
|
||||||
raw: ComponentWatchOptionItem,
|
raw: ComponentWatchOptionItem,
|
||||||
ctx: Data,
|
ctx: Data,
|
||||||
publicThis: ComponentPublicInstance,
|
publicThis: ComponentPublicInstance,
|
||||||
@ -958,19 +1026,30 @@ export function resolveMergedOptions(
|
|||||||
return (raw.__merged = options)
|
return (raw.__merged = options)
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeOptions(to: any, from: any, instance: ComponentInternalInstance) {
|
export function mergeOptions(
|
||||||
const strats = instance.appContext.config.optionMergeStrategies
|
to: any,
|
||||||
|
from: any,
|
||||||
|
instance?: ComponentInternalInstance | null,
|
||||||
|
strats = instance && instance.appContext.config.optionMergeStrategies
|
||||||
|
) {
|
||||||
|
if (__COMPAT__ && isFunction(from)) {
|
||||||
|
from = from.options
|
||||||
|
}
|
||||||
|
|
||||||
const { mixins, extends: extendsOptions } = from
|
const { mixins, extends: extendsOptions } = from
|
||||||
|
|
||||||
extendsOptions && mergeOptions(to, extendsOptions, instance)
|
extendsOptions && mergeOptions(to, extendsOptions, instance, strats)
|
||||||
mixins &&
|
mixins &&
|
||||||
mixins.forEach((m: ComponentOptionsMixin) => mergeOptions(to, m, instance))
|
mixins.forEach((m: ComponentOptionsMixin) =>
|
||||||
|
mergeOptions(to, m, instance, strats)
|
||||||
|
)
|
||||||
|
|
||||||
for (const key in from) {
|
for (const key in from) {
|
||||||
if (strats && hasOwn(strats, key)) {
|
if (strats && hasOwn(strats, key)) {
|
||||||
to[key] = strats[key](to[key], from[key], instance.proxy, key)
|
to[key] = strats[key](to[key], from[key], instance && instance.proxy, key)
|
||||||
} else {
|
} else {
|
||||||
to[key] = from[key]
|
to[key] = from[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return to
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,10 @@ import {
|
|||||||
import { isEmitListener } from './componentEmits'
|
import { isEmitListener } from './componentEmits'
|
||||||
import { InternalObjectKey } from './vnode'
|
import { InternalObjectKey } from './vnode'
|
||||||
import { AppContext } from './apiCreateApp'
|
import { AppContext } from './apiCreateApp'
|
||||||
|
import { createPropsDefaultThis } from './compat/props'
|
||||||
|
import { isCompatEnabled, softAssertCompatEnabled } from './compat/compatConfig'
|
||||||
|
import { DeprecationTypes } from './compat/compatConfig'
|
||||||
|
import { shouldSkipAttr } from './compat/attrsFallthrough'
|
||||||
|
|
||||||
export type ComponentPropsOptions<P = Data> =
|
export type ComponentPropsOptions<P = Data> =
|
||||||
| ComponentObjectPropsOptions<P>
|
| ComponentObjectPropsOptions<P>
|
||||||
@ -184,6 +188,7 @@ export function updateProps(
|
|||||||
} = instance
|
} = instance
|
||||||
const rawCurrentProps = toRaw(props)
|
const rawCurrentProps = toRaw(props)
|
||||||
const [options] = instance.propsOptions
|
const [options] = instance.propsOptions
|
||||||
|
let hasAttrsChanged = false
|
||||||
|
|
||||||
if (
|
if (
|
||||||
// always force full diff in dev
|
// always force full diff in dev
|
||||||
@ -209,7 +214,10 @@ export function updateProps(
|
|||||||
// attr / props separation was done on init and will be consistent
|
// attr / props separation was done on init and will be consistent
|
||||||
// in this code path, so just check if attrs have it.
|
// in this code path, so just check if attrs have it.
|
||||||
if (hasOwn(attrs, key)) {
|
if (hasOwn(attrs, key)) {
|
||||||
attrs[key] = value
|
if (value !== attrs[key]) {
|
||||||
|
attrs[key] = value
|
||||||
|
hasAttrsChanged = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const camelizedKey = camelize(key)
|
const camelizedKey = camelize(key)
|
||||||
props[camelizedKey] = resolvePropValue(
|
props[camelizedKey] = resolvePropValue(
|
||||||
@ -221,13 +229,21 @@ export function updateProps(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
attrs[key] = value
|
if (__COMPAT__ && shouldSkipAttr(key, attrs[key], instance)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (value !== attrs[key]) {
|
||||||
|
attrs[key] = value
|
||||||
|
hasAttrsChanged = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// full props update.
|
// full props update.
|
||||||
setFullProps(instance, rawProps, props, attrs)
|
if (setFullProps(instance, rawProps, props, attrs)) {
|
||||||
|
hasAttrsChanged = true
|
||||||
|
}
|
||||||
// in case of dynamic props, check if we need to delete keys from
|
// in case of dynamic props, check if we need to delete keys from
|
||||||
// the props object
|
// the props object
|
||||||
let kebabKey: string
|
let kebabKey: string
|
||||||
@ -267,13 +283,16 @@ export function updateProps(
|
|||||||
for (const key in attrs) {
|
for (const key in attrs) {
|
||||||
if (!rawProps || !hasOwn(rawProps, key)) {
|
if (!rawProps || !hasOwn(rawProps, key)) {
|
||||||
delete attrs[key]
|
delete attrs[key]
|
||||||
|
hasAttrsChanged = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// trigger updates for $attrs in case it's used in component slots
|
// trigger updates for $attrs in case it's used in component slots
|
||||||
trigger(instance, TriggerOpTypes.SET, '$attrs')
|
if (hasAttrsChanged) {
|
||||||
|
trigger(instance, TriggerOpTypes.SET, '$attrs')
|
||||||
|
}
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
validateProps(rawProps || {}, props, instance)
|
validateProps(rawProps || {}, props, instance)
|
||||||
@ -287,12 +306,27 @@ function setFullProps(
|
|||||||
attrs: Data
|
attrs: Data
|
||||||
) {
|
) {
|
||||||
const [options, needCastKeys] = instance.propsOptions
|
const [options, needCastKeys] = instance.propsOptions
|
||||||
|
let hasAttrsChanged = false
|
||||||
if (rawProps) {
|
if (rawProps) {
|
||||||
for (const key in rawProps) {
|
for (const key in rawProps) {
|
||||||
// key, ref are reserved and never passed down
|
// key, ref are reserved and never passed down
|
||||||
if (isReservedProp(key)) {
|
if (isReservedProp(key)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
if (key.startsWith('onHook:')) {
|
||||||
|
softAssertCompatEnabled(
|
||||||
|
DeprecationTypes.INSTANCE_EVENT_HOOKS,
|
||||||
|
instance,
|
||||||
|
key.slice(2).toLowerCase()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (key === 'inline-template') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const value = rawProps[key]
|
const value = rawProps[key]
|
||||||
// prop option names are camelized during normalization, so to support
|
// prop option names are camelized during normalization, so to support
|
||||||
// kebab -> camel conversion here we need to camelize the key.
|
// kebab -> camel conversion here we need to camelize the key.
|
||||||
@ -303,7 +337,13 @@ function setFullProps(
|
|||||||
// Any non-declared (either as a prop or an emitted event) props are put
|
// Any non-declared (either as a prop or an emitted event) props are put
|
||||||
// into a separate `attrs` object for spreading. Make sure to preserve
|
// into a separate `attrs` object for spreading. Make sure to preserve
|
||||||
// original key casing
|
// original key casing
|
||||||
attrs[key] = value
|
if (__COMPAT__ && shouldSkipAttr(key, attrs[key], instance)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (value !== attrs[key]) {
|
||||||
|
attrs[key] = value
|
||||||
|
hasAttrsChanged = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,6 +361,8 @@ function setFullProps(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return hasAttrsChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolvePropValue(
|
function resolvePropValue(
|
||||||
@ -342,7 +384,13 @@ function resolvePropValue(
|
|||||||
value = propsDefaults[key]
|
value = propsDefaults[key]
|
||||||
} else {
|
} else {
|
||||||
setCurrentInstance(instance)
|
setCurrentInstance(instance)
|
||||||
value = propsDefaults[key] = defaultValue(props)
|
value = propsDefaults[key] = defaultValue.call(
|
||||||
|
__COMPAT__ &&
|
||||||
|
isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
|
||||||
|
? createPropsDefaultThis(instance, props, key)
|
||||||
|
: null,
|
||||||
|
props
|
||||||
|
)
|
||||||
setCurrentInstance(null)
|
setCurrentInstance(null)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -381,6 +429,9 @@ export function normalizePropsOptions(
|
|||||||
let hasExtends = false
|
let hasExtends = false
|
||||||
if (__FEATURE_OPTIONS_API__ && !isFunction(comp)) {
|
if (__FEATURE_OPTIONS_API__ && !isFunction(comp)) {
|
||||||
const extendProps = (raw: ComponentOptions) => {
|
const extendProps = (raw: ComponentOptions) => {
|
||||||
|
if (__COMPAT__ && isFunction(raw)) {
|
||||||
|
raw = raw.options
|
||||||
|
}
|
||||||
hasExtends = true
|
hasExtends = true
|
||||||
const [props, keys] = normalizePropsOptions(raw, appContext, true)
|
const [props, keys] = normalizePropsOptions(raw, appContext, true)
|
||||||
extend(normalized, props)
|
extend(normalized, props)
|
||||||
|
@ -11,7 +11,8 @@ import {
|
|||||||
isGloballyWhitelisted,
|
isGloballyWhitelisted,
|
||||||
NOOP,
|
NOOP,
|
||||||
extend,
|
extend,
|
||||||
isString
|
isString,
|
||||||
|
isFunction
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
ReactiveEffect,
|
ReactiveEffect,
|
||||||
@ -40,6 +41,7 @@ import { markAttrsAccessed } from './componentRenderUtils'
|
|||||||
import { currentRenderingInstance } from './componentRenderContext'
|
import { currentRenderingInstance } from './componentRenderContext'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { UnionToIntersection } from './helpers/typeUtils'
|
import { UnionToIntersection } from './helpers/typeUtils'
|
||||||
|
import { installCompatInstanceProperties } from './compat/instance'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom properties added to component instances in any way and can be accessed through `this`
|
* Custom properties added to component instances in any way and can be accessed through `this`
|
||||||
@ -201,7 +203,10 @@ export type ComponentPublicInstance<
|
|||||||
M &
|
M &
|
||||||
ComponentCustomProperties
|
ComponentCustomProperties
|
||||||
|
|
||||||
type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>
|
export type PublicPropertiesMap = Record<
|
||||||
|
string,
|
||||||
|
(i: ComponentInternalInstance) => any
|
||||||
|
>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* #2437 In Vue 3, functional components do not have a public instance proxy but
|
* #2437 In Vue 3, functional components do not have a public instance proxy but
|
||||||
@ -233,6 +238,10 @@ const publicPropertiesMap: PublicPropertiesMap = extend(Object.create(null), {
|
|||||||
$watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
|
$watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
|
||||||
} as PublicPropertiesMap)
|
} as PublicPropertiesMap)
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
installCompatInstanceProperties(publicPropertiesMap)
|
||||||
|
}
|
||||||
|
|
||||||
const enum AccessTypes {
|
const enum AccessTypes {
|
||||||
SETUP,
|
SETUP,
|
||||||
DATA,
|
DATA,
|
||||||
@ -335,7 +344,17 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
|||||||
((globalProperties = appContext.config.globalProperties),
|
((globalProperties = appContext.config.globalProperties),
|
||||||
hasOwn(globalProperties, key))
|
hasOwn(globalProperties, key))
|
||||||
) {
|
) {
|
||||||
return globalProperties[key]
|
if (__COMPAT__) {
|
||||||
|
const desc = Object.getOwnPropertyDescriptor(globalProperties, key)!
|
||||||
|
if (desc.get) {
|
||||||
|
return desc.get.call(instance.proxy)
|
||||||
|
} else {
|
||||||
|
const val = globalProperties[key]
|
||||||
|
return isFunction(val) ? val.bind(instance.proxy) : val
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return globalProperties[key]
|
||||||
|
}
|
||||||
} else if (
|
} else if (
|
||||||
__DEV__ &&
|
__DEV__ &&
|
||||||
currentRenderingInstance &&
|
currentRenderingInstance &&
|
||||||
@ -483,17 +502,6 @@ export function createRenderContext(instance: ComponentInternalInstance) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// expose global properties
|
|
||||||
const { globalProperties } = instance.appContext.config
|
|
||||||
Object.keys(globalProperties).forEach(key => {
|
|
||||||
Object.defineProperty(target, key, {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: false,
|
|
||||||
get: () => globalProperties[key],
|
|
||||||
set: NOOP
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return target as ComponentRenderContext
|
return target as ComponentRenderContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,10 @@ export function setCurrentRenderingInstance(
|
|||||||
const prev = currentRenderingInstance
|
const prev = currentRenderingInstance
|
||||||
currentRenderingInstance = instance
|
currentRenderingInstance = instance
|
||||||
currentScopeId = (instance && instance.type.__scopeId) || null
|
currentScopeId = (instance && instance.type.__scopeId) || null
|
||||||
|
// v2 pre-compiled components uses _scopeId instead of __scopeId
|
||||||
|
if (__COMPAT__ && !currentScopeId) {
|
||||||
|
currentScopeId = (instance && (instance.type as any)._scopeId) || null
|
||||||
|
}
|
||||||
return prev
|
return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
FunctionalComponent,
|
FunctionalComponent,
|
||||||
Data
|
Data,
|
||||||
|
getComponentName
|
||||||
} from './component'
|
} from './component'
|
||||||
import {
|
import {
|
||||||
VNode,
|
VNode,
|
||||||
@ -20,6 +21,11 @@ import { isHmrUpdating } from './hmr'
|
|||||||
import { NormalizedProps } from './componentProps'
|
import { NormalizedProps } from './componentProps'
|
||||||
import { isEmitListener } from './componentEmits'
|
import { isEmitListener } from './componentEmits'
|
||||||
import { setCurrentRenderingInstance } from './componentRenderContext'
|
import { setCurrentRenderingInstance } from './componentRenderContext'
|
||||||
|
import {
|
||||||
|
DeprecationTypes,
|
||||||
|
isCompatEnabled,
|
||||||
|
warnDeprecation
|
||||||
|
} from './compat/compatConfig'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dev only flag to track whether $attrs was used during render.
|
* dev only flag to track whether $attrs was used during render.
|
||||||
@ -117,7 +123,7 @@ export function renderComponentRoot(
|
|||||||
;[root, setRoot] = getChildRoot(result)
|
;[root, setRoot] = getChildRoot(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Component.inheritAttrs !== false && fallthroughAttrs) {
|
if (fallthroughAttrs && Component.inheritAttrs !== false) {
|
||||||
const keys = Object.keys(fallthroughAttrs)
|
const keys = Object.keys(fallthroughAttrs)
|
||||||
const { shapeFlag } = root
|
const { shapeFlag } = root
|
||||||
if (keys.length) {
|
if (keys.length) {
|
||||||
@ -175,6 +181,29 @@ export function renderComponentRoot(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
__COMPAT__ &&
|
||||||
|
isCompatEnabled(DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE, instance) &&
|
||||||
|
vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT &&
|
||||||
|
(root.shapeFlag & ShapeFlags.ELEMENT ||
|
||||||
|
root.shapeFlag & ShapeFlags.COMPONENT)
|
||||||
|
) {
|
||||||
|
const { class: cls, style } = vnode.props || {}
|
||||||
|
if (cls || style) {
|
||||||
|
if (__DEV__ && Component.inheritAttrs === false) {
|
||||||
|
warnDeprecation(
|
||||||
|
DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE,
|
||||||
|
instance,
|
||||||
|
getComponentName(instance.type)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
root = cloneVNode(root, {
|
||||||
|
class: cls,
|
||||||
|
style: style
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// inherit directives
|
// inherit directives
|
||||||
if (vnode.dirs) {
|
if (vnode.dirs) {
|
||||||
if (__DEV__ && !isElementRoot(root)) {
|
if (__DEV__ && !isElementRoot(root)) {
|
||||||
|
@ -19,6 +19,7 @@ import { warn } from './warning'
|
|||||||
import { isKeepAlive } from './components/KeepAlive'
|
import { isKeepAlive } from './components/KeepAlive'
|
||||||
import { withCtx } from './componentRenderContext'
|
import { withCtx } from './componentRenderContext'
|
||||||
import { isHmrUpdating } from './hmr'
|
import { isHmrUpdating } from './hmr'
|
||||||
|
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
|
||||||
|
|
||||||
export type Slot = (...args: any[]) => VNode[]
|
export type Slot = (...args: any[]) => VNode[]
|
||||||
|
|
||||||
@ -72,7 +73,11 @@ const normalizeSlot = (
|
|||||||
return normalizeSlotValue(rawSlot(props))
|
return normalizeSlotValue(rawSlot(props))
|
||||||
}, ctx) as Slot
|
}, ctx) as Slot
|
||||||
|
|
||||||
const normalizeObjectSlots = (rawSlots: RawSlots, slots: InternalSlots) => {
|
const normalizeObjectSlots = (
|
||||||
|
rawSlots: RawSlots,
|
||||||
|
slots: InternalSlots,
|
||||||
|
instance: ComponentInternalInstance
|
||||||
|
) => {
|
||||||
const ctx = rawSlots._ctx
|
const ctx = rawSlots._ctx
|
||||||
for (const key in rawSlots) {
|
for (const key in rawSlots) {
|
||||||
if (isInternalKey(key)) continue
|
if (isInternalKey(key)) continue
|
||||||
@ -80,7 +85,13 @@ const normalizeObjectSlots = (rawSlots: RawSlots, slots: InternalSlots) => {
|
|||||||
if (isFunction(value)) {
|
if (isFunction(value)) {
|
||||||
slots[key] = normalizeSlot(key, value, ctx)
|
slots[key] = normalizeSlot(key, value, ctx)
|
||||||
} else if (value != null) {
|
} else if (value != null) {
|
||||||
if (__DEV__) {
|
if (
|
||||||
|
__DEV__ &&
|
||||||
|
!(
|
||||||
|
__COMPAT__ &&
|
||||||
|
isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)
|
||||||
|
)
|
||||||
|
) {
|
||||||
warn(
|
warn(
|
||||||
`Non-function value encountered for slot "${key}". ` +
|
`Non-function value encountered for slot "${key}". ` +
|
||||||
`Prefer function slots for better performance.`
|
`Prefer function slots for better performance.`
|
||||||
@ -96,7 +107,11 @@ const normalizeVNodeSlots = (
|
|||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
children: VNodeNormalizedChildren
|
children: VNodeNormalizedChildren
|
||||||
) => {
|
) => {
|
||||||
if (__DEV__ && !isKeepAlive(instance.vnode)) {
|
if (
|
||||||
|
__DEV__ &&
|
||||||
|
!isKeepAlive(instance.vnode) &&
|
||||||
|
!(__COMPAT__ && isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance))
|
||||||
|
) {
|
||||||
warn(
|
warn(
|
||||||
`Non-function value encountered for default slot. ` +
|
`Non-function value encountered for default slot. ` +
|
||||||
`Prefer function slots for better performance.`
|
`Prefer function slots for better performance.`
|
||||||
@ -117,7 +132,11 @@ export const initSlots = (
|
|||||||
// make compiler marker non-enumerable
|
// make compiler marker non-enumerable
|
||||||
def(children as InternalSlots, '_', type)
|
def(children as InternalSlots, '_', type)
|
||||||
} else {
|
} else {
|
||||||
normalizeObjectSlots(children as RawSlots, (instance.slots = {}))
|
normalizeObjectSlots(
|
||||||
|
children as RawSlots,
|
||||||
|
(instance.slots = {}),
|
||||||
|
instance
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
instance.slots = {}
|
instance.slots = {}
|
||||||
@ -162,7 +181,7 @@ export const updateSlots = (
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
needDeletionCheck = !(children as RawSlots).$stable
|
needDeletionCheck = !(children as RawSlots).$stable
|
||||||
normalizeObjectSlots(children as RawSlots, slots)
|
normalizeObjectSlots(children as RawSlots, slots, instance)
|
||||||
}
|
}
|
||||||
deletionComparisonTarget = children as RawSlots
|
deletionComparisonTarget = children as RawSlots
|
||||||
} else if (children) {
|
} else if (children) {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
SetupContext,
|
SetupContext,
|
||||||
ComponentInternalInstance
|
ComponentInternalInstance,
|
||||||
|
ComponentOptions
|
||||||
} from '../component'
|
} from '../component'
|
||||||
import {
|
import {
|
||||||
cloneVNode,
|
cloneVNode,
|
||||||
@ -110,7 +111,7 @@ export function useTransitionState(): TransitionState {
|
|||||||
|
|
||||||
const TransitionHookValidator = [Function, Array]
|
const TransitionHookValidator = [Function, Array]
|
||||||
|
|
||||||
const BaseTransitionImpl = {
|
const BaseTransitionImpl: ComponentOptions = {
|
||||||
name: `BaseTransition`,
|
name: `BaseTransition`,
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@ -250,6 +251,10 @@ const BaseTransitionImpl = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
BaseTransitionImpl.__isBuiltIn = true
|
||||||
|
}
|
||||||
|
|
||||||
// export the public type for h/tsx inference
|
// export the public type for h/tsx inference
|
||||||
// also to avoid inline import() in generated d.ts files
|
// also to avoid inline import() in generated d.ts files
|
||||||
export const BaseTransition = (BaseTransitionImpl as any) as {
|
export const BaseTransition = (BaseTransitionImpl as any) as {
|
||||||
|
@ -5,7 +5,8 @@ import {
|
|||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
LifecycleHooks,
|
LifecycleHooks,
|
||||||
currentInstance,
|
currentInstance,
|
||||||
getComponentName
|
getComponentName,
|
||||||
|
ComponentOptions
|
||||||
} from '../component'
|
} from '../component'
|
||||||
import { VNode, cloneVNode, isVNode, VNodeProps } from '../vnode'
|
import { VNode, cloneVNode, isVNode, VNodeProps } from '../vnode'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
@ -63,7 +64,7 @@ export interface KeepAliveContext extends ComponentRenderContext {
|
|||||||
export const isKeepAlive = (vnode: VNode): boolean =>
|
export const isKeepAlive = (vnode: VNode): boolean =>
|
||||||
(vnode.type as any).__isKeepAlive
|
(vnode.type as any).__isKeepAlive
|
||||||
|
|
||||||
const KeepAliveImpl = {
|
const KeepAliveImpl: ComponentOptions = {
|
||||||
name: `KeepAlive`,
|
name: `KeepAlive`,
|
||||||
|
|
||||||
// Marker for special handling inside the renderer. We are not using a ===
|
// Marker for special handling inside the renderer. We are not using a ===
|
||||||
@ -313,6 +314,10 @@ const KeepAliveImpl = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
KeepAliveImpl.__isBuildIn = true
|
||||||
|
}
|
||||||
|
|
||||||
// export the public type for h/tsx inference
|
// export the public type for h/tsx inference
|
||||||
// also to avoid inline import() in generated d.ts files
|
// also to avoid inline import() in generated d.ts files
|
||||||
export const KeepAlive = (KeepAliveImpl as any) as {
|
export const KeepAlive = (KeepAliveImpl as any) as {
|
||||||
|
@ -18,6 +18,7 @@ import { ComponentInternalInstance, Data } from './component'
|
|||||||
import { currentRenderingInstance } from './componentRenderContext'
|
import { currentRenderingInstance } from './componentRenderContext'
|
||||||
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
|
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
|
||||||
import { ComponentPublicInstance } from './componentPublicInstance'
|
import { ComponentPublicInstance } from './componentPublicInstance'
|
||||||
|
import { mapCompatDirectiveHook } from './compat/customDirective'
|
||||||
|
|
||||||
export interface DirectiveBinding<V = any> {
|
export interface DirectiveBinding<V = any> {
|
||||||
instance: ComponentPublicInstance | null
|
instance: ComponentPublicInstance | null
|
||||||
@ -124,7 +125,10 @@ export function invokeDirectiveHook(
|
|||||||
if (oldBindings) {
|
if (oldBindings) {
|
||||||
binding.oldValue = oldBindings[i].value
|
binding.oldValue = oldBindings[i].value
|
||||||
}
|
}
|
||||||
const hook = binding.dir[name] as DirectiveHook | undefined
|
let hook = binding.dir[name] as DirectiveHook | DirectiveHook[] | undefined
|
||||||
|
if (__COMPAT__ && !hook) {
|
||||||
|
hook = mapCompatDirectiveHook(name, binding.dir, instance)
|
||||||
|
}
|
||||||
if (hook) {
|
if (hook) {
|
||||||
callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, [
|
callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, [
|
||||||
vnode.el,
|
vnode.el,
|
||||||
|
@ -10,8 +10,11 @@ import { camelize, capitalize, isString } from '@vue/shared'
|
|||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
import { VNodeTypes } from '../vnode'
|
import { VNodeTypes } from '../vnode'
|
||||||
|
|
||||||
const COMPONENTS = 'components'
|
export const COMPONENTS = 'components'
|
||||||
const DIRECTIVES = 'directives'
|
export const DIRECTIVES = 'directives'
|
||||||
|
export const FILTERS = 'filters'
|
||||||
|
|
||||||
|
export type AssetTypes = typeof COMPONENTS | typeof DIRECTIVES | typeof FILTERS
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@ -44,6 +47,14 @@ export function resolveDirective(name: string): Directive | undefined {
|
|||||||
return resolveAsset(DIRECTIVES, name)
|
return resolveAsset(DIRECTIVES, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v2 compat only
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function resolveFilter(name: string): Function | undefined {
|
||||||
|
return resolveAsset(FILTERS, name)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* overload 1: components
|
* overload 1: components
|
||||||
@ -60,8 +71,11 @@ function resolveAsset(
|
|||||||
name: string
|
name: string
|
||||||
): Directive | undefined
|
): Directive | undefined
|
||||||
// implementation
|
// implementation
|
||||||
|
// overload 3: filters (compat only)
|
||||||
|
function resolveAsset(type: typeof FILTERS, name: string): Function | undefined
|
||||||
|
// implementation
|
||||||
function resolveAsset(
|
function resolveAsset(
|
||||||
type: typeof COMPONENTS | typeof DIRECTIVES,
|
type: AssetTypes,
|
||||||
name: string,
|
name: string,
|
||||||
warnMissing = true,
|
warnMissing = true,
|
||||||
maybeSelfReference = false
|
maybeSelfReference = false
|
||||||
|
@ -179,7 +179,8 @@ export {
|
|||||||
ComponentOptionsBase,
|
ComponentOptionsBase,
|
||||||
RenderFunction,
|
RenderFunction,
|
||||||
MethodOptions,
|
MethodOptions,
|
||||||
ComputedOptions
|
ComputedOptions,
|
||||||
|
RuntimeCompilerOptions
|
||||||
} from './componentOptions'
|
} from './componentOptions'
|
||||||
export { EmitsOptions, ObjectEmitsOptions } from './componentEmits'
|
export { EmitsOptions, ObjectEmitsOptions } from './componentEmits'
|
||||||
export {
|
export {
|
||||||
@ -279,3 +280,38 @@ const _ssrUtils = {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export const ssrUtils = (__NODE_JS__ ? _ssrUtils : null) as typeof _ssrUtils
|
export const ssrUtils = (__NODE_JS__ ? _ssrUtils : null) as typeof _ssrUtils
|
||||||
|
|
||||||
|
// 2.x COMPAT ------------------------------------------------------------------
|
||||||
|
|
||||||
|
export { DeprecationTypes } from './compat/compatConfig'
|
||||||
|
export { CompatVue } from './compat/global'
|
||||||
|
export { LegacyConfig } from './compat/globalConfig'
|
||||||
|
|
||||||
|
import { warnDeprecation } from './compat/compatConfig'
|
||||||
|
import { createCompatVue } from './compat/global'
|
||||||
|
import {
|
||||||
|
isCompatEnabled,
|
||||||
|
checkCompatEnabled,
|
||||||
|
softAssertCompatEnabled
|
||||||
|
} from './compat/compatConfig'
|
||||||
|
import { resolveFilter as _resolveFilter } from './helpers/resolveAssets'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal only exposed in compat builds
|
||||||
|
*/
|
||||||
|
export const resolveFilter = __COMPAT__ ? _resolveFilter : null
|
||||||
|
|
||||||
|
const _compatUtils = {
|
||||||
|
warnDeprecation,
|
||||||
|
createCompatVue,
|
||||||
|
isCompatEnabled,
|
||||||
|
checkCompatEnabled,
|
||||||
|
softAssertCompatEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal only exposed in compat builds.
|
||||||
|
*/
|
||||||
|
export const compatUtils = (__COMPAT__
|
||||||
|
? _compatUtils
|
||||||
|
: null) as typeof _compatUtils
|
||||||
|
@ -76,7 +76,6 @@ import {
|
|||||||
import { createHydrationFunctions, RootHydrateFunction } from './hydration'
|
import { createHydrationFunctions, RootHydrateFunction } from './hydration'
|
||||||
import { invokeDirectiveHook } from './directives'
|
import { invokeDirectiveHook } from './directives'
|
||||||
import { startMeasure, endMeasure } from './profiling'
|
import { startMeasure, endMeasure } from './profiling'
|
||||||
import { ComponentPublicInstance } from './componentPublicInstance'
|
|
||||||
import {
|
import {
|
||||||
devtoolsComponentAdded,
|
devtoolsComponentAdded,
|
||||||
devtoolsComponentRemoved,
|
devtoolsComponentRemoved,
|
||||||
@ -85,6 +84,9 @@ import {
|
|||||||
} from './devtools'
|
} from './devtools'
|
||||||
import { initFeatureFlags } from './featureFlags'
|
import { initFeatureFlags } from './featureFlags'
|
||||||
import { isAsyncWrapper } from './apiAsyncComponent'
|
import { isAsyncWrapper } from './apiAsyncComponent'
|
||||||
|
import { isCompatEnabled } from './compat/compatConfig'
|
||||||
|
import { DeprecationTypes } from './compat/compatConfig'
|
||||||
|
import { registerLegacyRef } from './compat/ref'
|
||||||
|
|
||||||
export interface Renderer<HostElement = RendererElement> {
|
export interface Renderer<HostElement = RendererElement> {
|
||||||
render: RootRenderFunction<HostElement>
|
render: RootRenderFunction<HostElement>
|
||||||
@ -307,7 +309,8 @@ export const setRef = (
|
|||||||
rawRef: VNodeNormalizedRef,
|
rawRef: VNodeNormalizedRef,
|
||||||
oldRawRef: VNodeNormalizedRef | null,
|
oldRawRef: VNodeNormalizedRef | null,
|
||||||
parentSuspense: SuspenseBoundary | null,
|
parentSuspense: SuspenseBoundary | null,
|
||||||
vnode: VNode | null
|
vnode: VNode,
|
||||||
|
isUnmount = false
|
||||||
) => {
|
) => {
|
||||||
if (isArray(rawRef)) {
|
if (isArray(rawRef)) {
|
||||||
rawRef.forEach((r, i) =>
|
rawRef.forEach((r, i) =>
|
||||||
@ -315,26 +318,25 @@ export const setRef = (
|
|||||||
r,
|
r,
|
||||||
oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),
|
oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),
|
||||||
parentSuspense,
|
parentSuspense,
|
||||||
vnode
|
vnode,
|
||||||
|
isUnmount
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let value: ComponentPublicInstance | RendererNode | Record<string, any> | null
|
if (isAsyncWrapper(vnode) && !isUnmount) {
|
||||||
if (!vnode) {
|
|
||||||
// means unmount
|
|
||||||
value = null
|
|
||||||
} else if (isAsyncWrapper(vnode)) {
|
|
||||||
// when mounting async components, nothing needs to be done,
|
// when mounting async components, nothing needs to be done,
|
||||||
// because the template ref is forwarded to inner component
|
// because the template ref is forwarded to inner component
|
||||||
return
|
return
|
||||||
} else if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
|
|
||||||
value = vnode.component!.exposed || vnode.component!.proxy
|
|
||||||
} else {
|
|
||||||
value = vnode.el
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const refValue =
|
||||||
|
vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
|
||||||
|
? vnode.component!.exposed || vnode.component!.proxy
|
||||||
|
: vnode.el
|
||||||
|
const value = isUnmount ? null : refValue
|
||||||
|
|
||||||
const { i: owner, r: ref } = rawRef
|
const { i: owner, r: ref } = rawRef
|
||||||
if (__DEV__ && !owner) {
|
if (__DEV__ && !owner) {
|
||||||
warn(
|
warn(
|
||||||
@ -347,7 +349,7 @@ export const setRef = (
|
|||||||
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
|
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
|
||||||
const setupState = owner.setupState
|
const setupState = owner.setupState
|
||||||
|
|
||||||
// unset old ref
|
// dynamic ref changed. unset old ref
|
||||||
if (oldRef != null && oldRef !== ref) {
|
if (oldRef != null && oldRef !== ref) {
|
||||||
if (isString(oldRef)) {
|
if (isString(oldRef)) {
|
||||||
refs[oldRef] = null
|
refs[oldRef] = null
|
||||||
@ -361,7 +363,11 @@ export const setRef = (
|
|||||||
|
|
||||||
if (isString(ref)) {
|
if (isString(ref)) {
|
||||||
const doSet = () => {
|
const doSet = () => {
|
||||||
refs[ref] = value
|
if (__COMPAT__ && isCompatEnabled(DeprecationTypes.V_FOR_REF, owner)) {
|
||||||
|
registerLegacyRef(refs, ref, refValue, owner, rawRef.f, isUnmount)
|
||||||
|
} else {
|
||||||
|
refs[ref] = value
|
||||||
|
}
|
||||||
if (hasOwn(setupState, ref)) {
|
if (hasOwn(setupState, ref)) {
|
||||||
setupState[ref] = value
|
setupState[ref] = value
|
||||||
}
|
}
|
||||||
@ -440,6 +446,9 @@ function baseCreateRenderer(
|
|||||||
options: RendererOptions,
|
options: RendererOptions,
|
||||||
createHydrationFns?: typeof createHydrationFunctions
|
createHydrationFns?: typeof createHydrationFunctions
|
||||||
): any {
|
): any {
|
||||||
|
const isHookEventCompatEnabled =
|
||||||
|
__COMPAT__ && isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, null)
|
||||||
|
|
||||||
// compile-time feature flags check
|
// compile-time feature flags check
|
||||||
if (__ESM_BUNDLER__ && !__TEST__) {
|
if (__ESM_BUNDLER__ && !__TEST__) {
|
||||||
initFeatureFlags()
|
initFeatureFlags()
|
||||||
@ -579,7 +588,7 @@ function baseCreateRenderer(
|
|||||||
|
|
||||||
// set ref
|
// set ref
|
||||||
if (ref != null && parentComponent) {
|
if (ref != null && parentComponent) {
|
||||||
setRef(ref, n1 && n1.ref, parentSuspense, n2)
|
setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1292,11 +1301,16 @@ function baseCreateRenderer(
|
|||||||
isSVG,
|
isSVG,
|
||||||
optimized
|
optimized
|
||||||
) => {
|
) => {
|
||||||
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
|
// 2.x compat may pre-creaate the component instance before actually
|
||||||
initialVNode,
|
// mounting
|
||||||
parentComponent,
|
const compatMountInstance = __COMPAT__ && initialVNode.component
|
||||||
parentSuspense
|
const instance: ComponentInternalInstance =
|
||||||
))
|
compatMountInstance ||
|
||||||
|
(initialVNode.component = createComponentInstance(
|
||||||
|
initialVNode,
|
||||||
|
parentComponent,
|
||||||
|
parentSuspense
|
||||||
|
))
|
||||||
|
|
||||||
if (__DEV__ && instance.type.__hmrId) {
|
if (__DEV__ && instance.type.__hmrId) {
|
||||||
registerHMR(instance)
|
registerHMR(instance)
|
||||||
@ -1313,12 +1327,14 @@ function baseCreateRenderer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// resolve props and slots for setup context
|
// resolve props and slots for setup context
|
||||||
if (__DEV__) {
|
if (!(__COMPAT__ && compatMountInstance)) {
|
||||||
startMeasure(instance, `init`)
|
if (__DEV__) {
|
||||||
}
|
startMeasure(instance, `init`)
|
||||||
setupComponent(instance)
|
}
|
||||||
if (__DEV__) {
|
setupComponent(instance)
|
||||||
endMeasure(instance, `init`)
|
if (__DEV__) {
|
||||||
|
endMeasure(instance, `init`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup() is async. This component relies on async logic to be resolved
|
// setup() is async. This component relies on async logic to be resolved
|
||||||
@ -1410,6 +1426,9 @@ function baseCreateRenderer(
|
|||||||
if ((vnodeHook = props && props.onVnodeBeforeMount)) {
|
if ((vnodeHook = props && props.onVnodeBeforeMount)) {
|
||||||
invokeVNodeHook(vnodeHook, parent, initialVNode)
|
invokeVNodeHook(vnodeHook, parent, initialVNode)
|
||||||
}
|
}
|
||||||
|
if (__COMPAT__ && isHookEventCompatEnabled) {
|
||||||
|
instance.emit('hook:beforeMount')
|
||||||
|
}
|
||||||
|
|
||||||
// render
|
// render
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
@ -1460,19 +1479,29 @@ function baseCreateRenderer(
|
|||||||
// onVnodeMounted
|
// onVnodeMounted
|
||||||
if ((vnodeHook = props && props.onVnodeMounted)) {
|
if ((vnodeHook = props && props.onVnodeMounted)) {
|
||||||
const scopedInitialVNode = initialVNode
|
const scopedInitialVNode = initialVNode
|
||||||
queuePostRenderEffect(() => {
|
queuePostRenderEffect(
|
||||||
invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode)
|
() => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
|
||||||
}, parentSuspense)
|
parentSuspense
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
if (__COMPAT__ && isHookEventCompatEnabled) {
|
||||||
|
queuePostRenderEffect(
|
||||||
|
() => instance.emit('hook:mounted'),
|
||||||
|
parentSuspense
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// activated hook for keep-alive roots.
|
// activated hook for keep-alive roots.
|
||||||
// #1742 activated hook must be accessed after first render
|
// #1742 activated hook must be accessed after first render
|
||||||
// since the hook may be injected by a child keep-alive
|
// since the hook may be injected by a child keep-alive
|
||||||
const { a } = instance
|
if (initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
|
||||||
if (
|
instance.a && queuePostRenderEffect(instance.a, parentSuspense)
|
||||||
a &&
|
if (__COMPAT__ && isHookEventCompatEnabled) {
|
||||||
initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
queuePostRenderEffect(
|
||||||
) {
|
() => instance.emit('hook:activated'),
|
||||||
queuePostRenderEffect(a, parentSuspense)
|
parentSuspense
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
instance.isMounted = true
|
instance.isMounted = true
|
||||||
|
|
||||||
@ -1508,6 +1537,9 @@ function baseCreateRenderer(
|
|||||||
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
|
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
|
||||||
invokeVNodeHook(vnodeHook, parent, next, vnode)
|
invokeVNodeHook(vnodeHook, parent, next, vnode)
|
||||||
}
|
}
|
||||||
|
if (__COMPAT__ && isHookEventCompatEnabled) {
|
||||||
|
instance.emit('hook:beforeUpdate')
|
||||||
|
}
|
||||||
|
|
||||||
// render
|
// render
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
@ -1550,9 +1582,16 @@ function baseCreateRenderer(
|
|||||||
}
|
}
|
||||||
// onVnodeUpdated
|
// onVnodeUpdated
|
||||||
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
|
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
|
||||||
queuePostRenderEffect(() => {
|
queuePostRenderEffect(
|
||||||
invokeVNodeHook(vnodeHook!, parent, next!, vnode)
|
() => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
|
||||||
}, parentSuspense)
|
parentSuspense
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (__COMPAT__ && isHookEventCompatEnabled) {
|
||||||
|
queuePostRenderEffect(
|
||||||
|
() => instance.emit('hook:updated'),
|
||||||
|
parentSuspense
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||||
@ -1564,6 +1603,11 @@ function baseCreateRenderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
|
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
// @ts-ignore
|
||||||
|
instance.update.ownerInstance = instance
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateComponentPreRender = (
|
const updateComponentPreRender = (
|
||||||
@ -2073,7 +2117,7 @@ function baseCreateRenderer(
|
|||||||
} = vnode
|
} = vnode
|
||||||
// unset ref
|
// unset ref
|
||||||
if (ref != null) {
|
if (ref != null) {
|
||||||
setRef(ref, null, parentSuspense, null)
|
setRef(ref, null, parentSuspense, vnode, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
|
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
|
||||||
@ -2204,10 +2248,15 @@ function baseCreateRenderer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { bum, effects, update, subTree, um } = instance
|
const { bum, effects, update, subTree, um } = instance
|
||||||
|
|
||||||
// beforeUnmount hook
|
// beforeUnmount hook
|
||||||
if (bum) {
|
if (bum) {
|
||||||
invokeArrayFns(bum)
|
invokeArrayFns(bum)
|
||||||
}
|
}
|
||||||
|
if (__COMPAT__ && isHookEventCompatEnabled) {
|
||||||
|
instance.emit('hook:beforeDestroy')
|
||||||
|
}
|
||||||
|
|
||||||
if (effects) {
|
if (effects) {
|
||||||
for (let i = 0; i < effects.length; i++) {
|
for (let i = 0; i < effects.length; i++) {
|
||||||
stop(effects[i])
|
stop(effects[i])
|
||||||
@ -2223,6 +2272,12 @@ function baseCreateRenderer(
|
|||||||
if (um) {
|
if (um) {
|
||||||
queuePostRenderEffect(um, parentSuspense)
|
queuePostRenderEffect(um, parentSuspense)
|
||||||
}
|
}
|
||||||
|
if (__COMPAT__ && isHookEventCompatEnabled) {
|
||||||
|
queuePostRenderEffect(
|
||||||
|
() => instance.emit('hook:destroyed'),
|
||||||
|
parentSuspense
|
||||||
|
)
|
||||||
|
}
|
||||||
queuePostRenderEffect(() => {
|
queuePostRenderEffect(() => {
|
||||||
instance.isUnmounted = true
|
instance.isUnmounted = true
|
||||||
}, parentSuspense)
|
}, parentSuspense)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
import { isArray } from '@vue/shared'
|
import { isArray } from '@vue/shared'
|
||||||
import { ComponentPublicInstance } from './componentPublicInstance'
|
import { ComponentPublicInstance } from './componentPublicInstance'
|
||||||
|
import { ComponentInternalInstance, getComponentName } from './component'
|
||||||
|
import { warn } from './warning'
|
||||||
|
|
||||||
export interface SchedulerJob {
|
export interface SchedulerJob {
|
||||||
(): void
|
(): void
|
||||||
@ -22,6 +24,7 @@ export interface SchedulerJob {
|
|||||||
* stabilizes (#1727).
|
* stabilizes (#1727).
|
||||||
*/
|
*/
|
||||||
allowRecurse?: boolean
|
allowRecurse?: boolean
|
||||||
|
ownerInstance?: ComponentInternalInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SchedulerCb = Function & { id?: number }
|
export type SchedulerCb = Function & { id?: number }
|
||||||
@ -164,8 +167,11 @@ export function flushPreFlushCbs(
|
|||||||
preFlushIndex < activePreFlushCbs.length;
|
preFlushIndex < activePreFlushCbs.length;
|
||||||
preFlushIndex++
|
preFlushIndex++
|
||||||
) {
|
) {
|
||||||
if (__DEV__) {
|
if (
|
||||||
|
__DEV__ &&
|
||||||
checkRecursiveUpdates(seen!, activePreFlushCbs[preFlushIndex])
|
checkRecursiveUpdates(seen!, activePreFlushCbs[preFlushIndex])
|
||||||
|
) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
activePreFlushCbs[preFlushIndex]()
|
activePreFlushCbs[preFlushIndex]()
|
||||||
}
|
}
|
||||||
@ -200,8 +206,11 @@ export function flushPostFlushCbs(seen?: CountMap) {
|
|||||||
postFlushIndex < activePostFlushCbs.length;
|
postFlushIndex < activePostFlushCbs.length;
|
||||||
postFlushIndex++
|
postFlushIndex++
|
||||||
) {
|
) {
|
||||||
if (__DEV__) {
|
if (
|
||||||
|
__DEV__ &&
|
||||||
checkRecursiveUpdates(seen!, activePostFlushCbs[postFlushIndex])
|
checkRecursiveUpdates(seen!, activePostFlushCbs[postFlushIndex])
|
||||||
|
) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
activePostFlushCbs[postFlushIndex]()
|
activePostFlushCbs[postFlushIndex]()
|
||||||
}
|
}
|
||||||
@ -235,8 +244,8 @@ function flushJobs(seen?: CountMap) {
|
|||||||
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
|
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
|
||||||
const job = queue[flushIndex]
|
const job = queue[flushIndex]
|
||||||
if (job) {
|
if (job) {
|
||||||
if (__DEV__) {
|
if (__DEV__ && checkRecursiveUpdates(seen!, job)) {
|
||||||
checkRecursiveUpdates(seen!, job)
|
continue
|
||||||
}
|
}
|
||||||
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
|
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
|
||||||
}
|
}
|
||||||
@ -263,13 +272,18 @@ function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob | SchedulerCb) {
|
|||||||
} else {
|
} else {
|
||||||
const count = seen.get(fn)!
|
const count = seen.get(fn)!
|
||||||
if (count > RECURSION_LIMIT) {
|
if (count > RECURSION_LIMIT) {
|
||||||
throw new Error(
|
const instance = (fn as SchedulerJob).ownerInstance
|
||||||
`Maximum recursive updates exceeded. ` +
|
const componentName = instance && getComponentName(instance.type)
|
||||||
|
warn(
|
||||||
|
`Maximum recursive updates exceeded${
|
||||||
|
componentName ? ` in component <${componentName}>` : ``
|
||||||
|
}. ` +
|
||||||
`This means you have a reactive effect that is mutating its own ` +
|
`This means you have a reactive effect that is mutating its own ` +
|
||||||
`dependencies and thus recursively triggering itself. Possible sources ` +
|
`dependencies and thus recursively triggering itself. Possible sources ` +
|
||||||
`include component template, render function, updated hook or ` +
|
`include component template, render function, updated hook or ` +
|
||||||
`watcher source function.`
|
`watcher source function.`
|
||||||
)
|
)
|
||||||
|
return true
|
||||||
} else {
|
} else {
|
||||||
seen.set(fn, count + 1)
|
seen.set(fn, count + 1)
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,10 @@ import { RendererNode, RendererElement } from './renderer'
|
|||||||
import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
|
import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
|
||||||
import { hmrDirtyComponents } from './hmr'
|
import { hmrDirtyComponents } from './hmr'
|
||||||
import { setCompiledSlotRendering } from './helpers/renderSlot'
|
import { setCompiledSlotRendering } from './helpers/renderSlot'
|
||||||
|
import { convertLegacyComponent } from './compat/component'
|
||||||
|
import { convertLegacyVModelProps } from './compat/vModel'
|
||||||
|
import { defineLegacyVNodeProperties } from './compat/renderFn'
|
||||||
|
import { convertLegacyRefInFor } from './compat/ref'
|
||||||
|
|
||||||
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
|
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
|
||||||
__isFragment: true
|
__isFragment: true
|
||||||
@ -71,6 +75,7 @@ export type VNodeRef =
|
|||||||
export type VNodeNormalizedRefAtom = {
|
export type VNodeNormalizedRefAtom = {
|
||||||
i: ComponentInternalInstance
|
i: ComponentInternalInstance
|
||||||
r: VNodeRef
|
r: VNodeRef
|
||||||
|
f?: boolean // v2 compat only, refInFor marker
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VNodeNormalizedRef =
|
export type VNodeNormalizedRef =
|
||||||
@ -127,10 +132,12 @@ export interface VNode<
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
__v_isVNode: true
|
__v_isVNode: true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
[ReactiveFlags.SKIP]: true
|
[ReactiveFlags.SKIP]: true
|
||||||
|
|
||||||
type: VNodeTypes
|
type: VNodeTypes
|
||||||
props: (VNodeProps & ExtraProps) | null
|
props: (VNodeProps & ExtraProps) | null
|
||||||
key: string | number | null
|
key: string | number | null
|
||||||
@ -358,6 +365,11 @@ function _createVNode(
|
|||||||
type = type.__vccOpts
|
type = type.__vccOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2.x async/functional component compat
|
||||||
|
if (__COMPAT__) {
|
||||||
|
type = convertLegacyComponent(type, currentRenderingInstance)
|
||||||
|
}
|
||||||
|
|
||||||
// class & style normalization.
|
// class & style normalization.
|
||||||
if (props) {
|
if (props) {
|
||||||
// for reactive or proxy objects, we need to clone it to enable mutation.
|
// for reactive or proxy objects, we need to clone it to enable mutation.
|
||||||
@ -405,7 +417,7 @@ function _createVNode(
|
|||||||
|
|
||||||
const vnode: VNode = {
|
const vnode: VNode = {
|
||||||
__v_isVNode: true,
|
__v_isVNode: true,
|
||||||
[ReactiveFlags.SKIP]: true,
|
__v_skip: true,
|
||||||
type,
|
type,
|
||||||
props,
|
props,
|
||||||
key: props && normalizeKey(props),
|
key: props && normalizeKey(props),
|
||||||
@ -463,6 +475,12 @@ function _createVNode(
|
|||||||
currentBlock.push(vnode)
|
currentBlock.push(vnode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
convertLegacyVModelProps(vnode)
|
||||||
|
convertLegacyRefInFor(vnode)
|
||||||
|
defineLegacyVNodeProperties(vnode)
|
||||||
|
}
|
||||||
|
|
||||||
return vnode
|
return vnode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,9 +493,9 @@ export function cloneVNode<T, U>(
|
|||||||
// key enumeration cost.
|
// key enumeration cost.
|
||||||
const { props, ref, patchFlag, children } = vnode
|
const { props, ref, patchFlag, children } = vnode
|
||||||
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
|
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
|
||||||
return {
|
const cloned: VNode = {
|
||||||
__v_isVNode: true,
|
__v_isVNode: true,
|
||||||
[ReactiveFlags.SKIP]: true,
|
__v_skip: true,
|
||||||
type: vnode.type,
|
type: vnode.type,
|
||||||
props: mergedProps,
|
props: mergedProps,
|
||||||
key: mergedProps && normalizeKey(mergedProps),
|
key: mergedProps && normalizeKey(mergedProps),
|
||||||
@ -529,6 +547,10 @@ export function cloneVNode<T, U>(
|
|||||||
el: vnode.el,
|
el: vnode.el,
|
||||||
anchor: vnode.anchor
|
anchor: vnode.anchor
|
||||||
}
|
}
|
||||||
|
if (__COMPAT__) {
|
||||||
|
defineLegacyVNodeProperties(cloned)
|
||||||
|
}
|
||||||
|
return cloned as any
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -671,7 +693,7 @@ export function mergeProps(...args: (Data & VNodeProps)[]) {
|
|||||||
const incoming = toMerge[key]
|
const incoming = toMerge[key]
|
||||||
if (existing !== incoming) {
|
if (existing !== incoming) {
|
||||||
ret[key] = existing
|
ret[key] = existing
|
||||||
? [].concat(existing as any, toMerge[key] as any)
|
? [].concat(existing as any, incoming as any)
|
||||||
: incoming
|
: incoming
|
||||||
}
|
}
|
||||||
} else if (key !== '') {
|
} else if (key !== '') {
|
||||||
|
@ -71,7 +71,7 @@ export function warn(msg: string, ...args: any[]) {
|
|||||||
resetTracking()
|
resetTracking()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getComponentTrace(): ComponentTraceStack {
|
export function getComponentTrace(): ComponentTraceStack {
|
||||||
let currentVNode: VNode | null = stack[stack.length - 1]
|
let currentVNode: VNode | null = stack[stack.length - 1]
|
||||||
if (!currentVNode) {
|
if (!currentVNode) {
|
||||||
return []
|
return []
|
||||||
|
@ -3,7 +3,9 @@ import {
|
|||||||
BaseTransitionProps,
|
BaseTransitionProps,
|
||||||
h,
|
h,
|
||||||
warn,
|
warn,
|
||||||
FunctionalComponent
|
FunctionalComponent,
|
||||||
|
compatUtils,
|
||||||
|
DeprecationTypes
|
||||||
} from '@vue/runtime-core'
|
} from '@vue/runtime-core'
|
||||||
import { isObject, toNumber, extend } from '@vue/shared'
|
import { isObject, toNumber, extend } from '@vue/shared'
|
||||||
|
|
||||||
@ -44,6 +46,10 @@ export const Transition: FunctionalComponent<TransitionProps> = (
|
|||||||
|
|
||||||
Transition.displayName = 'Transition'
|
Transition.displayName = 'Transition'
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
Transition.__isBuiltIn = true
|
||||||
|
}
|
||||||
|
|
||||||
const DOMTransitionPropsValidators = {
|
const DOMTransitionPropsValidators = {
|
||||||
name: String,
|
name: String,
|
||||||
type: String,
|
type: String,
|
||||||
@ -72,10 +78,20 @@ export const TransitionPropsValidators = (Transition.props = /*#__PURE__*/ exten
|
|||||||
export function resolveTransitionProps(
|
export function resolveTransitionProps(
|
||||||
rawProps: TransitionProps
|
rawProps: TransitionProps
|
||||||
): BaseTransitionProps<Element> {
|
): BaseTransitionProps<Element> {
|
||||||
let {
|
const baseProps: BaseTransitionProps<Element> = {}
|
||||||
|
for (const key in rawProps) {
|
||||||
|
if (!(key in DOMTransitionPropsValidators)) {
|
||||||
|
;(baseProps as any)[key] = (rawProps as any)[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawProps.css === false) {
|
||||||
|
return baseProps
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
name = 'v',
|
name = 'v',
|
||||||
type,
|
type,
|
||||||
css = true,
|
|
||||||
duration,
|
duration,
|
||||||
enterFromClass = `${name}-enter-from`,
|
enterFromClass = `${name}-enter-from`,
|
||||||
enterActiveClass = `${name}-enter-active`,
|
enterActiveClass = `${name}-enter-active`,
|
||||||
@ -88,15 +104,24 @@ export function resolveTransitionProps(
|
|||||||
leaveToClass = `${name}-leave-to`
|
leaveToClass = `${name}-leave-to`
|
||||||
} = rawProps
|
} = rawProps
|
||||||
|
|
||||||
const baseProps: BaseTransitionProps<Element> = {}
|
// legacy transition class compat
|
||||||
for (const key in rawProps) {
|
const legacyClassEnabled =
|
||||||
if (!(key in DOMTransitionPropsValidators)) {
|
__COMPAT__ &&
|
||||||
;(baseProps as any)[key] = (rawProps as any)[key]
|
compatUtils.isCompatEnabled(DeprecationTypes.TRANSITION_CLASSES, null)
|
||||||
|
let legacyEnterFromClass: string
|
||||||
|
let legacyAppearFromClass: string
|
||||||
|
let legacyLeaveFromClass: string
|
||||||
|
if (__COMPAT__ && legacyClassEnabled) {
|
||||||
|
const toLegacyClass = (cls: string) => cls.replace(/-from$/, '')
|
||||||
|
if (!rawProps.enterFromClass) {
|
||||||
|
legacyEnterFromClass = toLegacyClass(enterFromClass)
|
||||||
|
}
|
||||||
|
if (!rawProps.appearFromClass) {
|
||||||
|
legacyAppearFromClass = toLegacyClass(appearFromClass)
|
||||||
|
}
|
||||||
|
if (!rawProps.leaveFromClass) {
|
||||||
|
legacyLeaveFromClass = toLegacyClass(leaveFromClass)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!css) {
|
|
||||||
return baseProps
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const durations = normalizeDuration(duration)
|
const durations = normalizeDuration(duration)
|
||||||
@ -132,6 +157,12 @@ export function resolveTransitionProps(
|
|||||||
hook && hook(el, resolve)
|
hook && hook(el, resolve)
|
||||||
nextFrame(() => {
|
nextFrame(() => {
|
||||||
removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass)
|
removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass)
|
||||||
|
if (__COMPAT__ && legacyClassEnabled) {
|
||||||
|
removeTransitionClass(
|
||||||
|
el,
|
||||||
|
isAppear ? legacyAppearFromClass : legacyEnterFromClass
|
||||||
|
)
|
||||||
|
}
|
||||||
addTransitionClass(el, isAppear ? appearToClass : enterToClass)
|
addTransitionClass(el, isAppear ? appearToClass : enterToClass)
|
||||||
if (!(hook && hook.length > 1)) {
|
if (!(hook && hook.length > 1)) {
|
||||||
whenTransitionEnds(el, type, enterDuration, resolve)
|
whenTransitionEnds(el, type, enterDuration, resolve)
|
||||||
@ -144,11 +175,17 @@ export function resolveTransitionProps(
|
|||||||
onBeforeEnter(el) {
|
onBeforeEnter(el) {
|
||||||
onBeforeEnter && onBeforeEnter(el)
|
onBeforeEnter && onBeforeEnter(el)
|
||||||
addTransitionClass(el, enterFromClass)
|
addTransitionClass(el, enterFromClass)
|
||||||
|
if (__COMPAT__ && legacyClassEnabled) {
|
||||||
|
addTransitionClass(el, legacyEnterFromClass)
|
||||||
|
}
|
||||||
addTransitionClass(el, enterActiveClass)
|
addTransitionClass(el, enterActiveClass)
|
||||||
},
|
},
|
||||||
onBeforeAppear(el) {
|
onBeforeAppear(el) {
|
||||||
onBeforeAppear && onBeforeAppear(el)
|
onBeforeAppear && onBeforeAppear(el)
|
||||||
addTransitionClass(el, appearFromClass)
|
addTransitionClass(el, appearFromClass)
|
||||||
|
if (__COMPAT__ && legacyClassEnabled) {
|
||||||
|
addTransitionClass(el, legacyAppearFromClass)
|
||||||
|
}
|
||||||
addTransitionClass(el, appearActiveClass)
|
addTransitionClass(el, appearActiveClass)
|
||||||
},
|
},
|
||||||
onEnter: makeEnterHook(false),
|
onEnter: makeEnterHook(false),
|
||||||
@ -156,11 +193,17 @@ export function resolveTransitionProps(
|
|||||||
onLeave(el, done) {
|
onLeave(el, done) {
|
||||||
const resolve = () => finishLeave(el, done)
|
const resolve = () => finishLeave(el, done)
|
||||||
addTransitionClass(el, leaveFromClass)
|
addTransitionClass(el, leaveFromClass)
|
||||||
|
if (__COMPAT__ && legacyClassEnabled) {
|
||||||
|
addTransitionClass(el, legacyLeaveFromClass)
|
||||||
|
}
|
||||||
// force reflow so *-leave-from classes immediately take effect (#2593)
|
// force reflow so *-leave-from classes immediately take effect (#2593)
|
||||||
forceReflow()
|
forceReflow()
|
||||||
addTransitionClass(el, leaveActiveClass)
|
addTransitionClass(el, leaveActiveClass)
|
||||||
nextFrame(() => {
|
nextFrame(() => {
|
||||||
removeTransitionClass(el, leaveFromClass)
|
removeTransitionClass(el, leaveFromClass)
|
||||||
|
if (__COMPAT__ && legacyClassEnabled) {
|
||||||
|
removeTransitionClass(el, legacyLeaveFromClass)
|
||||||
|
}
|
||||||
addTransitionClass(el, leaveToClass)
|
addTransitionClass(el, leaveToClass)
|
||||||
if (!(onLeave && onLeave.length > 1)) {
|
if (!(onLeave && onLeave.length > 1)) {
|
||||||
whenTransitionEnds(el, type, leaveDuration, resolve)
|
whenTransitionEnds(el, type, leaveDuration, resolve)
|
||||||
|
@ -20,7 +20,10 @@ import {
|
|||||||
createVNode,
|
createVNode,
|
||||||
onUpdated,
|
onUpdated,
|
||||||
SetupContext,
|
SetupContext,
|
||||||
toRaw
|
toRaw,
|
||||||
|
compatUtils,
|
||||||
|
DeprecationTypes,
|
||||||
|
ComponentOptions
|
||||||
} from '@vue/runtime-core'
|
} from '@vue/runtime-core'
|
||||||
import { extend } from '@vue/shared'
|
import { extend } from '@vue/shared'
|
||||||
|
|
||||||
@ -37,7 +40,7 @@ export type TransitionGroupProps = Omit<TransitionProps, 'mode'> & {
|
|||||||
moveClass?: string
|
moveClass?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const TransitionGroupImpl = {
|
const TransitionGroupImpl: ComponentOptions = {
|
||||||
name: 'TransitionGroup',
|
name: 'TransitionGroup',
|
||||||
|
|
||||||
props: /*#__PURE__*/ extend({}, TransitionPropsValidators, {
|
props: /*#__PURE__*/ extend({}, TransitionPropsValidators, {
|
||||||
@ -99,7 +102,19 @@ const TransitionGroupImpl = {
|
|||||||
return () => {
|
return () => {
|
||||||
const rawProps = toRaw(props)
|
const rawProps = toRaw(props)
|
||||||
const cssTransitionProps = resolveTransitionProps(rawProps)
|
const cssTransitionProps = resolveTransitionProps(rawProps)
|
||||||
const tag = rawProps.tag || Fragment
|
let tag = rawProps.tag || Fragment
|
||||||
|
|
||||||
|
if (
|
||||||
|
__COMPAT__ &&
|
||||||
|
!rawProps.tag &&
|
||||||
|
compatUtils.checkCompatEnabled(
|
||||||
|
DeprecationTypes.TRANSITION_GROUP_ROOT,
|
||||||
|
instance.parent
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
tag = 'span'
|
||||||
|
}
|
||||||
|
|
||||||
prevChildren = children
|
prevChildren = children
|
||||||
children = slots.default ? getTransitionRawChildren(slots.default()) : []
|
children = slots.default ? getTransitionRawChildren(slots.default()) : []
|
||||||
|
|
||||||
@ -131,6 +146,10 @@ const TransitionGroupImpl = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
TransitionGroupImpl.__isBuiltIn = true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TransitionGroup does not support "mode" so we need to remove it from the
|
* TransitionGroup does not support "mode" so we need to remove it from the
|
||||||
* props declarations, but direct delete operation is considered a side effect
|
* props declarations, but direct delete operation is considered a side effect
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
import { hyphenate } from '@vue/shared'
|
import {
|
||||||
|
getCurrentInstance,
|
||||||
|
DeprecationTypes,
|
||||||
|
LegacyConfig,
|
||||||
|
compatUtils,
|
||||||
|
ComponentInternalInstance
|
||||||
|
} from '@vue/runtime-core'
|
||||||
|
import { hyphenate, isArray } from '@vue/shared'
|
||||||
|
|
||||||
const systemModifiers = ['ctrl', 'shift', 'alt', 'meta']
|
const systemModifiers = ['ctrl', 'shift', 'alt', 'meta']
|
||||||
|
|
||||||
@ -51,15 +58,60 @@ const keyNames: Record<string, string | string[]> = {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export const withKeys = (fn: Function, modifiers: string[]) => {
|
export const withKeys = (fn: Function, modifiers: string[]) => {
|
||||||
return (event: KeyboardEvent) => {
|
let globalKeyCodes: LegacyConfig['keyCodes']
|
||||||
if (!('key' in event)) return
|
let instance: ComponentInternalInstance | null = null
|
||||||
const eventKey = hyphenate(event.key)
|
if (__COMPAT__) {
|
||||||
|
instance = getCurrentInstance()
|
||||||
if (
|
if (
|
||||||
// None of the provided key modifiers match the current event key
|
compatUtils.isCompatEnabled(DeprecationTypes.CONFIG_KEY_CODES, instance)
|
||||||
!modifiers.some(k => k === eventKey || keyNames[k] === eventKey)
|
|
||||||
) {
|
) {
|
||||||
|
if (instance) {
|
||||||
|
globalKeyCodes = ((instance.appContext.config as any) as LegacyConfig)
|
||||||
|
.keyCodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (__DEV__ && modifiers.some(m => /^\d+$/.test(m))) {
|
||||||
|
compatUtils.warnDeprecation(
|
||||||
|
DeprecationTypes.V_ON_KEYCODE_MODIFIER,
|
||||||
|
instance
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (event: KeyboardEvent) => {
|
||||||
|
if (!('key' in event)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return fn(event)
|
|
||||||
|
const eventKey = hyphenate(event.key)
|
||||||
|
if (modifiers.some(k => k === eventKey || keyNames[k] === eventKey)) {
|
||||||
|
return fn(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
const keyCode = String(event.keyCode)
|
||||||
|
if (
|
||||||
|
compatUtils.isCompatEnabled(
|
||||||
|
DeprecationTypes.V_ON_KEYCODE_MODIFIER,
|
||||||
|
instance
|
||||||
|
) &&
|
||||||
|
modifiers.some(mod => mod == keyCode)
|
||||||
|
) {
|
||||||
|
return fn(event)
|
||||||
|
}
|
||||||
|
if (globalKeyCodes) {
|
||||||
|
for (const mod of modifiers) {
|
||||||
|
const codes = globalKeyCodes[mod]
|
||||||
|
if (codes) {
|
||||||
|
const matches = isArray(codes)
|
||||||
|
? codes.some(code => String(code) === keyCode)
|
||||||
|
: String(codes) === keyCode
|
||||||
|
if (matches) {
|
||||||
|
return fn(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,9 @@ import {
|
|||||||
HydrationRenderer,
|
HydrationRenderer,
|
||||||
App,
|
App,
|
||||||
RootHydrateFunction,
|
RootHydrateFunction,
|
||||||
isRuntimeOnly
|
isRuntimeOnly,
|
||||||
|
DeprecationTypes,
|
||||||
|
compatUtils
|
||||||
} from '@vue/runtime-core'
|
} from '@vue/runtime-core'
|
||||||
import { nodeOps } from './nodeOps'
|
import { nodeOps } from './nodeOps'
|
||||||
import { patchProp, forcePatchProp } from './patchProp'
|
import { patchProp, forcePatchProp } from './patchProp'
|
||||||
@ -56,17 +58,36 @@ export const createApp = ((...args) => {
|
|||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
injectNativeTagCheck(app)
|
injectNativeTagCheck(app)
|
||||||
injectCustomElementCheck(app)
|
injectCompilerOptionsCheck(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { mount } = app
|
const { mount } = app
|
||||||
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
|
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
|
||||||
const container = normalizeContainer(containerOrSelector)
|
const container = normalizeContainer(containerOrSelector)
|
||||||
if (!container) return
|
if (!container) return
|
||||||
|
|
||||||
const component = app._component
|
const component = app._component
|
||||||
if (!isFunction(component) && !component.render && !component.template) {
|
if (!isFunction(component) && !component.render && !component.template) {
|
||||||
|
// __UNSAFE__
|
||||||
|
// Reason: potential execution of JS expressions in in-DOM template.
|
||||||
|
// The user must make sure the in-DOM template is trusted. If it's
|
||||||
|
// rendered by the server, the template should not contain any user data.
|
||||||
component.template = container.innerHTML
|
component.template = container.innerHTML
|
||||||
|
// 2.x compat check
|
||||||
|
if (__COMPAT__ && __DEV__) {
|
||||||
|
for (let i = 0; i < container.attributes.length; i++) {
|
||||||
|
const attr = container.attributes[i]
|
||||||
|
if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
|
||||||
|
compatUtils.warnDeprecation(
|
||||||
|
DeprecationTypes.GLOBAL_MOUNT_CONTAINER,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear content before mounting
|
// clear content before mounting
|
||||||
container.innerHTML = ''
|
container.innerHTML = ''
|
||||||
const proxy = mount(container, false, container instanceof SVGElement)
|
const proxy = mount(container, false, container instanceof SVGElement)
|
||||||
@ -85,7 +106,7 @@ export const createSSRApp = ((...args) => {
|
|||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
injectNativeTagCheck(app)
|
injectNativeTagCheck(app)
|
||||||
injectCustomElementCheck(app)
|
injectCompilerOptionsCheck(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { mount } = app
|
const { mount } = app
|
||||||
@ -109,21 +130,40 @@ function injectNativeTagCheck(app: App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// dev only
|
// dev only
|
||||||
function injectCustomElementCheck(app: App) {
|
function injectCompilerOptionsCheck(app: App) {
|
||||||
if (isRuntimeOnly()) {
|
if (isRuntimeOnly()) {
|
||||||
const value = app.config.isCustomElement
|
const isCustomElement = app.config.isCustomElement
|
||||||
Object.defineProperty(app.config, 'isCustomElement', {
|
Object.defineProperty(app.config, 'isCustomElement', {
|
||||||
get() {
|
get() {
|
||||||
return value
|
return isCustomElement
|
||||||
},
|
},
|
||||||
set() {
|
set() {
|
||||||
warn(
|
warn(
|
||||||
`The \`isCustomElement\` config option is only respected when using the runtime compiler.` +
|
`The \`isCustomElement\` config option is deprecated. Use ` +
|
||||||
`If you are using the runtime-only build, \`isCustomElement\` must be passed to \`@vue/compiler-dom\` in the build setup instead` +
|
`\`compilerOptions.isCustomElement\` instead.`
|
||||||
`- for example, via the \`compilerOptions\` option in vue-loader: https://vue-loader.vuejs.org/options.html#compileroptions.`
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const compilerOptions = app.config.compilerOptions
|
||||||
|
const msg =
|
||||||
|
`The \`compilerOptions\` config option is only respected when using ` +
|
||||||
|
`a build of Vue.js that includes the runtime compiler (aka "full build"). ` +
|
||||||
|
`Since you are using the runtime-only build, \`compilerOptions\` ` +
|
||||||
|
`must be passed to \`@vue/compiler-dom\` in the build setup instead.\n` +
|
||||||
|
`- For vue-loader: pass it via vue-loader's \`compilerOptions\` loader option.\n` +
|
||||||
|
`- For vue-cli: see https://cli.vuejs.org/guide/webpack.html#modifying-options-of-a-loader\n` +
|
||||||
|
`- For vite: pass it via @vitejs/plugin-vue options. See https://github.com/vitejs/vite/tree/main/packages/plugin-vue#example-for-passing-options-to-vuecompiler-dom`
|
||||||
|
|
||||||
|
Object.defineProperty(app.config, 'compilerOptions', {
|
||||||
|
get() {
|
||||||
|
warn(msg)
|
||||||
|
return compilerOptions
|
||||||
|
},
|
||||||
|
set() {
|
||||||
|
warn(msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,10 @@ export function patchAttr(
|
|||||||
el.setAttributeNS(xlinkNS, key, value)
|
el.setAttributeNS(xlinkNS, key, value)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (__COMPAT__ && compatCoerceAttr(el, key, value)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// note we are only checking boolean attributes that don't have a
|
// note we are only checking boolean attributes that don't have a
|
||||||
// corresponding dom prop of the same name here.
|
// corresponding dom prop of the same name here.
|
||||||
const isBoolean = isSpecialBooleanAttr(key)
|
const isBoolean = isSpecialBooleanAttr(key)
|
||||||
@ -25,3 +29,51 @@ export function patchAttr(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2.x compat
|
||||||
|
import { makeMap, NOOP } from '@vue/shared'
|
||||||
|
import { compatUtils, DeprecationTypes } from '@vue/runtime-core'
|
||||||
|
|
||||||
|
const isEnumeratedAttr = __COMPAT__
|
||||||
|
? /*#__PURE__*/ makeMap('contenteditable,draggable,spellcheck')
|
||||||
|
: NOOP
|
||||||
|
|
||||||
|
export function compatCoerceAttr(
|
||||||
|
el: Element,
|
||||||
|
key: string,
|
||||||
|
value: unknown
|
||||||
|
): boolean {
|
||||||
|
if (isEnumeratedAttr(key)) {
|
||||||
|
const v2CocercedValue =
|
||||||
|
value === null
|
||||||
|
? 'false'
|
||||||
|
: typeof value !== 'boolean' && value !== undefined
|
||||||
|
? 'true'
|
||||||
|
: null
|
||||||
|
if (
|
||||||
|
v2CocercedValue &&
|
||||||
|
compatUtils.softAssertCompatEnabled(
|
||||||
|
DeprecationTypes.ATTR_ENUMERATED_COERSION,
|
||||||
|
null,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
v2CocercedValue
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
el.setAttribute(key, v2CocercedValue)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
value === false &&
|
||||||
|
!isSpecialBooleanAttr(key) &&
|
||||||
|
compatUtils.softAssertCompatEnabled(
|
||||||
|
DeprecationTypes.ATTR_FALSE_VALUE,
|
||||||
|
null,
|
||||||
|
key
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
el.removeAttribute(key)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"formats": [
|
"formats": [
|
||||||
"global"
|
"global"
|
||||||
],
|
],
|
||||||
|
"compat": true,
|
||||||
"env": "development",
|
"env": "development",
|
||||||
"enableNonBrowserBranches": true
|
"enableNonBrowserBranches": true
|
||||||
},
|
},
|
||||||
|
@ -8,12 +8,13 @@ export const compilerOptions: CompilerOptions = reactive({
|
|||||||
mode: 'module',
|
mode: 'module',
|
||||||
filename: 'Foo.vue',
|
filename: 'Foo.vue',
|
||||||
prefixIdentifiers: false,
|
prefixIdentifiers: false,
|
||||||
optimizeImports: false,
|
|
||||||
hoistStatic: false,
|
hoistStatic: false,
|
||||||
cacheHandlers: false,
|
cacheHandlers: false,
|
||||||
scopeId: null,
|
scopeId: null,
|
||||||
inline: false,
|
inline: false,
|
||||||
ssrCssVars: `{ color }`,
|
ssrCssVars: `{ color }`,
|
||||||
|
compatConfig: { MODE: 3 },
|
||||||
|
whitespace: 'condense',
|
||||||
bindingMetadata: {
|
bindingMetadata: {
|
||||||
TestComponent: BindingTypes.SETUP_CONST,
|
TestComponent: BindingTypes.SETUP_CONST,
|
||||||
setupRef: BindingTypes.SETUP_REF,
|
setupRef: BindingTypes.SETUP_REF,
|
||||||
@ -83,6 +84,32 @@ const App = {
|
|||||||
h('label', { for: 'mode-function' }, 'function')
|
h('label', { for: 'mode-function' }, 'function')
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
// whitespace handling
|
||||||
|
h('li', { id: 'whitespace' }, [
|
||||||
|
h('span', { class: 'label' }, 'whitespace: '),
|
||||||
|
h('input', {
|
||||||
|
type: 'radio',
|
||||||
|
id: 'whitespace-condense',
|
||||||
|
name: 'whitespace',
|
||||||
|
checked: compilerOptions.whitespace === 'condense',
|
||||||
|
onChange() {
|
||||||
|
compilerOptions.whitespace = 'condense'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
h('label', { for: 'whitespace-condense' }, 'condense'),
|
||||||
|
' ',
|
||||||
|
h('input', {
|
||||||
|
type: 'radio',
|
||||||
|
id: 'whitespace-preserve',
|
||||||
|
name: 'whitespace',
|
||||||
|
checked: compilerOptions.whitespace === 'preserve',
|
||||||
|
onChange() {
|
||||||
|
compilerOptions.whitespace = 'preserve'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
h('label', { for: 'whitespace-preserve' }, 'preserve')
|
||||||
|
]),
|
||||||
|
|
||||||
// SSR
|
// SSR
|
||||||
h('li', [
|
h('li', [
|
||||||
h('input', {
|
h('input', {
|
||||||
@ -170,18 +197,20 @@ const App = {
|
|||||||
h('label', { for: 'inline' }, 'inline')
|
h('label', { for: 'inline' }, 'inline')
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// toggle optimizeImports
|
// compat mode
|
||||||
h('li', [
|
h('li', [
|
||||||
h('input', {
|
h('input', {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
id: 'optimize-imports',
|
id: 'compat',
|
||||||
disabled: !isModule || isSSR,
|
checked: compilerOptions.compatConfig!.MODE === 2,
|
||||||
checked: isModule && !isSSR && compilerOptions.optimizeImports,
|
|
||||||
onChange(e: Event) {
|
onChange(e: Event) {
|
||||||
compilerOptions.optimizeImports = (e.target as HTMLInputElement).checked
|
compilerOptions.compatConfig!.MODE = (e.target as HTMLInputElement)
|
||||||
|
.checked
|
||||||
|
? 2
|
||||||
|
: 3
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
h('label', { for: 'optimize-imports' }, 'optimizeImports')
|
h('label', { for: 'compat' }, 'v2 compat mode')
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
|
1
packages/vue-compat/README.md
Normal file
1
packages/vue-compat/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# @vue/compat
|
7
packages/vue-compat/api-extractor.json
Normal file
7
packages/vue-compat/api-extractor.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../api-extractor.json",
|
||||||
|
"mainEntryPointFilePath": "./dist/packages/<unscopedPackageName>/src/index.d.ts",
|
||||||
|
"dtsRollup": {
|
||||||
|
"publicTrimmedFilePath": "./dist/<unscopedPackageName>.d.ts"
|
||||||
|
}
|
||||||
|
}
|
7
packages/vue-compat/index.js
Normal file
7
packages/vue-compat/index.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
module.exports = require('./dist/compat.cjs.prod.js')
|
||||||
|
} else {
|
||||||
|
module.exports = require('./dist/compat.cjs.js')
|
||||||
|
}
|
44
packages/vue-compat/package.json
Normal file
44
packages/vue-compat/package.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "@vue/compat",
|
||||||
|
"version": "3.0.11",
|
||||||
|
"description": "Vue 3 compatibility build for Vue 2",
|
||||||
|
"main": "index.js",
|
||||||
|
"module": "dist/vue.runtime.esm-bundler.js",
|
||||||
|
"types": "dist/vue.d.ts",
|
||||||
|
"unpkg": "dist/vue.global.js",
|
||||||
|
"jsdelivr": "dist/vue.global.js",
|
||||||
|
"files": [
|
||||||
|
"index.js",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"buildOptions": {
|
||||||
|
"name": "Vue",
|
||||||
|
"filename": "vue",
|
||||||
|
"compat": true,
|
||||||
|
"formats": [
|
||||||
|
"esm-bundler",
|
||||||
|
"esm-bundler-runtime",
|
||||||
|
"cjs",
|
||||||
|
"global",
|
||||||
|
"global-runtime",
|
||||||
|
"esm-browser",
|
||||||
|
"esm-browser-runtime"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/vuejs/vue.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"vue"
|
||||||
|
],
|
||||||
|
"author": "Evan You",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/vuejs/vue/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/vue-compat#readme",
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "3.0.11"
|
||||||
|
}
|
||||||
|
}
|
46
packages/vue-compat/src/createCompatVue.ts
Normal file
46
packages/vue-compat/src/createCompatVue.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// This entry exports the runtime only, and is built as
|
||||||
|
// `dist/vue.esm-bundler.js` which is used by default for bundlers.
|
||||||
|
import { initDev } from './dev'
|
||||||
|
import {
|
||||||
|
compatUtils,
|
||||||
|
createApp,
|
||||||
|
Transition,
|
||||||
|
TransitionGroup,
|
||||||
|
KeepAlive,
|
||||||
|
DeprecationTypes,
|
||||||
|
vShow,
|
||||||
|
vModelDynamic
|
||||||
|
} from '@vue/runtime-dom'
|
||||||
|
import { extend } from '@vue/shared'
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
initDev()
|
||||||
|
}
|
||||||
|
|
||||||
|
import * as runtimeDom from '@vue/runtime-dom'
|
||||||
|
|
||||||
|
function wrappedCreateApp(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
const app = createApp(...args)
|
||||||
|
if (compatUtils.isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, null)) {
|
||||||
|
// register built-in components so that they can be resolved via strings
|
||||||
|
// in the legacy h() call. The __compat__ prefix is to ensure that v3 h()
|
||||||
|
// doesn't get affected.
|
||||||
|
app.component('__compat__transition', Transition)
|
||||||
|
app.component('__compat__transition-group', TransitionGroup)
|
||||||
|
app.component('__compat__keep-alive', KeepAlive)
|
||||||
|
// built-in directives. No need for prefix since there's no render fn API
|
||||||
|
// for resolving directives via string in v3.
|
||||||
|
app._context.directives.show = vShow
|
||||||
|
app._context.directives.model = vModelDynamic
|
||||||
|
}
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCompatVue() {
|
||||||
|
const Vue = compatUtils.createCompatVue(wrappedCreateApp)
|
||||||
|
extend(Vue, runtimeDom)
|
||||||
|
// @ts-ignore
|
||||||
|
Vue.createApp = wrappedCreateApp
|
||||||
|
return Vue
|
||||||
|
}
|
14
packages/vue-compat/src/dev.ts
Normal file
14
packages/vue-compat/src/dev.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { initCustomFormatter } from '@vue/runtime-dom'
|
||||||
|
|
||||||
|
export function initDev() {
|
||||||
|
if (__BROWSER__) {
|
||||||
|
if (!__ESM_BUNDLER__) {
|
||||||
|
console.info(
|
||||||
|
`You are running a development build of Vue.\n` +
|
||||||
|
`Make sure to use the production build (*.prod.js) when deploying for production.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
initCustomFormatter()
|
||||||
|
}
|
||||||
|
}
|
3
packages/vue-compat/src/esm-index.ts
Normal file
3
packages/vue-compat/src/esm-index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import Vue from './index'
|
||||||
|
export default Vue
|
||||||
|
export * from '@vue/runtime-dom'
|
3
packages/vue-compat/src/esm-runtime.ts
Normal file
3
packages/vue-compat/src/esm-runtime.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import Vue from './runtime'
|
||||||
|
export default Vue
|
||||||
|
export * from '@vue/runtime-dom'
|
97
packages/vue-compat/src/index.ts
Normal file
97
packages/vue-compat/src/index.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// This entry is the "full-build" that includes both the runtime
|
||||||
|
// and the compiler, and supports on-the-fly compilation of the template option.
|
||||||
|
import { createCompatVue } from './createCompatVue'
|
||||||
|
import { compile, CompilerError, CompilerOptions } from '@vue/compiler-dom'
|
||||||
|
import { registerRuntimeCompiler, RenderFunction, warn } from '@vue/runtime-dom'
|
||||||
|
import { isString, NOOP, generateCodeFrame, extend } from '@vue/shared'
|
||||||
|
import { InternalRenderFunction } from 'packages/runtime-core/src/component'
|
||||||
|
import * as runtimeDom from '@vue/runtime-dom'
|
||||||
|
import {
|
||||||
|
DeprecationTypes,
|
||||||
|
warnDeprecation
|
||||||
|
} from '../../runtime-core/src/compat/compatConfig'
|
||||||
|
|
||||||
|
const compileCache: Record<string, RenderFunction> = Object.create(null)
|
||||||
|
|
||||||
|
function compileToFunction(
|
||||||
|
template: string | HTMLElement,
|
||||||
|
options?: CompilerOptions
|
||||||
|
): RenderFunction {
|
||||||
|
if (!isString(template)) {
|
||||||
|
if (template.nodeType) {
|
||||||
|
template = template.innerHTML
|
||||||
|
} else {
|
||||||
|
__DEV__ && warn(`invalid template option: `, template)
|
||||||
|
return NOOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = template
|
||||||
|
const cached = compileCache[key]
|
||||||
|
if (cached) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template[0] === '#') {
|
||||||
|
const el = document.querySelector(template)
|
||||||
|
if (__DEV__ && !el) {
|
||||||
|
warn(`Template element not found or is empty: ${template}`)
|
||||||
|
}
|
||||||
|
// __UNSAFE__
|
||||||
|
// Reason: potential execution of JS expressions in in-DOM template.
|
||||||
|
// The user must make sure the in-DOM template is trusted. If it's rendered
|
||||||
|
// by the server, the template should not contain any user data.
|
||||||
|
template = el ? el.innerHTML : ``
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__DEV__ && !__TEST__ && (!options || !options.whitespace)) {
|
||||||
|
warnDeprecation(DeprecationTypes.CONFIG_WHITESPACE, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { code } = compile(
|
||||||
|
template,
|
||||||
|
extend(
|
||||||
|
{
|
||||||
|
hoistStatic: true,
|
||||||
|
whitespace: 'preserve',
|
||||||
|
onError: __DEV__ ? onError : undefined,
|
||||||
|
onWarn: __DEV__ ? e => onError(e, true) : NOOP
|
||||||
|
} as CompilerOptions,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
function onError(err: CompilerError, asWarning = false) {
|
||||||
|
const message = asWarning
|
||||||
|
? err.message
|
||||||
|
: `Template compilation error: ${err.message}`
|
||||||
|
const codeFrame =
|
||||||
|
err.loc &&
|
||||||
|
generateCodeFrame(
|
||||||
|
template as string,
|
||||||
|
err.loc.start.offset,
|
||||||
|
err.loc.end.offset
|
||||||
|
)
|
||||||
|
warn(codeFrame ? `${message}\n${codeFrame}` : message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The wildcard import results in a huge object with every export
|
||||||
|
// with keys that cannot be mangled, and can be quite heavy size-wise.
|
||||||
|
// In the global build we know `Vue` is available globally so we can avoid
|
||||||
|
// the wildcard object.
|
||||||
|
const render = (__GLOBAL__
|
||||||
|
? new Function(code)()
|
||||||
|
: new Function('Vue', code)(runtimeDom)) as RenderFunction
|
||||||
|
|
||||||
|
// mark the function as runtime compiled
|
||||||
|
;(render as InternalRenderFunction)._rc = true
|
||||||
|
|
||||||
|
return (compileCache[key] = render)
|
||||||
|
}
|
||||||
|
|
||||||
|
registerRuntimeCompiler(compileToFunction)
|
||||||
|
|
||||||
|
const Vue = createCompatVue()
|
||||||
|
Vue.compile = compileToFunction
|
||||||
|
|
||||||
|
export default Vue
|
23
packages/vue-compat/src/runtime.ts
Normal file
23
packages/vue-compat/src/runtime.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// This entry exports the runtime only, and is built as
|
||||||
|
// `dist/vue.esm-bundler.js` which is used by default for bundlers.
|
||||||
|
import { createCompatVue } from './createCompatVue'
|
||||||
|
import { warn } from '@vue/runtime-core'
|
||||||
|
|
||||||
|
const Vue = createCompatVue()
|
||||||
|
|
||||||
|
Vue.compile = (() => {
|
||||||
|
if (__DEV__) {
|
||||||
|
warn(
|
||||||
|
`Runtime compilation is not supported in this build of Vue.` +
|
||||||
|
(__ESM_BUNDLER__
|
||||||
|
? ` Configure your bundler to alias "vue" to "@vue/compat/dist/vue.esm-bundler.js".`
|
||||||
|
: __ESM_BROWSER__
|
||||||
|
? ` Use "vue.esm-browser.js" instead.`
|
||||||
|
: __GLOBAL__
|
||||||
|
? ` Use "vue.global.js" instead.`
|
||||||
|
: ``) /* should not happen */
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}) as any
|
||||||
|
|
||||||
|
export default Vue
|
104
packages/vue/__tests__/runtimeCompilerOptions.spec.ts
Normal file
104
packages/vue/__tests__/runtimeCompilerOptions.spec.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
|
||||||
|
describe('config.compilerOptions', () => {
|
||||||
|
test('isCustomElement', () => {
|
||||||
|
const app = createApp({
|
||||||
|
template: `<foo/>`
|
||||||
|
})
|
||||||
|
app.config.compilerOptions.isCustomElement = (tag: string) => tag === 'foo'
|
||||||
|
const root = document.createElement('div')
|
||||||
|
app.mount(root)
|
||||||
|
expect(root.innerHTML).toBe('<foo></foo>')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('comments', () => {
|
||||||
|
const app = createApp({
|
||||||
|
template: `<div/><!--test--><div/>`
|
||||||
|
})
|
||||||
|
app.config.compilerOptions.comments = true
|
||||||
|
// the comments option is only relevant in production mode
|
||||||
|
__DEV__ = false
|
||||||
|
const root = document.createElement('div')
|
||||||
|
app.mount(root)
|
||||||
|
expect(root.innerHTML).toBe('<div></div><!--test--><div></div>')
|
||||||
|
__DEV__ = true
|
||||||
|
})
|
||||||
|
|
||||||
|
test('whitespace', () => {
|
||||||
|
const app = createApp({
|
||||||
|
template: `<div><span/>\n <span/></div>`
|
||||||
|
})
|
||||||
|
app.config.compilerOptions.whitespace = 'preserve'
|
||||||
|
const root = document.createElement('div')
|
||||||
|
app.mount(root)
|
||||||
|
expect(root.firstChild!.childNodes.length).toBe(3)
|
||||||
|
expect(root.firstChild!.childNodes[1].nodeType).toBe(Node.TEXT_NODE)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('delimiters', () => {
|
||||||
|
const app = createApp({
|
||||||
|
data: () => ({ foo: 'hi' }),
|
||||||
|
template: `[[ foo ]]`
|
||||||
|
})
|
||||||
|
app.config.compilerOptions.delimiters = [`[[`, `]]`]
|
||||||
|
const root = document.createElement('div')
|
||||||
|
app.mount(root)
|
||||||
|
expect(root.textContent).toBe('hi')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('per-component compilerOptions', () => {
|
||||||
|
test('isCustomElement', () => {
|
||||||
|
const app = createApp({
|
||||||
|
template: `<foo/>`,
|
||||||
|
compilerOptions: {
|
||||||
|
isCustomElement: (tag: string) => tag === 'foo'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const root = document.createElement('div')
|
||||||
|
app.mount(root)
|
||||||
|
expect(root.innerHTML).toBe('<foo></foo>')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('comments', () => {
|
||||||
|
const app = createApp({
|
||||||
|
template: `<div/><!--test--><div/>`,
|
||||||
|
compilerOptions: {
|
||||||
|
comments: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
app.config.compilerOptions.comments = false
|
||||||
|
// the comments option is only relevant in production mode
|
||||||
|
__DEV__ = false
|
||||||
|
const root = document.createElement('div')
|
||||||
|
app.mount(root)
|
||||||
|
expect(root.innerHTML).toBe('<div></div><!--test--><div></div>')
|
||||||
|
__DEV__ = true
|
||||||
|
})
|
||||||
|
|
||||||
|
test('whitespace', () => {
|
||||||
|
const app = createApp({
|
||||||
|
template: `<div><span/>\n <span/></div>`,
|
||||||
|
compilerOptions: {
|
||||||
|
whitespace: 'preserve'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const root = document.createElement('div')
|
||||||
|
app.mount(root)
|
||||||
|
expect(root.firstChild!.childNodes.length).toBe(3)
|
||||||
|
expect(root.firstChild!.childNodes[1].nodeType).toBe(Node.TEXT_NODE)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('delimiters', () => {
|
||||||
|
const app = createApp({
|
||||||
|
data: () => ({ foo: 'hi' }),
|
||||||
|
template: `[[ foo ]]`,
|
||||||
|
compilerOptions: {
|
||||||
|
delimiters: [`[[`, `]]`]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const root = document.createElement('div')
|
||||||
|
app.mount(root)
|
||||||
|
expect(root.textContent).toBe('hi')
|
||||||
|
})
|
||||||
|
})
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "vue",
|
"name": "vue",
|
||||||
"version": "3.0.11",
|
"version": "3.0.11",
|
||||||
"description": "vue",
|
"description": "The progressive JavaScript framework for buiding modern web UI.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/vue.runtime.esm-bundler.js",
|
"module": "dist/vue.runtime.esm-bundler.js",
|
||||||
"types": "dist/vue.d.ts",
|
"types": "dist/vue.d.ts",
|
||||||
|
@ -49,27 +49,27 @@ function compileToFunction(
|
|||||||
extend(
|
extend(
|
||||||
{
|
{
|
||||||
hoistStatic: true,
|
hoistStatic: true,
|
||||||
onError(err: CompilerError) {
|
onError: __DEV__ ? onError : undefined,
|
||||||
if (__DEV__) {
|
onWarn: __DEV__ ? e => onError(e, true) : NOOP
|
||||||
const message = `Template compilation error: ${err.message}`
|
} as CompilerOptions,
|
||||||
const codeFrame =
|
|
||||||
err.loc &&
|
|
||||||
generateCodeFrame(
|
|
||||||
template as string,
|
|
||||||
err.loc.start.offset,
|
|
||||||
err.loc.end.offset
|
|
||||||
)
|
|
||||||
warn(codeFrame ? `${message}\n${codeFrame}` : message)
|
|
||||||
} else {
|
|
||||||
/* istanbul ignore next */
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function onError(err: CompilerError, asWarning = false) {
|
||||||
|
const message = asWarning
|
||||||
|
? err.message
|
||||||
|
: `Template compilation error: ${err.message}`
|
||||||
|
const codeFrame =
|
||||||
|
err.loc &&
|
||||||
|
generateCodeFrame(
|
||||||
|
template as string,
|
||||||
|
err.loc.start.offset,
|
||||||
|
err.loc.end.offset
|
||||||
|
)
|
||||||
|
warn(codeFrame ? `${message}\n${codeFrame}` : message)
|
||||||
|
}
|
||||||
|
|
||||||
// The wildcard import results in a huge object with every export
|
// The wildcard import results in a huge object with every export
|
||||||
// with keys that cannot be mangled, and can be quite heavy size-wise.
|
// with keys that cannot be mangled, and can be quite heavy size-wise.
|
||||||
// In the global build we know `Vue` is available globally so we can avoid
|
// In the global build we know `Vue` is available globally so we can avoid
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// @ts-check
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import ts from 'rollup-plugin-typescript2'
|
import ts from 'rollup-plugin-typescript2'
|
||||||
import replace from '@rollup/plugin-replace'
|
import replace from '@rollup/plugin-replace'
|
||||||
@ -10,10 +11,10 @@ if (!process.env.TARGET) {
|
|||||||
const masterVersion = require('./package.json').version
|
const masterVersion = require('./package.json').version
|
||||||
const packagesDir = path.resolve(__dirname, 'packages')
|
const packagesDir = path.resolve(__dirname, 'packages')
|
||||||
const packageDir = path.resolve(packagesDir, process.env.TARGET)
|
const packageDir = path.resolve(packagesDir, process.env.TARGET)
|
||||||
const name = path.basename(packageDir)
|
|
||||||
const resolve = p => path.resolve(packageDir, p)
|
const resolve = p => path.resolve(packageDir, p)
|
||||||
const pkg = require(resolve(`package.json`))
|
const pkg = require(resolve(`package.json`))
|
||||||
const packageOptions = pkg.buildOptions || {}
|
const packageOptions = pkg.buildOptions || {}
|
||||||
|
const name = packageOptions.filename || path.basename(packageDir)
|
||||||
|
|
||||||
// ensure TS checks only once for each build
|
// ensure TS checks only once for each build
|
||||||
let hasTSChecked = false
|
let hasTSChecked = false
|
||||||
@ -89,6 +90,7 @@ function createConfig(format, output, plugins = []) {
|
|||||||
const isBrowserESMBuild = /esm-browser/.test(format)
|
const isBrowserESMBuild = /esm-browser/.test(format)
|
||||||
const isNodeBuild = format === 'cjs'
|
const isNodeBuild = format === 'cjs'
|
||||||
const isGlobalBuild = /global/.test(format)
|
const isGlobalBuild = /global/.test(format)
|
||||||
|
const isCompatBuild = !!packageOptions.compat
|
||||||
|
|
||||||
if (isGlobalBuild) {
|
if (isGlobalBuild) {
|
||||||
output.name = packageOptions.name
|
output.name = packageOptions.name
|
||||||
@ -114,21 +116,34 @@ function createConfig(format, output, plugins = []) {
|
|||||||
// during a single build.
|
// during a single build.
|
||||||
hasTSChecked = true
|
hasTSChecked = true
|
||||||
|
|
||||||
const entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`
|
let entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`
|
||||||
|
|
||||||
const external =
|
// the compat build needs both default AND named exports. This will cause
|
||||||
isGlobalBuild || isBrowserESMBuild
|
// Rollup to complain for non-ESM targets, so we use separate entries for
|
||||||
? packageOptions.enableNonBrowserBranches
|
// esm vs. non-esm builds.
|
||||||
? []
|
if (isCompatBuild && (isBrowserESMBuild || isBundlerESMBuild)) {
|
||||||
: // normal browser builds - non-browser only imports are tree-shaken,
|
entryFile = /runtime$/.test(format)
|
||||||
// they are only listed here to suppress warnings.
|
? `src/esm-runtime.ts`
|
||||||
['source-map', '@babel/parser', 'estree-walker']
|
: `src/esm-index.ts`
|
||||||
: // Node / esm-bundler builds. Externalize everything.
|
}
|
||||||
[
|
|
||||||
...Object.keys(pkg.dependencies || {}),
|
let external = []
|
||||||
...Object.keys(pkg.peerDependencies || {}),
|
|
||||||
...['path', 'url', 'stream'] // for @vue/compiler-sfc / server-renderer
|
if (isGlobalBuild || isBrowserESMBuild || isCompatBuild) {
|
||||||
]
|
if (!packageOptions.enableNonBrowserBranches) {
|
||||||
|
// normal browser builds - non-browser only imports are tree-shaken,
|
||||||
|
// they are only listed here to suppress warnings.
|
||||||
|
external = ['source-map', '@babel/parser', 'estree-walker']
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Node / esm-bundler builds.
|
||||||
|
// externalize all deps unless it's the compat build.
|
||||||
|
external = [
|
||||||
|
...Object.keys(pkg.dependencies || {}),
|
||||||
|
...Object.keys(pkg.peerDependencies || {}),
|
||||||
|
...['path', 'url', 'stream'] // for @vue/compiler-sfc / server-renderer
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// the browser builds of @vue/compiler-sfc requires postcss to be available
|
// the browser builds of @vue/compiler-sfc requires postcss to be available
|
||||||
// as a global (e.g. http://wzrd.in/standalone/postcss)
|
// as a global (e.g. http://wzrd.in/standalone/postcss)
|
||||||
@ -139,9 +154,11 @@ function createConfig(format, output, plugins = []) {
|
|||||||
const nodePlugins =
|
const nodePlugins =
|
||||||
packageOptions.enableNonBrowserBranches && format !== 'cjs'
|
packageOptions.enableNonBrowserBranches && format !== 'cjs'
|
||||||
? [
|
? [
|
||||||
|
// @ts-ignore
|
||||||
require('@rollup/plugin-commonjs')({
|
require('@rollup/plugin-commonjs')({
|
||||||
sourceMap: false
|
sourceMap: false
|
||||||
}),
|
}),
|
||||||
|
// @ts-ignore
|
||||||
require('rollup-plugin-polyfill-node')(),
|
require('rollup-plugin-polyfill-node')(),
|
||||||
require('@rollup/plugin-node-resolve').nodeResolve()
|
require('@rollup/plugin-node-resolve').nodeResolve()
|
||||||
]
|
]
|
||||||
@ -165,7 +182,8 @@ function createConfig(format, output, plugins = []) {
|
|||||||
(isGlobalBuild || isBrowserESMBuild || isBundlerESMBuild) &&
|
(isGlobalBuild || isBrowserESMBuild || isBundlerESMBuild) &&
|
||||||
!packageOptions.enableNonBrowserBranches,
|
!packageOptions.enableNonBrowserBranches,
|
||||||
isGlobalBuild,
|
isGlobalBuild,
|
||||||
isNodeBuild
|
isNodeBuild,
|
||||||
|
isCompatBuild
|
||||||
),
|
),
|
||||||
...nodePlugins,
|
...nodePlugins,
|
||||||
...plugins
|
...plugins
|
||||||
@ -188,7 +206,8 @@ function createReplacePlugin(
|
|||||||
isBrowserESMBuild,
|
isBrowserESMBuild,
|
||||||
isBrowserBuild,
|
isBrowserBuild,
|
||||||
isGlobalBuild,
|
isGlobalBuild,
|
||||||
isNodeBuild
|
isNodeBuild,
|
||||||
|
isCompatBuild
|
||||||
) {
|
) {
|
||||||
const replacements = {
|
const replacements = {
|
||||||
__COMMIT__: `"${process.env.COMMIT}"`,
|
__COMMIT__: `"${process.env.COMMIT}"`,
|
||||||
@ -208,6 +227,9 @@ function createReplacePlugin(
|
|||||||
// is targeting Node (SSR)?
|
// is targeting Node (SSR)?
|
||||||
__NODE_JS__: isNodeBuild,
|
__NODE_JS__: isNodeBuild,
|
||||||
|
|
||||||
|
// 2.x compat build
|
||||||
|
__COMPAT__: isCompatBuild,
|
||||||
|
|
||||||
// feature flags
|
// feature flags
|
||||||
__FEATURE_SUSPENSE__: true,
|
__FEATURE_SUSPENSE__: true,
|
||||||
__FEATURE_OPTIONS_API__: isBundlerESMBuild ? `__VUE_OPTIONS_API__` : true,
|
__FEATURE_OPTIONS_API__: isBundlerESMBuild ? `__VUE_OPTIONS_API__` : true,
|
||||||
@ -231,6 +253,7 @@ function createReplacePlugin(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
return replace({
|
return replace({
|
||||||
|
// @ts-ignore
|
||||||
values: replacements,
|
values: replacements,
|
||||||
preventAssignment: true
|
preventAssignment: true
|
||||||
})
|
})
|
||||||
|
@ -169,6 +169,7 @@ function checkAllSizes(targets) {
|
|||||||
function checkSize(target) {
|
function checkSize(target) {
|
||||||
const pkgDir = path.resolve(`packages/${target}`)
|
const pkgDir = path.resolve(`packages/${target}`)
|
||||||
checkFileSize(`${pkgDir}/dist/${target}.global.prod.js`)
|
checkFileSize(`${pkgDir}/dist/${target}.global.prod.js`)
|
||||||
|
checkFileSize(`${pkgDir}/dist/${target}.runtime.global.prod.js`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkFileSize(filePath) {
|
function checkFileSize(filePath) {
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"types": ["jest", "puppeteer", "node"],
|
"types": ["jest", "puppeteer", "node"],
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"@vue/compat": ["packages/vue-compat/src"],
|
||||||
"@vue/*": ["packages/*/src"],
|
"@vue/*": ["packages/*/src"],
|
||||||
"vue": ["packages/vue/src"]
|
"vue": ["packages/vue/src"]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user