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