Skip to content

Commit

Permalink
fix(tsconfig): support multiple tsconfigs to be specified (#327)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: tsconfig passed in by path is now applied regardless of whether it matches
  • Loading branch information
eamodio authored and privatenumber committed Aug 9, 2023
1 parent 507c947 commit 9dbbae6
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 26 deletions.
60 changes: 38 additions & 22 deletions src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ import {
parseTsconfig,
createFilesMatcher,
type TsConfigResult,
type FileMatcher,
} from 'get-tsconfig';
import type { LoaderOptions } from './types.js';

let foundTsconfig: TsConfigResult | null;
let fileMatcher: FileMatcher;
const tsconfigCache = new Map<string, TsConfigResult>();

async function ESBuildLoader(
this: webpack.loader.LoaderContext<LoaderOptions>,
Expand All @@ -25,7 +23,7 @@ async function ESBuildLoader(
const options: LoaderOptions = typeof this.getOptions === 'function' ? this.getOptions() : getOptions(this);
const {
implementation,
tsconfig,
tsconfig: tsconfigPath,
...esbuildTransformOptions
} = options;

Expand All @@ -49,26 +47,44 @@ async function ESBuildLoader(
};

if (!('tsconfigRaw' in transformOptions)) {
if (!fileMatcher) {
const tsconfigPath = tsconfig && path.resolve(tsconfig);
foundTsconfig = (
tsconfigPath
? {
config: parseTsconfig(tsconfigPath),
path: tsconfigPath,
}
: getTsconfig()
);
if (foundTsconfig) {
fileMatcher = createFilesMatcher(foundTsconfig);
const { resourcePath } = this;
/**
* If a tsconfig.json path is specified, force apply it
* Same way a provided tsconfigRaw is applied regardless
* of whether it actually matches
*
* However in this case, we also warn if it doesn't match
*/
if (tsconfigPath) {
const tsconfigFullPath = path.resolve(tsconfigPath);
let tsconfig = tsconfigCache.get(tsconfigFullPath);
if (!tsconfig) {
tsconfig = {
config: parseTsconfig(tsconfigFullPath),
path: tsconfigFullPath,
};
tsconfigCache.set(tsconfigFullPath, tsconfig);
}
}

if (fileMatcher) {
transformOptions.tsconfigRaw = fileMatcher(
// Doesn't include query
this.resourcePath,
) as TransformOptions['tsconfigRaw'];
const filesMatcher = createFilesMatcher(tsconfig);
const matches = filesMatcher(resourcePath);

if (!matches) {
this.emitWarning(
new Error(`[esbuild-loader] The specified tsconfig at "${tsconfigFullPath}" was applied to the file "${resourcePath}" but does not match its "include" patterns`),
);
}

transformOptions.tsconfigRaw = tsconfig.config as TransformOptions['tsconfigRaw'];
} else {
/* Detect tsconfig file */

// Webpack shouldn't be loading the same path multiple times so doesn't need to be cached
const tsconfig = getTsconfig(resourcePath);
if (tsconfig) {
const fileMatcher = createFilesMatcher(tsconfig);
transformOptions.tsconfigRaw = fileMatcher(resourcePath) as TransformOptions['tsconfigRaw'];
}
}
}

Expand Down
96 changes: 92 additions & 4 deletions tests/specs/tsconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,20 @@ export default testSuite(({ describe }) => {
test('finds tsconfig.json and applies strict mode', async ({ onTestFinish }) => {
const fixture = await createFixture({
src: {
'index.ts': `module.exports = [${detectStrictMode}, require("./not-strict.ts")];`,
'index.ts': `module.exports = [
${detectStrictMode},
require("./not-strict.ts"),
require("./different-config/strict.ts"),
];`,
'not-strict.ts': `module.exports = ${detectStrictMode}`,
'different-config': {
'strict.ts': `module.exports = ${detectStrictMode}`,
'tsconfig.json': tsconfigJson({
compilerOptions: {
strict: true,
},
}),
},
},
'webpack.config.js': `
module.exports = {
Expand Down Expand Up @@ -66,7 +78,7 @@ export default testSuite(({ describe }) => {
const require = createRequire(import.meta.url);
expect(
require(path.join(fixture.path, 'dist/main.js')),
).toStrictEqual([true, false]);
).toStrictEqual([true, false, true]);
});

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

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

await execa(webpackCli, {
const { stdout } = await execa(webpackCli, {
cwd: fixture.path,
});

expect(stdout).toMatch('does not match its "include" patterns');

const require = createRequire(import.meta.url);
expect(
require(path.join(fixture.path, 'dist/main.js')),
).toStrictEqual([false, true]);
).toStrictEqual([true, true]);
});

test('applies different tsconfig.json paths', async ({ onTestFinish }) => {
const fixture = await createFixture({
src: {
'index.ts': 'export class C { foo = 100; }',
'index2.ts': 'export class C { foo = 100; }',
},
'webpack.config.js': `
module.exports = {
mode: 'production',
optimization: {
minimize: false,
},
resolveLoader: {
alias: {
'esbuild-loader': ${JSON.stringify(esbuildLoader)},
},
},
module: {
rules: [
{
test: /index\\.ts$/,
loader: 'esbuild-loader',
options: {
tsconfig: './tsconfig.custom1.json',
}
},
{
test: /index2\\.ts$/,
loader: 'esbuild-loader',
options: {
tsconfig: './tsconfig.custom2.json',
}
}
],
},
entry: {
index1: './src/index.ts',
index2: './src/index2.ts',
},
output: {
libraryTarget: 'commonjs2',
},
};
`,
'tsconfig.custom1.json': tsconfigJson({
compilerOptions: {
useDefineForClassFields: false,
},
}),
'tsconfig.custom2.json': tsconfigJson({
compilerOptions: {
useDefineForClassFields: true,
},
}),
});

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

await execa(webpackCli, {
cwd: fixture.path,
});

const code1 = await fixture.readFile('dist/index1.js', 'utf8');
expect(code1).toMatch('this.foo = 100;');

const code2 = await fixture.readFile('dist/index2.js', 'utf8');
expect(code2).toMatch('__publicField(this, "foo", 100);');
});
});

Expand Down

0 comments on commit 9dbbae6

Please sign in to comment.