diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eead78f8..b396a94e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## v8.0.8 +* [Fixed memory leak when using multiple webpack instances](https://github.com/TypeStrong/ts-loader/pull/1205) - thanks @valerio + ## v8.0.7 * [Speeds up project reference build and doesnt store the result in memory](https://github.com/TypeStrong/ts-loader/pull/1202) - thanks @sheetalkamat diff --git a/package.json b/package.json index f9ffda999..e5e48f498 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ts-loader", - "version": "8.0.7", + "version": "8.0.8", "description": "TypeScript loader for webpack", "main": "index.js", "types": "dist", diff --git a/src/index.ts b/src/index.ts index 37af8a480..95e53e301 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,7 +27,6 @@ import { isReferencedFile, } from './utils'; -const webpackInstances: webpack.Compiler[] = []; const loaderOptionsCache: LoaderOptionsCache = {}; /** @@ -174,12 +173,6 @@ function getOptionsHash(loaderOptions: LoaderOptions) { * or creates them, adds them to the cache and returns */ function getLoaderOptions(loaderContext: webpack.loader.LoaderContext) { - // differentiate the TypeScript instance based on the webpack instance - let webpackIndex = webpackInstances.indexOf(loaderContext._compiler); - if (webpackIndex === -1) { - webpackIndex = webpackInstances.push(loaderContext._compiler) - 1; - } - const loaderOptions = loaderUtils.getOptions(loaderContext) || ({} as LoaderOptions); @@ -187,9 +180,7 @@ function getLoaderOptions(loaderContext: webpack.loader.LoaderContext) { // If no instance name is given in the options, use the hash of the loader options // In this way, if different options are given the instances will be different const instanceName = - webpackIndex + - '_' + - (loaderOptions.instance || 'default_' + getOptionsHash(loaderOptions)); + loaderOptions.instance || 'default_' + getOptionsHash(loaderOptions); if (!loaderOptionsCache.hasOwnProperty(instanceName)) { loaderOptionsCache[instanceName] = new WeakMap(); diff --git a/src/instances.ts b/src/instances.ts index 0078a5aaa..69008844e 100644 --- a/src/instances.ts +++ b/src/instances.ts @@ -33,9 +33,22 @@ import { } from './utils'; import { makeWatchRun } from './watch-run'; -const instances = new Map(); +// Each TypeScript instance is based on the webpack instance (key of the WeakMap) +// and also the name that was generated or passed via the options (string key of the +// internal Map) +const instanceCache = new WeakMap>(); const instancesBySolutionBuilderConfigs = new Map(); +function addTSInstanceToCache( + key: webpack.Compiler, + instanceName: string, + instance: TSInstance +) { + const instances = instanceCache.get(key) ?? new Map(); + instances.set(instanceName, instance); + instanceCache.set(key, instances); +} + /** * The loader is executed once for each file seen by webpack. However, we need to keep * a persistent instance of TypeScript that contains all of the files in the program @@ -47,6 +60,12 @@ export function getTypeScriptInstance( loaderOptions: LoaderOptions, loader: webpack.loader.LoaderContext ): { instance?: TSInstance; error?: WebpackError } { + let instances = instanceCache.get(loader._compiler); + if (!instances) { + instances = new Map(); + instanceCache.set(loader._compiler, instances); + } + const existing = instances.get(loaderOptions.instance); if (existing) { if (!existing.initialSetupPending) { @@ -141,7 +160,7 @@ function successfulTypeScriptInstance( const existing = getExistingSolutionBuilderHost(configFileKey); if (existing) { // Reuse the instance if config file for project references is shared. - instances.set(loaderOptions.instance, existing); + addTSInstanceToCache(loader._compiler, loaderOptions.instance, existing); return { instance: existing }; } } @@ -226,7 +245,12 @@ function successfulTypeScriptInstance( log, filePathKeyMapper, }; - instances.set(loaderOptions.instance, transpileInstance); + + addTSInstanceToCache( + loader._compiler, + loaderOptions.instance, + transpileInstance + ); return { instance: transpileInstance }; } @@ -278,7 +302,8 @@ function successfulTypeScriptInstance( log, filePathKeyMapper, }; - instances.set(loaderOptions.instance, instance); + + addTSInstanceToCache(loader._compiler, loaderOptions.instance, instance); return { instance }; }