]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(sfc): defineExpose
authorEvan You <yyx990803@gmail.com>
Fri, 25 Jun 2021 17:14:49 +0000 (13:14 -0400)
committerEvan You <yyx990803@gmail.com>
Fri, 25 Jun 2021 17:14:49 +0000 (13:14 -0400)
packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
packages/compiler-sfc/__tests__/__snapshots__/cssVars.spec.ts.snap
packages/compiler-sfc/__tests__/compileScript.spec.ts
packages/compiler-sfc/src/compileScript.ts

index a4738da046a14d6c7d58cb4439db2f7d9a6f43f6..d65806a49af0b46ef0bec53690c1309eb573e43c 100644 (file)
@@ -6,8 +6,8 @@ exports[`SFC compile <script setup> <script> and <script setup> co-usage script
       export const n = 1
       
 export default {
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
       x()
       
@@ -21,8 +21,8 @@ exports[`SFC compile <script setup> <script> and <script setup> co-usage script
 "import { x } from './x'
       
 export default {
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
       x()
       
@@ -35,9 +35,9 @@ return { 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()
 
 
 
@@ -49,9 +49,9 @@ return { myEmit }
 
 exports[`SFC compile <script setup> defineEmits() 1`] = `
 "export default {
-  expose: [],
   emits: ['foo', 'bar'],
-  setup(__props, { emit: myEmit }) {
+  setup(__props, { expose, emit: myEmit }) {
+  expose()
 
 
 
@@ -61,13 +61,25 @@ return { myEmit }
 }"
 `;
 
+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
 
@@ -83,7 +95,6 @@ exports[`SFC compile <script setup> errors should allow defineProps/Emit() refer
 "import { bar } from './bar'
         
 export default {
-  expose: [],
   props: {
           foo: {
             default: () => bar
@@ -92,7 +103,8 @@ export default {
   emits: {
           foo: () => bar > 1
         },
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
         
         
@@ -105,7 +117,6 @@ return { bar }
 
 exports[`SFC compile <script setup> errors should allow defineProps/Emit() referencing scope var 1`] = `
 "export default {
-  expose: [],
   props: {
             foo: {
               default: bar => bar + 1
@@ -114,7 +125,8 @@ exports[`SFC compile <script setup> errors should allow defineProps/Emit() refer
   emits: {
             foo: bar => bar > 1
           },
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
           const bar = 1
           
@@ -131,8 +143,8 @@ exports[`SFC compile <script setup> imports dedupe between user & helper 1`] = `
 import { ref } from 'vue'
       
 export default {
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
       const foo = _ref(1)
       
@@ -146,8 +158,8 @@ exports[`SFC compile <script setup> imports import dedupe between <script> and <
 "import { x } from './x'
         
 export default {
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
         x()
         
@@ -161,10 +173,10 @@ exports[`SFC compile <script setup> imports should allow defineProps/Emit at the
 "import { ref } from 'vue'
       
 export default {
-  expose: [],
   props: ['foo'],
   emits: ['bar'],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
       
       
@@ -181,8 +193,8 @@ exports[`SFC compile <script setup> imports should extract comment for import or
         import b from 'b'
         
 export default {
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
         
 return { a, b }
@@ -196,8 +208,8 @@ exports[`SFC compile <script setup> imports should hoist and expose imports 1`]
           import 'foo/css'
         
 export default {
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
           
 return { ref }
@@ -214,7 +226,6 @@ import { ref } from 'vue'
         import other from './util'
         
 export default {
-  expose: [],
   setup(__props) {
 
         const count = ref(0)
@@ -247,7 +258,6 @@ import ChildComp from './Child.vue'
         import myDir from './my-dir'
         
 export default {
-  expose: [],
   setup(__props) {
 
         
@@ -271,7 +281,6 @@ const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxxx\\")
 
 
 export default {
-  expose: [],
   setup(__props) {
 
         const msg = 1
@@ -292,7 +301,6 @@ const _hoisted_1 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"static\\", -1 /
 import { ref } from 'vue'
         
 export default {
-  expose: [],
   setup(__props) {
 
         const count = ref(0)
@@ -315,7 +323,6 @@ import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate }
 import { ref } from 'vue'
         
 export default {
-  expose: [],
   __ssrInlineRender: true,
   setup(__props) {
 
@@ -348,7 +355,6 @@ exports[`SFC compile <script setup> inlineTemplate mode template assignment expr
 import { ref } from 'vue'
         
 export default {
-  expose: [],
   setup(__props) {
 
         const count = ref(0)
@@ -386,7 +392,6 @@ exports[`SFC compile <script setup> inlineTemplate mode template destructure ass
 import { ref } from 'vue'
         
 export default {
-  expose: [],
   setup(__props) {
 
         const val = {}
@@ -418,7 +423,6 @@ exports[`SFC compile <script setup> inlineTemplate mode template update expressi
 import { ref } from 'vue'
         
 export default {
-  expose: [],
   setup(__props) {
 
         const count = ref(0)
@@ -458,7 +462,6 @@ exports[`SFC compile <script setup> inlineTemplate mode v-model codegen 1`] = `
 import { ref } from 'vue'
         
 export default {
-  expose: [],
   setup(__props) {
 
         const count = ref(0)
@@ -489,12 +492,25 @@ return (_ctx, _cache) => {
 }"
 `;
 
+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)
@@ -512,8 +528,8 @@ exports[`SFC compile <script setup> ref: syntax sugar array destructure 1`] = `
 "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);
@@ -531,8 +547,8 @@ exports[`SFC compile <script setup> ref: syntax sugar convert ref declarations 1
 "import { ref as _ref } from 'vue'
 
 export default {
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
       const foo = _ref()
       const a = _ref(1)
@@ -552,8 +568,8 @@ exports[`SFC compile <script setup> ref: syntax sugar multi ref declarations 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
@@ -569,8 +585,8 @@ exports[`SFC compile <script setup> ref: syntax sugar mutating ref binding 1`] =
 "import { ref as _ref } from 'vue'
 
 export default {
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
       const a = _ref(1)
       const b = _ref({ count: 0 })
@@ -593,8 +609,8 @@ exports[`SFC compile <script setup> ref: syntax sugar nested destructure 1`] = `
 "import { ref as _ref } from 'vue'
 
 export default {
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
       const [{ a: { b: __b }}] = useFoo()
 const b = _ref(__b);
@@ -613,8 +629,8 @@ exports[`SFC compile <script setup> ref: syntax sugar object destructure 1`] = `
 "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);
@@ -634,8 +650,8 @@ return { n, a, c, d, f, g, foo }
 
 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
@@ -651,8 +667,8 @@ exports[`SFC compile <script setup> ref: syntax sugar should not rewrite scope v
 "import { ref as _ref } from 'vue'
 
 export default {
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
         const a = _ref(1)
         const b = _ref(1)
@@ -680,8 +696,8 @@ exports[`SFC compile <script setup> ref: syntax sugar using ref binding in prope
 "import { ref as _ref } from 'vue'
 
 export default {
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
       const a = _ref(1)
       const b = { a: a.value }
@@ -699,8 +715,8 @@ exports[`SFC compile <script setup> should expose top level declarations 1`] = `
 "import { x } from './x'
       
 export default {
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
       let a = 1
       const b = 2
@@ -718,13 +734,9 @@ exports[`SFC compile <script setup> with TypeScript defineEmits w/ type (type li
 
       
 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()
 
       
       
@@ -739,13 +751,9 @@ exports[`SFC compile <script setup> with TypeScript defineEmits w/ type 1`] = `
 
       
 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()
 
       
       
@@ -764,7 +772,6 @@ exports[`SFC compile <script setup> with TypeScript defineProps w/ type 1`] = `
 
       
 export default _defineComponent({
-  expose: [],
   props: {
     string: { type: String, required: true },
     number: { type: Number, required: true },
@@ -811,7 +818,8 @@ export default _defineComponent({
         literalUnion: 'foo' | 'bar'
         literalUnionMixed: 'foo' | 1 | boolean
         intersection: Test & {}
-      }) {
+      }, { expose }) {
+  expose()
 
       
       
@@ -826,10 +834,10 @@ exports[`SFC compile <script setup> with TypeScript defineProps/Emit w/ runtime
 
 
 export default _defineComponent({
-  expose: [],
   props: { foo: String },
   emits: ['a', 'b'],
-  setup(__props, { emit }) {
+  setup(__props, { expose, emit }) {
+  expose()
 
 const props = __props
 
@@ -847,8 +855,8 @@ export interface Foo {}
         type Bar = {}
       
 export default _defineComponent({
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
         
 return {  }
index 99f963804b5396e86ff74bf625cdefcd022f8326..a5effc3530f7c53a48c1fc3d7880e0cd3fbdd775 100644 (file)
@@ -53,8 +53,8 @@ exports[`CSS vars injection codegen w/ <script setup> 1`] = `
 "import { useCssVars as _useCssVars, unref as _unref } from 'vue'
 
 export default {
-  expose: [],
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
 _useCssVars(_ctx => ({
   \\"xxxxxxxx-color\\": (color)
@@ -88,11 +88,11 @@ exports[`CSS vars injection w/ <script setup> binding analysis 1`] = `
 import { ref } from 'vue'
         
 export default {
-  expose: [],
   props: {
           foo: String
         },
-  setup(__props) {
+  setup(__props, { expose }) {
+  expose()
 
 _useCssVars(_ctx => ({
   \\"xxxxxxxx-color\\": (color),
index d2bf3698aec5ce304b8984adb90f9b337a8c9025..085e2956f71d99efa528d3f9375104234f05269e 100644 (file)
@@ -38,12 +38,11 @@ const bar = 1
     // 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
 },`)
@@ -63,10 +62,9 @@ const myEmit = defineEmit(['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'],`)
   })
 
@@ -84,13 +82,28 @@ const myEmit = defineEmits(['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(`
@@ -198,6 +211,25 @@ const myEmit = defineEmits(['foo', 'bar'])
       // 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', () => {
@@ -456,10 +488,9 @@ const emit = defineEmits(['a', 'b'])
       `)
       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', () => {
index a56888a1c29c8d630530e4a423387fd7ba1bcd7f..08f4863444d7781c4ccf1cf0c9036f41fcd23d6c 100644 (file)
@@ -37,6 +37,7 @@ import { rewriteDefault } from './rewriteDefault'
 const DEFINE_PROPS = 'defineProps'
 const DEFINE_EMIT = 'defineEmit'
 const DEFINE_EMITS = 'defineEmits'
+const DEFINE_EXPOSE = 'defineExpose'
 
 export interface SFCScriptCompileOptions {
   /**
@@ -188,6 +189,7 @@ export function compileScript(
   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
@@ -324,6 +326,17 @@ export function compileScript(
     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 => {
@@ -633,7 +646,8 @@ export function compileScript(
           source === 'vue' &&
           (imported === DEFINE_PROPS ||
             imported === DEFINE_EMIT ||
-            imported === DEFINE_EMITS)
+            imported === DEFINE_EMITS ||
+            imported === DEFINE_EXPOSE)
         ) {
           removeSpecifier(i)
         } else if (existing) {
@@ -657,14 +671,24 @@ export function compileScript(
       }
     }
 
-    // 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) {
@@ -863,18 +887,21 @@ export function compileScript(
   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 }`
     }
   }
 
@@ -957,8 +984,7 @@ export function compileScript(
   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,`
   }
@@ -976,6 +1002,11 @@ export function compileScript(
   } 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
@@ -991,7 +1022,7 @@ export function compileScript(
         `defineComponent`
       )}({${def}${runtimeOptions}\n  ${
         hasAwait ? `async ` : ``
-      }setup(${args}) {\n`
+      }setup(${args}) {\n${exposeCall}`
     )
     s.appendRight(endOffset, `})`)
   } else {
@@ -1008,7 +1039,7 @@ export function compileScript(
       s.prependLeft(
         startOffset,
         `\nexport default {${runtimeOptions}\n  ` +
-          `${hasAwait ? `async ` : ``}setup(${args}) {\n`
+          `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`
       )
       s.appendRight(endOffset, `}`)
     }