]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(compiler-sfc): split all macros
authorEvan You <yyx990803@gmail.com>
Tue, 11 Apr 2023 07:35:05 +0000 (15:35 +0800)
committerEvan You <yyx990803@gmail.com>
Tue, 11 Apr 2023 08:05:00 +0000 (16:05 +0800)
packages/compiler-sfc/src/compileScript.ts
packages/compiler-sfc/src/script/context.ts
packages/compiler-sfc/src/script/defineExpose.ts [new file with mode: 0644]
packages/compiler-sfc/src/script/defineOptions.ts [new file with mode: 0644]
packages/compiler-sfc/src/script/defineSlots.ts [new file with mode: 0644]
packages/compiler-sfc/src/script/resolveType.ts
packages/compiler-sfc/src/script/topLevelAwait.ts [new file with mode: 0644]

index 3fb435223bc455cfdb6d9d774e4de669e75f7461..8693fb29bf39e19a54b6669ee4d3f7de5308a45c 100644 (file)
@@ -18,8 +18,6 @@ import {
   ExportSpecifier,
   Statement,
   CallExpression,
-  AwaitExpression,
-  LVal,
   TSEnumDeclaration
 } from '@babel/types'
 import { walk } from 'estree-walker'
@@ -46,16 +44,15 @@ import {
   genRuntimeEmits,
   DEFINE_EMITS
 } from './script/defineEmits'
+import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose'
+import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
+import { processDefineSlots } from './script/defineSlots'
 import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
 import { isLiteralNode, unwrapTSNode, isCallOf } from './script/utils'
 import { inferRuntimeType } from './script/resolveType'
 import { analyzeScriptBindings } from './script/analyzeScriptBindings'
 import { isImportUsed } from './script/importUsageCheck'
-
-// Special compiler macros
-const DEFINE_EXPOSE = 'defineExpose'
-const DEFINE_OPTIONS = 'defineOptions'
-const DEFINE_SLOTS = 'defineSlots'
+import { processAwait } from './script/topLevelAwait'
 
 export interface SFCScriptCompileOptions {
   /**
@@ -250,30 +247,15 @@ export function compileScript(
   const setupBindings: Record<string, BindingTypes> = Object.create(null)
 
   let defaultExport: Node | undefined
-  let optionsRuntimeDecl: Node | undefined
   let hasAwait = false
   let hasInlinedSsrRenderFn = false
 
-  // magic-string state
-  const startOffset = scriptSetup.loc.start.offset
-  const endOffset = scriptSetup.loc.end.offset
+  // string offsets
+  const startOffset = ctx.startOffset!
+  const endOffset = ctx.endOffset!
   const scriptStartOffset = script && script.loc.start.offset
   const scriptEndOffset = script && script.loc.end.offset
 
-  function error(
-    msg: string,
-    node: Node,
-    end: number = node.end! + startOffset
-  ): never {
-    throw new Error(
-      `[@vue/compiler-sfc] ${msg}\n\n${sfc.filename}\n${generateCodeFrame(
-        source,
-        node.start! + startOffset,
-        end
-      )}`
-    )
-  }
-
   function hoistNode(node: Statement) {
     const start = node.start! + startOffset
     let end = node.end! + startOffset
@@ -324,108 +306,12 @@ export function compileScript(
     }
   }
 
-  function processDefineSlots(node: Node, declId?: LVal): boolean {
-    if (!isCallOf(node, DEFINE_SLOTS)) {
-      return false
-    }
-    if (ctx.hasDefineSlotsCall) {
-      error(`duplicate ${DEFINE_SLOTS}() call`, node)
-    }
-    ctx.hasDefineSlotsCall = true
-
-    if (node.arguments.length > 0) {
-      error(`${DEFINE_SLOTS}() cannot accept arguments`, node)
-    }
-
-    if (declId) {
-      ctx.s.overwrite(
-        startOffset + node.start!,
-        startOffset + node.end!,
-        `${ctx.helper('useSlots')}()`
-      )
-    }
-
-    return true
-  }
-
-  function processDefineOptions(node: Node): boolean {
-    if (!isCallOf(node, DEFINE_OPTIONS)) {
-      return false
-    }
-    if (ctx.hasDefineOptionsCall) {
-      error(`duplicate ${DEFINE_OPTIONS}() call`, node)
-    }
-    if (node.typeParameters) {
-      error(`${DEFINE_OPTIONS}() cannot accept type arguments`, node)
-    }
-    if (!node.arguments[0]) return true
-
-    ctx.hasDefineOptionsCall = true
-    optionsRuntimeDecl = unwrapTSNode(node.arguments[0])
-
-    let propsOption = undefined
-    let emitsOption = undefined
-    let exposeOption = undefined
-    let slotsOption = undefined
-    if (optionsRuntimeDecl.type === 'ObjectExpression') {
-      for (const prop of optionsRuntimeDecl.properties) {
-        if (
-          (prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') &&
-          prop.key.type === 'Identifier'
-        ) {
-          if (prop.key.name === 'props') propsOption = prop
-          if (prop.key.name === 'emits') emitsOption = prop
-          if (prop.key.name === 'expose') exposeOption = prop
-          if (prop.key.name === 'slots') slotsOption = prop
-        }
-      }
-    }
-
-    if (propsOption) {
-      error(
-        `${DEFINE_OPTIONS}() cannot be used to declare props. Use ${DEFINE_PROPS}() instead.`,
-        propsOption
-      )
-    }
-    if (emitsOption) {
-      error(
-        `${DEFINE_OPTIONS}() cannot be used to declare emits. Use ${DEFINE_EMITS}() instead.`,
-        emitsOption
-      )
-    }
-    if (exposeOption) {
-      error(
-        `${DEFINE_OPTIONS}() cannot be used to declare expose. Use ${DEFINE_EXPOSE}() instead.`,
-        exposeOption
-      )
-    }
-    if (slotsOption) {
-      error(
-        `${DEFINE_OPTIONS}() cannot be used to declare slots. Use ${DEFINE_SLOTS}() instead.`,
-        slotsOption
-      )
-    }
-
-    return true
-  }
-
-  function processDefineExpose(node: Node): boolean {
-    if (isCallOf(node, DEFINE_EXPOSE)) {
-      if (ctx.hasDefineExposeCall) {
-        error(`duplicate ${DEFINE_EXPOSE}() call`, node)
-      }
-      ctx.hasDefineExposeCall = true
-      return true
-    }
-    return false
-  }
-
   function checkInvalidScopeReference(node: Node | undefined, method: string) {
     if (!node) return
     walkIdentifiers(node, id => {
       const binding = setupBindings[id.name]
       if (binding && binding !== BindingTypes.LITERAL_CONST) {
-        error(
+        ctx.error(
           `\`${method}()\` in <script setup> cannot reference locally ` +
             `declared variables because it will be hoisted outside of the ` +
             `setup() function. If your component options require initialization ` +
@@ -437,56 +323,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)
-
-    ctx.s.overwrite(
-      node.start! + startOffset,
-      argumentStart + startOffset,
-      `${needSemi ? `;` : ``}(\n  ([__temp,__restore] = ${ctx.helper(
-        `withAsyncContext`
-      )}(${containsNestedAwait ? `async ` : ``}() => `
-    )
-    ctx.s.appendLeft(
-      node.end! + startOffset,
-      `)),\n  ${isStatement ? `` : `__temp = `}await __temp,\n  __restore()${
-        isStatement ? `` : `,\n  __temp`
-      }\n)`
-    )
-  }
-
   const scriptAst = ctx.scriptAst
   const scriptSetupAst = ctx.scriptSetupAst!
 
@@ -556,7 +392,10 @@ export function compileScript(
             // already imported in <script setup>, dedupe
             removeSpecifier(i)
           } else {
-            error(`different imports aliased to same local name.`, specifier)
+            ctx.error(
+              `different imports aliased to same local name.`,
+              specifier
+            )
           }
         } else {
           registerUserImport(
@@ -725,7 +564,7 @@ export function compileScript(
       node.label.name === 'ref' &&
       node.body.type === 'ExpressionStatement'
     ) {
-      error(
+      ctx.error(
         `ref sugar using the label syntax was an experimental proposal and ` +
           `has been dropped based on community feedback. Please check out ` +
           `the new proposal at https://github.com/vuejs/rfcs/discussions/369`,
@@ -739,11 +578,11 @@ export function compileScript(
       if (
         processDefineProps(ctx, expr) ||
         processDefineEmits(ctx, expr) ||
-        processDefineOptions(expr) ||
-        processDefineSlots(expr)
+        processDefineOptions(ctx, expr) ||
+        processDefineSlots(ctx, expr)
       ) {
         ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
-      } else if (processDefineExpose(expr)) {
+      } else if (processDefineExpose(ctx, expr)) {
         // defineExpose({}) -> expose({})
         const callee = (expr as CallExpression).callee
         ctx.s.overwrite(
@@ -765,8 +604,8 @@ export function compileScript(
         const decl = node.declarations[i]
         const init = decl.init && unwrapTSNode(decl.init)
         if (init) {
-          if (processDefineOptions(init)) {
-            error(
+          if (processDefineOptions(ctx, init)) {
+            ctx.error(
               `${DEFINE_OPTIONS}() has no returning value, it cannot be assigned.`,
               node
             )
@@ -777,7 +616,7 @@ export function compileScript(
           const isDefineEmits =
             !isDefineProps && processDefineEmits(ctx, init, decl.id)
           !isDefineEmits &&
-            (processDefineSlots(init, decl.id) ||
+            (processDefineSlots(ctx, init, decl.id) ||
               processDefineModel(ctx, init, decl.id))
 
           if (isDefineProps || isDefineEmits) {
@@ -858,6 +697,7 @@ export function compileScript(
               )
             })
             processAwait(
+              ctx,
               child,
               needsSemi,
               parent.type === 'ExpressionStatement'
@@ -875,7 +715,7 @@ export function compileScript(
       node.type === 'ExportAllDeclaration' ||
       node.type === 'ExportDefaultDeclaration'
     ) {
-      error(
+      ctx.error(
         `<script setup> cannot contain ES module exports. ` +
           `If you are using a previous version of <script setup>, please ` +
           `consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.`,
@@ -929,7 +769,7 @@ export function compileScript(
   checkInvalidScopeReference(ctx.propsRuntimeDefaults, DEFINE_PROPS)
   checkInvalidScopeReference(ctx.propsDestructureDecl, DEFINE_PROPS)
   checkInvalidScopeReference(ctx.emitsRuntimeDecl, DEFINE_EMITS)
-  checkInvalidScopeReference(optionsRuntimeDecl, DEFINE_OPTIONS)
+  checkInvalidScopeReference(ctx.optionsRuntimeDecl, DEFINE_OPTIONS)
 
   // 6. remove non-script content
   if (script) {
@@ -1171,9 +1011,9 @@ export function compileScript(
   if (emitsDecl) runtimeOptions += `\n  emits: ${emitsDecl},`
 
   let definedOptions = ''
-  if (optionsRuntimeDecl) {
+  if (ctx.optionsRuntimeDecl) {
     definedOptions = scriptSetup.content
-      .slice(optionsRuntimeDecl.start!, optionsRuntimeDecl.end!)
+      .slice(ctx.optionsRuntimeDecl.start!, ctx.optionsRuntimeDecl.end!)
       .trim()
   }
 
index 60dda463efab3ddfbf684fab3668f3f0dddc3f8e..c5e325c743407a499311cab1b4c34772d2518a99 100644 (file)
@@ -19,8 +19,6 @@ export class ScriptCompileContext {
   s = new MagicString(this.descriptor.source)
   startOffset = this.descriptor.scriptSetup?.loc.start.offset
   endOffset = this.descriptor.scriptSetup?.loc.end.offset
-  scriptStartOffset = this.descriptor.script?.loc.start.offset
-  scriptEndOffset = this.descriptor.script?.loc.end.offset
 
   declaredTypes: Record<string, string[]> = Object.create(null)
 
@@ -51,6 +49,9 @@ export class ScriptCompileContext {
   // defineModel
   modelDecls: Record<string, ModelDecl> = {}
 
+  // defineOptions
+  optionsRuntimeDecl: Node | undefined
+
   // codegen
   bindingMetadata: BindingMetadata = {}
 
@@ -125,7 +126,7 @@ export class ScriptCompileContext {
           plugins,
           sourceType: 'module'
         },
-        this.scriptStartOffset!
+        this.descriptor.script.loc.start.offset
       )
 
     this.scriptSetupAst =
diff --git a/packages/compiler-sfc/src/script/defineExpose.ts b/packages/compiler-sfc/src/script/defineExpose.ts
new file mode 100644 (file)
index 0000000..636fdba
--- /dev/null
@@ -0,0 +1,19 @@
+import { Node } from '@babel/types'
+import { isCallOf } from './utils'
+import { ScriptCompileContext } from './context'
+
+export const DEFINE_EXPOSE = 'defineExpose'
+
+export function processDefineExpose(
+  ctx: ScriptCompileContext,
+  node: Node
+): boolean {
+  if (isCallOf(node, DEFINE_EXPOSE)) {
+    if (ctx.hasDefineExposeCall) {
+      ctx.error(`duplicate ${DEFINE_EXPOSE}() call`, node)
+    }
+    ctx.hasDefineExposeCall = true
+    return true
+  }
+  return false
+}
diff --git a/packages/compiler-sfc/src/script/defineOptions.ts b/packages/compiler-sfc/src/script/defineOptions.ts
new file mode 100644 (file)
index 0000000..8da3dbc
--- /dev/null
@@ -0,0 +1,73 @@
+import { Node } from '@babel/types'
+import { ScriptCompileContext } from './context'
+import { isCallOf, unwrapTSNode } from './utils'
+import { DEFINE_PROPS } from './defineProps'
+import { DEFINE_EMITS } from './defineEmits'
+import { DEFINE_EXPOSE } from './defineExpose'
+import { DEFINE_SLOTS } from './defineSlots'
+
+export const DEFINE_OPTIONS = 'defineOptions'
+
+export function processDefineOptions(
+  ctx: ScriptCompileContext,
+  node: Node
+): boolean {
+  if (!isCallOf(node, DEFINE_OPTIONS)) {
+    return false
+  }
+  if (ctx.hasDefineOptionsCall) {
+    ctx.error(`duplicate ${DEFINE_OPTIONS}() call`, node)
+  }
+  if (node.typeParameters) {
+    ctx.error(`${DEFINE_OPTIONS}() cannot accept type arguments`, node)
+  }
+  if (!node.arguments[0]) return true
+
+  ctx.hasDefineOptionsCall = true
+  ctx.optionsRuntimeDecl = unwrapTSNode(node.arguments[0])
+
+  let propsOption = undefined
+  let emitsOption = undefined
+  let exposeOption = undefined
+  let slotsOption = undefined
+  if (ctx.optionsRuntimeDecl.type === 'ObjectExpression') {
+    for (const prop of ctx.optionsRuntimeDecl.properties) {
+      if (
+        (prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') &&
+        prop.key.type === 'Identifier'
+      ) {
+        if (prop.key.name === 'props') propsOption = prop
+        if (prop.key.name === 'emits') emitsOption = prop
+        if (prop.key.name === 'expose') exposeOption = prop
+        if (prop.key.name === 'slots') slotsOption = prop
+      }
+    }
+  }
+
+  if (propsOption) {
+    ctx.error(
+      `${DEFINE_OPTIONS}() cannot be used to declare props. Use ${DEFINE_PROPS}() instead.`,
+      propsOption
+    )
+  }
+  if (emitsOption) {
+    ctx.error(
+      `${DEFINE_OPTIONS}() cannot be used to declare emits. Use ${DEFINE_EMITS}() instead.`,
+      emitsOption
+    )
+  }
+  if (exposeOption) {
+    ctx.error(
+      `${DEFINE_OPTIONS}() cannot be used to declare expose. Use ${DEFINE_EXPOSE}() instead.`,
+      exposeOption
+    )
+  }
+  if (slotsOption) {
+    ctx.error(
+      `${DEFINE_OPTIONS}() cannot be used to declare slots. Use ${DEFINE_SLOTS}() instead.`,
+      slotsOption
+    )
+  }
+
+  return true
+}
diff --git a/packages/compiler-sfc/src/script/defineSlots.ts b/packages/compiler-sfc/src/script/defineSlots.ts
new file mode 100644 (file)
index 0000000..cd7dc2e
--- /dev/null
@@ -0,0 +1,33 @@
+import { LVal, Node } from '@babel/types'
+import { isCallOf } from './utils'
+import { ScriptCompileContext } from './context'
+
+export const DEFINE_SLOTS = 'defineSlots'
+
+export function processDefineSlots(
+  ctx: ScriptCompileContext,
+  node: Node,
+  declId?: LVal
+): boolean {
+  if (!isCallOf(node, DEFINE_SLOTS)) {
+    return false
+  }
+  if (ctx.hasDefineSlotsCall) {
+    ctx.error(`duplicate ${DEFINE_SLOTS}() call`, node)
+  }
+  ctx.hasDefineSlotsCall = true
+
+  if (node.arguments.length > 0) {
+    ctx.error(`${DEFINE_SLOTS}() cannot accept arguments`, node)
+  }
+
+  if (declId) {
+    ctx.s.overwrite(
+      ctx.startOffset! + node.start!,
+      ctx.startOffset! + node.end!,
+      `${ctx.helper('useSlots')}()`
+    )
+  }
+
+  return true
+}
index cf02729976f3099dda44657433f0b918713ca0cb..73e76d13791beea838210017ec1ecaca5baad9d2 100644 (file)
@@ -8,11 +8,6 @@ import {
 import { FromNormalScript, UNKNOWN_TYPE } from './utils'
 import { ScriptCompileContext } from './context'
 
-/**
- * Resolve a type Node into
- */
-export function resolveType() {}
-
 export function resolveQualifiedType(
   ctx: ScriptCompileContext,
   node: Node,
diff --git a/packages/compiler-sfc/src/script/topLevelAwait.ts b/packages/compiler-sfc/src/script/topLevelAwait.ts
new file mode 100644 (file)
index 0000000..26534ee
--- /dev/null
@@ -0,0 +1,69 @@
+import { AwaitExpression } from '@babel/types'
+import { ScriptCompileContext } from './context'
+
+/**
+ * Support context-persistence between top-level await expressions:
+ *
+ * ```js
+ * const instance = getCurrentInstance()
+ * await foo()
+ * expect(getCurrentInstance()).toBe(instance)
+ * ```
+ *
+ * In the future we can potentially get rid of this when Async Context
+ * becomes generally available: https://github.com/tc39/proposal-async-context
+ *
+ * ```js
+ * // input
+ * await foo()
+ * // output
+ * ;(
+ *   ([__temp,__restore] = withAsyncContext(() => foo())),
+ *   await __temp,
+ *   __restore()
+ * )
+ *
+ * // input
+ * const a = await foo()
+ * // output
+ * const a = (
+ *   ([__temp, __restore] = withAsyncContext(() => foo())),
+ *   __temp = await __temp,
+ *   __restore(),
+ *   __temp
+ * )
+ * ```
+ */
+export function processAwait(
+  ctx: ScriptCompileContext,
+  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 startOffset = ctx.startOffset!
+  const argumentStr = ctx.descriptor.source.slice(
+    argumentStart + startOffset,
+    node.argument.end! + startOffset
+  )
+
+  const containsNestedAwait = /\bawait\b/.test(argumentStr)
+
+  ctx.s.overwrite(
+    node.start! + startOffset,
+    argumentStart + startOffset,
+    `${needSemi ? `;` : ``}(\n  ([__temp,__restore] = ${ctx.helper(
+      `withAsyncContext`
+    )}(${containsNestedAwait ? `async ` : ``}() => `
+  )
+  ctx.s.appendLeft(
+    node.end! + startOffset,
+    `)),\n  ${isStatement ? `` : `__temp = `}await __temp,\n  __restore()${
+      isStatement ? `` : `,\n  __temp`
+    }\n)`
+  )
+}