temps: number
ssrHelpers?: symbol[]
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
+
+ // v2 compat only
+ filters?: string[]
}
export type ElementNode =
CREATE_BLOCK,
OPEN_BLOCK,
CREATE_STATIC,
- WITH_CTX
+ WITH_CTX,
+ RESOLVE_FILTER
} from './runtimeHelpers'
import { ImportItem } from './transform'
newline()
}
}
+ if (__COMPAT__ && ast.filters && ast.filters.length) {
+ newline()
+ genAssets(ast.filters, 'filter', context)
+ newline()
+ }
+
if (ast.temps > 0) {
push(`let `)
for (let i = 0; i < ast.temps; i++) {
function genAssets(
assets: string[],
- type: 'component' | 'directive',
+ type: 'component' | 'directive' | 'filter',
{ helper, push, newline }: CodegenContext
) {
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++) {
let id = assets[i]
COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE',
COMPILER_NATIVE_TEMPLATE = 'COMPILER_NATIVE_TEMPLATE',
COMPILER_INLINE_TEMPLATE = 'COMPILER_INLINE_TEMPLATE',
- COMPILER_FILTER = 'COMPILER_FILTER'
+ COMPILER_FILTERS = 'COMPILER_FILTER'
}
type DeprecationData = {
link: `https://v3.vuejs.org/guide/migration/inline-template-attribute.html`
},
- [CompilerDeprecationTypes.COMPILER_FILTER]: {
- message: `filters have been removed in Vue 3.`,
+ [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`
}
}
--- /dev/null
+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
+ }`
+ }
+}
import { transformText } from './transforms/transformText'
import { transformOnce } from './transforms/vOnce'
import { transformModel } from './transforms/vModel'
+import { transformFilter } from './compat/transformFilter'
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
export type TransformPreset = [
transformOnce,
transformIf,
transformFor,
+ ...(__COMPAT__ ? [transformFilter] : []),
...(!__BROWSER__ && prefixIdentifiers
? [
// order is important
__DEV__ ? `resolveDynamicComponent` : ``
)
export const RESOLVE_DIRECTIVE = Symbol(__DEV__ ? `resolveDirective` : ``)
+export const RESOLVE_FILTER = Symbol(__DEV__ ? `resolveFilter` : ``)
export const WITH_DIRECTIVES = Symbol(__DEV__ ? `withDirectives` : ``)
export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``)
export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``)
[RESOLVE_COMPONENT]: `resolveComponent`,
[RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
[RESOLVE_DIRECTIVE]: `resolveDirective`,
+ [RESOLVE_FILTER]: `resolveFilter`,
[WITH_DIRECTIVES]: `withDirectives`,
[RENDER_LIST]: `renderList`,
[RENDER_SLOT]: `renderSlot`,
hoist(exp: JSChildNode): SimpleExpressionNode
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
constantCache: Map<TemplateChildNode, ConstantTypes>
+
+ // 2.x Compat only
+ filters?: Set<string>
}
export function createTransformContext(
}
}
+ if (__COMPAT__) {
+ context.filters = new Set()
+ }
+
function addId(id: string) {
const { identifiers } = context
if (identifiers[id] === undefined) {
root.hoists = context.hoists
root.temps = context.temps
root.cached = context.cached
+
+ if (__COMPAT__) {
+ root.filters = [...context.filters!]
+ }
}
function createRootCodegen(root: RootNode, context: TransformContext) {
parent && parentStack.push(parent)
if (node.type === 'Identifier') {
if (!isDuplicate(node)) {
+ // v2 wrapped filter call
+ if (__COMPAT__ && node.name.startsWith('_filter_')) {
+ return
+ }
+
const needPrefix = shouldPrefix(node, parent!, parentStack)
if (!knownIds[node.name] && needPrefix) {
if (isStaticProperty(parent!) && parent.shorthand) {
export function toValidAssetId(
name: string,
- type: 'component' | 'directive'
+ type: 'component' | 'directive' | 'filter'
): string {
return `_${type}_${name.replace(/[^\w]/g, '_')}`
}
import { version } from '.'
import { installCompatMount } from './compat/global'
import { installLegacyConfigProperties } from './compat/globalConfig'
+import { installGlobalFilterMethod } from './compat/filter'
export interface App<HostElement = any> {
version: string
_context: AppContext
/**
- * @internal 2.x compat only
+ * v2 compat only
+ */
+ filter?(name: string): Function | undefined
+ filter?(name: string, filter: Function): this
+
+ /**
+ * @internal v3 compat only
*/
_createRoot?(options: ComponentOptions): ComponentPublicInstance
}
* @internal
*/
reload?: () => void
+ /**
+ * v2 compat only
+ * @internal
+ */
+ filters?: Record<string, Function>
}
type PluginInstallFunction = (app: App, ...options: any[]) => any
if (__COMPAT__) {
installCompatMount(app, context, render, hydrate)
+ installGlobalFilterMethod(app, context)
if (__DEV__) installLegacyConfigProperties(app.config)
}
COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL',
COMPONENT_V_MODEL = 'COMPONENT_V_MODEL',
- RENDER_FUNCTION = 'RENDER_FUNCTION'
+ RENDER_FUNCTION = 'RENDER_FUNCTION',
+
+ FILTERS = 'FILTERS'
}
type DeprecationData = {
}: 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`
}
}
--- /dev/null
+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
+ }
+}
* @internal
*/
directives: Record<string, Directive> | null
+ /**
+ * Resolved filters registry, v2 compat only
+ * @internal
+ */
+ filters?: Record<string, Function>
/**
* resolved props options
* @internal
isCompatEnabled,
softAssertCompatEnabled
} from './compat/compatConfig'
+import {
+ AssetTypes,
+ COMPONENTS,
+ DIRECTIVES,
+ FILTERS
+} from './helpers/resolveAssets'
/**
* Interface for declaring custom options.
provide?: Data | Function
inject?: ComponentInjectOptions
+ // assets
+ filters?: Record<string, Function>
+
// composition
mixins?: Mixin[]
extends?: Extends
watch: watchOptions,
provide: provideOptions,
inject: injectOptions,
- // assets
- components,
- directives,
// lifecycle
beforeMount,
mounted,
// To reduce memory usage, only components with mixins or extends will have
// resolved asset registry attached to instance.
if (asMixin) {
- if (components) {
- extend(
- instance.components ||
- (instance.components = extend(
- {},
- (instance.type as ComponentOptions).components
- ) as Record<string, ConcreteComponent>),
- components
- )
- }
- if (directives) {
- extend(
- instance.directives ||
- (instance.directives = extend(
- {},
- (instance.type as ComponentOptions).directives
- )),
- directives
- )
+ resolveInstanceAssets(instance, options, COMPONENTS)
+ resolveInstanceAssets(instance, options, DIRECTIVES)
+ if (__COMPAT__ && isCompatEnabled(DeprecationTypes.FILTERS, instance)) {
+ resolveInstanceAssets(instance, options, FILTERS)
}
}
}
}
+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,
import { warn } from '../warning'
import { VNodeTypes } from '../vnode'
-const COMPONENTS = 'components'
-const DIRECTIVES = 'directives'
+export const COMPONENTS = 'components'
+export const DIRECTIVES = 'directives'
+export const FILTERS = 'filters'
+
+export type AssetTypes = typeof COMPONENTS | typeof DIRECTIVES | typeof FILTERS
/**
* @private
return resolveAsset(DIRECTIVES, name)
}
+/**
+ * v2 compat only
+ * @internal
+ */
+export function resolveFilter(name: string): Function | undefined {
+ return resolveAsset(FILTERS, name)
+}
+
/**
* @private
* overload 1: components
name: string
): Directive | undefined
// implementation
+// overload 3: filters (compat only)
+function resolveAsset(type: typeof FILTERS, name: string): Function | undefined
+// implementation
function resolveAsset(
- type: typeof COMPONENTS | typeof DIRECTIVES,
+ type: AssetTypes,
name: string,
warnMissing = true,
maybeSelfReference = false
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,
"formats": [
"global"
],
+ "compat": true,
"env": "development",
"enableNonBrowserBranches": true
},
mode: 'module',
filename: 'Foo.vue',
prefixIdentifiers: false,
- optimizeImports: false,
hoistStatic: false,
cacheHandlers: false,
scopeId: null,
inline: false,
ssrCssVars: `{ color }`,
+ compatConfig: { MODE: 3 },
bindingMetadata: {
TestComponent: BindingTypes.SETUP_CONST,
setupRef: BindingTypes.SETUP_REF,
h('label', { for: 'inline' }, 'inline')
]),
- // toggle optimizeImports
+ // compat mode
h('li', [
h('input', {
type: 'checkbox',
- id: 'optimize-imports',
- disabled: !isModule || isSSR,
- checked: isModule && !isSSR && compilerOptions.optimizeImports,
+ id: 'compat',
+ checked: compilerOptions.compatConfig!.MODE === 2,
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')
])
])
])