From b24933dfe9b285578d90e15846ea4f5991254415 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Wed, 8 Jul 2020 21:30:19 -0500 Subject: [PATCH] fix(typescript): correctly patch typescript import Closes #2561 --- src/compiler/config/load-config.ts | 29 +++++++------- .../sys/typescript/typescript-load.ts | 38 ++++++++++--------- .../sys/typescript/typescript-patch.ts | 12 +++--- .../typescript/typescript-resolve-module.ts | 2 +- src/compiler/sys/typescript/typescript-sys.ts | 23 ++++++----- 5 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/compiler/config/load-config.ts b/src/compiler/config/load-config.ts index ff68286a719..407f7d10940 100644 --- a/src/compiler/config/load-config.ts +++ b/src/compiler/config/load-config.ts @@ -1,5 +1,4 @@ import type { CompilerSystem, Config, Diagnostic, LoadConfigInit, LoadConfigResults } from '../../declarations'; -import type TypeScript from 'typescript'; import { buildError, catchError, isString, normalizePath, hasError, IS_NODE_ENV } from '@utils'; import { createLogger } from '../sys/logger/console-logger'; import { createSystem } from '../sys/stencil-sys'; @@ -7,6 +6,7 @@ import { dirname, resolve } from 'path'; import { loadTypescript } from '../sys/typescript/typescript-load'; import { validateConfig } from './validate-config'; import { validateTsConfig } from '../sys/typescript/typescript-config'; +import type TypeScript from 'typescript'; export const loadConfig = async (init: LoadConfigInit = {}) => { const results: LoadConfigResults = { @@ -22,7 +22,9 @@ export const loadConfig = async (init: LoadConfigInit = {}) => { const config = init.config || {}; let configPath = init.configPath || config.configPath; - const loadedConfigFile = await loadConfigFile(sys, results.diagnostics, configPath, init.typescriptPath); + const loadedTs = await loadTypescript(sys, init.typescriptPath, false); + + const loadedConfigFile = await loadConfigFile(loadedTs, sys, results.diagnostics, configPath); if (hasError(results.diagnostics)) { return results; } @@ -62,7 +64,6 @@ export const loadConfig = async (init: LoadConfigInit = {}) => { results.config.logger = init.logger || results.config.logger || createLogger(); results.config.logger.setLevel(results.config.logLevel); - const loadedTs = await loadTypescript(sys, init.typescriptPath, false); if (!hasError(results.diagnostics)) { const tsConfigResults = await validateTsConfig(loadedTs, results.config, sys, init); results.diagnostics.push(...tsConfigResults.diagnostics); @@ -82,12 +83,12 @@ export const loadConfig = async (init: LoadConfigInit = {}) => { return results; }; -const loadConfigFile = async (sys: CompilerSystem, diagnostics: Diagnostic[], configPath: string, typeScriptPath: string) => { +const loadConfigFile = async (loadedTs: typeof TypeScript, sys: CompilerSystem, diagnostics: Diagnostic[], configPath: string) => { let config: Config = null; if (isString(configPath)) { // the passed in config was a string, so it's probably a path to the config we need to load - const configFileData = await evaluateConfigFile(sys, diagnostics, configPath, typeScriptPath); + const configFileData = await evaluateConfigFile(loadedTs, sys, diagnostics, configPath); if (hasError(diagnostics)) { return config; } @@ -105,12 +106,10 @@ const loadConfigFile = async (sys: CompilerSystem, diagnostics: Diagnostic[], co return config; }; -const evaluateConfigFile = async (sys: CompilerSystem, diagnostics: Diagnostic[], configFilePath: string, typeScriptPath: string) => { +const evaluateConfigFile = async (loadedTs: typeof TypeScript, sys: CompilerSystem, diagnostics: Diagnostic[], configFilePath: string) => { let configFileData: { config?: Config } = null; try { - const ts = await loadTypescript(sys, typeScriptPath, false); - if (IS_NODE_ENV) { // ensure we cleared out node's internal require() cache for this file delete require.cache[resolve(configFilePath)]; @@ -123,7 +122,7 @@ const evaluateConfigFile = async (sys: CompilerSystem, diagnostics: Diagnostic[] if (configFilePath.endsWith('.ts')) { // looks like we've got a typed config file // let's transpile it to .js quick - sourceText = transpileTypedConfig(ts, diagnostics, sourceText, configFilePath); + sourceText = transpileTypedConfig(loadedTs, diagnostics, sourceText, configFilePath); } else { // quick hack to turn a modern es module // into and old school commonjs module @@ -141,7 +140,7 @@ const evaluateConfigFile = async (sys: CompilerSystem, diagnostics: Diagnostic[] } else { // browser environment, can't use node's require() to evaluate let sourceText = await sys.readFile(configFilePath); - sourceText = transpileTypedConfig(ts, diagnostics, sourceText, configFilePath); + sourceText = transpileTypedConfig(loadedTs, diagnostics, sourceText, configFilePath); if (hasError(diagnostics)) { return configFileData; } @@ -156,7 +155,7 @@ const evaluateConfigFile = async (sys: CompilerSystem, diagnostics: Diagnostic[] return configFileData; }; -const transpileTypedConfig = (ts: typeof TypeScript, diagnostics: Diagnostic[], sourceText: string, filePath: string) => { +const transpileTypedConfig = (loadedTs: typeof TypeScript, diagnostics: Diagnostic[], sourceText: string, filePath: string) => { // let's transpile an awesome stencil.config.ts file into // a boring stencil.config.js file if (hasError(diagnostics)) { @@ -166,16 +165,16 @@ const transpileTypedConfig = (ts: typeof TypeScript, diagnostics: Diagnostic[], const opts: TypeScript.TranspileOptions = { fileName: filePath, compilerOptions: { - module: ts.ModuleKind.CommonJS, - moduleResolution: ts.ModuleResolutionKind.NodeJs, + module: loadedTs.ModuleKind.CommonJS, + moduleResolution: loadedTs.ModuleResolutionKind.NodeJs, esModuleInterop: true, - target: ts.ScriptTarget.ES2015, + target: loadedTs.ScriptTarget.ES2015, allowJs: true, }, reportDiagnostics: false, }; - const output = ts.transpileModule(sourceText, opts); + const output = loadedTs.transpileModule(sourceText, opts); return output.outputText; }; diff --git a/src/compiler/sys/typescript/typescript-load.ts b/src/compiler/sys/typescript/typescript-load.ts index 38e1d02217f..e13e6a312b1 100644 --- a/src/compiler/sys/typescript/typescript-load.ts +++ b/src/compiler/sys/typescript/typescript-load.ts @@ -6,12 +6,16 @@ import { nodeLoadTypeScript } from '../../../sys/node/node-load-typescript'; import { patchRemoteTsSys } from './typescript-patch'; import ts from 'typescript'; -export const loadTypescript = (sys: d.CompilerSystem, typescriptPath: string, sync: boolean): TypeScriptModule | Promise => { - // try sync load typescript methods first +const importedTs: ImportTypeScriptModule = { + ts: null, + p: null, +}; - if ((ts as TypeScriptModule).__loaded) { +export const loadTypescript = (sys: d.CompilerSystem, typescriptPath: string, sync: boolean): typeof ts | Promise => { + // try sync load typescript methods first + if (importedTs.ts) { // already loaded - return ts as TypeScriptModule; + return importedTs.ts; } // check if the global object has "ts" on it @@ -33,7 +37,7 @@ export const loadTypescript = (sys: d.CompilerSystem, typescriptPath: string, sy if (IS_WEB_WORKER_ENV) { const webWorkerTs = getLoadedTs(webWorkerLoadTypeScript(tsUrl)); if (webWorkerTs) { - patchRemoteTsSys(tsUrl); + patchRemoteTsSys(webWorkerTs, tsUrl); return webWorkerTs; } } @@ -43,17 +47,17 @@ export const loadTypescript = (sys: d.CompilerSystem, typescriptPath: string, sy } // async at this point - if (!(ts as TypeScriptModule).__promise) { + if (!importedTs.p) { if (IS_DENO_ENV) { - (ts as TypeScriptModule).__promise = denoLoadTypeScript(sys, typescriptPath); + importedTs.p = denoLoadTypeScript(sys, typescriptPath); } else if (IS_BROWSER_ENV) { - (ts as TypeScriptModule).__promise = browserMainLoadTypeScript(tsUrl); + importedTs.p = browserMainLoadTypeScript(tsUrl); } else { throw new Error(`Unable to load TypeScript`); } } - return (ts as TypeScriptModule).__promise; + return importedTs.p; }; const webWorkerLoadTypeScript = (tsUrl: string) => { @@ -72,7 +76,7 @@ const browserMainLoadTypeScript = (tsUrl: string): any => scriptElm.onload = () => { const browserTs = getLoadedTs((globalThis as any).ts); if (browserTs) { - patchRemoteTsSys(tsUrl); + patchRemoteTsSys(browserTs, tsUrl); resolve(browserTs); } else { reject(`Unable to load TypeScript via browser script`); @@ -91,17 +95,15 @@ const getTsUrl = (sys: d.CompilerSystem, typeScriptPath: string) => { return sys.getRemoteModuleUrl({ moduleId: typecriptDep.name, version: typecriptDep.version, path: typecriptDep.main }); }; -const getLoadedTs = (loadedTs: TypeScriptModule): TypeScriptModule => { +const getLoadedTs = (loadedTs: typeof ts) => { if (loadedTs != null && isFunction(loadedTs.transpileModule)) { - loadedTs.__loaded = true; - return Object.assign(ts, loadedTs); + Object.assign(ts, loadedTs); + return (importedTs.ts = loadedTs); } return null; }; -type TypeScript = typeof ts; - -export interface TypeScriptModule extends TypeScript { - __loaded: boolean; - __promise?: Promise; +export interface ImportTypeScriptModule { + ts: typeof ts; + p: Promise; } diff --git a/src/compiler/sys/typescript/typescript-patch.ts b/src/compiler/sys/typescript/typescript-patch.ts index 815ccee05a7..6a0404e10a8 100644 --- a/src/compiler/sys/typescript/typescript-patch.ts +++ b/src/compiler/sys/typescript/typescript-patch.ts @@ -1,6 +1,6 @@ import type * as d from '../../../declarations'; import { exit, getCurrentDirectory, hasError, isBoolean, noop } from '@utils'; -import { loadTypescript, TypeScriptModule } from './typescript-load'; +import { loadTypescript } from './typescript-load'; import { patchTypeScriptResolveModule } from './typescript-resolve-module'; import { patchTypeScriptSys, patchTypeScriptGetParsedCommandLineOfConfigFile } from './typescript-sys'; import { resolve } from 'path'; @@ -13,16 +13,16 @@ export const patchTypescript = async (config: d.Config, diagnostics: d.Diagnosti }; export const patchTypescriptSync = (config: d.Config, diagnostics: d.Diagnostic[], inMemoryFs: d.InMemoryFileSystem) => { - const loadedTs = loadTypescript(config.sys, config.typescriptPath, true) as TypeScriptModule; + const loadedTs = loadTypescript(config.sys, config.typescriptPath, true) as typeof ts; patchTypescriptModule(config, diagnostics, inMemoryFs, loadedTs); }; -const patchTypescriptModule = async (config: d.Config, diagnostics: d.Diagnostic[], inMemoryFs: d.InMemoryFileSystem, loadedTs: TypeScriptModule) => { +const patchTypescriptModule = async (config: d.Config, diagnostics: d.Diagnostic[], inMemoryFs: d.InMemoryFileSystem, loadedTs: typeof ts) => { if (loadedTs && !hasError(diagnostics)) { // override some properties on the original imported ts object patchTypeScriptSys(loadedTs, config, inMemoryFs); patchTypeScriptResolveModule(loadedTs, config, inMemoryFs); - patchTypeScriptGetParsedCommandLineOfConfigFile(loadedTs, config); + patchTypeScriptGetParsedCommandLineOfConfigFile(loadedTs); // the ts object you see imported here is actually a bogus {} object right now // so assign the loaded ts object to our project's imported "ts" object @@ -31,9 +31,9 @@ const patchTypescriptModule = async (config: d.Config, diagnostics: d.Diagnostic } }; -export const patchRemoteTsSys = (tsUrl: string) => { +export const patchRemoteTsSys = (loadedTs: typeof ts, tsUrl: string) => { // patches just the bare minimum - const tsSys: ts.System = (ts.sys = ts.sys || ({} as any)); + const tsSys: ts.System = (loadedTs.sys = loadedTs.sys || ({} as any)); tsSys.getExecutingFilePath = () => tsUrl; if (!tsSys.getCurrentDirectory) { diff --git a/src/compiler/sys/typescript/typescript-resolve-module.ts b/src/compiler/sys/typescript/typescript-resolve-module.ts index ba4cd5095f7..bbfa318a1b0 100644 --- a/src/compiler/sys/typescript/typescript-resolve-module.ts +++ b/src/compiler/sys/typescript/typescript-resolve-module.ts @@ -32,7 +32,7 @@ export const tsResolveModuleName = (config: d.Config, compilerCtx: d.CompilerCtx const resolveModuleName: typeof ts.resolveModuleName = (ts as any).__resolveModuleName || ts.resolveModuleName; if (moduleName && resolveModuleName && config.tsCompilerOptions) { - const host: ts.ModuleResolutionHost = patchTsSystemFileSystem(config, config.sys, compilerCtx.fs, {} as any); + const host: ts.ModuleResolutionHost = patchTsSystemFileSystem(config, config.sys, compilerCtx.fs, ts, {} as any); const compilerOptions: ts.CompilerOptions = { ...config.tsCompilerOptions }; compilerOptions.resolveJsonModule = true; diff --git a/src/compiler/sys/typescript/typescript-sys.ts b/src/compiler/sys/typescript/typescript-sys.ts index 8b31f124c47..2b77f88dda7 100644 --- a/src/compiler/sys/typescript/typescript-sys.ts +++ b/src/compiler/sys/typescript/typescript-sys.ts @@ -2,19 +2,18 @@ import type * as d from '../../../declarations'; import { basename } from 'path'; import { fetchUrlSync } from '../fetch/fetch-module-sync'; import { IS_CASE_SENSITIVE_FILE_NAMES, IS_WEB_WORKER_ENV, isRemoteUrl, isString, normalizePath } from '@utils'; -import { TypeScriptModule } from './typescript-load'; -import ts from 'typescript'; +import type ts from 'typescript'; -export const patchTypeScriptSys = (loadedTs: TypeScriptModule, config: d.Config, inMemoryFs: d.InMemoryFileSystem) => { +export const patchTypeScriptSys = (loadedTs: typeof ts, config: d.Config, inMemoryFs: d.InMemoryFileSystem) => { loadedTs.sys = loadedTs.sys || ({} as ts.System); if (config.sys) { - patchTsSystemFileSystem(config, config.sys, inMemoryFs, loadedTs.sys); - patchTsSystemWatch(config.sys, loadedTs.sys); + patchTsSystemFileSystem(config, config.sys, inMemoryFs, loadedTs, loadedTs.sys); + patchTsSystemWatch(config.sys, loadedTs, loadedTs.sys); } }; -export const patchTsSystemFileSystem = (config: d.Config, stencilSys: d.CompilerSystem, inMemoryFs: d.InMemoryFileSystem, tsSys: ts.System) => { +export const patchTsSystemFileSystem = (config: d.Config, stencilSys: d.CompilerSystem, inMemoryFs: d.InMemoryFileSystem, loadedTs: typeof ts, tsSys: ts.System) => { const realpath = (path: string) => { const rp = stencilSys.realpathSync(path); if (isString(rp)) { @@ -84,7 +83,7 @@ export const patchTsSystemFileSystem = (config: d.Config, stencilSys: d.Compiler tsSys.readDirectory = (path, extensions, exclude, include, depth) => { const cwd = stencilSys.getCurrentDirectory(); - return (ts as any).matchFiles(path, extensions, exclude, include, IS_CASE_SENSITIVE_FILE_NAMES, cwd, depth, getAccessibleFileSystemEntries, realpath); + return (loadedTs as any).matchFiles(path, extensions, exclude, include, IS_CASE_SENSITIVE_FILE_NAMES, cwd, depth, getAccessibleFileSystemEntries, realpath); }; tsSys.readFile = p => { @@ -116,7 +115,7 @@ export const patchTsSystemFileSystem = (config: d.Config, stencilSys: d.Compiler return tsSys; }; -const patchTsSystemWatch = (stencilSys: d.CompilerSystem, tsSys: ts.System) => { +const patchTsSystemWatch = (stencilSys: d.CompilerSystem, loadedTs: typeof ts, tsSys: ts.System) => { tsSys.watchDirectory = (p, cb, recursive) => { const watcher = stencilSys.watchDirectory( p, @@ -135,11 +134,11 @@ const patchTsSystemWatch = (stencilSys: d.CompilerSystem, tsSys: ts.System) => { tsSys.watchFile = (p, cb) => { const watcher = stencilSys.watchFile(p, (filePath, eventKind) => { if (eventKind === 'fileAdd') { - cb(filePath, ts.FileWatcherEventKind.Created); + cb(filePath, loadedTs.FileWatcherEventKind.Created); } else if (eventKind === 'fileUpdate') { - cb(filePath, ts.FileWatcherEventKind.Changed); + cb(filePath, loadedTs.FileWatcherEventKind.Changed); } else if (eventKind === 'fileDelete') { - cb(filePath, ts.FileWatcherEventKind.Deleted); + cb(filePath, loadedTs.FileWatcherEventKind.Deleted); } }); return { @@ -160,7 +159,7 @@ export const getTypescriptPathFromUrl = (config: d.Config, tsExecutingUrl: strin return url; }; -export const patchTypeScriptGetParsedCommandLineOfConfigFile = (loadedTs: TypeScriptModule, _config: d.Config) => { +export const patchTypeScriptGetParsedCommandLineOfConfigFile = (loadedTs: typeof ts) => { const orgGetParsedCommandLineOfConfigFile = loadedTs.getParsedCommandLineOfConfigFile; loadedTs.getParsedCommandLineOfConfigFile = (configFileName, optionsToExtend, host, extendedConfigCache) => {