From d2c3d8b70b2df6e16f053a7ac58e6b04e7b2078f Mon Sep 17 00:00:00 2001
From: Evan You <yyx990803@gmail.com>
Date: Thu, 15 Jun 2023 16:38:44 +0800
Subject: [PATCH] perf(compiler-sfc): lazy require typescript

---
 .../compileScript/resolveType.spec.ts         |  2 +-
 .../compiler-sfc/src/script/resolveType.ts    | 44 ++++++++++++-------
 packages/vue/compiler-sfc/register-ts.js      |  2 +-
 3 files changed, 30 insertions(+), 18 deletions(-)

diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
index 85d67e01cb8..607654a952b 100644
--- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
@@ -10,7 +10,7 @@ import {
 } from '../../src/script/resolveType'
 
 import ts from 'typescript'
-registerTS(ts)
+registerTS(() => ts)
 
 describe('resolveType', () => {
   test('type literal', () => {
diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts
index d30150a82d5..146c454729c 100644
--- a/packages/compiler-sfc/src/script/resolveType.ts
+++ b/packages/compiler-sfc/src/script/resolveType.ts
@@ -708,13 +708,14 @@ function resolveGlobalScope(ctx: TypeResolveContext): TypeScope[] | undefined {
   }
 }
 
-let ts: typeof TS
+let ts: typeof TS | undefined
+let loadTS: (() => typeof TS) | undefined
 
 /**
  * @private
  */
-export function registerTS(_ts: any) {
-  ts = _ts
+export function registerTS(_loadTS: () => typeof TS) {
+  loadTS = _loadTS
 }
 
 type FS = NonNullable<SFCScriptCompileOptions['fs']>
@@ -723,7 +724,10 @@ function resolveFS(ctx: TypeResolveContext): FS | undefined {
   if (ctx.fs) {
     return ctx.fs
   }
-  const fs = ctx.options.fs || ts.sys
+  if (!ts && loadTS) {
+    ts = loadTS()
+  }
+  const fs = ctx.options.fs || ts?.sys
   if (!fs) {
     return
   }
@@ -779,22 +783,25 @@ function importSourceToScope(
     } else {
       // module or aliased import - use full TS resolution, only supported in Node
       if (!__NODE_JS__) {
-        ctx.error(
+        return ctx.error(
           `Type import from non-relative sources is not supported in the browser build.`,
           node,
           scope
         )
       }
       if (!ts) {
-        ctx.error(
-          `Failed to resolve import source ${JSON.stringify(source)}. ` +
-            `typescript is required as a peer dep for vue in order ` +
-            `to support resolving types from module imports.`,
-          node,
-          scope
-        )
+        if (loadTS) ts = loadTS()
+        if (!ts) {
+          return ctx.error(
+            `Failed to resolve import source ${JSON.stringify(source)}. ` +
+              `typescript is required as a peer dep for vue in order ` +
+              `to support resolving types from module imports.`,
+            node,
+            scope
+          )
+        }
       }
-      resolved = resolveWithTS(scope.filename, source, fs)
+      resolved = resolveWithTS(scope.filename, source, ts, fs)
     }
     if (resolved) {
       resolved = scope.resolvedImportSources[source] = normalizePath(resolved)
@@ -839,6 +846,7 @@ const tsConfigRefMap = new Map<string, string>()
 function resolveWithTS(
   containingFile: string,
   source: string,
+  ts: typeof TS,
   fs: FS
 ): string | undefined {
   if (!__NODE_JS__) return
@@ -853,7 +861,7 @@ function resolveWithTS(
     const normalizedConfigPath = normalizePath(configPath)
     const cached = tsConfigCache.get(normalizedConfigPath)
     if (!cached) {
-      configs = loadTSConfig(configPath, fs).map(config => ({ config }))
+      configs = loadTSConfig(configPath, ts, fs).map(config => ({ config }))
       tsConfigCache.set(normalizedConfigPath, configs)
     } else {
       configs = cached
@@ -918,7 +926,11 @@ function resolveWithTS(
   }
 }
 
-function loadTSConfig(configPath: string, fs: FS): TS.ParsedCommandLine[] {
+function loadTSConfig(
+  configPath: string,
+  ts: typeof TS,
+  fs: FS
+): TS.ParsedCommandLine[] {
   // The only case where `fs` is NOT `ts.sys` is during tests.
   // parse config host requires an extra `readDirectory` method
   // during tests, which is stubbed.
@@ -940,7 +952,7 @@ function loadTSConfig(configPath: string, fs: FS): TS.ParsedCommandLine[] {
   if (config.projectReferences) {
     for (const ref of config.projectReferences) {
       tsConfigRefMap.set(ref.path, configPath)
-      res.unshift(...loadTSConfig(ref.path, fs))
+      res.unshift(...loadTSConfig(ref.path, ts, fs))
     }
   }
   return res
diff --git a/packages/vue/compiler-sfc/register-ts.js b/packages/vue/compiler-sfc/register-ts.js
index 87f61b64863..7a073b3a3f8 100644
--- a/packages/vue/compiler-sfc/register-ts.js
+++ b/packages/vue/compiler-sfc/register-ts.js
@@ -1,5 +1,5 @@
 if (typeof require !== 'undefined') {
   try {
-    require('@vue/compiler-sfc').registerTS(require('typescript'))
+    require('@vue/compiler-sfc').registerTS(() => require('typescript'))
   } catch (e) {}
 }