Skip to content

Commit 108a0ef

Browse files
authored
simonhaenisch#15 added basic tsconfig extends support
1 parent 7032eed commit 108a0ef

File tree

1 file changed

+180
-120
lines changed

1 file changed

+180
-120
lines changed

index.ts

+180-120
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,199 @@
1-
import { join } from 'path';
1+
import path from 'path';
22
import { Plugin } from 'rollup';
33
import {
4-
CompilerOptions,
5-
findConfigFile,
6-
nodeModuleNameResolver,
7-
parseConfigFileTextToJson,
8-
sys,
4+
CompilerOptions,
5+
findConfigFile,
6+
nodeModuleNameResolver,
7+
parseConfigFileTextToJson,
8+
sys,
99
} from 'typescript';
1010

1111
export const typescriptPaths = ({
12-
absolute = true,
13-
nonRelative = false,
14-
preserveExtensions = false,
15-
tsConfigPath = findConfigFile('./', sys.fileExists),
16-
transform,
12+
absolute = true,
13+
nonRelative = false,
14+
preserveExtensions = false,
15+
tsConfigPath = findConfigFile('./', sys.fileExists),
16+
transform,
1717
}: Options = {}): Plugin => {
18-
const { compilerOptions, outDir } = getTsConfig(tsConfigPath);
19-
20-
return {
21-
name: 'resolve-typescript-paths',
22-
resolveId: (importee: string, importer?: string) => {
23-
const enabled = Boolean(
24-
compilerOptions.paths || (compilerOptions.baseUrl && nonRelative),
25-
);
26-
27-
if (
28-
typeof importer === 'undefined' ||
29-
importee.startsWith('\0') ||
30-
!enabled
31-
) {
32-
return null;
33-
}
34-
35-
const hasMatchingPath =
36-
!!compilerOptions.paths &&
37-
Object.keys(compilerOptions.paths).some((path) =>
38-
new RegExp('^' + path.replace('*', '.+') + '$').test(importee),
39-
);
40-
41-
if (!hasMatchingPath && !nonRelative) {
42-
return null;
43-
}
44-
45-
if (importee.startsWith('.')) {
46-
return null; // never resolve relative modules, only non-relative
47-
}
48-
49-
const { resolvedModule } = nodeModuleNameResolver(
50-
importee,
51-
importer,
52-
compilerOptions,
53-
sys,
54-
);
55-
56-
if (!resolvedModule) {
57-
return null;
58-
}
59-
60-
const { resolvedFileName } = resolvedModule;
61-
62-
if (!resolvedFileName || resolvedFileName.endsWith('.d.ts')) {
63-
return null;
64-
}
65-
66-
const targetFileName = join(
67-
outDir,
68-
preserveExtensions
69-
? resolvedFileName
70-
: resolvedFileName.replace(/\.tsx?$/i, '.js'),
71-
);
72-
73-
const resolved = absolute
74-
? sys.resolvePath(targetFileName)
75-
: targetFileName;
76-
77-
return transform ? transform(resolved) : resolved;
78-
},
79-
};
18+
const { compilerOptions } = getTsConfig(tsConfigPath);
19+
const outDir = compilerOptions.outDir ?? '.';
20+
21+
return {
22+
name: 'resolve-typescript-paths',
23+
resolveId: (importee: string, importer?: string) => {
24+
const enabled = Boolean(
25+
compilerOptions.paths || (compilerOptions.baseUrl && nonRelative)
26+
);
27+
28+
if (
29+
typeof importer === 'undefined' ||
30+
importee.startsWith('\0') ||
31+
!enabled
32+
) {
33+
return null;
34+
}
35+
36+
const hasMatchingPath =
37+
!!compilerOptions.paths &&
38+
Object.keys(compilerOptions.paths).some((path) =>
39+
new RegExp('^' + path.replace('*', '.+') + '$').test(importee)
40+
);
41+
42+
if (!hasMatchingPath && !nonRelative) {
43+
return null;
44+
}
45+
46+
if (importee.startsWith('.')) {
47+
return null; // never resolve relative modules, only non-relative
48+
}
49+
50+
const { resolvedModule } = nodeModuleNameResolver(
51+
importee,
52+
importer,
53+
compilerOptions,
54+
sys
55+
);
56+
57+
if (!resolvedModule) {
58+
return null;
59+
}
60+
61+
const { resolvedFileName } = resolvedModule;
62+
63+
if (!resolvedFileName || resolvedFileName.endsWith('.d.ts')) {
64+
return null;
65+
}
66+
67+
const targetFileName = path.join(
68+
outDir,
69+
preserveExtensions
70+
? resolvedFileName
71+
: resolvedFileName.replace(/\.tsx?$/i, '.js')
72+
);
73+
74+
const resolved = absolute
75+
? sys.resolvePath(targetFileName)
76+
: targetFileName;
77+
78+
return transform ? transform(resolved) : resolved;
79+
},
80+
};
8081
};
8182

8283
const getTsConfig = (configPath?: string): TsConfig => {
83-
const defaults: TsConfig = { compilerOptions: {}, outDir: '.' };
84-
85-
if (!configPath) {
86-
return defaults;
87-
}
88-
89-
const configJson = sys.readFile(configPath);
90-
91-
if (!configJson) {
92-
return defaults;
93-
}
94-
95-
const { config } = parseConfigFileTextToJson(configPath, configJson);
84+
const defaults: TsConfig = { compilerOptions: { outDir: '.' } };
85+
if (typeof configPath !== 'string') {
86+
return defaults;
87+
}
88+
89+
// Read in tsconfig.json
90+
const configJson = sys.readFile(configPath);
91+
if (configJson == null) {
92+
return defaults;
93+
}
94+
95+
const { config: rootConfig } = parseConfigFileTextToJson(
96+
configPath,
97+
configJson
98+
);
99+
const rootConfigWithDefaults = {
100+
...rootConfig,
101+
...defaults,
102+
compilerOptions: {
103+
...defaults.compilerOptions,
104+
...(rootConfig.compilerOptions ?? {}),
105+
},
106+
};
107+
const resolvedConfig = handleTsConfigExtends(
108+
rootConfigWithDefaults,
109+
configPath
110+
);
111+
112+
return resolvedConfig;
113+
};
96114

97-
return { ...defaults, ...config };
115+
const handleTsConfigExtends = (
116+
config: TsConfig,
117+
rootConfigPath: string
118+
): TsConfig => {
119+
if (!('extends' in config) || typeof config.extends !== 'string') {
120+
return config;
121+
}
122+
123+
let extendedConfigPath;
124+
try {
125+
// Try to resolve as a module (npm)
126+
extendedConfigPath = require.resolve(config.extends);
127+
} catch (e) {
128+
// Try to resolve as a file relative to the current config
129+
extendedConfigPath = path.join(
130+
path.dirname(rootConfigPath),
131+
config.extends
132+
);
133+
}
134+
135+
// Read in extended tsconfig.json
136+
const extendedConfig = getTsConfig(extendedConfigPath);
137+
138+
// Merge base config and current config.
139+
// This does not handle array concatenation or nested objects,
140+
// besides 'compilerOptions' paths as the other options are not relevant
141+
config = {
142+
...extendedConfig,
143+
...config,
144+
compilerOptions: {
145+
...extendedConfig.compilerOptions,
146+
...config.compilerOptions,
147+
paths: {
148+
...(extendedConfig.compilerOptions.paths ?? {}),
149+
...(config.compilerOptions.paths ?? {}),
150+
},
151+
},
152+
};
153+
154+
// Remove the "extends" field
155+
delete config.extends;
156+
157+
return config;
98158
};
99159

100160
export interface Options {
101-
/**
102-
* Whether to resolve to absolute paths; defaults to `true`.
103-
*/
104-
absolute?: boolean;
105-
106-
/**
107-
* Whether to resolve non-relative paths based on tsconfig's `baseUrl`, even
108-
* if none of the `paths` are matched; defaults to `false`.
109-
*
110-
* @see https://www.typescriptlang.org/docs/handbook/module-resolution.html#relative-vs-non-relative-module-imports
111-
* @see https://www.typescriptlang.org/docs/handbook/module-resolution.html#base-url
112-
*/
113-
nonRelative?: boolean;
114-
115-
/**
116-
* Whether to preserve `.ts` and `.tsx` file extensions instead of having them
117-
* changed to `.js`; defaults to `false`.
118-
*/
119-
preserveExtensions?: boolean;
120-
121-
/**
122-
* Custom path to your `tsconfig.json`. Use this if the plugin can't seem to
123-
* find the correct one by itself.
124-
*/
125-
tsConfigPath?: string;
126-
127-
/**
128-
* If the plugin successfully resolves a path, this function allows you to
129-
* hook into the process and transform that path before it is returned.
130-
*/
131-
transform?(path: string): string;
161+
/**
162+
* Whether to resolve to absolute paths; defaults to `true`.
163+
*/
164+
absolute?: boolean;
165+
166+
/**
167+
* Whether to resolve non-relative paths based on tsconfig's `baseUrl`, even
168+
* if none of the `paths` are matched; defaults to `false`.
169+
*
170+
* @see https://www.typescriptlang.org/docs/handbook/module-resolution.html#relative-vs-non-relative-module-imports
171+
* @see https://www.typescriptlang.org/docs/handbook/module-resolution.html#base-url
172+
*/
173+
nonRelative?: boolean;
174+
175+
/**
176+
* Whether to preserve `.ts` and `.tsx` file extensions instead of having them
177+
* changed to `.js`; defaults to `false`.
178+
*/
179+
preserveExtensions?: boolean;
180+
181+
/**
182+
* Custom path to your `tsconfig.json`. Use this if the plugin can't seem to
183+
* find the correct one by itself.
184+
*/
185+
tsConfigPath?: string;
186+
187+
/**
188+
* If the plugin successfully resolves a path, this function allows you to
189+
* hook into the process and transform that path before it is returned.
190+
*/
191+
transform?(path: string): string;
132192
}
133193

134194
interface TsConfig {
135-
compilerOptions: CompilerOptions;
136-
outDir: string;
195+
compilerOptions: CompilerOptions;
196+
extends?: string;
137197
}
138198

139199
/**

0 commit comments

Comments
 (0)