Skip to content

Commit 95b6fe8

Browse files
Uses existing instance if config file is same as already built solution (#1177)
* Test for different instance that does not match * Share the ts instance if the project references match completely or config is one of the built project reference * Update package.json * Update CHANGELOG.md Co-authored-by: John Reilly <[email protected]>
1 parent b38678a commit 95b6fe8

File tree

104 files changed

+3666
-33
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+3666
-33
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.4
4+
* [Uses existing instance if config file is same as already built solution](https://github.com/TypeStrong/ts-loader/pull/1177) - thanks @sheetalkamat
5+
36
## v8.0.3
47
* [Fix the wrong instance caching when using `appendTsSuffixTo` and `appendTsxSuffixTo` together](https://github.com/TypeStrong/ts-loader/pull/1170) - thanks @meowtec
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.3",
3+
"version": "8.0.4",
44
"description": "TypeScript loader for webpack",
55
"main": "index.js",
66
"types": "dist",

src/after-compile.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as constants from './constants';
66
import { getEmitFromWatchHost, getEmitOutput } from './instances';
77
import {
88
FilePathKey,
9+
LoaderOptions,
910
TSFiles,
1011
TSInstance,
1112
WebpackModule,
@@ -17,6 +18,7 @@ import {
1718
formatErrors,
1819
isReferencedFile,
1920
populateReverseDependencyGraph,
21+
tsLoaderSource,
2022
} from './utils';
2123

2224
export function makeAfterCompile(
@@ -41,7 +43,7 @@ export function makeAfterCompile(
4143
callback();
4244
return;
4345
}
44-
removeCompilationTSLoaderErrors(compilation);
46+
removeCompilationTSLoaderErrors(compilation, instance.loaderOptions);
4547

4648
provideCompilerOptionDiagnosticErrorsToWebpack(
4749
getCompilerOptionDiagnostics,
@@ -235,7 +237,7 @@ function provideErrorsToWebpack(
235237
const associatedModules = modules.get(instance.filePathKeyMapper(fileName));
236238
if (associatedModules !== undefined) {
237239
associatedModules.forEach(module => {
238-
removeModuleTSLoaderError(module);
240+
removeModuleTSLoaderError(module, loaderOptions);
239241

240242
// append errors
241243
const formattedErrors = formatErrors(
@@ -299,7 +301,7 @@ function provideSolutionErrorsToWebpack(
299301
const associatedModules = modules.get(filePath);
300302
if (associatedModules !== undefined) {
301303
associatedModules.forEach(module => {
302-
removeModuleTSLoaderError(module);
304+
removeModuleTSLoaderError(module, loaderOptions);
303305

304306
// append errors
305307
const formattedErrors = formatErrors(
@@ -445,14 +447,18 @@ function provideAssetsFromSolutionBuilderHost(
445447
* the loader, we need to detect and remove any pre-existing errors.
446448
*/
447449
function removeCompilationTSLoaderErrors(
448-
compilation: webpack.compilation.Compilation
450+
compilation: webpack.compilation.Compilation,
451+
loaderOptions: LoaderOptions
449452
) {
450453
compilation.errors = compilation.errors.filter(
451-
error => error.loaderSource !== 'ts-loader'
454+
error => error.loaderSource !== tsLoaderSource(loaderOptions)
452455
);
453456
}
454457

455-
function removeModuleTSLoaderError(module: WebpackModule) {
458+
function removeModuleTSLoaderError(
459+
module: WebpackModule,
460+
loaderOptions: LoaderOptions
461+
) {
456462
/**
457463
* Since webpack 5, the `errors` property is deprecated,
458464
* so we can check if some methods for reporting errors exist.
@@ -464,11 +470,13 @@ function removeModuleTSLoaderError(module: WebpackModule) {
464470

465471
Array.from(warnings || []).forEach(warning => module.addWarning(warning));
466472
Array.from(errors || [])
467-
.filter((error: any) => error.loaderSource !== 'ts-loader')
473+
.filter(
474+
(error: any) => error.loaderSource !== tsLoaderSource(loaderOptions)
475+
)
468476
.forEach(error => module.addError(error));
469477
} else {
470478
module.errors = module.errors.filter(
471-
error => error.loaderSource !== 'ts-loader'
479+
error => error.loaderSource !== tsLoaderSource(loaderOptions)
472480
);
473481
}
474482
}

src/instances.ts

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
LoaderOptions,
1414
TSFiles,
1515
TSInstance,
16-
TSInstances,
1716
WebpackError,
1817
} from './interfaces';
1918
import * as logger from './logger';
@@ -34,7 +33,8 @@ import {
3433
} from './utils';
3534
import { makeWatchRun } from './watch-run';
3635

37-
const instances = {} as TSInstances;
36+
const instances = new Map<string, TSInstance>();
37+
const instancesBySolutionBuilderConfigs = new Map<FilePathKey, TSInstance>();
3838

3939
/**
4040
* The loader is executed once for each file seen by webpack. However, we need to keep
@@ -47,20 +47,26 @@ export function getTypeScriptInstance(
4747
loaderOptions: LoaderOptions,
4848
loader: webpack.loader.LoaderContext
4949
): { instance?: TSInstance; error?: WebpackError } {
50-
if (instances.hasOwnProperty(loaderOptions.instance)) {
51-
const instance = instances[loaderOptions.instance];
52-
if (!instance.initialSetupPending) {
53-
ensureProgram(instance);
50+
const existing = instances.get(loaderOptions.instance);
51+
if (existing) {
52+
if (!existing.initialSetupPending) {
53+
ensureProgram(existing);
5454
}
55-
return { instance: instances[loaderOptions.instance] };
55+
return { instance: existing };
5656
}
5757

5858
const colors = new chalk.constructor({ enabled: loaderOptions.colors });
5959
const log = logger.makeLogger(loaderOptions, colors);
6060
const compiler = getCompiler(loaderOptions, log);
6161

6262
if (compiler.errorMessage !== undefined) {
63-
return { error: makeError(colors.red(compiler.errorMessage), undefined) };
63+
return {
64+
error: makeError(
65+
loaderOptions,
66+
colors.red(compiler.errorMessage),
67+
undefined
68+
),
69+
};
6470
}
6571

6672
return successfulTypeScriptInstance(
@@ -121,13 +127,25 @@ function successfulTypeScriptInstance(
121127
const { message, file } = configFileAndPath.configFileError;
122128
return {
123129
error: makeError(
130+
loaderOptions,
124131
colors.red('error while reading tsconfig.json:' + EOL + message),
125132
file
126133
),
127134
};
128135
}
129136

130137
const { configFilePath, configFile } = configFileAndPath;
138+
const filePathKeyMapper = createFilePathKeyMapper(compiler, loaderOptions);
139+
if (configFilePath && loaderOptions.projectReferences) {
140+
const configFileKey = filePathKeyMapper(configFilePath);
141+
const existing = getExistingSolutionBuilderHost(configFileKey);
142+
if (existing) {
143+
// Reuse the instance if config file for project references is shared.
144+
instances.set(loaderOptions.instance, existing);
145+
return { instance: existing };
146+
}
147+
}
148+
131149
const basePath = loaderOptions.context || path.dirname(configFilePath || '');
132150
const configParseResult = getConfigParseResult(
133151
compiler,
@@ -151,6 +169,7 @@ function successfulTypeScriptInstance(
151169

152170
return {
153171
error: makeError(
172+
loaderOptions,
154173
colors.red('error while parsing tsconfig.json'),
155174
configFilePath
156175
),
@@ -174,12 +193,11 @@ function successfulTypeScriptInstance(
174193
filePath
175194
)
176195
: (filePath: string) => filePath;
177-
const filePathKeyMapper = createFilePathKeyMapper(compiler, loaderOptions);
178196

179197
if (loaderOptions.transpileOnly) {
180198
// quick return for transpiling
181199
// we do need to check for any issues with TS options though
182-
const transpileInstance: TSInstance = (instances[loaderOptions.instance] = {
200+
const transpileInstance: TSInstance = {
183201
compiler,
184202
compilerOptions,
185203
appendTsTsxSuffixesIfRequired,
@@ -198,8 +216,8 @@ function successfulTypeScriptInstance(
198216
configParseResult,
199217
log,
200218
filePathKeyMapper,
201-
});
202-
219+
};
220+
instances.set(loaderOptions.instance, transpileInstance);
203221
return { instance: transpileInstance };
204222
}
205223

@@ -223,6 +241,7 @@ function successfulTypeScriptInstance(
223241
} catch (exc) {
224242
return {
225243
error: makeError(
244+
loaderOptions,
226245
colors.red(
227246
`A file specified in tsconfig.json could not be found: ${normalizedFilePath!}`
228247
),
@@ -231,7 +250,7 @@ function successfulTypeScriptInstance(
231250
};
232251
}
233252

234-
const instance: TSInstance = (instances[loaderOptions.instance] = {
253+
const instance: TSInstance = {
235254
compiler,
236255
compilerOptions,
237256
appendTsTsxSuffixesIfRequired,
@@ -249,11 +268,22 @@ function successfulTypeScriptInstance(
249268
configParseResult,
250269
log,
251270
filePathKeyMapper,
252-
});
253-
271+
};
272+
instances.set(loaderOptions.instance, instance);
254273
return { instance };
255274
}
256275

276+
function getExistingSolutionBuilderHost(key: FilePathKey) {
277+
const existing = instancesBySolutionBuilderConfigs.get(key);
278+
if (existing) return existing;
279+
for (const instance of instancesBySolutionBuilderConfigs.values()) {
280+
if (instance.solutionBuilderHost!.configFileInfo.has(key)) {
281+
return instance;
282+
}
283+
}
284+
return undefined;
285+
}
286+
257287
export function initializeInstance(
258288
loader: webpack.loader.LoaderContext,
259289
instance: TSInstance
@@ -421,13 +451,17 @@ export function buildSolutionReferences(
421451
loader,
422452
instance
423453
);
424-
instance.solutionBuilder = instance.compiler.createSolutionBuilderWithWatch(
454+
const solutionBuilder = instance.compiler.createSolutionBuilderWithWatch(
425455
instance.solutionBuilderHost,
426456
instance.configParseResult.projectReferences!.map(ref => ref.path),
427457
{ verbose: true }
428458
);
429-
instance.solutionBuilder!.build();
459+
solutionBuilder.build();
430460
ensureAllReferences(instance);
461+
instancesBySolutionBuilderConfigs.set(
462+
instance.filePathKeyMapper(instance.configFilePath!),
463+
instance
464+
);
431465
} else {
432466
instance.solutionBuilderHost.buildReferences();
433467
}

src/interfaces.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,6 @@ export interface TSInstance {
204204

205205
reportTranspileErrors?: boolean;
206206
solutionBuilderHost?: SolutionBuilderWithWatchHost;
207-
solutionBuilder?: typescript.SolutionBuilder<
208-
typescript.EmitAndSemanticDiagnosticsBuilderProgram
209-
>;
210207
configFilePath: string | undefined;
211208

212209
filePathKeyMapper: (fileName: string) => FilePathKey;
@@ -220,9 +217,6 @@ export interface LoaderOptionsCache {
220217
[name: string]: WeakMap<LoaderOptions, LoaderOptions>;
221218
}
222219

223-
export interface TSInstances {
224-
[name: string]: TSInstance;
225-
}
226220
export type DependencyGraph = Map<FilePathKey, ResolvedModule[]>;
227221
export type ReverseDependencyGraph = Map<FilePathKey, Map<FilePathKey, true>>;
228222

src/utils.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export function formatErrors(
100100
: loaderOptions.errorFormatter(errorInfo, colors);
101101

102102
const error = makeError(
103+
loaderOptions,
103104
message,
104105
merge.file === undefined ? errorInfo.file : merge.file,
105106
position === undefined
@@ -124,6 +125,7 @@ export function fsReadFile(
124125
}
125126

126127
export function makeError(
128+
loaderOptions: LoaderOptions,
127129
message: string,
128130
file: string | undefined,
129131
location?: { line: number; character: number }
@@ -132,10 +134,14 @@ export function makeError(
132134
message,
133135
location,
134136
file,
135-
loaderSource: 'ts-loader',
137+
loaderSource: tsLoaderSource(loaderOptions),
136138
};
137139
}
138140

141+
export function tsLoaderSource(loaderOptions: LoaderOptions) {
142+
return `ts-loader-${loaderOptions.instance}`;
143+
}
144+
139145
export function appendSuffixIfMatch(
140146
patterns: (RegExp | string)[],
141147
filePath: string,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { lib } from '../lib';
2+
import { utils } from "../utils";
3+
4+
console.log(lib.one, lib.two, lib.three);
5+
utils();
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"types": []
4+
},
5+
"files": [
6+
"./app.ts"
7+
],
8+
"references": [
9+
{ "path": "../lib" },
10+
{ "path": "../utils" },
11+
]
12+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
var path = require('path')
2+
3+
module.exports = {
4+
mode: 'development',
5+
entry: './app.ts',
6+
output: {
7+
filename: 'bundle.js'
8+
},
9+
resolve: {
10+
extensions: ['.ts', '.js']
11+
},
12+
module: {
13+
rules: [
14+
{ test: /(app|lib|common|indirect).+\.ts$/, loader: 'ts-loader', options: { projectReferences: true } },
15+
{ test: /utils.+\.ts$/, loader: 'ts-loader', options: { instance: 'different', projectReferences: true } }
16+
]
17+
}
18+
}
19+
20+
// for test harness purposes only, you would not need this in a normal project
21+
module.exports.resolveLoader = { alias: { 'ts-loader': require('path').join(__dirname, "../../../../index.js") } }
22+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function common() {
2+
return 30;
3+
}

0 commit comments

Comments
 (0)