Skip to content

Commit 842be9f

Browse files
committed
improve: remove unnecessary ast parse
1 parent f61958e commit 842be9f

File tree

4 files changed

+136
-102
lines changed

4 files changed

+136
-102
lines changed

src/core/transformer.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import Debug from 'debug'
22
import MagicString from 'magic-string'
33
import { TransformResult } from 'unplugin'
4-
import { CallExpression } from '@babel/types'
5-
import { parse } from '@babel/parser'
6-
import traverse from '@babel/traverse'
4+
import { VueVersion } from '..'
75
import type { Transformer } from '../types'
86
import { DISABLE_COMMENT } from './constants'
97
import { Context } from './context'
@@ -12,26 +10,22 @@ import transformDirectives from './transforms/directive'
1210

1311
const debug = Debug('unplugin-vue-components:transformer')
1412

15-
export default (ctx: Context, version: 'vue2'|'vue3'): Transformer => {
13+
export interface ResolveResult {
14+
rawName: string
15+
replace: (resolved: string) => void
16+
}
17+
18+
export default (ctx: Context, version: VueVersion): Transformer => {
1619
return async(code, id, path) => {
1720
ctx.searchGlob()
1821

1922
const sfcPath = ctx.normalizePath(path)
2023
debug(sfcPath)
2124

2225
const s = new MagicString(code)
23-
const ast = parse(code, {
24-
sourceType: 'module',
25-
})
26-
const nodes: CallExpression[] = []
27-
traverse(ast, {
28-
CallExpression(path) {
29-
nodes.push(path.node)
30-
},
31-
})
32-
33-
await transformComponent(nodes, version, s, ctx, sfcPath)
34-
await transformDirectives(nodes, version, s, ctx, sfcPath, ast)
26+
27+
await transformComponent(code, version, s, ctx, sfcPath)
28+
await transformDirectives(code, version, s, ctx, sfcPath)
3529

3630
s.prepend(DISABLE_COMMENT)
3731

src/core/transforms/component.ts

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,64 @@
11
import Debug from 'debug'
2-
import type { CallExpression } from '@babel/types'
32
import type MagicString from 'magic-string'
43
import { pascalCase, stringifyComponentImport } from '../utils'
54
import type { Context } from '../context'
5+
import { ResolveResult } from '../transformer'
6+
import { VueVersion } from '../..'
67

78
const debug = Debug('unplugin-vue-components:transform:component')
89

9-
export default async(nodes: CallExpression[], version: 'vue2' | 'vue3', s: MagicString, ctx: Context, sfcPath: string) => {
10+
const resolveVue2 = (code: string, s: MagicString) => {
11+
const results: ResolveResult[] = []
12+
13+
for (const match of code.matchAll(/_c\([\s\n\t]*['"](.+?)["']([,)])/g)) {
14+
const [full, matchedName, append] = match
15+
16+
if (match.index != null && matchedName && !matchedName.startsWith('_')) {
17+
const start = match.index
18+
const end = start + full.length
19+
results.push({
20+
rawName: matchedName,
21+
replace: resolved => s.overwrite(start, end, `_c(${resolved}${append}`),
22+
})
23+
}
24+
}
25+
26+
return results
27+
}
28+
29+
const resolveVue3 = (code: string, s: MagicString) => {
30+
const results: ResolveResult[] = []
31+
32+
for (const match of code.matchAll(/_resolveComponent\("(.+?)"\)/g)) {
33+
const matchedName = match[1]
34+
if (match.index != null && matchedName && !matchedName.startsWith('_')) {
35+
const start = match.index
36+
const end = start + match[0].length
37+
results.push({
38+
rawName: matchedName,
39+
replace: resolved => s.overwrite(start, end, resolved),
40+
})
41+
}
42+
}
43+
44+
return results
45+
}
46+
47+
export default async(code: string, version: VueVersion, s: MagicString, ctx: Context, sfcPath: string) => {
1048
let no = 0
1149

12-
for (const node of nodes) {
13-
const { callee, arguments: args } = node
14-
if (callee.type !== 'Identifier' || callee.name !== (version === 'vue2' ? '_c' : '_resolveComponent') || args[0].type !== 'StringLiteral')
15-
continue
16-
const componentName = args[0].value
17-
if (!componentName || componentName.startsWith('_'))
18-
continue
50+
const results = version === 'vue2' ? resolveVue2(code, s) : resolveVue3(code, s)
1951

20-
debug(`| ${componentName}`)
21-
const name = pascalCase(componentName)
52+
for (const { rawName, replace } of results) {
53+
debug(`| ${rawName}`)
54+
const name = pascalCase(rawName)
2255
ctx.updateUsageMap(sfcPath, [name])
23-
2456
const component = await ctx.findComponent(name, 'component', [sfcPath])
2557
if (component) {
26-
const var_name = `__unplugin_components_${no}`
27-
s.prepend(`${stringifyComponentImport({ ...component, name: var_name }, ctx)};\n`)
58+
const varName = `__unplugin_components_${no}`
59+
s.prepend(`${stringifyComponentImport({ ...component, name: varName }, ctx)};\n`)
2860
no += 1
29-
30-
const replacedNode = version === 'vue2' ? args[0] : node
31-
s.overwrite(replacedNode.start!, replacedNode.end!, var_name)
61+
replace(varName)
3262
}
3363
}
3464

src/core/transforms/directive.ts

Lines changed: 76 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@ import type {
33
CallExpression, ObjectProperty, File, VariableDeclaration, FunctionExpression, BlockStatement,
44
} from '@babel/types'
55
import type MagicString from 'magic-string'
6-
import { ParseResult } from '@babel/parser'
6+
import { parse, ParseResult } from '@babel/parser'
7+
import traverse from '@babel/traverse'
78
import { pascalCase, stringifyComponentImport } from '../utils'
89
import type { Context } from '../context'
10+
import { ResolveResult } from '../transformer'
11+
import { VueVersion } from '../..'
912

1013
const debug = Debug('unplugin-vue-components:transform:directive')
1114

12-
interface ResolveResult {
13-
rawName: string
14-
replace: (resolved: string) => void
15-
}
16-
1715
/**
1816
* get Vue 2 render function position
1917
* @param ast
@@ -31,84 +29,94 @@ const getRenderFnStart = (ast: ParseResult<File>): number => {
3129
return start + 1
3230
}
3331

34-
const resolveVue2 = async(node: CallExpression, s: MagicString, ast: ParseResult<File>): Promise<ResolveResult[]> => {
35-
const { callee, arguments: args } = node
36-
if (callee.type !== 'Identifier' || callee.name !== '_c' || args[1].type !== 'ObjectExpression')
37-
return []
38-
39-
const directives = args[1].properties.find(
40-
(property): property is ObjectProperty =>
41-
property.type === 'ObjectProperty'
42-
&& property.key.type === 'Identifier'
43-
&& property.key.name === 'directives',
44-
)?.value
45-
if (!directives || directives.type !== 'ArrayExpression')
46-
return []
47-
48-
const renderStart = getRenderFnStart(ast)
32+
const resolveVue2 = (code: string, s: MagicString): ResolveResult[] => {
33+
const ast = parse(code, {
34+
sourceType: 'module',
35+
})
36+
const nodes: CallExpression[] = []
37+
traverse(ast, {
38+
CallExpression(path) {
39+
nodes.push(path.node)
40+
},
41+
})
4942

5043
const results: ResolveResult[] = []
51-
for (const directive of directives.elements) {
52-
if (directive?.type !== 'ObjectExpression') continue
53-
const nameNode = directive.properties.find(
54-
(p): p is ObjectProperty =>
55-
p.type === 'ObjectProperty'
56-
&& p.key.type === 'Identifier'
57-
&& p.key.name === 'name',
44+
for (const node of nodes) {
45+
const { callee, arguments: args } = node
46+
// _c(_, {})
47+
if (callee.type !== 'Identifier' || callee.name !== '_c' || args[1].type !== 'ObjectExpression')
48+
continue
49+
50+
// { directives: [] }
51+
const directives = args[1].properties.find(
52+
(property): property is ObjectProperty =>
53+
property.type === 'ObjectProperty'
54+
&& property.key.type === 'Identifier'
55+
&& property.key.name === 'directives',
5856
)?.value
59-
if (nameNode?.type !== 'StringLiteral') continue
60-
const name = nameNode.value
61-
if (!name || name.startsWith('_')) continue
62-
results.push({
63-
rawName: name,
64-
replace: (resolved) => {
65-
s.prependLeft(renderStart!, `\nthis.$options.directives["${name}"] = ${resolved};`)
66-
},
67-
})
57+
if (!directives || directives.type !== 'ArrayExpression')
58+
continue
59+
60+
const renderStart = getRenderFnStart(ast)
61+
62+
for (const directive of directives.elements) {
63+
if (directive?.type !== 'ObjectExpression') continue
64+
65+
const nameNode = directive.properties.find(
66+
(p): p is ObjectProperty =>
67+
p.type === 'ObjectProperty'
68+
&& p.key.type === 'Identifier'
69+
&& p.key.name === 'name',
70+
)?.value
71+
if (nameNode?.type !== 'StringLiteral') continue
72+
const name = nameNode.value
73+
if (!name || name.startsWith('_')) continue
74+
results.push({
75+
rawName: name,
76+
replace: (resolved) => {
77+
s.prependLeft(renderStart!, `\nthis.$options.directives["${name}"] = ${resolved};`)
78+
},
79+
})
80+
}
6881
}
6982

7083
return results
7184
}
7285

73-
const resolveVue3 = async(node: CallExpression, s: MagicString): Promise<ResolveResult[]> => {
74-
const { callee, arguments: args } = node
75-
if (
76-
callee.type !== 'Identifier'
77-
|| callee.name !== '_resolveDirective'
78-
|| args[0].type !== 'StringLiteral'
79-
)
80-
return []
86+
const resolveVue3 = (code: string, s: MagicString): ResolveResult[] => {
87+
const results: ResolveResult[] = []
8188

82-
const rawName = args[0].value
83-
if (!rawName || rawName.startsWith('_')) return []
89+
for (const match of code.matchAll(/_resolveDirective\("(.+?)"\)/g)) {
90+
const matchedName = match[1]
91+
if (match.index != null && matchedName && !matchedName.startsWith('_')) {
92+
const start = match.index
93+
const end = start + match[0].length
94+
results.push({
95+
rawName: matchedName,
96+
replace: resolved => s.overwrite(start, end, resolved),
97+
})
98+
}
99+
}
84100

85-
return [{
86-
rawName,
87-
replace: resolved => s.overwrite(node.start!, node.end!, resolved),
88-
}]
101+
return results
89102
}
90103

91-
export default async(nodes: CallExpression[], version: 'vue2' | 'vue3', s: MagicString, ctx: Context, sfcPath: string, ast: ParseResult<File>) => {
104+
export default async(code: string, version: VueVersion, s: MagicString, ctx: Context, sfcPath: string) => {
92105
let no = 0
93106

94-
for (const node of nodes) {
95-
const results = version === 'vue2' ? await resolveVue2(node, s, ast) : await resolveVue3(node, s)
96-
if (results.length === 0) continue
107+
const results = version === 'vue2' ? resolveVue2(code, s) : resolveVue3(code, s)
108+
for (const { rawName, replace } of results) {
109+
debug(`| ${rawName}`)
110+
const name = pascalCase(rawName)
111+
ctx.updateUsageMap(sfcPath, [name])
97112

98-
for (const result of results) {
99-
debug(`| ${result.rawName}`)
113+
const directive = await ctx.findComponent(name, 'directive', [sfcPath])
114+
if (!directive) continue
100115

101-
const name = pascalCase(result.rawName)
102-
ctx.updateUsageMap(sfcPath, [name])
103-
const directive = await ctx.findComponent(name, 'directive', [sfcPath])
104-
105-
if (!directive) continue
106-
107-
const varName = `__unplugin_directives_${no}`
108-
s.prepend(`${stringifyComponentImport({ ...directive, name: varName }, ctx)};\n`)
109-
no += 1
110-
result.replace(varName)
111-
}
116+
const varName = `__unplugin_directives_${no}`
117+
s.prepend(`${stringifyComponentImport({ ...directive, name: varName }, ctx)};\n`)
118+
no += 1
119+
replace(varName)
112120
}
113121

114122
debug(`^ (${no})`)

src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export type Matcher = (id: string) => boolean | null | undefined
2828

2929
export type Transformer = (code: string, id: string, path: string, query: Record<string, string>) => Awaitable<TransformResult | null>
3030

31+
export type VueVersion = 'vue3' | 'vue2'
32+
3133
/**
3234
* Plugin options.
3335
*/
@@ -102,7 +104,7 @@ export interface Options {
102104
*
103105
* @default 'vue3'
104106
*/
105-
transformer?: 'vue3' | 'vue2'
107+
transformer?: VueVersion
106108

107109
/**
108110
* Generate TypeScript declaration for global components

0 commit comments

Comments
 (0)