fix(compiler-sfc): generate valid TS in script and script setup co-usage with TS
fix #5094
This commit is contained in:
parent
ea1fcfba37
commit
7e4f0a8694
@ -4,8 +4,10 @@ exports[`SFC compile <script setup> <script> and <script setup> co-usage script
|
||||
"import { x } from './x'
|
||||
|
||||
export const n = 1
|
||||
|
||||
const __default__ = {}
|
||||
|
||||
export default {
|
||||
export default /*#__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose }) {
|
||||
expose();
|
||||
|
||||
@ -14,13 +16,15 @@ export default {
|
||||
return { n, x }
|
||||
}
|
||||
|
||||
}"
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> <script> and <script setup> co-usage script setup first 1`] = `
|
||||
"import { x } from './x'
|
||||
"export const n = 1
|
||||
const __default__ = {}
|
||||
import { x } from './x'
|
||||
|
||||
export default {
|
||||
export default /*#__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose }) {
|
||||
expose();
|
||||
|
||||
@ -29,29 +33,48 @@ export default {
|
||||
return { n, x }
|
||||
}
|
||||
|
||||
}
|
||||
export const n = 1"
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> <script> and <script setup> co-usage script setup first, lang="ts", script block content export default 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { x } from './x'
|
||||
|
||||
const __default__ = {
|
||||
name: \\"test\\"
|
||||
}
|
||||
import { x } from './x'
|
||||
|
||||
function setup(__props, { expose }) {
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
...__default__,
|
||||
setup(__props, { expose }) {
|
||||
expose();
|
||||
|
||||
x()
|
||||
|
||||
return { x }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
const __default__ = {
|
||||
name: \\"test\\"
|
||||
}
|
||||
exports[`SFC compile <script setup> <script> and <script setup> co-usage script setup first, named default export 1`] = `
|
||||
"export const n = 1
|
||||
const def = {}
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
...__default__,
|
||||
setup})"
|
||||
|
||||
const __default__ = def
|
||||
import { x } from './x'
|
||||
|
||||
export default /*#__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose }) {
|
||||
expose();
|
||||
|
||||
x()
|
||||
|
||||
return { n, def, x }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
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
|
||||
some:'option'
|
||||
}
|
||||
|
||||
function setup(__props, { expose }) {
|
||||
export default /*#__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose }) {
|
||||
expose();
|
||||
|
||||
x()
|
||||
|
||||
return { n, x }
|
||||
}
|
||||
|
||||
|
||||
export default /*#__PURE__*/ Object.assign(__default__, {
|
||||
setup
|
||||
})"
|
||||
`;
|
||||
|
||||
@ -83,16 +105,15 @@ exports[`SFC compile <script setup> <script> and <script setup> co-usage spaces
|
||||
some:'option'
|
||||
}
|
||||
|
||||
function setup(__props, { expose }) {
|
||||
export default /*#__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose }) {
|
||||
expose();
|
||||
|
||||
x()
|
||||
|
||||
return { n, x }
|
||||
}
|
||||
|
||||
|
||||
export default /*#__PURE__*/ Object.assign(__default__, {
|
||||
setup
|
||||
})"
|
||||
`;
|
||||
|
||||
@ -980,7 +1001,12 @@ return () => {}
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> should expose top level declarations 1`] = `
|
||||
"import { x } from './x'
|
||||
"import { xx } from './x'
|
||||
let aa = 1
|
||||
const bb = 2
|
||||
function cc() {}
|
||||
class dd {}
|
||||
import { x } from './x'
|
||||
|
||||
export default {
|
||||
setup(__props, { expose }) {
|
||||
@ -994,12 +1020,7 @@ export default {
|
||||
return { aa, bb, cc, dd, a, b, c, d, xx, x }
|
||||
}
|
||||
|
||||
}
|
||||
import { xx } from './x'
|
||||
let aa = 1
|
||||
const bb = 2
|
||||
function cc() {}
|
||||
class dd {}"
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> with TypeScript const Enum 1`] = `
|
||||
|
@ -169,6 +169,68 @@ defineExpose({ foo: 123 })
|
||||
})
|
||||
|
||||
describe('<script> and <script setup> co-usage', () => {
|
||||
test('script first', () => {
|
||||
const { content } = compile(`
|
||||
<script>
|
||||
export const n = 1
|
||||
|
||||
export default {}
|
||||
</script>
|
||||
<script setup>
|
||||
import { x } from './x'
|
||||
x()
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
test('script setup first', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
import { x } from './x'
|
||||
x()
|
||||
</script>
|
||||
<script>
|
||||
export const n = 1
|
||||
export default {}
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
test('script setup first, named default export', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
import { x } from './x'
|
||||
x()
|
||||
</script>
|
||||
<script>
|
||||
export const n = 1
|
||||
const def = {}
|
||||
export { def as default }
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
// #4395
|
||||
test('script setup first, lang="ts", script block content export default', () => {
|
||||
const { content } = compile(`
|
||||
<script setup lang="ts">
|
||||
import { x } from './x'
|
||||
x()
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "test"
|
||||
}
|
||||
</script>
|
||||
`)
|
||||
// ensure __default__ is declared before used
|
||||
expect(content).toMatch(/const __default__[\S\s]*\.\.\.__default__/m)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
describe('spaces in ExportDefaultDeclaration node', () => {
|
||||
// #4371
|
||||
test('with many spaces and newline', () => {
|
||||
@ -205,50 +267,6 @@ defineExpose({ foo: 123 })
|
||||
assertCode(content)
|
||||
})
|
||||
})
|
||||
|
||||
test('script first', () => {
|
||||
const { content } = compile(`
|
||||
<script>
|
||||
export const n = 1
|
||||
</script>
|
||||
<script setup>
|
||||
import { x } from './x'
|
||||
x()
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
test('script setup first', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
import { x } from './x'
|
||||
x()
|
||||
</script>
|
||||
<script>
|
||||
export const n = 1
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
// #4395
|
||||
test('script setup first, lang="ts", script block content export default', () => {
|
||||
const { content } = compile(`
|
||||
<script setup lang="ts">
|
||||
import { x } from './x'
|
||||
x()
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "test"
|
||||
}
|
||||
</script>
|
||||
`)
|
||||
// ensure __default__ is declared before used
|
||||
expect(content).toMatch(/const __default__[\S\s]*\.\.\.__default__/m)
|
||||
assertCode(content)
|
||||
})
|
||||
})
|
||||
|
||||
describe('imports', () => {
|
||||
|
@ -59,6 +59,9 @@ const DEFINE_EMITS = 'defineEmits'
|
||||
const DEFINE_EXPOSE = 'defineExpose'
|
||||
const WITH_DEFAULTS = 'withDefaults'
|
||||
|
||||
// constants
|
||||
const DEFAULT_VAR = `__default__`
|
||||
|
||||
const isBuiltInDir = makeMap(
|
||||
`once,memo,if,else,else-if,slot,text,html,on,bind,model,show,cloak,is`
|
||||
)
|
||||
@ -214,14 +217,14 @@ export function compileScript(
|
||||
}
|
||||
}
|
||||
if (cssVars.length) {
|
||||
content = rewriteDefault(content, `__default__`, plugins)
|
||||
content = rewriteDefault(content, DEFAULT_VAR, plugins)
|
||||
content += genNormalScriptCssVarsCode(
|
||||
cssVars,
|
||||
bindings,
|
||||
scopeId,
|
||||
isProd
|
||||
)
|
||||
content += `\nexport default __default__`
|
||||
content += `\nexport default ${DEFAULT_VAR}`
|
||||
}
|
||||
return {
|
||||
...script,
|
||||
@ -251,7 +254,6 @@ export function compileScript(
|
||||
|
||||
// metadata that needs to be returned
|
||||
const bindingMetadata: BindingMetadata = {}
|
||||
const defaultTempVar = `__default__`
|
||||
const helperImports: Set<string> = new Set()
|
||||
const userImports: Record<string, ImportBinding> = Object.create(null)
|
||||
const userImportAlias: Record<string, string> = Object.create(null)
|
||||
@ -780,7 +782,6 @@ export function compileScript(
|
||||
// 1. process normal <script> first if it exists
|
||||
let scriptAst: Program | undefined
|
||||
if (script) {
|
||||
// import dedupe between <script> and <script setup>
|
||||
scriptAst = parse(
|
||||
script.content,
|
||||
{
|
||||
@ -809,9 +810,10 @@ export function compileScript(
|
||||
} else if (node.type === 'ExportDefaultDeclaration') {
|
||||
// export default
|
||||
defaultExport = node
|
||||
// export default { ... } --> const __default__ = { ... }
|
||||
const start = node.start! + scriptStartOffset!
|
||||
const end = node.declaration.start! + scriptStartOffset!
|
||||
s.overwrite(start, end, `const ${defaultTempVar} = `)
|
||||
s.overwrite(start, end, `const ${DEFAULT_VAR} = `)
|
||||
} else if (node.type === 'ExportNamedDeclaration') {
|
||||
const defaultSpecifier = node.specifiers.find(
|
||||
s => s.exported.type === 'Identifier' && s.exported.name === 'default'
|
||||
@ -835,13 +837,14 @@ export function compileScript(
|
||||
// rewrite to `import { x as __default__ } from './x'` and
|
||||
// add to top
|
||||
s.prepend(
|
||||
`import { ${defaultSpecifier.local.name} as ${defaultTempVar} } from '${node.source.value}'\n`
|
||||
`import { ${defaultSpecifier.local.name} as ${DEFAULT_VAR} } from '${node.source.value}'\n`
|
||||
)
|
||||
} else {
|
||||
// export { x as default }
|
||||
// rewrite to `const __default__ = x` and move to end
|
||||
s.append(
|
||||
`\nconst ${defaultTempVar} = ${defaultSpecifier.local.name}\n`
|
||||
s.appendLeft(
|
||||
scriptEndOffset!,
|
||||
`\nconst ${DEFAULT_VAR} = ${defaultSpecifier.local.name}\n`
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -871,6 +874,13 @@ export function compileScript(
|
||||
helperImports.add(h)
|
||||
}
|
||||
}
|
||||
|
||||
// <script> after <script setup>
|
||||
// we need to move the block up so that `const __default__` is
|
||||
// declared before being used in the actual component definition
|
||||
if (scriptStartOffset! > startOffset) {
|
||||
s.move(scriptStartOffset!, scriptEndOffset!, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. parse <script setup> and walk over top level statements
|
||||
@ -1384,46 +1394,33 @@ export function compileScript(
|
||||
// explicitly call `defineExpose`, call expose() with no args.
|
||||
const exposeCall =
|
||||
hasDefineExposeCall || options.inlineTemplate ? `` : ` expose();\n`
|
||||
// wrap setup code with function.
|
||||
if (isTS) {
|
||||
// for TS, make sure the exported type is still valid type with
|
||||
// correct props information
|
||||
// we have to use object spread for types to be merged properly
|
||||
// user's TS setting should compile it down to proper targets
|
||||
const def = defaultExport ? `\n ...${defaultTempVar},` : ``
|
||||
// wrap setup code with function.
|
||||
// export the content of <script setup> as a named export, `setup`.
|
||||
// this allows `import { setup } from '*.vue'` for testing purposes.
|
||||
if (defaultExport) {
|
||||
s.prependLeft(
|
||||
startOffset,
|
||||
`\n${hasAwait ? `async ` : ``}function setup(${args}) {\n`
|
||||
)
|
||||
s.append(
|
||||
`\nexport default /*#__PURE__*/${helper(
|
||||
`defineComponent`
|
||||
)}({${def}${runtimeOptions}\n setup})`
|
||||
)
|
||||
} else {
|
||||
s.prependLeft(
|
||||
startOffset,
|
||||
`\nexport default /*#__PURE__*/${helper(
|
||||
`defineComponent`
|
||||
)}({${def}${runtimeOptions}\n ${
|
||||
hasAwait ? `async ` : ``
|
||||
}setup(${args}) {\n${exposeCall}`
|
||||
)
|
||||
s.appendRight(endOffset, `})`)
|
||||
}
|
||||
// export default defineComponent({ ...__default__, ... })
|
||||
const def = defaultExport ? `\n ...${DEFAULT_VAR},` : ``
|
||||
s.prependLeft(
|
||||
startOffset,
|
||||
`\nexport default /*#__PURE__*/${helper(
|
||||
`defineComponent`
|
||||
)}({${def}${runtimeOptions}\n ${
|
||||
hasAwait ? `async ` : ``
|
||||
}setup(${args}) {\n${exposeCall}`
|
||||
)
|
||||
s.appendRight(endOffset, `})`)
|
||||
} else {
|
||||
if (defaultExport) {
|
||||
// can't rely on spread operator in non ts mode
|
||||
// without TS, can't rely on rest spread, so we use Object.assign
|
||||
// export default Object.assign(__default__, { ... })
|
||||
s.prependLeft(
|
||||
startOffset,
|
||||
`\n${hasAwait ? `async ` : ``}function setup(${args}) {\n`
|
||||
)
|
||||
s.append(
|
||||
`\nexport default /*#__PURE__*/ Object.assign(${defaultTempVar}, {${runtimeOptions}\n setup\n})\n`
|
||||
`\nexport default /*#__PURE__*/Object.assign(${DEFAULT_VAR}, {${runtimeOptions}\n ` +
|
||||
`${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`
|
||||
)
|
||||
s.appendRight(endOffset, `})`)
|
||||
} else {
|
||||
s.prependLeft(
|
||||
startOffset,
|
||||
|
Loading…
Reference in New Issue
Block a user