Skip to content

Commit 7e4f0a8

Browse files
committedDec 12, 2021
fix(compiler-sfc): generate valid TS in script and script setup co-usage with TS
fix #5094
1 parent ea1fcfb commit 7e4f0a8

File tree

3 files changed

+140
-104
lines changed

3 files changed

+140
-104
lines changed
 

‎packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap

+50-29
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ exports[`SFC compile <script setup> <script> and <script setup> co-usage script
44
"import { x } from './x'
55
66
export const n = 1
7+
8+
const __default__ = {}
79
8-
export default {
10+
export default /*#__PURE__*/Object.assign(__default__, {
911
setup(__props, { expose }) {
1012
expose();
1113
@@ -14,13 +16,15 @@ export default {
1416
return { n, x }
1517
}
1618
17-
}"
19+
})"
1820
`;
1921
2022
exports[`SFC compile <script setup> <script> and <script setup> co-usage script setup first 1`] = `
21-
"import { x } from './x'
23+
"export const n = 1
24+
const __default__ = {}
25+
import { x } from './x'
2226
23-
export default {
27+
export default /*#__PURE__*/Object.assign(__default__, {
2428
setup(__props, { expose }) {
2529
expose();
2630
@@ -29,29 +33,48 @@ export default {
2933
return { n, x }
3034
}
3135
32-
}
33-
export const n = 1"
36+
})"
3437
`;
3538
3639
exports[`SFC compile <script setup> <script> and <script setup> co-usage script setup first, lang="ts", script block content export default 1`] = `
3740
"import { defineComponent as _defineComponent } from 'vue'
38-
import { x } from './x'
41+
42+
const __default__ = {
43+
name: \\"test\\"
44+
}
45+
import { x } from './x'
3946
40-
function setup(__props, { expose }) {
47+
export default /*#__PURE__*/_defineComponent({
48+
...__default__,
49+
setup(__props, { expose }) {
50+
expose();
4151
4252
x()
4353
4454
return { x }
4555
}
4656
57+
})"
58+
`;
4759
48-
const __default__ = {
49-
name: \\"test\\"
50-
}
60+
exports[`SFC compile <script setup> <script> and <script setup> co-usage script setup first, named default export 1`] = `
61+
"export const n = 1
62+
const def = {}
5163
52-
export default /*#__PURE__*/_defineComponent({
53-
...__default__,
54-
setup})"
64+
65+
const __default__ = def
66+
import { x } from './x'
67+
68+
export default /*#__PURE__*/Object.assign(__default__, {
69+
setup(__props, { expose }) {
70+
expose();
71+
72+
x()
73+
74+
return { n, def, x }
75+
}
76+
77+
})"
5578
`;
5679
5780
exports[`SFC compile <script setup> <script> and <script setup> co-usage spaces in ExportDefaultDeclaration node with many spaces and newline 1`] = `
@@ -62,16 +85,15 @@ exports[`SFC compile <script setup> <script> and <script setup> co-usage spaces
6285
some:'option'
6386
}
6487
65-
function setup(__props, { expose }) {
88+
export default /*#__PURE__*/Object.assign(__default__, {
89+
setup(__props, { expose }) {
90+
expose();
6691
6792
x()
6893
6994
return { n, x }
7095
}
7196
72-
73-
export default /*#__PURE__*/ Object.assign(__default__, {
74-
setup
7597
})"
7698
`;
7799
@@ -83,16 +105,15 @@ exports[`SFC compile <script setup> <script> and <script setup> co-usage spaces
83105
some:'option'
84106
}
85107
86-
function setup(__props, { expose }) {
108+
export default /*#__PURE__*/Object.assign(__default__, {
109+
setup(__props, { expose }) {
110+
expose();
87111
88112
x()
89113
90114
return { n, x }
91115
}
92116
93-
94-
export default /*#__PURE__*/ Object.assign(__default__, {
95-
setup
96117
})"
97118
`;
98119
@@ -980,7 +1001,12 @@ return () => {}
9801001
`;
9811002
9821003
exports[`SFC compile <script setup> should expose top level declarations 1`] = `
983-
"import { x } from './x'
1004+
"import { xx } from './x'
1005+
let aa = 1
1006+
const bb = 2
1007+
function cc() {}
1008+
class dd {}
1009+
import { x } from './x'
9841010
9851011
export default {
9861012
setup(__props, { expose }) {
@@ -994,12 +1020,7 @@ export default {
9941020
return { aa, bb, cc, dd, a, b, c, d, xx, x }
9951021
}
9961022
997-
}
998-
import { xx } from './x'
999-
let aa = 1
1000-
const bb = 2
1001-
function cc() {}
1002-
class dd {}"
1023+
}"
10031024
`;
10041025
10051026
exports[`SFC compile <script setup> with TypeScript const Enum 1`] = `

‎packages/compiler-sfc/__tests__/compileScript.spec.ts

+55-37
Original file line numberDiff line numberDiff line change
@@ -169,47 +169,12 @@ defineExpose({ foo: 123 })
169169
})
170170

171171
describe('<script> and <script setup> co-usage', () => {
172-
describe('spaces in ExportDefaultDeclaration node', () => {
173-
// #4371
174-
test('with many spaces and newline', () => {
175-
// #4371
176-
const { content } = compile(`
177-
<script>
178-
export const n = 1
179-
export default
180-
{
181-
some:'option'
182-
}
183-
</script>
184-
<script setup>
185-
import { x } from './x'
186-
x()
187-
</script>
188-
`)
189-
assertCode(content)
190-
})
191-
192-
test('with minimal spaces', () => {
193-
const { content } = compile(`
194-
<script>
195-
export const n = 1
196-
export default{
197-
some:'option'
198-
}
199-
</script>
200-
<script setup>
201-
import { x } from './x'
202-
x()
203-
</script>
204-
`)
205-
assertCode(content)
206-
})
207-
})
208-
209172
test('script first', () => {
210173
const { content } = compile(`
211174
<script>
212175
export const n = 1
176+
177+
export default {}
213178
</script>
214179
<script setup>
215180
import { x } from './x'
@@ -227,6 +192,22 @@ defineExpose({ foo: 123 })
227192
</script>
228193
<script>
229194
export const n = 1
195+
export default {}
196+
</script>
197+
`)
198+
assertCode(content)
199+
})
200+
201+
test('script setup first, named default export', () => {
202+
const { content } = compile(`
203+
<script setup>
204+
import { x } from './x'
205+
x()
206+
</script>
207+
<script>
208+
export const n = 1
209+
const def = {}
210+
export { def as default }
230211
</script>
231212
`)
232213
assertCode(content)
@@ -249,6 +230,43 @@ defineExpose({ foo: 123 })
249230
expect(content).toMatch(/const __default__[\S\s]*\.\.\.__default__/m)
250231
assertCode(content)
251232
})
233+
234+
describe('spaces in ExportDefaultDeclaration node', () => {
235+
// #4371
236+
test('with many spaces and newline', () => {
237+
// #4371
238+
const { content } = compile(`
239+
<script>
240+
export const n = 1
241+
export default
242+
{
243+
some:'option'
244+
}
245+
</script>
246+
<script setup>
247+
import { x } from './x'
248+
x()
249+
</script>
250+
`)
251+
assertCode(content)
252+
})
253+
254+
test('with minimal spaces', () => {
255+
const { content } = compile(`
256+
<script>
257+
export const n = 1
258+
export default{
259+
some:'option'
260+
}
261+
</script>
262+
<script setup>
263+
import { x } from './x'
264+
x()
265+
</script>
266+
`)
267+
assertCode(content)
268+
})
269+
})
252270
})
253271

254272
describe('imports', () => {

‎packages/compiler-sfc/src/compileScript.ts

+35-38
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ const DEFINE_EMITS = 'defineEmits'
5959
const DEFINE_EXPOSE = 'defineExpose'
6060
const WITH_DEFAULTS = 'withDefaults'
6161

62+
// constants
63+
const DEFAULT_VAR = `__default__`
64+
6265
const isBuiltInDir = makeMap(
6366
`once,memo,if,else,else-if,slot,text,html,on,bind,model,show,cloak,is`
6467
)
@@ -214,14 +217,14 @@ export function compileScript(
214217
}
215218
}
216219
if (cssVars.length) {
217-
content = rewriteDefault(content, `__default__`, plugins)
220+
content = rewriteDefault(content, DEFAULT_VAR, plugins)
218221
content += genNormalScriptCssVarsCode(
219222
cssVars,
220223
bindings,
221224
scopeId,
222225
isProd
223226
)
224-
content += `\nexport default __default__`
227+
content += `\nexport default ${DEFAULT_VAR}`
225228
}
226229
return {
227230
...script,
@@ -251,7 +254,6 @@ export function compileScript(
251254

252255
// metadata that needs to be returned
253256
const bindingMetadata: BindingMetadata = {}
254-
const defaultTempVar = `__default__`
255257
const helperImports: Set<string> = new Set()
256258
const userImports: Record<string, ImportBinding> = Object.create(null)
257259
const userImportAlias: Record<string, string> = Object.create(null)
@@ -780,7 +782,6 @@ export function compileScript(
780782
// 1. process normal <script> first if it exists
781783
let scriptAst: Program | undefined
782784
if (script) {
783-
// import dedupe between <script> and <script setup>
784785
scriptAst = parse(
785786
script.content,
786787
{
@@ -809,9 +810,10 @@ export function compileScript(
809810
} else if (node.type === 'ExportDefaultDeclaration') {
810811
// export default
811812
defaultExport = node
813+
// export default { ... } --> const __default__ = { ... }
812814
const start = node.start! + scriptStartOffset!
813815
const end = node.declaration.start! + scriptStartOffset!
814-
s.overwrite(start, end, `const ${defaultTempVar} = `)
816+
s.overwrite(start, end, `const ${DEFAULT_VAR} = `)
815817
} else if (node.type === 'ExportNamedDeclaration') {
816818
const defaultSpecifier = node.specifiers.find(
817819
s => s.exported.type === 'Identifier' && s.exported.name === 'default'
@@ -835,13 +837,14 @@ export function compileScript(
835837
// rewrite to `import { x as __default__ } from './x'` and
836838
// add to top
837839
s.prepend(
838-
`import { ${defaultSpecifier.local.name} as ${defaultTempVar} } from '${node.source.value}'\n`
840+
`import { ${defaultSpecifier.local.name} as ${DEFAULT_VAR} } from '${node.source.value}'\n`
839841
)
840842
} else {
841843
// export { x as default }
842844
// rewrite to `const __default__ = x` and move to end
843-
s.append(
844-
`\nconst ${defaultTempVar} = ${defaultSpecifier.local.name}\n`
845+
s.appendLeft(
846+
scriptEndOffset!,
847+
`\nconst ${DEFAULT_VAR} = ${defaultSpecifier.local.name}\n`
845848
)
846849
}
847850
}
@@ -871,6 +874,13 @@ export function compileScript(
871874
helperImports.add(h)
872875
}
873876
}
877+
878+
// <script> after <script setup>
879+
// we need to move the block up so that `const __default__` is
880+
// declared before being used in the actual component definition
881+
if (scriptStartOffset! > startOffset) {
882+
s.move(scriptStartOffset!, scriptEndOffset!, 0)
883+
}
874884
}
875885

876886
// 2. parse <script setup> and walk over top level statements
@@ -1384,46 +1394,33 @@ export function compileScript(
13841394
// explicitly call `defineExpose`, call expose() with no args.
13851395
const exposeCall =
13861396
hasDefineExposeCall || options.inlineTemplate ? `` : ` expose();\n`
1397+
// wrap setup code with function.
13871398
if (isTS) {
13881399
// for TS, make sure the exported type is still valid type with
13891400
// correct props information
13901401
// we have to use object spread for types to be merged properly
13911402
// user's TS setting should compile it down to proper targets
1392-
const def = defaultExport ? `\n ...${defaultTempVar},` : ``
1393-
// wrap setup code with function.
1394-
// export the content of <script setup> as a named export, `setup`.
1395-
// this allows `import { setup } from '*.vue'` for testing purposes.
1396-
if (defaultExport) {
1397-
s.prependLeft(
1398-
startOffset,
1399-
`\n${hasAwait ? `async ` : ``}function setup(${args}) {\n`
1400-
)
1401-
s.append(
1402-
`\nexport default /*#__PURE__*/${helper(
1403-
`defineComponent`
1404-
)}({${def}${runtimeOptions}\n setup})`
1405-
)
1406-
} else {
1407-
s.prependLeft(
1408-
startOffset,
1409-
`\nexport default /*#__PURE__*/${helper(
1410-
`defineComponent`
1411-
)}({${def}${runtimeOptions}\n ${
1412-
hasAwait ? `async ` : ``
1413-
}setup(${args}) {\n${exposeCall}`
1414-
)
1415-
s.appendRight(endOffset, `})`)
1416-
}
1403+
// export default defineComponent({ ...__default__, ... })
1404+
const def = defaultExport ? `\n ...${DEFAULT_VAR},` : ``
1405+
s.prependLeft(
1406+
startOffset,
1407+
`\nexport default /*#__PURE__*/${helper(
1408+
`defineComponent`
1409+
)}({${def}${runtimeOptions}\n ${
1410+
hasAwait ? `async ` : ``
1411+
}setup(${args}) {\n${exposeCall}`
1412+
)
1413+
s.appendRight(endOffset, `})`)
14171414
} else {
14181415
if (defaultExport) {
1419-
// can't rely on spread operator in non ts mode
1416+
// without TS, can't rely on rest spread, so we use Object.assign
1417+
// export default Object.assign(__default__, { ... })
14201418
s.prependLeft(
14211419
startOffset,
1422-
`\n${hasAwait ? `async ` : ``}function setup(${args}) {\n`
1423-
)
1424-
s.append(
1425-
`\nexport default /*#__PURE__*/ Object.assign(${defaultTempVar}, {${runtimeOptions}\n setup\n})\n`
1420+
`\nexport default /*#__PURE__*/Object.assign(${DEFAULT_VAR}, {${runtimeOptions}\n ` +
1421+
`${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`
14261422
)
1423+
s.appendRight(endOffset, `})`)
14271424
} else {
14281425
s.prependLeft(
14291426
startOffset,

0 commit comments

Comments
 (0)
Please sign in to comment.