mirror of https://github.com/vuejs/core.git
feat(compiler-sfc): add defineOptions macro (#5738)
This commit is contained in:
parent
f77bd36a24
commit
bcf5841dde
|
@ -634,6 +634,19 @@ return { }
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> > defineOptions() > basic usage 1`] = `
|
||||||
|
"export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, {
|
||||||
|
setup(__props, { expose }) {
|
||||||
|
expose();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { }
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> > defineProps w/ external definition 1`] = `
|
exports[`SFC compile <script setup> > defineProps w/ external definition 1`] = `
|
||||||
"import { propsModel } from './props'
|
"import { propsModel } from './props'
|
||||||
|
|
||||||
|
|
|
@ -172,6 +172,68 @@ const myEmit = defineEmits(['foo', 'bar'])
|
||||||
expect(content).toMatch(`emits: ['a'],`)
|
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()', () => {
|
test('defineExpose()', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@ -1136,7 +1198,7 @@ const emit = defineEmits(['a', 'b'])
|
||||||
`)
|
`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
})
|
})
|
||||||
|
|
||||||
// #7111
|
// #7111
|
||||||
test('withDefaults (static) w/ production mode', () => {
|
test('withDefaults (static) w/ production mode', () => {
|
||||||
const { content } = compile(
|
const { content } = compile(
|
||||||
|
@ -1277,7 +1339,6 @@ const emit = defineEmits(['a', 'b'])
|
||||||
expect(content).toMatch(`emits: ["foo", "bar"]`)
|
expect(content).toMatch(`emits: ["foo", "bar"]`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
test('defineEmits w/ type from normal script', () => {
|
test('defineEmits w/ type from normal script', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
|
@ -62,6 +62,7 @@ const DEFINE_PROPS = 'defineProps'
|
||||||
const DEFINE_EMITS = 'defineEmits'
|
const DEFINE_EMITS = 'defineEmits'
|
||||||
const DEFINE_EXPOSE = 'defineExpose'
|
const DEFINE_EXPOSE = 'defineExpose'
|
||||||
const WITH_DEFAULTS = 'withDefaults'
|
const WITH_DEFAULTS = 'withDefaults'
|
||||||
|
const DEFINE_OPTIONS = 'defineOptions'
|
||||||
|
|
||||||
// constants
|
// constants
|
||||||
const DEFAULT_VAR = `__default__`
|
const DEFAULT_VAR = `__default__`
|
||||||
|
@ -270,6 +271,7 @@ export function compileScript(
|
||||||
let hasDefineExposeCall = false
|
let hasDefineExposeCall = false
|
||||||
let hasDefaultExportName = false
|
let hasDefaultExportName = false
|
||||||
let hasDefaultExportRender = false
|
let hasDefaultExportRender = false
|
||||||
|
let hasDefineOptionsCall = false
|
||||||
let propsRuntimeDecl: Node | undefined
|
let propsRuntimeDecl: Node | undefined
|
||||||
let propsRuntimeDefaults: ObjectExpression | undefined
|
let propsRuntimeDefaults: ObjectExpression | undefined
|
||||||
let propsDestructureDecl: Node | undefined
|
let propsDestructureDecl: Node | undefined
|
||||||
|
@ -281,6 +283,7 @@ export function compileScript(
|
||||||
let emitsTypeDecl: EmitsDeclType | undefined
|
let emitsTypeDecl: EmitsDeclType | undefined
|
||||||
let emitsTypeDeclRaw: Node | undefined
|
let emitsTypeDeclRaw: Node | undefined
|
||||||
let emitIdentifier: string | undefined
|
let emitIdentifier: string | undefined
|
||||||
|
let optionsRuntimeDecl: Node | undefined
|
||||||
let hasAwait = false
|
let hasAwait = false
|
||||||
let hasInlinedSsrRenderFn = false
|
let hasInlinedSsrRenderFn = false
|
||||||
// props/emits declared via types
|
// props/emits declared via types
|
||||||
|
@ -647,6 +650,50 @@ export function compileScript(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
function resolveQualifiedType(
|
||||||
node: Node,
|
node: Node,
|
||||||
qualifier: (node: Node) => boolean
|
qualifier: (node: Node) => boolean
|
||||||
|
@ -1175,6 +1222,7 @@ export function compileScript(
|
||||||
if (
|
if (
|
||||||
processDefineProps(node.expression) ||
|
processDefineProps(node.expression) ||
|
||||||
processDefineEmits(node.expression) ||
|
processDefineEmits(node.expression) ||
|
||||||
|
processDefineOptions(node.expression) ||
|
||||||
processWithDefaults(node.expression)
|
processWithDefaults(node.expression)
|
||||||
) {
|
) {
|
||||||
s.remove(node.start! + startOffset, node.end! + startOffset)
|
s.remove(node.start! + startOffset, node.end! + startOffset)
|
||||||
|
@ -1195,6 +1243,13 @@ export function compileScript(
|
||||||
for (let i = 0; i < total; i++) {
|
for (let i = 0; i < total; i++) {
|
||||||
const decl = node.declarations[i]
|
const decl = node.declarations[i]
|
||||||
if (decl.init) {
|
if (decl.init) {
|
||||||
|
if (processDefineOptions(decl.init)) {
|
||||||
|
error(
|
||||||
|
`${DEFINE_OPTIONS}() has no returning value, it cannot be assigned.`,
|
||||||
|
node
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// defineProps / defineEmits
|
// defineProps / defineEmits
|
||||||
const isDefineProps =
|
const isDefineProps =
|
||||||
processDefineProps(decl.init, decl.id, node.kind) ||
|
processDefineProps(decl.init, decl.id, node.kind) ||
|
||||||
|
@ -1339,6 +1394,7 @@ export function compileScript(
|
||||||
checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS)
|
checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS)
|
||||||
checkInvalidScopeReference(propsDestructureDecl, DEFINE_PROPS)
|
checkInvalidScopeReference(propsDestructureDecl, DEFINE_PROPS)
|
||||||
checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_EMITS)
|
checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_EMITS)
|
||||||
|
checkInvalidScopeReference(optionsRuntimeDecl, DEFINE_OPTIONS)
|
||||||
|
|
||||||
// 6. remove non-script content
|
// 6. remove non-script content
|
||||||
if (script) {
|
if (script) {
|
||||||
|
@ -1626,6 +1682,13 @@ export function compileScript(
|
||||||
runtimeOptions += genRuntimeEmits(typeDeclaredEmits)
|
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
|
// <script setup> components are closed by default. If the user did not
|
||||||
// explicitly call `defineExpose`, call expose() with no args.
|
// explicitly call `defineExpose`, call expose() with no args.
|
||||||
const exposeCall =
|
const exposeCall =
|
||||||
|
@ -1637,7 +1700,9 @@ export function compileScript(
|
||||||
// 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
|
||||||
// export default defineComponent({ ...__default__, ... })
|
// export default defineComponent({ ...__default__, ... })
|
||||||
const def = defaultExport ? `\n ...${DEFAULT_VAR},` : ``
|
const def =
|
||||||
|
(defaultExport ? `\n ...${DEFAULT_VAR},` : ``) +
|
||||||
|
(definedOptions ? `\n ...${definedOptions},` : '')
|
||||||
s.prependLeft(
|
s.prependLeft(
|
||||||
startOffset,
|
startOffset,
|
||||||
`\nexport default /*#__PURE__*/${helper(
|
`\nexport default /*#__PURE__*/${helper(
|
||||||
|
@ -1648,12 +1713,14 @@ export function compileScript(
|
||||||
)
|
)
|
||||||
s.appendRight(endOffset, `})`)
|
s.appendRight(endOffset, `})`)
|
||||||
} else {
|
} else {
|
||||||
if (defaultExport) {
|
if (defaultExport || definedOptions) {
|
||||||
// without TS, can't rely on rest spread, so we use Object.assign
|
// without TS, can't rely on rest spread, so we use Object.assign
|
||||||
// export default Object.assign(__default__, { ... })
|
// export default Object.assign(__default__, { ... })
|
||||||
s.prependLeft(
|
s.prependLeft(
|
||||||
startOffset,
|
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}`
|
`${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`
|
||||||
)
|
)
|
||||||
s.appendRight(endOffset, `})`)
|
s.appendRight(endOffset, `})`)
|
||||||
|
|
|
@ -7,6 +7,12 @@ import {
|
||||||
unsetCurrentInstance
|
unsetCurrentInstance
|
||||||
} from './component'
|
} from './component'
|
||||||
import { EmitFn, EmitsOptions } from './componentEmits'
|
import { EmitFn, EmitsOptions } from './componentEmits'
|
||||||
|
import {
|
||||||
|
ComponentOptionsMixin,
|
||||||
|
ComponentOptionsWithoutProps,
|
||||||
|
ComputedOptions,
|
||||||
|
MethodOptions
|
||||||
|
} from './componentOptions'
|
||||||
import {
|
import {
|
||||||
ComponentPropsOptions,
|
ComponentPropsOptions,
|
||||||
ComponentObjectPropsOptions,
|
ComponentObjectPropsOptions,
|
||||||
|
@ -143,6 +149,33 @@ export function defineExpose<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 NotUndefined<T> = T extends undefined ? never : T
|
||||||
|
|
||||||
type InferDefaults<T> = {
|
type InferDefaults<T> = {
|
||||||
|
|
|
@ -68,6 +68,7 @@ export {
|
||||||
defineProps,
|
defineProps,
|
||||||
defineEmits,
|
defineEmits,
|
||||||
defineExpose,
|
defineExpose,
|
||||||
|
defineOptions,
|
||||||
withDefaults,
|
withDefaults,
|
||||||
// internal
|
// internal
|
||||||
mergeDefaults,
|
mergeDefaults,
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
type _defineProps = typeof defineProps
|
type _defineProps = typeof defineProps
|
||||||
type _defineEmits = typeof defineEmits
|
type _defineEmits = typeof defineEmits
|
||||||
type _defineExpose = typeof defineExpose
|
type _defineExpose = typeof defineExpose
|
||||||
|
type _defineOptions = typeof defineOptions
|
||||||
type _withDefaults = typeof withDefaults
|
type _withDefaults = typeof withDefaults
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
const defineProps: _defineProps
|
const defineProps: _defineProps
|
||||||
const defineEmits: _defineEmits
|
const defineEmits: _defineEmits
|
||||||
const defineExpose: _defineExpose
|
const defineExpose: _defineExpose
|
||||||
|
const defineOptions: _defineOptions
|
||||||
const withDefaults: _withDefaults
|
const withDefaults: _withDefaults
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue