فهرست منبع

feat: warn top level await usage in `<script setup>`

Evan You 3 سال پیش
والد
کامیت
efa8a74964

+ 6 - 80
packages/compiler-sfc/src/compileScript.ts

@@ -30,7 +30,6 @@ import {
   CallExpression,
   RestElement,
   TSInterfaceBody,
-  AwaitExpression,
   Program,
   ObjectMethod,
   LVal,
@@ -187,7 +186,6 @@ export function compileScript(
     | undefined
   let emitsTypeDeclRaw: Node | undefined
   let emitIdentifier: string | undefined
-  let hasAwait = false
   let hasInlinedSsrRenderFn = false
   // props/emits declared via types
   const typeDeclaredProps: Record<string, PropTypeData> = {}
@@ -471,56 +469,6 @@ export function compileScript(
     })
   }
 
-  /**
-   * await foo()
-   * -->
-   * ;(
-   *   ([__temp,__restore] = withAsyncContext(() => foo())),
-   *   await __temp,
-   *   __restore()
-   * )
-   *
-   * const a = await foo()
-   * -->
-   * const a = (
-   *   ([__temp, __restore] = withAsyncContext(() => foo())),
-   *   __temp = await __temp,
-   *   __restore(),
-   *   __temp
-   * )
-   */
-  function processAwait(
-    node: AwaitExpression,
-    needSemi: boolean,
-    isStatement: boolean
-  ) {
-    const argumentStart =
-      node.argument.extra && node.argument.extra.parenthesized
-        ? (node.argument.extra.parenStart as number)
-        : node.argument.start!
-
-    const argumentStr = source.slice(
-      argumentStart + startOffset,
-      node.argument.end! + startOffset
-    )
-
-    const containsNestedAwait = /\bawait\b/.test(argumentStr)
-
-    s.overwrite(
-      node.start! + startOffset,
-      argumentStart + startOffset,
-      `${needSemi ? `;` : ``}(\n  ([__temp,__restore] = ${helper(
-        `withAsyncContext`
-      )}(${containsNestedAwait ? `async ` : ``}() => `
-    )
-    s.appendLeft(
-      node.end! + startOffset,
-      `)),\n  ${isStatement ? `` : `__temp = `}await __temp,\n  __restore()${
-        isStatement ? `` : `,\n  __temp`
-      }\n)`
-    )
-  }
-
   /**
    * check defaults. If the default object is an object literal with only
    * static properties, we can directly generate more optimized default
@@ -984,23 +932,9 @@ export function compileScript(
             scope.push(child.body)
           }
           if (child.type === 'AwaitExpression') {
-            hasAwait = true
-            // if the await expression is an expression statement and
-            // - is in the root scope
-            // - or is not the first statement in a nested block scope
-            // then it needs a semicolon before the generated code.
-            const currentScope = scope[scope.length - 1]
-            const needsSemi = currentScope.some((n, i) => {
-              return (
-                (scope.length === 1 || i > 0) &&
-                n.type === 'ExpressionStatement' &&
-                n.start === child.start
-              )
-            })
-            processAwait(
-              child,
-              needsSemi,
-              parent.type === 'ExpressionStatement'
+            error(
+              `Vue 2 does not support top level await in <script setup>.`,
+              child
             )
           }
         },
@@ -1177,11 +1111,6 @@ export function compileScript(
       )}(__props, ${JSON.stringify(Object.keys(propsDestructuredBindings))})\n`
     )
   }
-  // inject temp variables for async context preservation
-  if (hasAwait) {
-    const any = isTS ? `: any` : ``
-    s.prependLeft(startOffset, `\nlet __temp${any}, __restore${any}\n`)
-  }
 
   const destructureElements = hasDefineExposeCall ? [`expose`] : []
   if (emitIdentifier) {
@@ -1268,9 +1197,7 @@ export function compileScript(
       startOffset,
       `\nexport default /*#__PURE__*/${helper(
         `defineComponent`
-      )}({${def}${runtimeOptions}\n  ${
-        hasAwait ? `async ` : ``
-      }setup(${args}) {\n`
+      )}({${def}${runtimeOptions}\n  setup(${args}) {\n`
     )
     s.appendRight(endOffset, `})`)
   } else {
@@ -1280,14 +1207,13 @@ export function compileScript(
       s.prependLeft(
         startOffset,
         `\nexport default /*#__PURE__*/Object.assign(${DEFAULT_VAR}, {${runtimeOptions}\n  ` +
-          `${hasAwait ? `async ` : ``}setup(${args}) {\n`
+          `setup(${args}) {\n`
       )
       s.appendRight(endOffset, `})`)
     } else {
       s.prependLeft(
         startOffset,
-        `\nexport default {${runtimeOptions}\n  ` +
-          `${hasAwait ? `async ` : ``}setup(${args}) {\n`
+        `\nexport default {${runtimeOptions}\n  setup(${args}) {\n`
       )
       s.appendRight(endOffset, `}`)
     }

+ 0 - 399
packages/compiler-sfc/test/__snapshots__/compileScript.spec.ts.snap

@@ -168,405 +168,6 @@ return { n, x }
 })"
 `;
 
-exports[`SFC compile <script setup> > async/await detection > expression statement 1`] = `
-"import { withAsyncContext as _withAsyncContext } from 'vue'
-
-export default {
-  async setup(__props) {
-
-let __temp, __restore
-;(
-  ([__temp,__restore] = _withAsyncContext(() => foo)),
-  await __temp,
-  __restore()
-)
-return {  }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > multiple \`if for\` nested statements 1`] = `
-"import { withAsyncContext as _withAsyncContext } from 'vue'
-
-export default {
-  async setup(__props) {
-
-let __temp, __restore
-if (ok) {
-        for (let a of [1,2,3]) {
-          (
-  ([__temp,__restore] = _withAsyncContext(() => a)),
-  await __temp,
-  __restore()
-)
-        }
-        for (let a of [1,2,3]) {
-          (
-  ([__temp,__restore] = _withAsyncContext(() => a)),
-  await __temp,
-  __restore()
-)
-          ;(
-  ([__temp,__restore] = _withAsyncContext(() => a)),
-  await __temp,
-  __restore()
-)
-        }
-      }
-return {  }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > multiple \`if while\` nested statements 1`] = `
-"import { withAsyncContext as _withAsyncContext } from 'vue'
-
-export default {
-  async setup(__props) {
-
-let __temp, __restore
-if (ok) {
-        while (d) {
-          (
-  ([__temp,__restore] = _withAsyncContext(() => 5)),
-  await __temp,
-  __restore()
-)
-        }
-        while (d) {
-          (
-  ([__temp,__restore] = _withAsyncContext(() => 5)),
-  await __temp,
-  __restore()
-)
-          ;(
-  ([__temp,__restore] = _withAsyncContext(() => 6)),
-  await __temp,
-  __restore()
-)
-          if (c) {
-            let f = 10
-            10 + (
-  ([__temp,__restore] = _withAsyncContext(() => 7)),
-  __temp = await __temp,
-  __restore(),
-  __temp
-)
-          } else {
-            (
-  ([__temp,__restore] = _withAsyncContext(() => 8)),
-  await __temp,
-  __restore()
-)
-            ;(
-  ([__temp,__restore] = _withAsyncContext(() => 9)),
-  await __temp,
-  __restore()
-)
-          }
-        }
-      }
-return {  }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > multiple \`if\` nested statements 1`] = `
-"import { withAsyncContext as _withAsyncContext } from 'vue'
-
-export default {
-  async setup(__props) {
-
-let __temp, __restore
-if (ok) {
-        let a = 'foo'
-        ;(
-  ([__temp,__restore] = _withAsyncContext(() => 0)),
-  __temp = await __temp,
-  __restore(),
-  __temp
-) + (
-  ([__temp,__restore] = _withAsyncContext(() => 1)),
-  __temp = await __temp,
-  __restore(),
-  __temp
-)
-        ;(
-  ([__temp,__restore] = _withAsyncContext(() => 2)),
-  await __temp,
-  __restore()
-)
-      } else if (a) {
-        (
-  ([__temp,__restore] = _withAsyncContext(() => 10)),
-  await __temp,
-  __restore()
-)
-        if (b) {
-          (
-  ([__temp,__restore] = _withAsyncContext(() => 0)),
-  __temp = await __temp,
-  __restore(),
-  __temp
-) + (
-  ([__temp,__restore] = _withAsyncContext(() => 1)),
-  __temp = await __temp,
-  __restore(),
-  __temp
-)
-        } else {
-          let a = 'foo'
-          ;(
-  ([__temp,__restore] = _withAsyncContext(() => 2)),
-  await __temp,
-  __restore()
-)
-        }
-        if (b) {
-          (
-  ([__temp,__restore] = _withAsyncContext(() => 3)),
-  await __temp,
-  __restore()
-)
-          ;(
-  ([__temp,__restore] = _withAsyncContext(() => 4)),
-  await __temp,
-  __restore()
-)
-        }
-      } else {
-        (
-  ([__temp,__restore] = _withAsyncContext(() => 5)),
-  await __temp,
-  __restore()
-)
-      }
-return {  }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > nested await 1`] = `
-"import { withAsyncContext as _withAsyncContext } from 'vue'
-
-export default {
-  async setup(__props) {
-
-let __temp, __restore
-;(
-  ([__temp,__restore] = _withAsyncContext(async () => ((
-  ([__temp,__restore] = _withAsyncContext(() => foo)),
-  __temp = await __temp,
-  __restore(),
-  __temp
-)))),
-  await __temp,
-  __restore()
-)
-return {  }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > nested await 2`] = `
-"import { withAsyncContext as _withAsyncContext } from 'vue'
-
-export default {
-  async setup(__props) {
-
-let __temp, __restore
-;(
-  ([__temp,__restore] = _withAsyncContext(async () => (((
-  ([__temp,__restore] = _withAsyncContext(() => foo)),
-  __temp = await __temp,
-  __restore(),
-  __temp
-))))),
-  await __temp,
-  __restore()
-)
-return {  }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > nested await 3`] = `
-"import { withAsyncContext as _withAsyncContext } from 'vue'
-
-export default {
-  async setup(__props) {
-
-let __temp, __restore
-;(
-  ([__temp,__restore] = _withAsyncContext(async () => ((
-  ([__temp,__restore] = _withAsyncContext(async () => ((
-  ([__temp,__restore] = _withAsyncContext(() => foo)),
-  __temp = await __temp,
-  __restore(),
-  __temp
-)))),
-  __temp = await __temp,
-  __restore(),
-  __temp
-)))),
-  await __temp,
-  __restore()
-)
-return {  }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > nested leading await in expression statement 1`] = `
-"import { withAsyncContext as _withAsyncContext } from 'vue'
-
-export default {
-  async setup(__props) {
-
-let __temp, __restore
-foo()
-;(
-  ([__temp,__restore] = _withAsyncContext(() => 1)),
-  __temp = await __temp,
-  __restore(),
-  __temp
-) + (
-  ([__temp,__restore] = _withAsyncContext(() => 2)),
-  __temp = await __temp,
-  __restore(),
-  __temp
-)
-return {  }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > nested statements 1`] = `
-"import { withAsyncContext as _withAsyncContext } from 'vue'
-
-export default {
-  async setup(__props) {
-
-let __temp, __restore
-if (ok) { (
-  ([__temp,__restore] = _withAsyncContext(() => foo)),
-  await __temp,
-  __restore()
-) } else { (
-  ([__temp,__restore] = _withAsyncContext(() => bar)),
-  await __temp,
-  __restore()
-) }
-return {  }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > ref 1`] = `
-"import { withAsyncContext as _withAsyncContext } from 'vue'
-
-export default {
-  async setup(__props) {
-
-let __temp, __restore
-let a = $ref(1 + ((
-  ([__temp,__restore] = _withAsyncContext(() => foo)),
-  __temp = await __temp,
-  __restore(),
-  __temp
-)))
-return { a }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > should ignore await inside functions 1`] = `
-"export default {
-  setup(__props) {
-async function foo() { await bar }
-return { foo }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > should ignore await inside functions 2`] = `
-"export default {
-  setup(__props) {
-const foo = async () => { await bar }
-return { foo }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > should ignore await inside functions 3`] = `
-"export default {
-  setup(__props) {
-const obj = { async method() { await bar }}
-return { obj }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > should ignore await inside functions 4`] = `
-"export default {
-  setup(__props) {
-const cls = class Foo { async method() { await bar }}
-return { cls }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > single line conditions 1`] = `
-"import { withAsyncContext as _withAsyncContext } from 'vue'
-
-export default {
-  async setup(__props) {
-
-let __temp, __restore
-if (false) (
-  ([__temp,__restore] = _withAsyncContext(() => foo())),
-  await __temp,
-  __restore()
-)
-return {  }
-}
-
-}"
-`;
-
-exports[`SFC compile <script setup> > async/await detection > variable 1`] = `
-"import { withAsyncContext as _withAsyncContext } from 'vue'
-
-export default {
-  async setup(__props) {
-
-let __temp, __restore
-const a = 1 + ((
-  ([__temp,__restore] = _withAsyncContext(() => foo)),
-  __temp = await __temp,
-  __restore(),
-  __temp
-))
-return { a }
-}
-
-}"
-`;
-
 exports[`SFC compile <script setup> > binding analysis for destructure 1`] = `
 "export default {
   setup(__props) {

+ 2 - 116
packages/compiler-sfc/test/compileScript.spec.ts

@@ -1,11 +1,11 @@
 import { BindingTypes } from '../src/types'
 import { parse, ParseOptions } from '../src/parse'
 import { parse as babelParse } from '@babel/parser'
-import { compileScript, ScriptCompileOptions } from '../src/compileScript'
+import { compileScript, SFCScriptCompileOptions } from '../src/compileScript'
 
 function compile(
   source: string,
-  options?: Partial<ScriptCompileOptions>,
+  options?: Partial<SFCScriptCompileOptions>,
   parseOptions?: Partial<ParseOptions>
 ) {
   const sfc = parse({
@@ -1165,120 +1165,6 @@ const emit = defineEmits(['a', 'b'])
     })
   })
 
-  describe('async/await detection', () => {
-    function assertAwaitDetection(code: string, shouldAsync = true) {
-      const { content } = compile(`<script setup>${code}</script>`)
-      if (shouldAsync) {
-        expect(content).toMatch(`let __temp, __restore`)
-      }
-      expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`)
-      assertCode(content)
-      return content
-    }
-
-    test('expression statement', () => {
-      assertAwaitDetection(`await foo`)
-    })
-
-    test('variable', () => {
-      assertAwaitDetection(`const a = 1 + (await foo)`)
-    })
-
-    test('ref', () => {
-      assertAwaitDetection(`let a = $ref(1 + (await foo))`)
-    })
-
-    // #4448
-    test('nested await', () => {
-      assertAwaitDetection(`await (await foo)`)
-      assertAwaitDetection(`await ((await foo))`)
-      assertAwaitDetection(`await (await (await foo))`)
-    })
-
-    // should prepend semicolon
-    test('nested leading await in expression statement', () => {
-      const code = assertAwaitDetection(`foo()\nawait 1 + await 2`)
-      expect(code).toMatch(`foo()\n;(`)
-    })
-
-    // #4596 should NOT prepend semicolon
-    test('single line conditions', () => {
-      const code = assertAwaitDetection(`if (false) await foo()`)
-      expect(code).not.toMatch(`if (false) ;(`)
-    })
-
-    test('nested statements', () => {
-      assertAwaitDetection(`if (ok) { await foo } else { await bar }`)
-    })
-
-    test('multiple `if` nested statements', () => {
-      assertAwaitDetection(`if (ok) {
-        let a = 'foo'
-        await 0 + await 1
-        await 2
-      } else if (a) {
-        await 10
-        if (b) {
-          await 0 + await 1
-        } else {
-          let a = 'foo'
-          await 2
-        }
-        if (b) {
-          await 3
-          await 4
-        }
-      } else {
-        await 5
-      }`)
-    })
-
-    test('multiple `if while` nested statements', () => {
-      assertAwaitDetection(`if (ok) {
-        while (d) {
-          await 5
-        }
-        while (d) {
-          await 5
-          await 6
-          if (c) {
-            let f = 10
-            10 + await 7
-          } else {
-            await 8
-            await 9
-          }
-        }
-      }`)
-    })
-
-    test('multiple `if for` nested statements', () => {
-      assertAwaitDetection(`if (ok) {
-        for (let a of [1,2,3]) {
-          await a
-        }
-        for (let a of [1,2,3]) {
-          await a
-          await a
-        }
-      }`)
-    })
-
-    test('should ignore await inside functions', () => {
-      // function declaration
-      assertAwaitDetection(`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(() =>