diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineOptions.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineOptions.spec.ts.snap
index 47f3cef0ae1..4a998168202 100644
--- a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineOptions.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineOptions.spec.ts.snap
@@ -25,3 +25,51 @@ return { }
}"
`;
+
+exports[`defineOptions() > should hoist comments even when called with no arguments 1`] = `
+"/** Script docstring. */
+const __default__ = {}
+
+/** Script setup docstring 1 */
+// Script setup docstring 2
+/**
+ * Script setup docstring 3
+ */
+export default /*#__PURE__*/Object.assign(__default__, {
+ setup(__props, { expose: __expose }) {
+ __expose();
+
+
+
+
+
+
+return { }
+}
+
+})"
+`;
+
+exports[`defineOptions() > should hoist leading comments up to export default 1`] = `
+"/** Script docstring. */
+const __default__ = {}
+
+/** Script setup docstring 1 */
+// Script setup docstring 2
+/**
+ * Script setup docstring 3
+ */
+export default /*#__PURE__*/Object.assign(__default__, {}, {
+ setup(__props, { expose: __expose }) {
+ __expose();
+
+
+
+
+
+
+return { }
+}
+
+})"
+`;
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineOptions.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineOptions.spec.ts
index e4f50be38f7..ee88474a949 100644
--- a/packages/compiler-sfc/__tests__/compileScript/defineOptions.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript/defineOptions.spec.ts
@@ -28,6 +28,66 @@ describe('defineOptions()', () => {
expect(content).not.toMatch('defineOptions')
})
+ it('should hoist leading comments up to export default', () => {
+ const { content } = compile(
+ `\n` +
+ `\n` +
+ `\n`
+ )
+ assertCode(content)
+ // export default comments are set before __default__.
+ expect(content).toMatch(`/** Script docstring. */\nconst __default__ = {}`)
+ // defineOptions comments are set before export default.
+ expect(content).toMatch(
+ `/** Script setup docstring 1 */\n` +
+ `// Script setup docstring 2\n` +
+ `/**\n` +
+ ` * Script setup docstring 3\n` +
+ ` */\n` +
+ `export default /*#__PURE__*/Object.assign(__default__, {}, {`
+ )
+ })
+
+ it('should hoist comments even when called with no arguments', () => {
+ const { content } = compile(
+ `\n` +
+ `\n` +
+ `\n`
+ )
+ assertCode(content)
+ // export default comments are set before __default__.
+ expect(content).toMatch(`/** Script docstring. */\nconst __default__ = {}`)
+ // defineOptions comments are set before export default.
+ expect(content).toMatch(
+ `/** Script setup docstring 1 */\n` +
+ `// Script setup docstring 2\n` +
+ `/**\n` +
+ ` * Script setup docstring 3\n` +
+ ` */\n` +
+ `export default /*#__PURE__*/Object.assign(__default__, {`
+ )
+ })
+
it('should emit an error with two defineOptions', () => {
expect(() =>
compile(`
diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts
index cfcc607c72d..ebf19bb51a3 100644
--- a/packages/compiler-sfc/src/compileScript.ts
+++ b/packages/compiler-sfc/src/compileScript.ts
@@ -511,10 +511,14 @@ export function compileScript(
if (
processDefineProps(ctx, expr) ||
processDefineEmits(ctx, expr) ||
- processDefineOptions(ctx, expr) ||
processDefineSlots(ctx, expr)
) {
ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
+ } else if (processDefineOptions(ctx, expr, node.leadingComments ?? [])) {
+ ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
+ for (const comment of node.leadingComments ?? []) {
+ ctx.s.remove(comment.start! + startOffset, comment.end! + startOffset)
+ }
} else if (processDefineExpose(ctx, expr)) {
// defineExpose({}) -> expose({})
const callee = (expr as CallExpression).callee
@@ -948,9 +952,23 @@ export function compileScript(
}
// 11. finalize default export
- const genDefaultAs = options.genDefaultAs
- ? `const ${options.genDefaultAs} =`
- : `export default`
+ const defaultLeadingComments = ctx.optionsLeadingComments
+ .map(c => {
+ switch (c.type) {
+ case 'CommentBlock':
+ return `/*${c.value}*/\n`
+ case 'CommentLine':
+ return `//${c.value}\n`
+ default:
+ c satisfies never
+ }
+ })
+ .join('')
+ const genDefaultAs =
+ defaultLeadingComments +
+ (options.genDefaultAs
+ ? `const ${options.genDefaultAs} =`
+ : `export default`)
let runtimeOptions = ``
if (!ctx.hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts
index 5fe09d28a42..c4dd8e71ca9 100644
--- a/packages/compiler-sfc/src/script/context.ts
+++ b/packages/compiler-sfc/src/script/context.ts
@@ -1,4 +1,10 @@
-import { CallExpression, Node, ObjectPattern, Program } from '@babel/types'
+import {
+ CallExpression,
+ Comment,
+ Node,
+ ObjectPattern,
+ Program
+} from '@babel/types'
import { SFCDescriptor } from '../parse'
import { generateCodeFrame } from '@vue/shared'
import { parse as babelParse, ParserPlugin } from '@babel/parser'
@@ -56,6 +62,7 @@ export class ScriptCompileContext {
modelDecls: Record = {}
// defineOptions
+ optionsLeadingComments: Comment[] = []
optionsRuntimeDecl: Node | undefined
// codegen
diff --git a/packages/compiler-sfc/src/script/defineOptions.ts b/packages/compiler-sfc/src/script/defineOptions.ts
index 8da3dbc0b4d..5d9fcf6ec65 100644
--- a/packages/compiler-sfc/src/script/defineOptions.ts
+++ b/packages/compiler-sfc/src/script/defineOptions.ts
@@ -1,4 +1,4 @@
-import { Node } from '@babel/types'
+import { Node, Comment } from '@babel/types'
import { ScriptCompileContext } from './context'
import { isCallOf, unwrapTSNode } from './utils'
import { DEFINE_PROPS } from './defineProps'
@@ -10,7 +10,8 @@ export const DEFINE_OPTIONS = 'defineOptions'
export function processDefineOptions(
ctx: ScriptCompileContext,
- node: Node
+ node: Node,
+ leadingComments: Comment[] = []
): boolean {
if (!isCallOf(node, DEFINE_OPTIONS)) {
return false
@@ -21,6 +22,10 @@ export function processDefineOptions(
if (node.typeParameters) {
ctx.error(`${DEFINE_OPTIONS}() cannot accept type arguments`, node)
}
+
+ // Always setting leadingComments, even when called with no arguments.
+ ctx.optionsLeadingComments = leadingComments
+
if (!node.arguments[0]) return true
ctx.hasDefineOptionsCall = true