wip: defineOptions -> defineProps + defineEmit + useContext

This commit is contained in:
Evan You
2020-11-24 15:12:59 -05:00
parent ae2caad740
commit 47d73c23e1
13 changed files with 593 additions and 523 deletions

View File

@@ -33,38 +33,55 @@ return { x }
export const n = 1"
`;
exports[`SFC compile <script setup> defineOptions() 1`] = `
exports[`SFC compile <script setup> defineEmit() 1`] = `
"export default {
expose: [],
props: {
foo: String
},
emit: ['a', 'b'],
setup(__props, { props, emit }) {
emits: ['foo', 'bar'],
setup(__props, { emit: myEmit }) {
const bar = 1
return { props, emit, bar }
return { myEmit }
}
}"
`;
exports[`SFC compile <script setup> errors should allow defineOptions() referencing imported binding 1`] = `
exports[`SFC compile <script setup> defineProps() 1`] = `
"export default {
expose: [],
props: {
foo: String
},
setup(__props) {
const props = __props
const bar = 1
return { props, bar }
}
}"
`;
exports[`SFC compile <script setup> errors should allow defineProps/Emit() referencing imported binding 1`] = `
"import { bar } from './bar'
export default {
expose: [],
props: {
foo: {
default: () => bar
}
},
foo: {
default: () => bar
}
},
emits: {
foo: () => bar > 1
},
setup(__props) {
return { bar }
}
@@ -72,18 +89,22 @@ return { bar }
}"
`;
exports[`SFC compile <script setup> errors should allow defineOptions() referencing scope var 1`] = `
exports[`SFC compile <script setup> errors should allow defineProps/Emit() referencing scope var 1`] = `
"export default {
expose: [],
props: {
foo: {
default: bar => bar + 1
}
},
foo: {
default: bar => bar + 1
}
},
emits: {
foo: bar => bar > 1
},
setup(__props) {
const bar = 1
return { bar }
}
@@ -594,37 +615,18 @@ return { a, b, c, d, x }
}"
`;
exports[`SFC compile <script setup> with TypeScript defineOptions w/ runtime options 1`] = `
exports[`SFC compile <script setup> with TypeScript defineEmit w/ type (union) 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
export default _defineComponent({
expose: [],
props: { foo: String },
emits: ['a', 'b'],
setup(__props, { props, emit }) {
return { props, emit }
}
})"
`;
exports[`SFC compile <script setup> with TypeScript defineOptions w/ type / extract emits (union) 1`] = `
"import { Slots as _Slots, defineComponent as _defineComponent } from 'vue'
export default _defineComponent({
expose: [],
emits: [\\"foo\\", \\"bar\\", \\"baz\\"] as unknown as undefined,
setup(__props, { emit }: {
props: {},
emit: ((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void),
slots: Slots,
attrs: Record<string, any>
}) {
emit: (((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)),
slots: any,
attrs: any
}) {
@@ -634,19 +636,18 @@ return { emit }
})"
`;
exports[`SFC compile <script setup> with TypeScript defineOptions w/ type / extract emits 1`] = `
"import { Slots as _Slots, defineComponent as _defineComponent } from 'vue'
exports[`SFC compile <script setup> with TypeScript defineEmit w/ type 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
export default _defineComponent({
expose: [],
emits: [\\"foo\\", \\"bar\\"] as unknown as undefined,
setup(__props, { emit }: {
props: {},
emit: (e: 'foo' | 'bar') => void,
slots: Slots,
attrs: Record<string, any>
}) {
emit: ((e: 'foo' | 'bar') => void),
slots: any,
attrs: any
}) {
@@ -656,7 +657,7 @@ return { emit }
})"
`;
exports[`SFC compile <script setup> with TypeScript defineOptions w/ type / extract props 1`] = `
exports[`SFC compile <script setup> with TypeScript defineProps w/ type 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
interface Test {}
@@ -689,7 +690,30 @@ export default _defineComponent({
literalUnionMixed: { type: [String, Number, Boolean], required: true },
intersection: { type: Object, required: true }
} as unknown as undefined,
setup(__props) {
setup(__props: {
string: string
number: number
boolean: boolean
object: object
objectLiteral: { a: number }
fn: (n: number) => void
functionRef: Function
objectRef: Object
array: string[]
arrayRef: Array<any>
tuple: [number, number]
set: Set<string>
literal: 'foo'
optional?: any
recordRef: Record<string, null>
interface: Test
alias: Alias
union: string | number
literalUnion: 'foo' | 'bar'
literalUnionMixed: 'foo' | 1 | boolean
intersection: Test & {}
}) {
@@ -699,6 +723,26 @@ return { }
})"
`;
exports[`SFC compile <script setup> with TypeScript defineProps/Emit w/ runtime options 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
export default _defineComponent({
expose: [],
props: { foo: String },
emits: ['a', 'b'],
setup(__props, { emit }) {
const props = __props
return { props, emit }
}
})"
`;
exports[`SFC compile <script setup> with TypeScript hoist type declarations 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
export interface Foo {}

View File

@@ -86,8 +86,8 @@ import { ref } from 'vue'
export default {
expose: [],
props: {
foo: String
},
foo: String
},
setup(__props) {
_useCssVars(_ctx => ({

View File

@@ -16,17 +16,13 @@ describe('SFC compile <script setup>', () => {
expect(content).toMatch('return { a, b, c, d, x }')
})
test('defineOptions()', () => {
test('defineProps()', () => {
const { content, bindings } = compile(`
<script setup>
import { defineOptions } from 'vue'
const { props, emit } = defineOptions({
props: {
foo: String
},
emit: ['a', 'b']
import { defineProps } from 'vue'
const props = defineProps({
foo: String
})
const bar = 1
</script>
`)
@@ -36,21 +32,42 @@ const bar = 1
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
bar: BindingTypes.SETUP_CONST,
props: BindingTypes.SETUP_CONST,
emit: BindingTypes.SETUP_CONST
props: BindingTypes.SETUP_CONST
})
// should remove defineOptions import and call
expect(content).not.toMatch('defineOptions')
expect(content).not.toMatch('defineProps')
// should generate correct setup signature
expect(content).toMatch(`setup(__props, { props, emit }) {`)
expect(content).toMatch(`setup(__props) {`)
// should assign user identifier to it
expect(content).toMatch(`const props = __props`)
// should include context options in default export
expect(content).toMatch(`export default {
expose: [],
props: {
foo: String
},
emit: ['a', 'b'],`)
foo: String
},`)
})
test('defineEmit()', () => {
const { content, bindings } = compile(`
<script setup>
import { defineEmit } from 'vue'
const myEmit = defineEmit(['foo', 'bar'])
</script>
`)
assertCode(content)
expect(bindings).toStrictEqual({
myEmit: BindingTypes.SETUP_CONST
})
// should remove defineOptions import and call
expect(content).not.toMatch('defineEmit')
// should generate correct setup signature
expect(content).toMatch(`setup(__props, { emit: myEmit }) {`)
// should include context options in default export
expect(content).toMatch(`export default {
expose: [],
emits: ['foo', 'bar'],`)
})
describe('<script> and <script setup> co-usage', () => {
@@ -174,7 +191,7 @@ const bar = 1
// function, const, component import
const { content } = compile(
`<script setup>
import { ref, defineOptions } from 'vue'
import { ref } from 'vue'
import Foo from './Foo.vue'
import other from './util'
const count = ref(0)
@@ -360,14 +377,12 @@ const bar = 1
assertCode(content)
})
test('defineOptions w/ runtime options', () => {
test('defineProps/Emit w/ runtime options', () => {
const { content } = compile(`
<script setup lang="ts">
import { defineOptions } from 'vue'
const { props, emit } = defineOptions({
props: { foo: String },
emits: ['a', 'b']
})
import { defineProps, defineEmit } from 'vue'
const props = defineProps({ foo: String })
const emit = defineEmit(['a', 'b'])
</script>
`)
assertCode(content)
@@ -375,42 +390,40 @@ const { props, emit } = defineOptions({
expose: [],
props: { foo: String },
emits: ['a', 'b'],
setup(__props, { props, emit }) {`)
setup(__props, { emit }) {`)
})
test('defineOptions w/ type / extract props', () => {
test('defineProps w/ type', () => {
const { content, bindings } = compile(`
<script setup lang="ts">
import { defineOptions } from 'vue'
import { defineProps } from 'vue'
interface Test {}
type Alias = number[]
defineOptions<{
props: {
string: string
number: number
boolean: boolean
object: object
objectLiteral: { a: number }
fn: (n: number) => void
functionRef: Function
objectRef: Object
array: string[]
arrayRef: Array<any>
tuple: [number, number]
set: Set<string>
literal: 'foo'
optional?: any
recordRef: Record<string, null>
interface: Test
alias: Alias
defineProps<{
string: string
number: number
boolean: boolean
object: object
objectLiteral: { a: number }
fn: (n: number) => void
functionRef: Function
objectRef: Object
array: string[]
arrayRef: Array<any>
tuple: [number, number]
set: Set<string>
literal: 'foo'
optional?: any
recordRef: Record<string, null>
interface: Test
alias: Alias
union: string | number
literalUnion: 'foo' | 'bar'
literalUnionMixed: 'foo' | 1 | boolean
intersection: Test & {}
}
union: string | number
literalUnion: 'foo' | 'bar'
literalUnionMixed: 'foo' | 1 | boolean
intersection: Test & {}
}>()
</script>`)
assertCode(content)
@@ -466,33 +479,28 @@ const { props, emit } = defineOptions({
})
})
test('defineOptions w/ type / extract emits', () => {
test('defineEmit w/ type', () => {
const { content } = compile(`
<script setup lang="ts">
import { defineOptions } from 'vue'
const { emit } = defineOptions<{
emit: (e: 'foo' | 'bar') => void
}>()
import { defineEmit } from 'vue'
const emit = defineEmit<(e: 'foo' | 'bar') => void>()
</script>
`)
assertCode(content)
expect(content).toMatch(`props: {},\n emit: (e: 'foo' | 'bar') => void,`)
expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
})
test('defineOptions w/ type / extract emits (union)', () => {
test('defineEmit w/ type (union)', () => {
const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
const { content } = compile(`
<script setup lang="ts">
import { defineOptions } from 'vue'
const { emit } = defineOptions<{
emit: ((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)
}>()
import { defineEmit } from 'vue'
const emit = defineEmit<${type}>()
</script>
`)
assertCode(content)
expect(content).toMatch(
`props: {},\n emit: ((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void),`
)
expect(content).toMatch(`emit: (${type}),`)
expect(content).toMatch(
`emits: ["foo", "bar", "baz"] as unknown as undefined`
)
@@ -774,71 +782,96 @@ const { props, emit } = defineOptions({
).toThrow(`ref: statements can only contain assignment expressions`)
})
test('defineOptions() w/ both type and non-type args', () => {
test('defineProps/Emit() w/ both type and non-type args', () => {
expect(() => {
compile(`<script setup lang="ts">
import { defineOptions } from 'vue'
defineOptions<{}>({})
import { defineProps } from 'vue'
defineProps<{}>({})
</script>`)
}).toThrow(`cannot accept both type and non-type arguments`)
expect(() => {
compile(`<script setup lang="ts">
import { defineEmit } from 'vue'
defineEmit<{}>({})
</script>`)
}).toThrow(`cannot accept both type and non-type arguments`)
})
test('defineOptions() referencing local var', () => {
test('defineProps/Emit() referencing local var', () => {
expect(() =>
compile(`<script setup>
import { defineOptions } from 'vue'
import { defineProps } from 'vue'
const bar = 1
defineOptions({
props: {
foo: {
default: () => bar
}
defineProps({
foo: {
default: () => bar
}
})
</script>`)
).toThrow(`cannot reference locally declared variables`)
})
test('defineOptions() referencing ref declarations', () => {
expect(() =>
compile(`<script setup>
import { defineOptions } from 'vue'
import { defineEmit } from 'vue'
const bar = 'hello'
defineEmit([bar])
</script>`)
).toThrow(`cannot reference locally declared variables`)
})
test('defineProps/Emit() referencing ref declarations', () => {
expect(() =>
compile(`<script setup>
import { defineProps } from 'vue'
ref: bar = 1
defineOptions({
props: { bar }
defineProps({
bar
})
</script>`)
).toThrow(`cannot reference locally declared variables`)
expect(() =>
compile(`<script setup>
import { defineEmit } from 'vue'
ref: bar = 1
defineEmit({
bar
})
</script>`)
).toThrow(`cannot reference locally declared variables`)
})
test('should allow defineOptions() referencing scope var', () => {
test('should allow defineProps/Emit() referencing scope var', () => {
assertCode(
compile(`<script setup>
import { defineOptions } from 'vue'
import { defineProps, defineEmit } from 'vue'
const bar = 1
defineOptions({
props: {
foo: {
default: bar => bar + 1
}
defineProps({
foo: {
default: bar => bar + 1
}
})
defineEmit({
foo: bar => bar > 1
})
</script>`).content
)
})
test('should allow defineOptions() referencing imported binding', () => {
test('should allow defineProps/Emit() referencing imported binding', () => {
assertCode(
compile(`<script setup>
import { defineOptions } from 'vue'
import { bar } from './bar'
defineOptions({
props: {
foo: {
default: () => bar
}
}
})
import { defineProps, defineEmit } from 'vue'
import { bar } from './bar'
defineProps({
foo: {
default: () => bar
}
})
defineEmit({
foo: () => bar > 1
})
</script>`).content
)
})
@@ -1063,11 +1096,9 @@ describe('SFC analyze <script> bindings', () => {
it('works for script setup', () => {
const { bindings } = compile(`
<script setup>
import { defineOptions, ref as r } from 'vue'
defineOptions({
props: {
foo: String,
}
import { defineProps, ref as r } from 'vue'
defineProps({
foo: String
})
const a = r(1)

View File

@@ -20,13 +20,11 @@ describe('CSS vars injection', () => {
test('w/ <script setup> binding analysis', () => {
const { content } = compileSFCScript(
`<script setup>
import { defineOptions, ref } from 'vue'
import { defineProps, ref } from 'vue'
const color = 'red'
const size = ref('10px')
defineOptions({
props: {
foo: String
}
defineProps({
foo: String
})
</script>\n` +
`<style>