Skip to content

Commit

Permalink
Fix memory leak when using multiple webpack instances (#1205)
Browse files Browse the repository at this point in the history
* Use WeakMaps to keep track of webpack  and typescript compiler instances

* Cache TS instances by webpack compiler and instance name

* Remove webpack instance cache

* Rename compilerMap to instanceCache and add small comment

* Add to changelog and bump version
  • Loading branch information
Valerio Pipolo committed Nov 4, 2020
1 parent 95050eb commit e90f8ad
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 15 deletions.
3 changes: 3 additions & 0 deletions 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

Expand Down
2 changes: 1 addition & 1 deletion 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",
Expand Down
11 changes: 1 addition & 10 deletions src/index.ts
Expand Up @@ -27,7 +27,6 @@ import {
isReferencedFile,
} from './utils';

const webpackInstances: webpack.Compiler[] = [];
const loaderOptionsCache: LoaderOptionsCache = {};

/**
Expand Down Expand Up @@ -174,22 +173,14 @@ 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<LoaderOptions>(loaderContext) ||
({} as LoaderOptions);

// 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();
Expand Down
33 changes: 29 additions & 4 deletions src/instances.ts
Expand Up @@ -33,9 +33,22 @@ import {
} from './utils';
import { makeWatchRun } from './watch-run';

const instances = new Map<string, TSInstance>();
// 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<webpack.Compiler, Map<string, TSInstance>>();
const instancesBySolutionBuilderConfigs = new Map<FilePathKey, TSInstance>();

function addTSInstanceToCache(
key: webpack.Compiler,
instanceName: string,
instance: TSInstance
) {
const instances = instanceCache.get(key) ?? new Map<string, TSInstance>();
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
Expand All @@ -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) {
Expand Down Expand Up @@ -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 };
}
}
Expand Down Expand Up @@ -226,7 +245,12 @@ function successfulTypeScriptInstance(
log,
filePathKeyMapper,
};
instances.set(loaderOptions.instance, transpileInstance);

addTSInstanceToCache(
loader._compiler,
loaderOptions.instance,
transpileInstance
);
return { instance: transpileInstance };
}

Expand Down Expand Up @@ -278,7 +302,8 @@ function successfulTypeScriptInstance(
log,
filePathKeyMapper,
};
instances.set(loaderOptions.instance, instance);

addTSInstanceToCache(loader._compiler, loaderOptions.instance, instance);
return { instance };
}

Expand Down

0 comments on commit e90f8ad

Please sign in to comment.