expect(content).toMatch(`emits: ['a'],`)
})
+ describe('defineOptions()', () => {
+ test('basic usage', () => {
+ const { content } = compile(`
+<script setup>
+defineOptions({ name: 'FooApp' })
+</script>
+ `)
+ assertCode(content)
+ // should remove defineOptions import and call
+ expect(content).not.toMatch('defineOptions')
+ // should include context options in default export
+ expect(content).toMatch(
+ `export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, `
+ )
+ })
+
+ it('should emit an error with two defineProps', () => {
+ expect(() =>
+ compile(`
+ <script setup>
+ defineOptions({ name: 'FooApp' })
+ defineOptions({ name: 'BarApp' })
+ </script>
+ `)
+ ).toThrowError('[@vue/compiler-sfc] duplicate defineOptions() call')
+ })
+
+ it('should emit an error with props or emits property', () => {
+ expect(() =>
+ compile(`
+ <script setup>
+ defineOptions({ props: { foo: String } })
+ </script>
+ `)
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.'
+ )
+
+ expect(() =>
+ compile(`
+ <script setup>
+ defineOptions({ emits: ['update'] })
+ </script>
+ `)
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead.'
+ )
+ })
+
+ it('should emit an error with type generic', () => {
+ expect(() =>
+ compile(`
+ <script setup lang="ts">
+ defineOptions<{ name: 'FooApp' }>()
+ </script>
+ `)
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot accept type arguments'
+ )
+ })
+ })
+
test('defineExpose()', () => {
const { content } = compile(`
<script setup>
`)
assertCode(content)
})
-
+
// #7111
test('withDefaults (static) w/ production mode', () => {
const { content } = compile(
expect(content).toMatch(`emits: ["foo", "bar"]`)
})
-
test('defineEmits w/ type from normal script', () => {
const { content } = compile(`
<script lang="ts">
const DEFINE_EMITS = 'defineEmits'
const DEFINE_EXPOSE = 'defineExpose'
const WITH_DEFAULTS = 'withDefaults'
+const DEFINE_OPTIONS = 'defineOptions'
// constants
const DEFAULT_VAR = `__default__`
let hasDefineExposeCall = false
let hasDefaultExportName = false
let hasDefaultExportRender = false
+ let hasDefineOptionsCall = false
let propsRuntimeDecl: Node | undefined
let propsRuntimeDefaults: ObjectExpression | undefined
let propsDestructureDecl: Node | undefined
let emitsTypeDecl: EmitsDeclType | undefined
let emitsTypeDeclRaw: Node | undefined
let emitIdentifier: string | undefined
+ let optionsRuntimeDecl: Node | undefined
let hasAwait = false
let hasInlinedSsrRenderFn = false
// props/emits declared via types
})
}
+ function processDefineOptions(node: Node): boolean {
+ if (!isCallOf(node, DEFINE_OPTIONS)) {
+ return false
+ }
+ if (hasDefineOptionsCall) {
+ error(`duplicate ${DEFINE_OPTIONS}() call`, node)
+ }
+ if (node.typeParameters) {
+ error(`${DEFINE_OPTIONS}() cannot accept type arguments`, node)
+ }
+
+ hasDefineOptionsCall = true
+ optionsRuntimeDecl = node.arguments[0]
+
+ let propsOption = undefined
+ let emitsOption = undefined
+ if (optionsRuntimeDecl.type === 'ObjectExpression') {
+ for (const prop of optionsRuntimeDecl.properties) {
+ if (
+ (prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') &&
+ prop.key.type === 'Identifier'
+ ) {
+ if (prop.key.name === 'props') propsOption = prop
+ if (prop.key.name === 'emits') emitsOption = prop
+ }
+ }
+ }
+
+ if (propsOption) {
+ error(
+ `${DEFINE_OPTIONS}() cannot be used to declare props. Use ${DEFINE_PROPS}() instead.`,
+ propsOption
+ )
+ }
+ if (emitsOption) {
+ error(
+ `${DEFINE_OPTIONS}() cannot be used to declare emits. Use ${DEFINE_EMITS}() instead.`,
+ emitsOption
+ )
+ }
+
+ return true
+ }
+
function resolveQualifiedType(
node: Node,
qualifier: (node: Node) => boolean
if (
processDefineProps(node.expression) ||
processDefineEmits(node.expression) ||
+ processDefineOptions(node.expression) ||
processWithDefaults(node.expression)
) {
s.remove(node.start! + startOffset, node.end! + startOffset)
for (let i = 0; i < total; i++) {
const decl = node.declarations[i]
if (decl.init) {
+ if (processDefineOptions(decl.init)) {
+ error(
+ `${DEFINE_OPTIONS}() has no returning value, it cannot be assigned.`,
+ node
+ )
+ }
+
// defineProps / defineEmits
const isDefineProps =
processDefineProps(decl.init, decl.id, node.kind) ||
checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS)
checkInvalidScopeReference(propsDestructureDecl, DEFINE_PROPS)
checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_EMITS)
+ checkInvalidScopeReference(optionsRuntimeDecl, DEFINE_OPTIONS)
// 6. remove non-script content
if (script) {
runtimeOptions += genRuntimeEmits(typeDeclaredEmits)
}
+ let definedOptions = ''
+ if (optionsRuntimeDecl) {
+ definedOptions = scriptSetup.content
+ .slice(optionsRuntimeDecl.start!, optionsRuntimeDecl.end!)
+ .trim()
+ }
+
// <script setup> components are closed by default. If the user did not
// explicitly call `defineExpose`, call expose() with no args.
const exposeCall =
// we have to use object spread for types to be merged properly
// user's TS setting should compile it down to proper targets
// export default defineComponent({ ...__default__, ... })
- const def = defaultExport ? `\n ...${DEFAULT_VAR},` : ``
+ const def =
+ (defaultExport ? `\n ...${DEFAULT_VAR},` : ``) +
+ (definedOptions ? `\n ...${definedOptions},` : '')
s.prependLeft(
startOffset,
`\nexport default /*#__PURE__*/${helper(
)
s.appendRight(endOffset, `})`)
} else {
- if (defaultExport) {
+ if (defaultExport || definedOptions) {
// without TS, can't rely on rest spread, so we use Object.assign
// export default Object.assign(__default__, { ... })
s.prependLeft(
startOffset,
- `\nexport default /*#__PURE__*/Object.assign(${DEFAULT_VAR}, {${runtimeOptions}\n ` +
+ `\nexport default /*#__PURE__*/Object.assign(${
+ defaultExport ? `${DEFAULT_VAR}, ` : ''
+ }${definedOptions ? `${definedOptions}, ` : ''}{${runtimeOptions}\n ` +
`${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`
)
s.appendRight(endOffset, `})`)
unsetCurrentInstance
} from './component'
import { EmitFn, EmitsOptions } from './componentEmits'
+import {
+ ComponentOptionsMixin,
+ ComponentOptionsWithoutProps,
+ ComputedOptions,
+ MethodOptions
+} from './componentOptions'
import {
ComponentPropsOptions,
ComponentObjectPropsOptions,
}
}
+export function defineOptions<
+ RawBindings = {},
+ D = {},
+ C extends ComputedOptions = {},
+ M extends MethodOptions = {},
+ Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
+ Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
+ E extends EmitsOptions = EmitsOptions,
+ EE extends string = string
+>(
+ options?: ComponentOptionsWithoutProps<
+ {},
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ E,
+ EE
+ > & { emits?: undefined }
+): void {
+ if (__DEV__) {
+ warnRuntimeUsage(`defineOptions`)
+ }
+}
+
type NotUndefined<T> = T extends undefined ? never : T
type InferDefaults<T> = {