wip: compiler deprecation config

This commit is contained in:
Evan You 2021-04-12 19:42:09 -04:00
parent 115372dd5b
commit e130c7db23
9 changed files with 198 additions and 18 deletions

View File

@ -0,0 +1,133 @@
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 {
IS_ON_ELEMENT = 'IS_ON_ELEMENT',
V_BIND_SYNC = 'V_BIND_SYNC',
V_BIND_OBJECT_ORDER = 'V_BIND_OBJECT_ORDER',
V_ON_NATIVE_MODIFIER = 'V_ON_NATIVE_MODIFIER',
KEY_V_IF = 'KEY_V_IF',
KEY_V_FOR_TEMPLATE = 'KEY_V_FOR_TEMPLATE',
V_IF_V_FOR_PRECEDENCE = 'V_IF_V_FOR_PRECEDENCE',
NATIVE_TEMPLATE = 'NATIVE_TEMPLATE'
}
type DeprecationData = {
message: string | ((...args: any[]) => string)
link?: string
}
const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
[CompilerDeprecationTypes.IS_ON_ELEMENT]: {
message: ``,
link: `https://v3.vuejs.org/guide/migration/custom-elements-interop.html`
},
[CompilerDeprecationTypes.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.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 attribute that appears ` +
`before v-bind in the case of conflicting keys. To retain 2.x behavior, ` +
`move v-bind to and make it the first attribute. If all occurences ` +
`of this warning are working as intended, you can suppress it.`,
link: `https://v3.vuejs.org/guide/migration/v-bind.html`
},
[CompilerDeprecationTypes.V_ON_NATIVE_MODIFIER]: {
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.KEY_V_IF]: {
message: ``,
link: `https://v3.vuejs.org/guide/migration/key-attribute.html#on-conditional-branches`
},
[CompilerDeprecationTypes.KEY_V_FOR_TEMPLATE]: {
message: ``,
link: `https://v3.vuejs.org/guide/migration/key-attribute.html#with-template-v-for`
},
[CompilerDeprecationTypes.V_IF_V_FOR_PRECEDENCE]: {
message:
`v-if / v-for precedence when used on the same element has changed ` +
`in Vue 3. It is best to avoid the ambiguity with either <template> tags ` +
`or a computed property that filters v-for data source.`,
link: `https://v3.vuejs.org/guide/migration/v-if-v-for.html`
},
[CompilerDeprecationTypes.NATIVE_TEMPLATE]: {
message:
`<template> with no special directives will render as a native template` +
`element instead of its inner content in Vue 3.`
}
}
function getCompatValue(
key: CompilerDeprecationTypes,
context: ParserContext | TransformContext
) {
const config = (context as ParserContext).options
? (context as ParserContext).options.compatConfig
: (context as TransformContext).compatConfig
return config && config[key]
}
export function checkCompatEnabled(
key: CompilerDeprecationTypes,
context: ParserContext | TransformContext,
loc: SourceLocation | null,
...args: any[]
): boolean {
const enabled = getCompatValue(key, context) !== false
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}` : ``}`
if (loc) {
const err = new SyntaxError(msg) as CompilerError
err.code = key
err.loc = loc
context.onWarn(err)
return
}
context.onWarn(msg)
}

View File

@ -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,11 @@ export function defaultOnError(error: CompilerError) {
throw error throw error
} }
export function defaultOnWarn(msg: string | CompilerError) {
__DEV__ &&
console.warn(`[Vue warn]`, typeof msg === 'string' ? msg : msg.message)
}
export function createCompilerError<T extends number>( export function createCompilerError<T extends number>(
code: T, code: T,
loc?: SourceLocation, loc?: SourceLocation,

View File

@ -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'

View File

@ -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?: (msg: string | 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
*/ */
@ -48,7 +56,6 @@ export interface ParserOptions {
* 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 +145,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 +223,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 {

View File

@ -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,12 @@ import {
createRoot, createRoot,
ConstantTypes ConstantTypes
} from './ast' } from './ast'
import { CompilerCompatOptions } from './compat/compatConfig'
type OptionalOptions = 'isNativeTag' | 'isBuiltInComponent' type OptionalOptions =
| '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 +68,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 +90,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 +122,8 @@ function createParserContext(
originalSource: content, originalSource: content,
source: content, source: content,
inPre: false, inPre: false,
inVPre: false inVPre: false,
onWarn: options.onWarn
} }
} }

View File

@ -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>
@ -136,7 +140,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 +166,8 @@ export function createTransformContext(
inline, inline,
isTS, isTS,
onError, onError,
onWarn,
compatConfig,
// state // state
root, root,

View File

@ -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 {

View File

@ -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'
@ -93,7 +95,11 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
if (!modifiers.length) return baseResult if (!modifiers.length) return baseResult
if (__COMPAT__ && __DEV__ && modifiers.includes('native')) { if (__COMPAT__ && __DEV__ && modifiers.includes('native')) {
console.warn('.native modifier for v-on has been removed') warnDeprecation(
CompilerDeprecationTypes.V_ON_NATIVE_MODIFIER,
context,
dir.loc
)
} }
let { key, value: handlerExp } = baseResult.props[0] let { key, value: handlerExp } = baseResult.props[0]

View File

@ -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 {