Skip to content

Commit e90f8ad

Browse files
author
Valerio Pipolo
authored
Fix memory leak when using multiple webpack instances (#1205)
* 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
1 parent 95050eb commit e90f8ad

File tree

4 files changed

+34
-15
lines changed

4 files changed

+34
-15
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## v8.0.8
4+
* [Fixed memory leak when using multiple webpack instances](https://github.com/TypeStrong/ts-loader/pull/1205) - thanks @valerio
5+
36
## v8.0.7
47
* [Speeds up project reference build and doesnt store the result in memory](https://github.com/TypeStrong/ts-loader/pull/1202) - thanks @sheetalkamat
58

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ts-loader",
3-
"version": "8.0.7",
3+
"version": "8.0.8",
44
"description": "TypeScript loader for webpack",
55
"main": "index.js",
66
"types": "dist",

src/index.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import {
2727
isReferencedFile,
2828
} from './utils';
2929

30-
const webpackInstances: webpack.Compiler[] = [];
3130
const loaderOptionsCache: LoaderOptionsCache = {};
3231

3332
/**
@@ -174,22 +173,14 @@ function getOptionsHash(loaderOptions: LoaderOptions) {
174173
* or creates them, adds them to the cache and returns
175174
*/
176175
function getLoaderOptions(loaderContext: webpack.loader.LoaderContext) {
177-
// differentiate the TypeScript instance based on the webpack instance
178-
let webpackIndex = webpackInstances.indexOf(loaderContext._compiler);
179-
if (webpackIndex === -1) {
180-
webpackIndex = webpackInstances.push(loaderContext._compiler) - 1;
181-
}
182-
183176
const loaderOptions =
184177
loaderUtils.getOptions<LoaderOptions>(loaderContext) ||
185178
({} as LoaderOptions);
186179

187180
// If no instance name is given in the options, use the hash of the loader options
188181
// In this way, if different options are given the instances will be different
189182
const instanceName =
190-
webpackIndex +
191-
'_' +
192-
(loaderOptions.instance || 'default_' + getOptionsHash(loaderOptions));
183+
loaderOptions.instance || 'default_' + getOptionsHash(loaderOptions);
193184

194185
if (!loaderOptionsCache.hasOwnProperty(instanceName)) {
195186
loaderOptionsCache[instanceName] = new WeakMap();

src/instances.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,22 @@ import {
3333
} from './utils';
3434
import { makeWatchRun } from './watch-run';
3535

36-
const instances = new Map<string, TSInstance>();
36+
// Each TypeScript instance is based on the webpack instance (key of the WeakMap)
37+
// and also the name that was generated or passed via the options (string key of the
38+
// internal Map)
39+
const instanceCache = new WeakMap<webpack.Compiler, Map<string, TSInstance>>();
3740
const instancesBySolutionBuilderConfigs = new Map<FilePathKey, TSInstance>();
3841

42+
function addTSInstanceToCache(
43+
key: webpack.Compiler,
44+
instanceName: string,
45+
instance: TSInstance
46+
) {
47+
const instances = instanceCache.get(key) ?? new Map<string, TSInstance>();
48+
instances.set(instanceName, instance);
49+
instanceCache.set(key, instances);
50+
}
51+
3952
/**
4053
* The loader is executed once for each file seen by webpack. However, we need to keep
4154
* a persistent instance of TypeScript that contains all of the files in the program
@@ -47,6 +60,12 @@ export function getTypeScriptInstance(
4760
loaderOptions: LoaderOptions,
4861
loader: webpack.loader.LoaderContext
4962
): { instance?: TSInstance; error?: WebpackError } {
63+
let instances = instanceCache.get(loader._compiler);
64+
if (!instances) {
65+
instances = new Map();
66+
instanceCache.set(loader._compiler, instances);
67+
}
68+
5069
const existing = instances.get(loaderOptions.instance);
5170
if (existing) {
5271
if (!existing.initialSetupPending) {
@@ -141,7 +160,7 @@ function successfulTypeScriptInstance(
141160
const existing = getExistingSolutionBuilderHost(configFileKey);
142161
if (existing) {
143162
// Reuse the instance if config file for project references is shared.
144-
instances.set(loaderOptions.instance, existing);
163+
addTSInstanceToCache(loader._compiler, loaderOptions.instance, existing);
145164
return { instance: existing };
146165
}
147166
}
@@ -226,7 +245,12 @@ function successfulTypeScriptInstance(
226245
log,
227246
filePathKeyMapper,
228247
};
229-
instances.set(loaderOptions.instance, transpileInstance);
248+
249+
addTSInstanceToCache(
250+
loader._compiler,
251+
loaderOptions.instance,
252+
transpileInstance
253+
);
230254
return { instance: transpileInstance };
231255
}
232256

@@ -278,7 +302,8 @@ function successfulTypeScriptInstance(
278302
log,
279303
filePathKeyMapper,
280304
};
281-
instances.set(loaderOptions.instance, instance);
305+
306+
addTSInstanceToCache(loader._compiler, loaderOptions.instance, instance);
282307
return { instance };
283308
}
284309

0 commit comments

Comments
 (0)