Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix memory leak when using multiple webpack instances #1205

Merged
merged 5 commits into from Nov 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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