Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix(typescript): correctly patch typescript import
Closes #2561
  • Loading branch information
adamdbradley committed Jul 9, 2020
1 parent 3928403 commit b24933d
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 52 deletions.
29 changes: 14 additions & 15 deletions src/compiler/config/load-config.ts
@@ -1,12 +1,12 @@
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';
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 = {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand All @@ -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)];
Expand All @@ -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
Expand All @@ -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;
}
Expand All @@ -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)) {
Expand All @@ -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;
};
Expand Down
38 changes: 20 additions & 18 deletions src/compiler/sys/typescript/typescript-load.ts
Expand Up @@ -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<TypeScriptModule> => {
// 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<typeof ts> => {
// 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
Expand All @@ -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;
}
}
Expand All @@ -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) => {
Expand All @@ -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`);
Expand All @@ -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<TypeScriptModule>;
export interface ImportTypeScriptModule {
ts: typeof ts;
p: Promise<typeof ts>;
}
12 changes: 6 additions & 6 deletions 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';
Expand All @@ -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
Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/sys/typescript/typescript-resolve-module.ts
Expand Up @@ -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;
Expand Down
23 changes: 11 additions & 12 deletions src/compiler/sys/typescript/typescript-sys.ts
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand All @@ -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) => {
Expand Down

0 comments on commit b24933d

Please sign in to comment.