Skip to content

Commit 0f2df77

Browse files
committed
fix(compiler-sfc): analyze export default by AST
closes #7038
1 parent 0fbc19f commit 0f2df77

File tree

3 files changed

+95
-80
lines changed

3 files changed

+95
-80
lines changed

packages/compiler-sfc/__tests__/rewriteDefault.spec.ts

Lines changed: 76 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,31 @@
1-
import { rewriteDefault } from '../src'
1+
import { babelParse, rewriteDefault } from '../src'
2+
3+
function compileRewriteDefault(code: string, as: string) {
4+
const ast = babelParse(code, {
5+
sourceType: 'module',
6+
plugins: ['typescript', 'decorators-legacy']
7+
})
8+
return rewriteDefault(code, ast.program.body, as)
9+
}
210

311
describe('compiler sfc: rewriteDefault', () => {
412
test('without export default', () => {
5-
expect(rewriteDefault(`export a = {}`, 'script')).toMatchInlineSnapshot(`
6-
"export a = {}
13+
expect(compileRewriteDefault(`export const a = {}`, 'script'))
14+
.toMatchInlineSnapshot(`
15+
"export const a = {}
716
const script = {}"
817
`)
918
})
1019

1120
test('rewrite export default', () => {
1221
expect(
13-
rewriteDefault(`export default {}`, 'script')
22+
compileRewriteDefault(`export default {}`, 'script')
1423
).toMatchInlineSnapshot(`"const script = {}"`)
1524
})
1625

1726
test('rewrite export named default', () => {
1827
expect(
19-
rewriteDefault(
28+
compileRewriteDefault(
2029
`const a = 1 \n export { a as b, a as default, a as c}`,
2130
'script'
2231
)
@@ -27,7 +36,7 @@ describe('compiler sfc: rewriteDefault', () => {
2736
`)
2837

2938
expect(
30-
rewriteDefault(
39+
compileRewriteDefault(
3140
`const a = 1 \n export { a as b, a as default , a as c}`,
3241
'script'
3342
)
@@ -39,16 +48,20 @@ describe('compiler sfc: rewriteDefault', () => {
3948
})
4049

4150
test('w/ comments', async () => {
42-
expect(rewriteDefault(`// export default\nexport default {}`, 'script'))
43-
.toMatchInlineSnapshot(`
51+
expect(
52+
compileRewriteDefault(`// export default\nexport default {}`, 'script')
53+
).toMatchInlineSnapshot(`
4454
"// export default
4555
const script = {}"
4656
`)
4757
})
4858

4959
test('export named default multiline', () => {
5060
expect(
51-
rewriteDefault(`let App = {}\n export {\nApp as default\n}`, '_sfc_main')
61+
compileRewriteDefault(
62+
`let App = {}\n export {\nApp as default\n}`,
63+
'_sfc_main'
64+
)
5265
).toMatchInlineSnapshot(`
5366
"let App = {}
5467
export {
@@ -60,7 +73,7 @@ describe('compiler sfc: rewriteDefault', () => {
6073

6174
test('export named default multiline /w comments', () => {
6275
expect(
63-
rewriteDefault(
76+
compileRewriteDefault(
6477
`const a = 1 \n export {\n a as b,\n a as default,\n a as c}\n` +
6578
`// export { myFunction as default }`,
6679
'script'
@@ -76,7 +89,7 @@ describe('compiler sfc: rewriteDefault', () => {
7689
`)
7790

7891
expect(
79-
rewriteDefault(
92+
compileRewriteDefault(
8093
`const a = 1 \n export {\n a as b,\n a as default ,\n a as c}\n` +
8194
`// export { myFunction as default }`,
8295
'script'
@@ -94,65 +107,74 @@ describe('compiler sfc: rewriteDefault', () => {
94107

95108
test(`export { default } from '...'`, async () => {
96109
expect(
97-
rewriteDefault(`export { default, foo } from './index.js'`, 'script')
110+
compileRewriteDefault(
111+
`export { default, foo } from './index.js'`,
112+
'script'
113+
)
98114
).toMatchInlineSnapshot(`
99-
"import { default as __VUE_DEFAULT__ } from './index.js'
100-
export { foo } from './index.js'
101-
const script = __VUE_DEFAULT__"
102-
`)
115+
"import { default as __VUE_DEFAULT__ } from './index.js'
116+
export { foo } from './index.js'
117+
const script = __VUE_DEFAULT__"
118+
`)
103119

104120
expect(
105-
rewriteDefault(`export { default , foo } from './index.js'`, 'script')
121+
compileRewriteDefault(
122+
`export { default , foo } from './index.js'`,
123+
'script'
124+
)
106125
).toMatchInlineSnapshot(`
107-
"import { default as __VUE_DEFAULT__ } from './index.js'
108-
export { foo } from './index.js'
109-
const script = __VUE_DEFAULT__"
110-
`)
126+
"import { default as __VUE_DEFAULT__ } from './index.js'
127+
export { foo } from './index.js'
128+
const script = __VUE_DEFAULT__"
129+
`)
111130

112131
expect(
113-
rewriteDefault(`export { foo, default } from './index.js'`, 'script')
132+
compileRewriteDefault(
133+
`export { foo, default } from './index.js'`,
134+
'script'
135+
)
114136
).toMatchInlineSnapshot(`
115-
"import { default as __VUE_DEFAULT__ } from './index.js'
116-
export { foo, } from './index.js'
117-
const script = __VUE_DEFAULT__"
118-
`)
137+
"import { default as __VUE_DEFAULT__ } from './index.js'
138+
export { foo, } from './index.js'
139+
const script = __VUE_DEFAULT__"
140+
`)
119141

120142
expect(
121-
rewriteDefault(
143+
compileRewriteDefault(
122144
`export { foo as default, bar } from './index.js'`,
123145
'script'
124146
)
125147
).toMatchInlineSnapshot(`
126-
"import { foo } from './index.js'
127-
export { bar } from './index.js'
128-
const script = foo"
129-
`)
148+
"import { foo } from './index.js'
149+
export { bar } from './index.js'
150+
const script = foo"
151+
`)
130152

131153
expect(
132-
rewriteDefault(
154+
compileRewriteDefault(
133155
`export { foo as default , bar } from './index.js'`,
134156
'script'
135157
)
136158
).toMatchInlineSnapshot(`
137-
"import { foo } from './index.js'
138-
export { bar } from './index.js'
139-
const script = foo"
140-
`)
159+
"import { foo } from './index.js'
160+
export { bar } from './index.js'
161+
const script = foo"
162+
`)
141163

142164
expect(
143-
rewriteDefault(
165+
compileRewriteDefault(
144166
`export { bar, foo as default } from './index.js'`,
145167
'script'
146168
)
147169
).toMatchInlineSnapshot(`
148-
"import { foo } from './index.js'
149-
export { bar, } from './index.js'
150-
const script = foo"
151-
`)
170+
"import { foo } from './index.js'
171+
export { bar, } from './index.js'
172+
const script = foo"
173+
`)
152174
})
153175

154176
test('export default class', async () => {
155-
expect(rewriteDefault(`export default class Foo {}`, 'script'))
177+
expect(compileRewriteDefault(`export default class Foo {}`, 'script'))
156178
.toMatchInlineSnapshot(`
157179
"class Foo {}
158180
const script = Foo"
@@ -161,7 +183,10 @@ describe('compiler sfc: rewriteDefault', () => {
161183

162184
test('export default class w/ comments', async () => {
163185
expect(
164-
rewriteDefault(`// export default\nexport default class Foo {}`, 'script')
186+
compileRewriteDefault(
187+
`// export default\nexport default class Foo {}`,
188+
'script'
189+
)
165190
).toMatchInlineSnapshot(`
166191
"// export default
167192
class Foo {}
@@ -171,7 +196,7 @@ describe('compiler sfc: rewriteDefault', () => {
171196

172197
test('export default class w/ comments 2', async () => {
173198
expect(
174-
rewriteDefault(
199+
compileRewriteDefault(
175200
`export default {}\n` + `// export default class Foo {}`,
176201
'script'
177202
)
@@ -183,7 +208,7 @@ describe('compiler sfc: rewriteDefault', () => {
183208

184209
test('export default class w/ comments 3', async () => {
185210
expect(
186-
rewriteDefault(
211+
compileRewriteDefault(
187212
`/*\nexport default class Foo {}*/\n` + `export default class Bar {}`,
188213
'script'
189214
)
@@ -196,8 +221,9 @@ describe('compiler sfc: rewriteDefault', () => {
196221
})
197222

198223
test('@Component\nexport default class', async () => {
199-
expect(rewriteDefault(`@Component\nexport default class Foo {}`, 'script'))
200-
.toMatchInlineSnapshot(`
224+
expect(
225+
compileRewriteDefault(`@Component\nexport default class Foo {}`, 'script')
226+
).toMatchInlineSnapshot(`
201227
"@Component
202228
class Foo {}
203229
const script = Foo"
@@ -206,7 +232,7 @@ describe('compiler sfc: rewriteDefault', () => {
206232

207233
test('@Component\nexport default class w/ comments', async () => {
208234
expect(
209-
rewriteDefault(
235+
compileRewriteDefault(
210236
`// export default\n@Component\nexport default class Foo {}`,
211237
'script'
212238
)
@@ -220,7 +246,7 @@ describe('compiler sfc: rewriteDefault', () => {
220246

221247
test('@Component\nexport default class w/ comments 2', async () => {
222248
expect(
223-
rewriteDefault(
249+
compileRewriteDefault(
224250
`export default {}\n` + `// @Component\n// export default class Foo {}`,
225251
'script'
226252
)
@@ -233,7 +259,7 @@ describe('compiler sfc: rewriteDefault', () => {
233259

234260
test('@Component\nexport default class w/ comments 3', async () => {
235261
expect(
236-
rewriteDefault(
262+
compileRewriteDefault(
237263
`/*\n@Component\nexport default class Foo {}*/\n` +
238264
`export default class Bar {}`,
239265
'script'

packages/compiler-sfc/src/compileScript.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ export function compileScript(
235235
}
236236
}
237237
if (cssVars.length) {
238-
content = rewriteDefault(content, DEFAULT_VAR, plugins)
238+
content = rewriteDefault(content, scriptAst.body, DEFAULT_VAR)
239239
content += genNormalScriptCssVarsCode(
240240
cssVars,
241241
bindings,

packages/compiler-sfc/src/rewriteDefault.ts

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,22 @@
1-
import { parse, ParserPlugin } from '@babel/parser'
21
import MagicString from 'magic-string'
3-
4-
const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/
5-
const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)(?:as)?(\s*)default/s
6-
const exportDefaultClassRE =
7-
/((?:^|\n|;)\s*)export\s+default\s+class\s+([\w$]+)/
2+
import type { Identifier, Statement } from '@babel/types'
83

94
/**
105
* Utility for rewriting `export default` in a script block into a variable
116
* declaration so that we can inject things into it
127
*/
138
export function rewriteDefault(
149
input: string,
15-
as: string,
16-
parserPlugins?: ParserPlugin[]
10+
ast: Statement[],
11+
as: string
1712
): string {
18-
if (!hasDefaultExport(input)) {
13+
if (!hasDefaultExport(ast)) {
1914
return input + `\nconst ${as} = {}`
2015
}
2116

22-
let replaced: string | undefined
23-
24-
const classMatch = input.match(exportDefaultClassRE)
25-
if (classMatch) {
26-
replaced =
27-
input.replace(exportDefaultClassRE, '$1class $2') +
28-
`\nconst ${as} = ${classMatch[2]}`
29-
} else {
30-
replaced = input.replace(defaultExportRE, `$1const ${as} =`)
31-
}
32-
if (!hasDefaultExport(replaced)) {
33-
return replaced
34-
}
35-
3617
// if the script somehow still contains `default export`, it probably has
3718
// multi-line comments or template strings. fallback to a full parse.
3819
const s = new MagicString(input)
39-
const ast = parse(input, {
40-
sourceType: 'module',
41-
plugins: parserPlugins
42-
}).program.body
4320
ast.forEach(node => {
4421
if (node.type === 'ExportDefaultDeclaration') {
4522
if (node.declaration.type === 'ClassDeclaration') {
@@ -88,8 +65,20 @@ export function rewriteDefault(
8865
return s.toString()
8966
}
9067

91-
export function hasDefaultExport(input: string): boolean {
92-
return defaultExportRE.test(input) || namedDefaultExportRE.test(input)
68+
export function hasDefaultExport(ast: Statement[]): boolean {
69+
for (const stmt of ast) {
70+
if (stmt.type === 'ExportDefaultDeclaration') {
71+
return true
72+
} else if (
73+
stmt.type === 'ExportNamedDeclaration' &&
74+
stmt.specifiers.length > 0
75+
) {
76+
return stmt.specifiers.some(
77+
spec => (spec.exported as Identifier).name === 'default'
78+
)
79+
}
80+
}
81+
return false
9382
}
9483

9584
function specifierEnd(input: string, end: number, nodeEnd: number | null) {

0 commit comments

Comments
 (0)