export const n = 1
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
x()
"import { x } from './x'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
x()
exports[`SFC compile <script setup> defineEmit() (deprecated) 1`] = `
"export default {
- expose: [],
emits: ['foo', 'bar'],
- setup(__props, { emit: myEmit }) {
+ setup(__props, { expose, emit: myEmit }) {
+ expose()
exports[`SFC compile <script setup> defineEmits() 1`] = `
"export default {
- expose: [],
emits: ['foo', 'bar'],
- setup(__props, { emit: myEmit }) {
+ setup(__props, { expose, emit: myEmit }) {
+ expose()
}"
`;
+exports[`SFC compile <script setup> defineExpose() 1`] = `
+"export default {
+ setup(__props, { expose }) {
+
+expose({ foo: 123 })
+
+return { }
+}
+
+}"
+`;
+
exports[`SFC compile <script setup> defineProps() 1`] = `
"export default {
- expose: [],
props: {
foo: String
},
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
const props = __props
"import { bar } from './bar'
export default {
- expose: [],
props: {
foo: {
default: () => bar
emits: {
foo: () => bar > 1
},
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
exports[`SFC compile <script setup> errors should allow defineProps/Emit() referencing scope var 1`] = `
"export default {
- expose: [],
props: {
foo: {
default: bar => bar + 1
emits: {
foo: bar => bar > 1
},
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
const bar = 1
import { ref } from 'vue'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
const foo = _ref(1)
"import { x } from './x'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
x()
"import { ref } from 'vue'
export default {
- expose: [],
props: ['foo'],
emits: ['bar'],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
import b from 'b'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
return { a, b }
import 'foo/css'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
return { ref }
import other from './util'
export default {
- expose: [],
setup(__props) {
const count = ref(0)
import myDir from './my-dir'
export default {
- expose: [],
setup(__props) {
export default {
- expose: [],
setup(__props) {
const msg = 1
import { ref } from 'vue'
export default {
- expose: [],
setup(__props) {
const count = ref(0)
import { ref } from 'vue'
export default {
- expose: [],
__ssrInlineRender: true,
setup(__props) {
import { ref } from 'vue'
export default {
- expose: [],
setup(__props) {
const count = ref(0)
import { ref } from 'vue'
export default {
- expose: [],
setup(__props) {
const val = {}
import { ref } from 'vue'
export default {
- expose: [],
setup(__props) {
const count = ref(0)
import { ref } from 'vue'
export default {
- expose: [],
setup(__props) {
const count = ref(0)
}"
`;
+exports[`SFC compile <script setup> inlineTemplate mode with defineExpose() 1`] = `
+"export default {
+ setup(__props, { expose }) {
+
+ const count = ref(0)
+ expose({ count })
+
+return () => {}
+}
+
+}"
+`;
+
exports[`SFC compile <script setup> ref: syntax sugar accessing ref binding 1`] = `
"import { ref as _ref } from 'vue'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
const a = _ref(1)
console.log(a.value)
"import { ref as _ref } from 'vue'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
const n = _ref(1), [__a, __b = 1, ...__c] = useFoo()
const a = _ref(__a);
"import { ref as _ref } from 'vue'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
const foo = _ref()
const a = _ref(1)
"import { ref as _ref } from 'vue'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
const a = _ref(1), b = _ref(2), c = _ref({
count: 0
"import { ref as _ref } from 'vue'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
const a = _ref(1)
const b = _ref({ count: 0 })
"import { ref as _ref } from 'vue'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
const [{ a: { b: __b }}] = useFoo()
const b = _ref(__b);
"import { ref as _ref } from 'vue'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
const n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()
const a = _ref(__a);
exports[`SFC compile <script setup> ref: syntax sugar should not convert non ref labels 1`] = `
"export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
foo: a = 1, b = 2, c = {
count: 0
"import { ref as _ref } from 'vue'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
const a = _ref(1)
const b = _ref(1)
"import { ref as _ref } from 'vue'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
const a = _ref(1)
const b = { a: a.value }
"import { x } from './x'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
let a = 1
const b = 2
export default _defineComponent({
- expose: [],
emits: [\\"foo\\", \\"bar\\", \\"baz\\"] as unknown as undefined,
- setup(__props, { emit }: {
- emit: ({(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}),
- slots: any,
- attrs: any
- }) {
+ setup(__props, { expose, emit }: { emit: ({(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}), expose: any, slots: any, attrs: any }) {
+ expose()
export default _defineComponent({
- expose: [],
emits: [\\"foo\\", \\"bar\\"] as unknown as undefined,
- setup(__props, { emit }: {
- emit: ((e: 'foo' | 'bar') => void),
- slots: any,
- attrs: any
- }) {
+ setup(__props, { expose, emit }: { emit: ((e: 'foo' | 'bar') => void), expose: any, slots: any, attrs: any }) {
+ expose()
export default _defineComponent({
- expose: [],
props: {
string: { type: String, required: true },
number: { type: Number, required: true },
literalUnion: 'foo' | 'bar'
literalUnionMixed: 'foo' | 1 | boolean
intersection: Test & {}
- }) {
+ }, { expose }) {
+ expose()
export default _defineComponent({
- expose: [],
props: { foo: String },
emits: ['a', 'b'],
- setup(__props, { emit }) {
+ setup(__props, { expose, emit }) {
+ expose()
const props = __props
type Bar = {}
export default _defineComponent({
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
return { }
"import { useCssVars as _useCssVars, unref as _unref } from 'vue'
export default {
- expose: [],
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
_useCssVars(_ctx => ({
\\"xxxxxxxx-color\\": (color)
import { ref } from 'vue'
export default {
- expose: [],
props: {
foo: String
},
- setup(__props) {
+ setup(__props, { expose }) {
+ expose()
_useCssVars(_ctx => ({
\\"xxxxxxxx-color\\": (color),
// should remove defineOptions import and call
expect(content).not.toMatch('defineProps')
// should generate correct setup signature
- expect(content).toMatch(`setup(__props) {`)
+ expect(content).toMatch(`setup(__props, { expose }) {`)
// 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
},`)
// should remove defineOptions import and call
expect(content).not.toMatch(/defineEmits?/)
// should generate correct setup signature
- expect(content).toMatch(`setup(__props, { emit: myEmit }) {`)
+ expect(content).toMatch(`setup(__props, { expose, emit: myEmit }) {`)
// should include context options in default export
expect(content).toMatch(`export default {
- expose: [],
emits: ['foo', 'bar'],`)
})
// should remove defineOptions import and call
expect(content).not.toMatch('defineEmits')
// should generate correct setup signature
- expect(content).toMatch(`setup(__props, { emit: myEmit }) {`)
+ expect(content).toMatch(`setup(__props, { expose, emit: myEmit }) {`)
// should include context options in default export
expect(content).toMatch(`export default {
- expose: [],
emits: ['foo', 'bar'],`)
})
+ test('defineExpose()', () => {
+ const { content } = compile(`
+<script setup>
+import { defineExpose } from 'vue'
+defineExpose({ foo: 123 })
+</script>
+ `)
+ assertCode(content)
+ // should remove defineOptions import and call
+ expect(content).not.toMatch('defineExpose')
+ // should generate correct setup signature
+ expect(content).toMatch(`setup(__props, { expose }) {`)
+ // should replace callee
+ expect(content).toMatch(/\bexpose\(\{ foo: 123 \}\)/)
+ })
+
describe('<script> and <script setup> co-usage', () => {
test('script first', () => {
const { content } = compile(`
// check snapshot and make sure helper imports and
// hoists are placed correctly.
assertCode(content)
+ // in inline mode, no need to call expose() since nothing is exposed
+ // anyway!
+ expect(content).not.toMatch(`expose()`)
+ })
+
+ test('with defineExpose()', () => {
+ const { content } = compile(
+ `
+ <script setup>
+ import { defineExpose } from 'vue'
+ const count = ref(0)
+ defineExpose({ count })
+ </script>
+ `,
+ { inlineTemplate: true }
+ )
+ assertCode(content)
+ expect(content).toMatch(`setup(__props, { expose })`)
+ expect(content).toMatch(`expose({ count })`)
})
test('referencing scope components and directives', () => {
`)
assertCode(content)
expect(content).toMatch(`export default _defineComponent({
- expose: [],
props: { foo: String },
emits: ['a', 'b'],
- setup(__props, { emit }) {`)
+ setup(__props, { expose, emit }) {`)
})
test('defineProps w/ type', () => {
const DEFINE_PROPS = 'defineProps'
const DEFINE_EMIT = 'defineEmit'
const DEFINE_EMITS = 'defineEmits'
+const DEFINE_EXPOSE = 'defineExpose'
export interface SFCScriptCompileOptions {
/**
let defaultExport: Node | undefined
let hasDefinePropsCall = false
let hasDefineEmitCall = false
+ let hasDefineExposeCall = false
let propsRuntimeDecl: Node | undefined
let propsTypeDecl: TSTypeLiteral | undefined
let propsIdentifier: string | undefined
return false
}
+ function processDefineExpose(node: Node): boolean {
+ if (isCallOf(node, DEFINE_EXPOSE)) {
+ if (hasDefineExposeCall) {
+ error(`duplicate ${DEFINE_EXPOSE}() call`, node)
+ }
+ hasDefineExposeCall = true
+ return true
+ }
+ return false
+ }
+
function checkInvalidScopeReference(node: Node | undefined, method: string) {
if (!node) return
walkIdentifiers(node, id => {
source === 'vue' &&
(imported === DEFINE_PROPS ||
imported === DEFINE_EMIT ||
- imported === DEFINE_EMITS)
+ imported === DEFINE_EMITS ||
+ imported === DEFINE_EXPOSE)
) {
removeSpecifier(i)
} else if (existing) {
}
}
- // process `defineProps` and `defineEmit(s)` calls
- if (
- node.type === 'ExpressionStatement' &&
- (processDefineProps(node.expression) ||
- processDefineEmits(node.expression))
- ) {
- s.remove(node.start! + startOffset, node.end! + startOffset)
+ if (node.type === 'ExpressionStatement') {
+ // process `defineProps` and `defineEmit(s)` calls
+ if (
+ processDefineProps(node.expression) ||
+ processDefineEmits(node.expression)
+ ) {
+ s.remove(node.start! + startOffset, node.end! + startOffset)
+ } else if (processDefineExpose(node.expression)) {
+ // defineExpose({}) -> expose({})
+ const callee = (node.expression as CallExpression).callee
+ s.overwrite(
+ callee.start! + startOffset,
+ callee.end! + startOffset,
+ 'expose'
+ )
+ }
}
+
if (node.type === 'VariableDeclaration' && !node.declare) {
for (const decl of node.declarations) {
if (decl.init) {
if (propsIdentifier) {
s.prependRight(startOffset, `\nconst ${propsIdentifier} = __props`)
}
+
+ const destructureElements =
+ hasDefineExposeCall || !options.inlineTemplate ? [`expose`] : []
if (emitIdentifier) {
- args +=
- emitIdentifier === `emit` ? `, { emit }` : `, { emit: ${emitIdentifier} }`
+ destructureElements.push(
+ emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`
+ )
+ }
+ if (destructureElements.length) {
+ args += `, { ${destructureElements.join(', ')} }`
if (emitTypeDecl) {
- args += `: {
- emit: (${scriptSetup.content.slice(
- emitTypeDecl.start!,
- emitTypeDecl.end!
- )}),
- slots: any,
- attrs: any
- }`
+ args += `: { emit: (${scriptSetup.content.slice(
+ emitTypeDecl.start!,
+ emitTypeDecl.end!
+ )}), expose: any, slots: any, attrs: any }`
}
}
s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
// 11. finalize default export
- // expose: [] makes <script setup> components "closed" by default.
- let runtimeOptions = `\n expose: [],`
+ let runtimeOptions = ``
if (hasInlinedSsrRenderFn) {
runtimeOptions += `\n __ssrInlineRender: true,`
}
} else if (emitTypeDecl) {
runtimeOptions += genRuntimeEmits(typeDeclaredEmits)
}
+
+ // <script setup> components are closed by default. If the user did not
+ // explicitly call `defineExpose`, call expose() with no args.
+ const exposeCall =
+ hasDefineExposeCall || options.inlineTemplate ? `` : ` expose()\n`
if (isTS) {
// for TS, make sure the exported type is still valid type with
// correct props information
`defineComponent`
)}({${def}${runtimeOptions}\n ${
hasAwait ? `async ` : ``
- }setup(${args}) {\n`
+ }setup(${args}) {\n${exposeCall}`
)
s.appendRight(endOffset, `})`)
} else {
s.prependLeft(
startOffset,
`\nexport default {${runtimeOptions}\n ` +
- `${hasAwait ? `async ` : ``}setup(${args}) {\n`
+ `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`
)
s.appendRight(endOffset, `}`)
}