Skip to content

Commit 7dc681c

Browse files
committed
wip: filters compat
1 parent 4670763 commit 7dc681c

18 files changed

+349
-41
lines changed

packages/compiler-core/src/ast.ts

+3
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ export interface RootNode extends Node {
109109
temps: number
110110
ssrHelpers?: symbol[]
111111
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
112+
113+
// v2 compat only
114+
filters?: string[]
112115
}
113116

114117
export type ElementNode =

packages/compiler-core/src/codegen.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ import {
5050
CREATE_BLOCK,
5151
OPEN_BLOCK,
5252
CREATE_STATIC,
53-
WITH_CTX
53+
WITH_CTX,
54+
RESOLVE_FILTER
5455
} from './runtimeHelpers'
5556
import { ImportItem } from './transform'
5657

@@ -274,6 +275,12 @@ export function generate(
274275
newline()
275276
}
276277
}
278+
if (__COMPAT__ && ast.filters && ast.filters.length) {
279+
newline()
280+
genAssets(ast.filters, 'filter', context)
281+
newline()
282+
}
283+
277284
if (ast.temps > 0) {
278285
push(`let `)
279286
for (let i = 0; i < ast.temps; i++) {
@@ -458,11 +465,15 @@ function genModulePreamble(
458465

459466
function genAssets(
460467
assets: string[],
461-
type: 'component' | 'directive',
468+
type: 'component' | 'directive' | 'filter',
462469
{ helper, push, newline }: CodegenContext
463470
) {
464471
const resolver = helper(
465-
type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
472+
__COMPAT__ && type === 'filter'
473+
? RESOLVE_FILTER
474+
: type === 'component'
475+
? RESOLVE_COMPONENT
476+
: RESOLVE_DIRECTIVE
466477
)
467478
for (let i = 0; i < assets.length; i++) {
468479
let id = assets[i]

packages/compiler-core/src/compat/compatConfig.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const enum CompilerDeprecationTypes {
2222
COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE',
2323
COMPILER_NATIVE_TEMPLATE = 'COMPILER_NATIVE_TEMPLATE',
2424
COMPILER_INLINE_TEMPLATE = 'COMPILER_INLINE_TEMPLATE',
25-
COMPILER_FILTER = 'COMPILER_FILTER'
25+
COMPILER_FILTERS = 'COMPILER_FILTER'
2626
}
2727

2828
type DeprecationData = {
@@ -89,8 +89,11 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
8989
link: `https://v3.vuejs.org/guide/migration/inline-template-attribute.html`
9090
},
9191

92-
[CompilerDeprecationTypes.COMPILER_FILTER]: {
93-
message: `filters have been removed in Vue 3.`,
92+
[CompilerDeprecationTypes.COMPILER_FILTERS]: {
93+
message:
94+
`filters have been removed in Vue 3. ` +
95+
`The "|" symbol will be treated as native JavaScript bitwise OR operator. ` +
96+
`Use method calls or computed properties instead.`,
9497
link: `https://v3.vuejs.org/guide/migration/filters.html`
9598
}
9699
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import { RESOLVE_FILTER } from '../runtimeHelpers'
2+
import {
3+
AttributeNode,
4+
DirectiveNode,
5+
NodeTransform,
6+
NodeTypes,
7+
SimpleExpressionNode,
8+
toValidAssetId,
9+
TransformContext
10+
} from '@vue/compiler-core'
11+
import {
12+
CompilerDeprecationTypes,
13+
isCompatEnabled,
14+
warnDeprecation
15+
} from './compatConfig'
16+
import { ExpressionNode } from '../ast'
17+
18+
const validDivisionCharRE = /[\w).+\-_$\]]/
19+
20+
export const transformFilter: NodeTransform = (node, context) => {
21+
if (!isCompatEnabled(CompilerDeprecationTypes.COMPILER_FILTERS, context)) {
22+
return
23+
}
24+
25+
if (node.type === NodeTypes.INTERPOLATION) {
26+
// filter rewrite is applied before expression transform so only
27+
// simple expressions are possible at this stage
28+
rewriteFilter(node.content, context)
29+
}
30+
31+
if (node.type === NodeTypes.ELEMENT) {
32+
node.props.forEach((prop: AttributeNode | DirectiveNode) => {
33+
if (
34+
prop.type === NodeTypes.DIRECTIVE &&
35+
prop.name !== 'for' &&
36+
prop.exp
37+
) {
38+
rewriteFilter(prop.exp, context)
39+
}
40+
})
41+
}
42+
}
43+
44+
function rewriteFilter(node: ExpressionNode, context: TransformContext) {
45+
if (node.type === NodeTypes.SIMPLE_EXPRESSION) {
46+
parseFilter(node, context)
47+
} else {
48+
for (let i = 0; i < node.children.length; i++) {
49+
const child = node.children[i]
50+
if (typeof child !== 'object') continue
51+
if (child.type === NodeTypes.SIMPLE_EXPRESSION) {
52+
parseFilter(child, context)
53+
} else if (child.type === NodeTypes.COMPOUND_EXPRESSION) {
54+
rewriteFilter(node, context)
55+
} else if (child.type === NodeTypes.INTERPOLATION) {
56+
rewriteFilter(child.content, context)
57+
}
58+
}
59+
}
60+
}
61+
62+
function parseFilter(node: SimpleExpressionNode, context: TransformContext) {
63+
const exp = node.content
64+
let inSingle = false
65+
let inDouble = false
66+
let inTemplateString = false
67+
let inRegex = false
68+
let curly = 0
69+
let square = 0
70+
let paren = 0
71+
let lastFilterIndex = 0
72+
let c,
73+
prev,
74+
i: number,
75+
expression,
76+
filters: string[] = []
77+
78+
for (i = 0; i < exp.length; i++) {
79+
prev = c
80+
c = exp.charCodeAt(i)
81+
if (inSingle) {
82+
if (c === 0x27 && prev !== 0x5c) inSingle = false
83+
} else if (inDouble) {
84+
if (c === 0x22 && prev !== 0x5c) inDouble = false
85+
} else if (inTemplateString) {
86+
if (c === 0x60 && prev !== 0x5c) inTemplateString = false
87+
} else if (inRegex) {
88+
if (c === 0x2f && prev !== 0x5c) inRegex = false
89+
} else if (
90+
c === 0x7c && // pipe
91+
exp.charCodeAt(i + 1) !== 0x7c &&
92+
exp.charCodeAt(i - 1) !== 0x7c &&
93+
!curly &&
94+
!square &&
95+
!paren
96+
) {
97+
if (expression === undefined) {
98+
// first filter, end of expression
99+
lastFilterIndex = i + 1
100+
expression = exp.slice(0, i).trim()
101+
} else {
102+
pushFilter()
103+
}
104+
} else {
105+
switch (c) {
106+
case 0x22:
107+
inDouble = true
108+
break // "
109+
case 0x27:
110+
inSingle = true
111+
break // '
112+
case 0x60:
113+
inTemplateString = true
114+
break // `
115+
case 0x28:
116+
paren++
117+
break // (
118+
case 0x29:
119+
paren--
120+
break // )
121+
case 0x5b:
122+
square++
123+
break // [
124+
case 0x5d:
125+
square--
126+
break // ]
127+
case 0x7b:
128+
curly++
129+
break // {
130+
case 0x7d:
131+
curly--
132+
break // }
133+
}
134+
if (c === 0x2f) {
135+
// /
136+
let j = i - 1
137+
let p
138+
// find first non-whitespace prev char
139+
for (; j >= 0; j--) {
140+
p = exp.charAt(j)
141+
if (p !== ' ') break
142+
}
143+
if (!p || !validDivisionCharRE.test(p)) {
144+
inRegex = true
145+
}
146+
}
147+
}
148+
}
149+
150+
if (expression === undefined) {
151+
expression = exp.slice(0, i).trim()
152+
} else if (lastFilterIndex !== 0) {
153+
pushFilter()
154+
}
155+
156+
function pushFilter() {
157+
filters.push(exp.slice(lastFilterIndex, i).trim())
158+
lastFilterIndex = i + 1
159+
}
160+
161+
if (
162+
filters.length &&
163+
warnDeprecation(
164+
CompilerDeprecationTypes.COMPILER_FILTERS,
165+
context,
166+
node.loc
167+
)
168+
) {
169+
for (i = 0; i < filters.length; i++) {
170+
expression = wrapFilter(expression, filters[i], context)
171+
}
172+
node.content = expression
173+
}
174+
}
175+
176+
function wrapFilter(
177+
exp: string,
178+
filter: string,
179+
context: TransformContext
180+
): string {
181+
context.helper(RESOLVE_FILTER)
182+
const i = filter.indexOf('(')
183+
if (i < 0) {
184+
context.filters!.add(filter)
185+
return `${toValidAssetId(filter, 'filter')}(${exp})`
186+
} else {
187+
const name = filter.slice(0, i)
188+
const args = filter.slice(i + 1)
189+
context.filters!.add(name)
190+
return `${toValidAssetId(name, 'filter')}(${exp}${
191+
args !== ')' ? ',' + args : args
192+
}`
193+
}
194+
}

packages/compiler-core/src/compile.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { trackSlotScopes, trackVForSlotScopes } from './transforms/vSlot'
1515
import { transformText } from './transforms/transformText'
1616
import { transformOnce } from './transforms/vOnce'
1717
import { transformModel } from './transforms/vModel'
18+
import { transformFilter } from './compat/transformFilter'
1819
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
1920

2021
export type TransformPreset = [
@@ -30,6 +31,7 @@ export function getBaseTransformPreset(
3031
transformOnce,
3132
transformIf,
3233
transformFor,
34+
...(__COMPAT__ ? [transformFilter] : []),
3335
...(!__BROWSER__ && prefixIdentifiers
3436
? [
3537
// order is important

packages/compiler-core/src/runtimeHelpers.ts

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const RESOLVE_DYNAMIC_COMPONENT = Symbol(
1414
__DEV__ ? `resolveDynamicComponent` : ``
1515
)
1616
export const RESOLVE_DIRECTIVE = Symbol(__DEV__ ? `resolveDirective` : ``)
17+
export const RESOLVE_FILTER = Symbol(__DEV__ ? `resolveFilter` : ``)
1718
export const WITH_DIRECTIVES = Symbol(__DEV__ ? `withDirectives` : ``)
1819
export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``)
1920
export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``)
@@ -50,6 +51,7 @@ export const helperNameMap: any = {
5051
[RESOLVE_COMPONENT]: `resolveComponent`,
5152
[RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
5253
[RESOLVE_DIRECTIVE]: `resolveDirective`,
54+
[RESOLVE_FILTER]: `resolveFilter`,
5355
[WITH_DIRECTIVES]: `withDirectives`,
5456
[RENDER_LIST]: `renderList`,
5557
[RENDER_SLOT]: `renderSlot`,

packages/compiler-core/src/transform.ts

+11
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ export interface TransformContext
118118
hoist(exp: JSChildNode): SimpleExpressionNode
119119
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
120120
constantCache: Map<TemplateChildNode, ConstantTypes>
121+
122+
// 2.x Compat only
123+
filters?: Set<string>
121124
}
122125

123126
export function createTransformContext(
@@ -289,6 +292,10 @@ export function createTransformContext(
289292
}
290293
}
291294

295+
if (__COMPAT__) {
296+
context.filters = new Set()
297+
}
298+
292299
function addId(id: string) {
293300
const { identifiers } = context
294301
if (identifiers[id] === undefined) {
@@ -321,6 +328,10 @@ export function transform(root: RootNode, options: TransformOptions) {
321328
root.hoists = context.hoists
322329
root.temps = context.temps
323330
root.cached = context.cached
331+
332+
if (__COMPAT__) {
333+
root.filters = [...context.filters!]
334+
}
324335
}
325336

326337
function createRootCodegen(root: RootNode, context: TransformContext) {

packages/compiler-core/src/transforms/transformExpression.ts

+5
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,11 @@ export function processExpression(
254254
parent && parentStack.push(parent)
255255
if (node.type === 'Identifier') {
256256
if (!isDuplicate(node)) {
257+
// v2 wrapped filter call
258+
if (__COMPAT__ && node.name.startsWith('_filter_')) {
259+
return
260+
}
261+
257262
const needPrefix = shouldPrefix(node, parent!, parentStack)
258263
if (!knownIds[node.name] && needPrefix) {
259264
if (isStaticProperty(parent!) && parent.shorthand) {

packages/compiler-core/src/utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ export function injectProp(
271271

272272
export function toValidAssetId(
273273
name: string,
274-
type: 'component' | 'directive'
274+
type: 'component' | 'directive' | 'filter'
275275
): string {
276276
return `_${type}_${name.replace(/[^\w]/g, '_')}`
277277
}

packages/runtime-core/src/apiCreateApp.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { isFunction, NO, isObject } from '@vue/shared'
1717
import { version } from '.'
1818
import { installCompatMount } from './compat/global'
1919
import { installLegacyConfigProperties } from './compat/globalConfig'
20+
import { installGlobalFilterMethod } from './compat/filter'
2021

2122
export interface App<HostElement = any> {
2223
version: string
@@ -43,7 +44,13 @@ export interface App<HostElement = any> {
4344
_context: AppContext
4445

4546
/**
46-
* @internal 2.x compat only
47+
* v2 compat only
48+
*/
49+
filter?(name: string): Function | undefined
50+
filter?(name: string, filter: Function): this
51+
52+
/**
53+
* @internal v3 compat only
4754
*/
4855
_createRoot?(options: ComponentOptions): ComponentPublicInstance
4956
}
@@ -92,6 +99,11 @@ export interface AppContext {
9299
* @internal
93100
*/
94101
reload?: () => void
102+
/**
103+
* v2 compat only
104+
* @internal
105+
*/
106+
filters?: Record<string, Function>
95107
}
96108

97109
type PluginInstallFunction = (app: App, ...options: any[]) => any
@@ -307,6 +319,7 @@ export function createAppAPI<HostElement>(
307319

308320
if (__COMPAT__) {
309321
installCompatMount(app, context, render, hydrate)
322+
installGlobalFilterMethod(app, context)
310323
if (__DEV__) installLegacyConfigProperties(app.config)
311324
}
312325

0 commit comments

Comments
 (0)