]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-sfc): `<script setup>` support (experimental)
authorEvan You <yyx990803@gmail.com>
Fri, 10 Jul 2020 22:00:13 +0000 (18:00 -0400)
committerEvan You <yyx990803@gmail.com>
Fri, 10 Jul 2020 22:00:13 +0000 (18:00 -0400)
This is the last commit for the feature which adds async/await detection.

packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-sfc/__tests__/compileScript.spec.ts
packages/compiler-sfc/src/compileScript.ts

index 1e223067c2a54c879e0bff8b16d5faa63bc43d39..6256569f04cb5f528116959162c4047fdfec1a1b 100644 (file)
@@ -263,8 +263,9 @@ export function processExpression(
   return ret
 }
 
-const isFunction = (node: Node): node is Function =>
-  /Function(Expression|Declaration)$/.test(node.type)
+const isFunction = (node: Node): node is Function => {
+  return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
+}
 
 const isStaticProperty = (node: Node): node is ObjectProperty =>
   node &&
index 4be8f5e1ffac0655f0e6fb1a56b3f1a04f76589a..4015683f5fb141ef17268b0cd788b6f17f8ec76f 100644 (file)
@@ -49,10 +49,6 @@ describe('SFC compile <script setup>', () => {
     )
   })
 
-  test('async/await detection', () => {
-    // TODO
-  })
-
   describe('exports', () => {
     test('export const x = ...', () => {
       const { content, bindings } = compile(
@@ -333,6 +329,45 @@ describe('SFC compile <script setup>', () => {
     })
   })
 
+  describe('async/await detection', () => {
+    function assertAwaitDetection(code: string, shouldAsync = true) {
+      const { content } = compile(`<script setup>${code}</script>`)
+      expect(content).toMatch(
+        `export ${shouldAsync ? `async ` : ``}function setup`
+      )
+    }
+
+    test('expression statement', () => {
+      assertAwaitDetection(`await foo`)
+    })
+
+    test('variable', () => {
+      assertAwaitDetection(`const a = 1 + (await foo)`)
+    })
+
+    test('export', () => {
+      assertAwaitDetection(`export const a = 1 + (await foo)`)
+    })
+
+    test('nested statements', () => {
+      assertAwaitDetection(`if (ok) { await foo } else { await bar }`)
+    })
+
+    test('should ignore await inside functions', () => {
+      // function declaration
+      assertAwaitDetection(`export async function foo() { await bar }`, false)
+      // function expression
+      assertAwaitDetection(`const foo = async () => { await bar }`, false)
+      // object method
+      assertAwaitDetection(`const obj = { async method() { await bar }}`, false)
+      // class method
+      assertAwaitDetection(
+        `const cls = class Foo { async method() { await bar }}`,
+        false
+      )
+    })
+  })
+
   describe('errors', () => {
     test('<script> and <script setup> must have same lang', () => {
       expect(
index 33d3c40f47b5418b1e1df08891cc36d348b4db7b..3bcd2b2fbb207d5757839e2bead35d335e6e3b3c 100644 (file)
@@ -11,6 +11,7 @@ import {
   ExpressionStatement,
   ArrowFunctionExpression,
   ExportSpecifier,
+  Function as FunctionNode,
   TSType,
   TSTypeLiteral,
   TSFunctionType,
@@ -87,7 +88,8 @@ export function compileScript(
   const setupExports: Record<string, boolean> = {}
   let exportAllIndex = 0
   let defaultExport: Node | undefined
-  let needDefaultExportRefCheck: boolean = false
+  let needDefaultExportRefCheck = false
+  let hasAwait = false
 
   const checkDuplicateDefaultExport = (node: Node) => {
     if (defaultExport) {
@@ -230,7 +232,11 @@ export function compileScript(
 
   // 3. parse <script setup> and  walk over top level statements
   for (const node of parse(scriptSetup.content, {
-    plugins,
+    plugins: [
+      ...plugins,
+      // allow top level await but only inside <script setup>
+      'topLevelAwait'
+    ],
     sourceType: 'module'
   }).program.body) {
     const start = node.start! + startOffset
@@ -439,6 +445,27 @@ export function compileScript(
       recordType(node, declaredTypes)
       s.move(start, end, 0)
     }
+
+    // walk statements & named exports / variable declarations for top level
+    // await
+    if (
+      node.type === 'VariableDeclaration' ||
+      (node.type === 'ExportNamedDeclaration' &&
+        node.declaration &&
+        node.declaration.type === 'VariableDeclaration') ||
+      node.type.endsWith('Statement')
+    ) {
+      ;(walk as any)(node, {
+        enter(node: Node) {
+          if (isFunction(node)) {
+            this.skip()
+          }
+          if (node.type === 'AwaitExpression') {
+            hasAwait = true
+          }
+        }
+      })
+    }
   }
 
   // 4. check default export to make sure it doesn't reference setup scope
@@ -503,7 +530,10 @@ export function compileScript(
   // 6. wrap setup code with function.
   // export the content of <script setup> as a named export, `setup`.
   // this allows `import { setup } from '*.vue'` for testing purposes.
-  s.prependLeft(startOffset, `\nexport function setup(${args}) {\n`)
+  s.prependLeft(
+    startOffset,
+    `\nexport ${hasAwait ? `async ` : ``}function setup(${args}) {\n`
+  )
 
   // generate return statement
   let returned = `{ ${Object.keys(setupExports).join(', ')} }`
@@ -867,11 +897,7 @@ function checkDefaultExport(
               )
           )
         }
-      } else if (
-        node.type === 'FunctionDeclaration' ||
-        node.type === 'FunctionExpression' ||
-        node.type === 'ArrowFunctionExpression'
-      ) {
+      } else if (isFunction(node)) {
         // walk function expressions and add its arguments to known identifiers
         // so that we don't prefix them
         node.params.forEach(p =>
@@ -927,6 +953,10 @@ function isStaticPropertyKey(node: Node, parent: Node): boolean {
   )
 }
 
+function isFunction(node: Node): node is FunctionNode {
+  return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
+}
+
 /**
  * Analyze bindings in normal `<script>`
  * Note that `compileScriptSetup` already analyzes bindings as part of its