Skip to content

Commit 9dbbae6

Browse files
eamodioprivatenumber
authored andcommitted
fix(tsconfig): support multiple tsconfigs to be specified (#327)
BREAKING CHANGE: tsconfig passed in by path is now applied regardless of whether it matches
1 parent 507c947 commit 9dbbae6

File tree

2 files changed

+130
-26
lines changed

2 files changed

+130
-26
lines changed

src/loader.ts

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,10 @@ import {
1010
parseTsconfig,
1111
createFilesMatcher,
1212
type TsConfigResult,
13-
type FileMatcher,
1413
} from 'get-tsconfig';
1514
import type { LoaderOptions } from './types.js';
1615

17-
let foundTsconfig: TsConfigResult | null;
18-
let fileMatcher: FileMatcher;
16+
const tsconfigCache = new Map<string, TsConfigResult>();
1917

2018
async function ESBuildLoader(
2119
this: webpack.loader.LoaderContext<LoaderOptions>,
@@ -25,7 +23,7 @@ async function ESBuildLoader(
2523
const options: LoaderOptions = typeof this.getOptions === 'function' ? this.getOptions() : getOptions(this);
2624
const {
2725
implementation,
28-
tsconfig,
26+
tsconfig: tsconfigPath,
2927
...esbuildTransformOptions
3028
} = options;
3129

@@ -49,26 +47,44 @@ async function ESBuildLoader(
4947
};
5048

5149
if (!('tsconfigRaw' in transformOptions)) {
52-
if (!fileMatcher) {
53-
const tsconfigPath = tsconfig && path.resolve(tsconfig);
54-
foundTsconfig = (
55-
tsconfigPath
56-
? {
57-
config: parseTsconfig(tsconfigPath),
58-
path: tsconfigPath,
59-
}
60-
: getTsconfig()
61-
);
62-
if (foundTsconfig) {
63-
fileMatcher = createFilesMatcher(foundTsconfig);
50+
const { resourcePath } = this;
51+
/**
52+
* If a tsconfig.json path is specified, force apply it
53+
* Same way a provided tsconfigRaw is applied regardless
54+
* of whether it actually matches
55+
*
56+
* However in this case, we also warn if it doesn't match
57+
*/
58+
if (tsconfigPath) {
59+
const tsconfigFullPath = path.resolve(tsconfigPath);
60+
let tsconfig = tsconfigCache.get(tsconfigFullPath);
61+
if (!tsconfig) {
62+
tsconfig = {
63+
config: parseTsconfig(tsconfigFullPath),
64+
path: tsconfigFullPath,
65+
};
66+
tsconfigCache.set(tsconfigFullPath, tsconfig);
6467
}
65-
}
6668

67-
if (fileMatcher) {
68-
transformOptions.tsconfigRaw = fileMatcher(
69-
// Doesn't include query
70-
this.resourcePath,
71-
) as TransformOptions['tsconfigRaw'];
69+
const filesMatcher = createFilesMatcher(tsconfig);
70+
const matches = filesMatcher(resourcePath);
71+
72+
if (!matches) {
73+
this.emitWarning(
74+
new Error(`[esbuild-loader] The specified tsconfig at "${tsconfigFullPath}" was applied to the file "${resourcePath}" but does not match its "include" patterns`),
75+
);
76+
}
77+
78+
transformOptions.tsconfigRaw = tsconfig.config as TransformOptions['tsconfigRaw'];
79+
} else {
80+
/* Detect tsconfig file */
81+
82+
// Webpack shouldn't be loading the same path multiple times so doesn't need to be cached
83+
const tsconfig = getTsconfig(resourcePath);
84+
if (tsconfig) {
85+
const fileMatcher = createFilesMatcher(tsconfig);
86+
transformOptions.tsconfigRaw = fileMatcher(resourcePath) as TransformOptions['tsconfigRaw'];
87+
}
7288
}
7389
}
7490

tests/specs/tsconfig.ts

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,20 @@ export default testSuite(({ describe }) => {
1616
test('finds tsconfig.json and applies strict mode', async ({ onTestFinish }) => {
1717
const fixture = await createFixture({
1818
src: {
19-
'index.ts': `module.exports = [${detectStrictMode}, require("./not-strict.ts")];`,
19+
'index.ts': `module.exports = [
20+
${detectStrictMode},
21+
require("./not-strict.ts"),
22+
require("./different-config/strict.ts"),
23+
];`,
2024
'not-strict.ts': `module.exports = ${detectStrictMode}`,
25+
'different-config': {
26+
'strict.ts': `module.exports = ${detectStrictMode}`,
27+
'tsconfig.json': tsconfigJson({
28+
compilerOptions: {
29+
strict: true,
30+
},
31+
}),
32+
},
2133
},
2234
'webpack.config.js': `
2335
module.exports = {
@@ -66,7 +78,7 @@ export default testSuite(({ describe }) => {
6678
const require = createRequire(import.meta.url);
6779
expect(
6880
require(path.join(fixture.path, 'dist/main.js')),
69-
).toStrictEqual([true, false]);
81+
).toStrictEqual([true, false, true]);
7082
});
7183

7284
test('handles resource with query', async ({ onTestFinish }) => {
@@ -174,14 +186,90 @@ export default testSuite(({ describe }) => {
174186

175187
onTestFinish(async () => await fixture.rm());
176188

177-
await execa(webpackCli, {
189+
const { stdout } = await execa(webpackCli, {
178190
cwd: fixture.path,
179191
});
180192

193+
expect(stdout).toMatch('does not match its "include" patterns');
194+
181195
const require = createRequire(import.meta.url);
182196
expect(
183197
require(path.join(fixture.path, 'dist/main.js')),
184-
).toStrictEqual([false, true]);
198+
).toStrictEqual([true, true]);
199+
});
200+
201+
test('applies different tsconfig.json paths', async ({ onTestFinish }) => {
202+
const fixture = await createFixture({
203+
src: {
204+
'index.ts': 'export class C { foo = 100; }',
205+
'index2.ts': 'export class C { foo = 100; }',
206+
},
207+
'webpack.config.js': `
208+
module.exports = {
209+
mode: 'production',
210+
211+
optimization: {
212+
minimize: false,
213+
},
214+
215+
resolveLoader: {
216+
alias: {
217+
'esbuild-loader': ${JSON.stringify(esbuildLoader)},
218+
},
219+
},
220+
221+
module: {
222+
rules: [
223+
{
224+
test: /index\\.ts$/,
225+
loader: 'esbuild-loader',
226+
options: {
227+
tsconfig: './tsconfig.custom1.json',
228+
}
229+
},
230+
{
231+
test: /index2\\.ts$/,
232+
loader: 'esbuild-loader',
233+
options: {
234+
tsconfig: './tsconfig.custom2.json',
235+
}
236+
}
237+
],
238+
},
239+
240+
entry: {
241+
index1: './src/index.ts',
242+
index2: './src/index2.ts',
243+
},
244+
245+
output: {
246+
libraryTarget: 'commonjs2',
247+
},
248+
};
249+
`,
250+
'tsconfig.custom1.json': tsconfigJson({
251+
compilerOptions: {
252+
useDefineForClassFields: false,
253+
},
254+
}),
255+
'tsconfig.custom2.json': tsconfigJson({
256+
compilerOptions: {
257+
useDefineForClassFields: true,
258+
},
259+
}),
260+
});
261+
262+
onTestFinish(async () => await fixture.rm());
263+
264+
await execa(webpackCli, {
265+
cwd: fixture.path,
266+
});
267+
268+
const code1 = await fixture.readFile('dist/index1.js', 'utf8');
269+
expect(code1).toMatch('this.foo = 100;');
270+
271+
const code2 = await fixture.readFile('dist/index2.js', 'utf8');
272+
expect(code2).toMatch('__publicField(this, "foo", 100);');
185273
});
186274
});
187275

0 commit comments

Comments
 (0)