}"
`;
-exports[`SFC compile <script setup> errors should allow useOptions() referencing imported binding 1`] = `
+exports[`SFC compile <script setup> defineOptions() 1`] = `
+"export default {
+ props: {
+ foo: String
+ },
+ emit: ['a', 'b'],
+ setup(__props, { props, emit }) {
+
+
+
+const bar = 1
+
+return { props, emit, bar }
+}
+
+}"
+`;
+
+exports[`SFC compile <script setup> errors should allow defineOptions() referencing imported binding 1`] = `
"import { bar } from './bar'
export default {
}"
`;
-exports[`SFC compile <script setup> errors should allow useOptions() referencing scope var 1`] = `
+exports[`SFC compile <script setup> errors should allow defineOptions() referencing scope var 1`] = `
"export default {
props: {
foo: {
}"
`;
-exports[`SFC compile <script setup> useOptions() 1`] = `
-"export default {
- props: {
- foo: String
- },
- emit: ['a', 'b'],
- setup(__props, { props, emit }) {
-
-
-
-const bar = 1
-
-return { props, emit, bar }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> with TypeScript hoist type declarations 1`] = `
-"import { defineComponent } from 'vue'
-export interface Foo {}
- type Bar = {}
-
-export default defineComponent({
- setup() {
-
-
-return { }
-}
-
-})"
-`;
-
-exports[`SFC compile <script setup> with TypeScript useOptions w/ runtime options 1`] = `
+exports[`SFC compile <script setup> with TypeScript defineOptions w/ runtime options 1`] = `
"import { defineComponent } from 'vue'
})"
`;
-exports[`SFC compile <script setup> with TypeScript useOptions w/ type / extract emits (union) 1`] = `
+exports[`SFC compile <script setup> with TypeScript defineOptions w/ type / extract emits (union) 1`] = `
"import { Slots, defineComponent } from 'vue'
})"
`;
-exports[`SFC compile <script setup> with TypeScript useOptions w/ type / extract emits 1`] = `
+exports[`SFC compile <script setup> with TypeScript defineOptions w/ type / extract emits 1`] = `
"import { Slots, defineComponent } from 'vue'
})"
`;
-exports[`SFC compile <script setup> with TypeScript useOptions w/ type / extract props 1`] = `
+exports[`SFC compile <script setup> with TypeScript defineOptions w/ type / extract props 1`] = `
"import { defineComponent } from 'vue'
interface Test {}
+return { }
+}
+
+})"
+`;
+
+exports[`SFC compile <script setup> with TypeScript hoist type declarations 1`] = `
+"import { defineComponent } from 'vue'
+export interface Foo {}
+ type Bar = {}
+
+export default defineComponent({
+ setup() {
+
+
return { }
}
expect(content).toMatch('return { a, b, c, d, x }')
})
- test('useOptions()', () => {
+ test('defineOptions()', () => {
const { content, bindings } = compile(`
<script setup>
-import { useOptions } from 'vue'
-const { props, emit } = useOptions({
+import { defineOptions } from 'vue'
+const { props, emit } = defineOptions({
props: {
foo: String
},
emit: 'const'
})
- // should remove useOptions import and call
- expect(content).not.toMatch('useOptions')
+ // should remove defineOptions import and call
+ expect(content).not.toMatch('defineOptions')
// should generate correct setup signature
expect(content).toMatch(`setup(__props, { props, emit }) {`)
// should include context options in default export
const { content } = compile(
`
<script setup>
- import { ref, useOptions } from 'vue'
+ import { ref, defineOptions } from 'vue'
import Foo from './Foo.vue'
import other from './util'
const count = ref(0)
assertCode(content)
})
- test('useOptions w/ runtime options', () => {
+ test('defineOptions w/ runtime options', () => {
const { content } = compile(`
<script setup lang="ts">
-import { useOptions } from 'vue'
-const { props, emit } = useOptions({
+import { defineOptions } from 'vue'
+const { props, emit } = defineOptions({
props: { foo: String },
emits: ['a', 'b']
})
setup(__props, { props, emit }) {`)
})
- test('useOptions w/ type / extract props', () => {
+ test('defineOptions w/ type / extract props', () => {
const { content, bindings } = compile(`
<script setup lang="ts">
- import { useOptions } from 'vue'
+ import { defineOptions } from 'vue'
interface Test {}
type Alias = number[]
- useOptions<{
+ defineOptions<{
props: {
string: string
number: number
})
})
- test('useOptions w/ type / extract emits', () => {
+ test('defineOptions w/ type / extract emits', () => {
const { content } = compile(`
<script setup lang="ts">
- import { useOptions } from 'vue'
- const { emit } = useOptions<{
+ import { defineOptions } from 'vue'
+ const { emit } = defineOptions<{
emit: (e: 'foo' | 'bar') => void
}>()
</script>
expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
})
- test('useOptions w/ type / extract emits (union)', () => {
+ test('defineOptions w/ type / extract emits (union)', () => {
const { content } = compile(`
<script setup lang="ts">
- import { useOptions } from 'vue'
- const { emit } = useOptions<{
+ import { defineOptions } from 'vue'
+ const { emit } = defineOptions<{
emit: ((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)
}>()
</script>
).toThrow(`ref: statements can only contain assignment expressions`)
})
- test('useOptions() w/ both type and non-type args', () => {
+ test('defineOptions() w/ both type and non-type args', () => {
expect(() => {
compile(`<script setup lang="ts">
- import { useOptions } from 'vue'
- useOptions<{}>({})
+ import { defineOptions } from 'vue'
+ defineOptions<{}>({})
</script>`)
}).toThrow(`cannot accept both type and non-type arguments`)
})
- test('useOptions() referencing local var', () => {
+ test('defineOptions() referencing local var', () => {
expect(() =>
compile(`<script setup>
- import { useOptions } from 'vue'
+ import { defineOptions } from 'vue'
const bar = 1
- useOptions({
+ defineOptions({
props: {
foo: {
default: () => bar
).toThrow(`cannot reference locally declared variables`)
})
- test('useOptions() referencing ref declarations', () => {
+ test('defineOptions() referencing ref declarations', () => {
expect(() =>
compile(`<script setup>
- import { useOptions } from 'vue'
+ import { defineOptions } from 'vue'
ref: bar = 1
- useOptions({
+ defineOptions({
props: { bar }
})
</script>`)
).toThrow(`cannot reference locally declared variables`)
})
- test('should allow useOptions() referencing scope var', () => {
+ test('should allow defineOptions() referencing scope var', () => {
assertCode(
compile(`<script setup>
- import { useOptions } from 'vue'
+ import { defineOptions } from 'vue'
const bar = 1
- useOptions({
+ defineOptions({
props: {
foo: {
default: bar => bar + 1
)
})
- test('should allow useOptions() referencing imported binding', () => {
+ test('should allow defineOptions() referencing imported binding', () => {
assertCode(
compile(`<script setup>
- import { useOptions } from 'vue'
+ import { defineOptions } from 'vue'
import { bar } from './bar'
- useOptions({
+ defineOptions({
props: {
foo: {
default: () => bar
it('works for script setup', () => {
const { bindings } = compile(`
<script setup>
- import { useOptions } from 'vue'
- useOptions({
+ import { defineOptions } from 'vue'
+ defineOptions({
props: {
foo: String,
}
import { genCssVarsCode, injectCssVarsCalls } from './genCssVars'
import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate'
-const USE_OPTIONS = 'useOptions'
+const DEFINE_OPTIONS = 'defineOptions'
export interface SFCScriptCompileOptions {
/**
let optionsArg: ObjectExpression | undefined
let optionsType: TSTypeLiteral | undefined
let hasAwait = false
+ // context types to generate
+ let propsType = `{}`
+ let emitType = `(e: string, ...args: any[]) => void`
+ let slotsType = `Slots`
+ let attrsType = `Record<string, any>`
+ // props/emits declared via types
+ const typeDeclaredProps: Record<string, PropTypeData> = {}
+ const typeDeclaredEmits: Set<string> = new Set()
+ // record declared types for runtime props type generation
+ const declaredTypes: Record<string, string[]> = {}
+ // magic-string state
const s = new MagicString(source)
const startOffset = scriptSetup.loc.start.offset
const endOffset = scriptSetup.loc.end.offset
)
}
- function processUseOptions(node: Node): boolean {
+ function processDefineOptions(node: Node): boolean {
if (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
- node.callee.name === USE_OPTIONS
+ node.callee.name === DEFINE_OPTIONS
) {
if (hasOptionsCall) {
- error(`duplicate ${USE_OPTIONS}() call`, node)
+ error(`duplicate ${DEFINE_OPTIONS}() call`, node)
}
hasOptionsCall = true
const optsArg = node.arguments[0]
if (optsArg.type === 'ObjectExpression') {
optionsArg = optsArg
} else {
- error(`${USE_OPTIONS}() argument must be an object literal.`, optsArg)
+ error(
+ `${DEFINE_OPTIONS}() argument must be an object literal.`,
+ optsArg
+ )
}
}
// context call has type parameters - infer runtime types from it
if (node.typeParameters) {
if (optionsArg) {
error(
- `${USE_OPTIONS}() cannot accept both type and non-type arguments ` +
+ `${DEFINE_OPTIONS}() cannot accept both type and non-type arguments ` +
`at the same time. Use one or the other.`,
node
)
optionsType = typeArg
} else {
error(
- `type argument passed to ${USE_OPTIONS}() must be a literal type.`,
+ `type argument passed to ${DEFINE_OPTIONS}() must be a literal type.`,
typeArg
)
}
}
}
- let propsType = `{}`
- let emitType = `(e: string, ...args: any[]) => void`
- let slotsType = `Slots`
- let attrsType = `Record<string, any>`
-
- // props/emits declared via types
- const typeDeclaredProps: Record<string, PropTypeData> = {}
- const typeDeclaredEmits: Set<string> = new Set()
- // record declared types for runtime props type generation
- const declaredTypes: Record<string, string[]> = {}
-
- // 3. parse <script setup> and walk over top level statements
+ // 2. parse <script setup> and walk over top level statements
const scriptSetupAst = parse(
scriptSetup.content,
{
specifier.imported.name
const source = node.source.value
const existing = userImports[local]
- if (source === 'vue' && imported === USE_OPTIONS) {
+ if (source === 'vue' && imported === DEFINE_OPTIONS) {
removed++
s.remove(
prev ? prev.end! + startOffset : specifier.start! + startOffset,
if (
node.type === 'ExpressionStatement' &&
- processUseOptions(node.expression)
+ processDefineOptions(node.expression)
) {
s.remove(node.start! + startOffset, node.end! + startOffset)
}
if (node.type === 'VariableDeclaration' && !node.declare) {
for (const decl of node.declarations) {
- if (decl.init && processUseOptions(decl.init)) {
+ if (decl.init && processDefineOptions(decl.init)) {
optionsExp = scriptSetup.content.slice(decl.id.start!, decl.id.end!)
if (node.declarations.length === 1) {
s.remove(node.start! + startOffset, node.end! + startOffset)
}
}
- // 4. Do a full walk to rewrite identifiers referencing let exports with ref
+ // 3. Do a full walk to rewrite identifiers referencing let exports with ref
// value access
if (enableRefSugar && Object.keys(refBindings).length) {
for (const node of scriptSetupAst) {
}
}
- // 5. extract runtime props/emits code from setup context type
+ // 4. extract runtime props/emits code from setup context type
if (optionsType) {
for (const m of optionsType.members) {
if (m.type === 'TSPropertySignature' && m.key.type === 'Identifier') {
walkIdentifiers(optionsArg, id => {
if (setupBindings[id.name]) {
error(
- `\`${USE_OPTIONS}()\` in <script setup> cannot reference locally ` +
+ `\`${DEFINE_OPTIONS}()\` in <script setup> cannot reference locally ` +
`declared variables because it will be hoisted outside of the ` +
`setup() function. If your component options requires initialization ` +
`in the module scope, use a separate normal <script> to export ` +
allBindings[key] = true
}
- // 9. inject `useCssVars` calls
+ // 8. inject `useCssVars` calls
if (hasCssVars) {
helperImports.add(`useCssVars`)
for (const style of styles) {
}
}
- // 10. analyze binding metadata
+ // 9. analyze binding metadata
if (scriptAst) {
Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst))
}
bindingMetadata[key] = setupBindings[key]
}
- // 11. generate return statement
+ // 10. generate return statement
let returned
if (options.inlineTemplate) {
if (sfc.template) {
}
s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
- // 12. finalize default export
+ // 11. finalize default export
let runtimeOptions = ``
if (optionsArg) {
runtimeOptions = `\n ${scriptSetup.content
runtimeOptions =
genRuntimeProps(typeDeclaredProps) + genRuntimeEmits(typeDeclaredEmits)
}
-
if (isTS) {
// for TS, make sure the exported type is still valid type with
// correct props information
}
}
- // 13. finalize Vue helper imports
+ // 12. finalize Vue helper imports
// TODO account for cases where user imports a helper with the same name
// from a non-vue source
const helpers = [...helperImports].filter(i => !userImports[i])
init &&
init.type === 'CallExpression' &&
init.callee.type === 'Identifier' &&
- init.callee.name === USE_OPTIONS
+ init.callee.name === DEFINE_OPTIONS
)
if (id.type === 'Identifier') {
bindings[id.name] =
-import { EmitFn, EmitsOptions } from '../componentEmits'
-import {
- ComponentObjectPropsOptions,
- ExtractPropTypes
-} from '../componentProps'
-import { Slots } from '../componentSlots'
-import { Directive } from '../directives'
-import { warn } from '../warning'
+import { EmitFn, EmitsOptions } from './componentEmits'
+import { ComponentObjectPropsOptions, ExtractPropTypes } from './componentProps'
+import { Slots } from './componentSlots'
+import { Directive } from './directives'
+import { warn } from './warning'
interface DefaultContext {
props: {}
* called at runtime.
*/
// overload 1: no props
-export function useOptions<
+export function defineOptions<
T extends Partial<DefaultContext> = {},
E extends EmitsOptions = EmitsOptions,
EE extends string = string
): InferContext<T, {}, E>
// overload 2: object props
-export function useOptions<
+export function defineOptions<
T extends Partial<DefaultContext> = {},
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
): InferContext<T, P, E>
// overload 3: object props
-export function useOptions<
+export function defineOptions<
T extends Partial<DefaultContext> = {},
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
): InferContext<T, P, E>
// implementation
-export function useOptions() {
+export function defineOptions() {
if (__DEV__) {
warn(
`defineContext() is a compiler-hint helper that is only usable inside ` +
export { nextTick } from './scheduler'
export { defineComponent } from './apiDefineComponent'
export { defineAsyncComponent } from './apiAsyncComponent'
-export { useOptions } from './helpers/useOptions'
+export { defineOptions } from './apiDefineOptions'
// Advanced API ----------------------------------------------------------------
-import { expectType, useOptions, Slots, describe } from './index'
+import { expectType, defineOptions, Slots, describe } from './index'
describe('no args', () => {
- const { props, attrs, emit, slots } = useOptions()
+ const { props, attrs, emit, slots } = defineOptions()
expectType<{}>(props)
expectType<Record<string, unknown>>(attrs)
expectType<(...args: any[]) => void>(emit)
})
describe('with type arg', () => {
- const { props, attrs, emit, slots } = useOptions<{
+ const { props, attrs, emit, slots } = defineOptions<{
props: {
foo: string
}
// with runtime arg
describe('with runtime arg (array syntax)', () => {
- const { props, emit } = useOptions({
+ const { props, emit } = defineOptions({
props: ['foo', 'bar'],
emits: ['foo', 'bar']
})
})
describe('with runtime arg (object syntax)', () => {
- const { props, emit } = useOptions({
+ const { props, emit } = defineOptions({
props: {
foo: String,
bar: {