Skip to content

Commit 780f2e7

Browse files
authored
feat(typescript): support 4.7 nodenext (#1194)
* Support typescript 4.7 nodenext module * Begin fixing tests * Add nodenext test * Try things out * Fix tests * Require typescript 3.7 * Add test * Iron out last details * Fix linter warnings * Use type cast * Fix glob * Use RegExp.prototype.test * chore: comment on glob * chore: simplify glob
1 parent 0bbb4fd commit 780f2e7

File tree

18 files changed

+248
-1779
lines changed

18 files changed

+248
-1779
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"prettier-plugin-package": "^1.3.0",
4040
"semver": "^7.3.2",
4141
"source-map-support": "^0.5.19",
42-
"ts-node": "10.1.0",
42+
"ts-node": "10.8.1",
4343
"tsconfig-paths": "^3.9.0",
4444
"typescript": "4.3.5",
4545
"write-pkg": "^4.0.0",

packages/typescript/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
"@types/node": "^10.0.0",
6666
"buble": "^0.20.0",
6767
"rollup": "^2.67.3",
68-
"typescript": "^4.2.2"
68+
"typescript": "^4.7.3"
6969
},
7070
"types": "types/index.d.ts"
7171
}

packages/typescript/src/index.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi
3636
const watchProgramHelper = new WatchProgramHelper();
3737

3838
const parsedOptions = parseTypescriptConfig(ts, tsconfig, compilerOptions, noForceEmit);
39-
const filter = createFilter(include || ['*.ts+(|x)', '**/*.ts+(|x)'], exclude, {
39+
const filter = createFilter(include || '{,**/}*.(cts|mts|ts|tsx)', exclude, {
4040
resolve: filterRoot ?? parsedOptions.options.rootDir
4141
});
4242
parsedOptions.fileNames = parsedOptions.fileNames.filter(filter);
@@ -107,10 +107,24 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi
107107
// Convert path from windows separators to posix separators
108108
const containingFile = normalizePath(importer);
109109

110-
const resolved = resolveModule(importee, containingFile);
110+
// when using node16 or nodenext module resolution, we need to tell ts if
111+
// we are resolving to a commonjs or esnext module
112+
const mode =
113+
typeof ts.getImpliedNodeFormatForFile === 'function'
114+
? ts.getImpliedNodeFormatForFile(
115+
// @ts-expect-error
116+
containingFile,
117+
undefined, // eslint-disable-line no-undefined
118+
{ ...ts.sys, ...formatHost },
119+
parsedOptions.options
120+
)
121+
: undefined; // eslint-disable-line no-undefined
122+
123+
// eslint-disable-next-line no-undefined
124+
const resolved = resolveModule(importee, containingFile, undefined, mode);
111125

112126
if (resolved) {
113-
if (resolved.extension === '.d.ts') return null;
127+
if (/\.d\.[cm]?ts/.test(resolved.extension)) return null;
114128
return path.normalize(resolved.resolvedFileName);
115129
}
116130

packages/typescript/src/moduleResolution.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
import type { ModuleResolutionHost, ResolvedModuleFull } from 'typescript';
1+
import {
2+
ModuleResolutionHost,
3+
ResolvedModuleFull,
4+
ResolvedProjectReference,
5+
ModuleKind
6+
} from 'typescript';
27

38
import { DiagnosticsHost } from './diagnostics/host';
49

510
type ModuleResolverHost = Partial<ModuleResolutionHost> & DiagnosticsHost;
611

712
export type Resolver = (
813
moduleName: string,
9-
containingFile: string
14+
containingFile: string,
15+
redirectedReference?: ResolvedProjectReference | undefined,
16+
mode?: ModuleKind.ESNext | ModuleKind.CommonJS | undefined
1017
) => ResolvedModuleFull | undefined;
1118

1219
/**
@@ -26,13 +33,15 @@ export default function createModuleResolver(
2633
);
2734
const moduleHost = { ...ts.sys, ...host };
2835

29-
return (moduleName, containingFile) => {
30-
const resolved = ts.nodeModuleNameResolver(
36+
return (moduleName, containingFile, redirectedReference, mode) => {
37+
const resolved = ts.resolveModuleName(
3138
moduleName,
3239
containingFile,
3340
compilerOptions,
3441
moduleHost,
35-
cache
42+
cache,
43+
redirectedReference,
44+
mode
3645
);
3746
return resolved.resolvedModule;
3847
};

packages/typescript/src/options/normalize.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export function normalizeCompilerOptions(
4747
switch (compilerOptions.module) {
4848
case ts.ModuleKind.ES2015:
4949
case ts.ModuleKind.ESNext:
50+
case ts.ModuleKind.Node16:
51+
case ts.ModuleKind.NodeNext:
5052
case ts.ModuleKind.CommonJS:
5153
// OK module type
5254
return autoSetSourceMap;
@@ -57,7 +59,7 @@ export function normalizeCompilerOptions(
5759
// Invalid module type
5860
const moduleType = ts.ModuleKind[compilerOptions.module];
5961
throw new Error(
60-
`@rollup/plugin-typescript: The module kind should be 'ES2015' or 'ESNext, found: '${moduleType}'`
62+
`@rollup/plugin-typescript: The module kind should be 'ES2015', 'ESNext', 'node16' or 'nodenext', found: '${moduleType}'`
6163
);
6264
}
6365
default:

packages/typescript/src/options/tsconfig.ts

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { readFileSync } from 'fs';
22
import { dirname, resolve } from 'path';
33

44
import { PluginContext } from 'rollup';
5-
import type {
5+
import {
66
Diagnostic,
77
ExtendedConfigCacheEntry,
88
MapLike,
9+
ModuleKind,
10+
ModuleResolutionKind,
911
ParsedCommandLine,
1012
ProjectReference,
1113
TypeAcquisition,
@@ -99,6 +101,28 @@ function containsEnumOptions(
99101
return enums.some((prop) => prop in compilerOptions && typeof compilerOptions[prop] === 'number');
100102
}
101103

104+
/**
105+
* The module resolution kind is a function of the resolved `compilerOptions.module`.
106+
* This needs to be set explicitly for `resolveModuleName` to select the correct resolution method
107+
*/
108+
function setModuleResolutionKind(parsedConfig: ParsedCommandLine): ParsedCommandLine {
109+
const moduleKind = parsedConfig.options.module;
110+
const moduleResolution =
111+
moduleKind === ModuleKind.Node16
112+
? ModuleResolutionKind.Node16
113+
: moduleKind === ModuleKind.NodeNext
114+
? ModuleResolutionKind.NodeNext
115+
: ModuleResolutionKind.NodeJs;
116+
117+
return {
118+
...parsedConfig,
119+
options: {
120+
...parsedConfig.options,
121+
moduleResolution
122+
}
123+
};
124+
}
125+
102126
const configCache = new Map() as import('typescript').Map<ExtendedConfigCacheEntry>;
103127

104128
/**
@@ -132,39 +156,43 @@ export function parseTypescriptConfig(
132156
// If compilerOptions has enums, it represents an CompilerOptions object instead of parsed JSON.
133157
// This determines where the data is passed to the parser.
134158
if (containsEnumOptions(compilerOptions)) {
135-
parsedConfig = ts.parseJsonConfigFileContent(
136-
{
137-
...tsConfigFile,
138-
compilerOptions: {
139-
...DEFAULT_COMPILER_OPTIONS,
140-
...tsConfigFile.compilerOptions
141-
}
142-
},
143-
ts.sys,
144-
basePath,
145-
{ ...compilerOptions, ...makeForcedCompilerOptions(noForceEmit) },
146-
tsConfigPath,
147-
undefined,
148-
undefined,
149-
configCache
159+
parsedConfig = setModuleResolutionKind(
160+
ts.parseJsonConfigFileContent(
161+
{
162+
...tsConfigFile,
163+
compilerOptions: {
164+
...DEFAULT_COMPILER_OPTIONS,
165+
...tsConfigFile.compilerOptions
166+
}
167+
},
168+
ts.sys,
169+
basePath,
170+
{ ...compilerOptions, ...makeForcedCompilerOptions(noForceEmit) },
171+
tsConfigPath,
172+
undefined,
173+
undefined,
174+
configCache
175+
)
150176
);
151177
} else {
152-
parsedConfig = ts.parseJsonConfigFileContent(
153-
{
154-
...tsConfigFile,
155-
compilerOptions: {
156-
...DEFAULT_COMPILER_OPTIONS,
157-
...tsConfigFile.compilerOptions,
158-
...compilerOptions
159-
}
160-
},
161-
ts.sys,
162-
basePath,
163-
makeForcedCompilerOptions(noForceEmit),
164-
tsConfigPath,
165-
undefined,
166-
undefined,
167-
configCache
178+
parsedConfig = setModuleResolutionKind(
179+
ts.parseJsonConfigFileContent(
180+
{
181+
...tsConfigFile,
182+
compilerOptions: {
183+
...DEFAULT_COMPILER_OPTIONS,
184+
...tsConfigFile.compilerOptions,
185+
...compilerOptions
186+
}
187+
},
188+
ts.sys,
189+
basePath,
190+
makeForcedCompilerOptions(noForceEmit),
191+
tsConfigPath,
192+
undefined,
193+
undefined,
194+
configCache
195+
)
168196
);
169197
}
170198

packages/typescript/src/preflight.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@ ${pluginName}: Rollup requires that TypeScript produces ES Modules. Unfortunatel
2020
const tsLibErrorMessage = `${pluginName}: Could not find module 'tslib', which is required by this plugin. Is it installed?`;
2121

2222
let undef;
23-
const validModules = [ModuleKind.ES2015, ModuleKind.ES2020, ModuleKind.ESNext, undef];
23+
const validModules = [
24+
ModuleKind.ES2015,
25+
ModuleKind.ES2020,
26+
ModuleKind.ESNext,
27+
ModuleKind.Node16,
28+
ModuleKind.NodeNext,
29+
undef
30+
];
2431

2532
// eslint-disable-next-line import/prefer-default-export
2633
export const preflight = ({ config, context, rollupOptions, tslib }: PreflightOptions) => {

packages/typescript/src/watchProgram.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function createDeferred(timeout?: number): Deferred {
5454
let resolve: DeferredResolve = () => {};
5555

5656
if (timeout) {
57-
promise = Promise.race<Promise<boolean>>([
57+
promise = Promise.race<boolean | void>([
5858
new Promise((r) => setTimeout(r, timeout, true)),
5959
new Promise((r) => (resolve = r))
6060
]);
@@ -175,8 +175,21 @@ function createWatchHost(
175175
return baseHost.afterProgramCreate!(program);
176176
},
177177
/** Add helper to deal with module resolution */
178-
resolveModuleNames(moduleNames, containingFile) {
179-
return moduleNames.map((moduleName) => resolveModule(moduleName, containingFile));
178+
resolveModuleNames(
179+
moduleNames,
180+
containingFile,
181+
_reusedNames,
182+
redirectedReference,
183+
_optionsOnlyWithNewerTsVersions,
184+
containingSourceFile
185+
) {
186+
return moduleNames.map((moduleName, i) => {
187+
const mode = containingSourceFile
188+
? ts.getModeForResolutionAtIndex?.(containingSourceFile, i)
189+
: undefined; // eslint-disable-line no-undefined
190+
191+
return resolveModule(moduleName, containingFile, redirectedReference, mode);
192+
});
180193
}
181194
};
182195
}

0 commit comments

Comments
 (0)