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'
|
"import { x } from './x'
|
||||||
|
|
||||||
export const n = 1
|
export const n = 1
|
||||||
|
|
||||||
|
const __default__ = {}
|
||||||
|
|
||||||
export default {
|
export default /*#__PURE__*/Object.assign(__default__, {
|
||||||
setup(__props, { expose }) {
|
setup(__props, { expose }) {
|
||||||
expose();
|
expose();
|
||||||
|
|
||||||
@ -14,13 +16,15 @@ export default {
|
|||||||
return { n, x }
|
return { n, x }
|
||||||
}
|
}
|
||||||
|
|
||||||
}"
|
})"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> <script> and <script setup> co-usage script setup first 1`] = `
|
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 }) {
|
setup(__props, { expose }) {
|
||||||
expose();
|
expose();
|
||||||
|
|
||||||
@ -29,29 +33,48 @@ export default {
|
|||||||
return { n, x }
|
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`] = `
|
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 { 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()
|
x()
|
||||||
|
|
||||||
return { x }
|
return { x }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
const __default__ = {
|
exports[`SFC compile <script setup> <script> and <script setup> co-usage script setup first, named default export 1`] = `
|
||||||
name: \\"test\\"
|
"export const n = 1
|
||||||
}
|
const def = {}
|
||||||
|
|
||||||
export default /*#__PURE__*/_defineComponent({
|
|
||||||
...__default__,
|
const __default__ = def
|
||||||
setup})"
|
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`] = `
|
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'
|
some:'option'
|
||||||
}
|
}
|
||||||
|
|
||||||
function setup(__props, { expose }) {
|
export default /*#__PURE__*/Object.assign(__default__, {
|
||||||
|
setup(__props, { expose }) {
|
||||||
|
expose();
|
||||||
|
|
||||||
x()
|
x()
|
||||||
|
|
||||||
return { n, 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'
|
some:'option'
|
||||||
}
|
}
|
||||||
|
|
||||||
function setup(__props, { expose }) {
|
export default /*#__PURE__*/Object.assign(__default__, {
|
||||||
|
setup(__props, { expose }) {
|
||||||
|
expose();
|
||||||
|
|
||||||
x()
|
x()
|
||||||
|
|
||||||
return { n, 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`] = `
|
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 {
|
export default {
|
||||||
setup(__props, { expose }) {
|
setup(__props, { expose }) {
|
||||||
@ -994,12 +1020,7 @@ export default {
|
|||||||
return { aa, bb, cc, dd, a, b, c, d, xx, x }
|
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`] = `
|
exports[`SFC compile <script setup> with TypeScript const Enum 1`] = `
|
||||||
|
@ -169,6 +169,68 @@ defineExpose({ foo: 123 })
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('<script> and <script setup> co-usage', () => {
|
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', () => {
|
describe('spaces in ExportDefaultDeclaration node', () => {
|
||||||
// #4371
|
// #4371
|
||||||
test('with many spaces and newline', () => {
|
test('with many spaces and newline', () => {
|
||||||
@ -205,50 +267,6 @@ defineExpose({ foo: 123 })
|
|||||||
assertCode(content)
|
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', () => {
|
describe('imports', () => {
|
||||||
|
@ -59,6 +59,9 @@ const DEFINE_EMITS = 'defineEmits'
|
|||||||
const DEFINE_EXPOSE = 'defineExpose'
|
const DEFINE_EXPOSE = 'defineExpose'
|
||||||
const WITH_DEFAULTS = 'withDefaults'
|
const WITH_DEFAULTS = 'withDefaults'
|
||||||
|
|
||||||
|
// constants
|
||||||
|
const DEFAULT_VAR = `__default__`
|
||||||
|
|
||||||
const isBuiltInDir = makeMap(
|
const isBuiltInDir = makeMap(
|
||||||
`once,memo,if,else,else-if,slot,text,html,on,bind,model,show,cloak,is`
|
`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) {
|
if (cssVars.length) {
|
||||||
content = rewriteDefault(content, `__default__`, plugins)
|
content = rewriteDefault(content, DEFAULT_VAR, plugins)
|
||||||
content += genNormalScriptCssVarsCode(
|
content += genNormalScriptCssVarsCode(
|
||||||
cssVars,
|
cssVars,
|
||||||
bindings,
|
bindings,
|
||||||
scopeId,
|
scopeId,
|
||||||
isProd
|
isProd
|
||||||
)
|
)
|
||||||
content += `\nexport default __default__`
|
content += `\nexport default ${DEFAULT_VAR}`
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...script,
|
...script,
|
||||||
@ -251,7 +254,6 @@ export function compileScript(
|
|||||||
|
|
||||||
// metadata that needs to be returned
|
// metadata that needs to be returned
|
||||||
const bindingMetadata: BindingMetadata = {}
|
const bindingMetadata: BindingMetadata = {}
|
||||||
const defaultTempVar = `__default__`
|
|
||||||
const helperImports: Set<string> = new Set()
|
const helperImports: Set<string> = new Set()
|
||||||
const userImports: Record<string, ImportBinding> = Object.create(null)
|
const userImports: Record<string, ImportBinding> = Object.create(null)
|
||||||
const userImportAlias: Record<string, string> = 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
|
// 1. process normal <script> first if it exists
|
||||||
let scriptAst: Program | undefined
|
let scriptAst: Program | undefined
|
||||||
if (script) {
|
if (script) {
|
||||||
// import dedupe between <script> and <script setup>
|
|
||||||
scriptAst = parse(
|
scriptAst = parse(
|
||||||
script.content,
|
script.content,
|
||||||
{
|
{
|
||||||
@ -809,9 +810,10 @@ export function compileScript(
|
|||||||
} else if (node.type === 'ExportDefaultDeclaration') {
|
} else if (node.type === 'ExportDefaultDeclaration') {
|
||||||
// export default
|
// export default
|
||||||
defaultExport = node
|
defaultExport = node
|
||||||
|
// export default { ... } --> const __default__ = { ... }
|
||||||
const start = node.start! + scriptStartOffset!
|
const start = node.start! + scriptStartOffset!
|
||||||
const end = node.declaration.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') {
|
} else if (node.type === 'ExportNamedDeclaration') {
|
||||||
const defaultSpecifier = node.specifiers.find(
|
const defaultSpecifier = node.specifiers.find(
|
||||||
s => s.exported.type === 'Identifier' && s.exported.name === 'default'
|
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
|
// rewrite to `import { x as __default__ } from './x'` and
|
||||||
// add to top
|
// add to top
|
||||||
s.prepend(
|
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 {
|
} else {
|
||||||
// export { x as default }
|
// export { x as default }
|
||||||
// rewrite to `const __default__ = x` and move to end
|
// rewrite to `const __default__ = x` and move to end
|
||||||
s.append(
|
s.appendLeft(
|
||||||
`\nconst ${defaultTempVar} = ${defaultSpecifier.local.name}\n`
|
scriptEndOffset!,
|
||||||
|
`\nconst ${DEFAULT_VAR} = ${defaultSpecifier.local.name}\n`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -871,6 +874,13 @@ export function compileScript(
|
|||||||
helperImports.add(h)
|
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
|
// 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.
|
// explicitly call `defineExpose`, call expose() with no args.
|
||||||
const exposeCall =
|
const exposeCall =
|
||||||
hasDefineExposeCall || options.inlineTemplate ? `` : ` expose();\n`
|
hasDefineExposeCall || options.inlineTemplate ? `` : ` expose();\n`
|
||||||
|
// wrap setup code with function.
|
||||||
if (isTS) {
|
if (isTS) {
|
||||||
// for TS, make sure the exported type is still valid type with
|
// for TS, make sure the exported type is still valid type with
|
||||||
// correct props information
|
// correct props information
|
||||||
// we have to use object spread for types to be merged properly
|
// we have to use object spread for types to be merged properly
|
||||||
// user's TS setting should compile it down to proper targets
|
// user's TS setting should compile it down to proper targets
|
||||||
const def = defaultExport ? `\n ...${defaultTempVar},` : ``
|
// export default defineComponent({ ...__default__, ... })
|
||||||
// wrap setup code with function.
|
const def = defaultExport ? `\n ...${DEFAULT_VAR},` : ``
|
||||||
// export the content of <script setup> as a named export, `setup`.
|
s.prependLeft(
|
||||||
// this allows `import { setup } from '*.vue'` for testing purposes.
|
startOffset,
|
||||||
if (defaultExport) {
|
`\nexport default /*#__PURE__*/${helper(
|
||||||
s.prependLeft(
|
`defineComponent`
|
||||||
startOffset,
|
)}({${def}${runtimeOptions}\n ${
|
||||||
`\n${hasAwait ? `async ` : ``}function setup(${args}) {\n`
|
hasAwait ? `async ` : ``
|
||||||
)
|
}setup(${args}) {\n${exposeCall}`
|
||||||
s.append(
|
)
|
||||||
`\nexport default /*#__PURE__*/${helper(
|
s.appendRight(endOffset, `})`)
|
||||||
`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, `})`)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (defaultExport) {
|
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(
|
s.prependLeft(
|
||||||
startOffset,
|
startOffset,
|
||||||
`\n${hasAwait ? `async ` : ``}function setup(${args}) {\n`
|
`\nexport default /*#__PURE__*/Object.assign(${DEFAULT_VAR}, {${runtimeOptions}\n ` +
|
||||||
)
|
`${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`
|
||||||
s.append(
|
|
||||||
`\nexport default /*#__PURE__*/ Object.assign(${defaultTempVar}, {${runtimeOptions}\n setup\n})\n`
|
|
||||||
)
|
)
|
||||||
|
s.appendRight(endOffset, `})`)
|
||||||
} else {
|
} else {
|
||||||
s.prependLeft(
|
s.prependLeft(
|
||||||
startOffset,
|
startOffset,
|
||||||
|
Loading…
Reference in New Issue
Block a user