Skip to content

Commit

Permalink
feat(typescript): support 4.7 nodenext (#1194)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
perrin4869 committed Sep 6, 2022
1 parent 0bbb4fd commit 780f2e7
Show file tree
Hide file tree
Showing 18 changed files with 248 additions and 1,779 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -39,7 +39,7 @@
"prettier-plugin-package": "^1.3.0",
"semver": "^7.3.2",
"source-map-support": "^0.5.19",
"ts-node": "10.1.0",
"ts-node": "10.8.1",
"tsconfig-paths": "^3.9.0",
"typescript": "4.3.5",
"write-pkg": "^4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/typescript/package.json
Expand Up @@ -65,7 +65,7 @@
"@types/node": "^10.0.0",
"buble": "^0.20.0",
"rollup": "^2.67.3",
"typescript": "^4.2.2"
"typescript": "^4.7.3"
},
"types": "types/index.d.ts"
}
20 changes: 17 additions & 3 deletions packages/typescript/src/index.ts
Expand Up @@ -36,7 +36,7 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi
const watchProgramHelper = new WatchProgramHelper();

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

const resolved = resolveModule(importee, containingFile);
// when using node16 or nodenext module resolution, we need to tell ts if
// we are resolving to a commonjs or esnext module
const mode =
typeof ts.getImpliedNodeFormatForFile === 'function'
? ts.getImpliedNodeFormatForFile(
// @ts-expect-error
containingFile,
undefined, // eslint-disable-line no-undefined
{ ...ts.sys, ...formatHost },
parsedOptions.options
)
: undefined; // eslint-disable-line no-undefined

// eslint-disable-next-line no-undefined
const resolved = resolveModule(importee, containingFile, undefined, mode);

if (resolved) {
if (resolved.extension === '.d.ts') return null;
if (/\.d\.[cm]?ts/.test(resolved.extension)) return null;
return path.normalize(resolved.resolvedFileName);
}

Expand Down
19 changes: 14 additions & 5 deletions packages/typescript/src/moduleResolution.ts
@@ -1,12 +1,19 @@
import type { ModuleResolutionHost, ResolvedModuleFull } from 'typescript';
import {
ModuleResolutionHost,
ResolvedModuleFull,
ResolvedProjectReference,
ModuleKind
} from 'typescript';

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

type ModuleResolverHost = Partial<ModuleResolutionHost> & DiagnosticsHost;

export type Resolver = (
moduleName: string,
containingFile: string
containingFile: string,
redirectedReference?: ResolvedProjectReference | undefined,
mode?: ModuleKind.ESNext | ModuleKind.CommonJS | undefined
) => ResolvedModuleFull | undefined;

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

return (moduleName, containingFile) => {
const resolved = ts.nodeModuleNameResolver(
return (moduleName, containingFile, redirectedReference, mode) => {
const resolved = ts.resolveModuleName(
moduleName,
containingFile,
compilerOptions,
moduleHost,
cache
cache,
redirectedReference,
mode
);
return resolved.resolvedModule;
};
Expand Down
4 changes: 3 additions & 1 deletion packages/typescript/src/options/normalize.ts
Expand Up @@ -47,6 +47,8 @@ export function normalizeCompilerOptions(
switch (compilerOptions.module) {
case ts.ModuleKind.ES2015:
case ts.ModuleKind.ESNext:
case ts.ModuleKind.Node16:
case ts.ModuleKind.NodeNext:
case ts.ModuleKind.CommonJS:
// OK module type
return autoSetSourceMap;
Expand All @@ -57,7 +59,7 @@ export function normalizeCompilerOptions(
// Invalid module type
const moduleType = ts.ModuleKind[compilerOptions.module];
throw new Error(
`@rollup/plugin-typescript: The module kind should be 'ES2015' or 'ESNext, found: '${moduleType}'`
`@rollup/plugin-typescript: The module kind should be 'ES2015', 'ESNext', 'node16' or 'nodenext', found: '${moduleType}'`
);
}
default:
Expand Down
92 changes: 60 additions & 32 deletions packages/typescript/src/options/tsconfig.ts
Expand Up @@ -2,10 +2,12 @@ import { readFileSync } from 'fs';
import { dirname, resolve } from 'path';

import { PluginContext } from 'rollup';
import type {
import {
Diagnostic,
ExtendedConfigCacheEntry,
MapLike,
ModuleKind,
ModuleResolutionKind,
ParsedCommandLine,
ProjectReference,
TypeAcquisition,
Expand Down Expand Up @@ -99,6 +101,28 @@ function containsEnumOptions(
return enums.some((prop) => prop in compilerOptions && typeof compilerOptions[prop] === 'number');
}

/**
* The module resolution kind is a function of the resolved `compilerOptions.module`.
* This needs to be set explicitly for `resolveModuleName` to select the correct resolution method
*/
function setModuleResolutionKind(parsedConfig: ParsedCommandLine): ParsedCommandLine {
const moduleKind = parsedConfig.options.module;
const moduleResolution =
moduleKind === ModuleKind.Node16
? ModuleResolutionKind.Node16
: moduleKind === ModuleKind.NodeNext
? ModuleResolutionKind.NodeNext
: ModuleResolutionKind.NodeJs;

return {
...parsedConfig,
options: {
...parsedConfig.options,
moduleResolution
}
};
}

const configCache = new Map() as import('typescript').Map<ExtendedConfigCacheEntry>;

/**
Expand Down Expand Up @@ -132,39 +156,43 @@ export function parseTypescriptConfig(
// If compilerOptions has enums, it represents an CompilerOptions object instead of parsed JSON.
// This determines where the data is passed to the parser.
if (containsEnumOptions(compilerOptions)) {
parsedConfig = ts.parseJsonConfigFileContent(
{
...tsConfigFile,
compilerOptions: {
...DEFAULT_COMPILER_OPTIONS,
...tsConfigFile.compilerOptions
}
},
ts.sys,
basePath,
{ ...compilerOptions, ...makeForcedCompilerOptions(noForceEmit) },
tsConfigPath,
undefined,
undefined,
configCache
parsedConfig = setModuleResolutionKind(
ts.parseJsonConfigFileContent(
{
...tsConfigFile,
compilerOptions: {
...DEFAULT_COMPILER_OPTIONS,
...tsConfigFile.compilerOptions
}
},
ts.sys,
basePath,
{ ...compilerOptions, ...makeForcedCompilerOptions(noForceEmit) },
tsConfigPath,
undefined,
undefined,
configCache
)
);
} else {
parsedConfig = ts.parseJsonConfigFileContent(
{
...tsConfigFile,
compilerOptions: {
...DEFAULT_COMPILER_OPTIONS,
...tsConfigFile.compilerOptions,
...compilerOptions
}
},
ts.sys,
basePath,
makeForcedCompilerOptions(noForceEmit),
tsConfigPath,
undefined,
undefined,
configCache
parsedConfig = setModuleResolutionKind(
ts.parseJsonConfigFileContent(
{
...tsConfigFile,
compilerOptions: {
...DEFAULT_COMPILER_OPTIONS,
...tsConfigFile.compilerOptions,
...compilerOptions
}
},
ts.sys,
basePath,
makeForcedCompilerOptions(noForceEmit),
tsConfigPath,
undefined,
undefined,
configCache
)
);
}

Expand Down
9 changes: 8 additions & 1 deletion packages/typescript/src/preflight.ts
Expand Up @@ -20,7 +20,14 @@ ${pluginName}: Rollup requires that TypeScript produces ES Modules. Unfortunatel
const tsLibErrorMessage = `${pluginName}: Could not find module 'tslib', which is required by this plugin. Is it installed?`;

let undef;
const validModules = [ModuleKind.ES2015, ModuleKind.ES2020, ModuleKind.ESNext, undef];
const validModules = [
ModuleKind.ES2015,
ModuleKind.ES2020,
ModuleKind.ESNext,
ModuleKind.Node16,
ModuleKind.NodeNext,
undef
];

// eslint-disable-next-line import/prefer-default-export
export const preflight = ({ config, context, rollupOptions, tslib }: PreflightOptions) => {
Expand Down
19 changes: 16 additions & 3 deletions packages/typescript/src/watchProgram.ts
Expand Up @@ -54,7 +54,7 @@ function createDeferred(timeout?: number): Deferred {
let resolve: DeferredResolve = () => {};

if (timeout) {
promise = Promise.race<Promise<boolean>>([
promise = Promise.race<boolean | void>([
new Promise((r) => setTimeout(r, timeout, true)),
new Promise((r) => (resolve = r))
]);
Expand Down Expand Up @@ -175,8 +175,21 @@ function createWatchHost(
return baseHost.afterProgramCreate!(program);
},
/** Add helper to deal with module resolution */
resolveModuleNames(moduleNames, containingFile) {
return moduleNames.map((moduleName) => resolveModule(moduleName, containingFile));
resolveModuleNames(
moduleNames,
containingFile,
_reusedNames,
redirectedReference,
_optionsOnlyWithNewerTsVersions,
containingSourceFile
) {
return moduleNames.map((moduleName, i) => {
const mode = containingSourceFile
? ts.getModeForResolutionAtIndex?.(containingSourceFile, i)
: undefined; // eslint-disable-line no-undefined

return resolveModule(moduleName, containingFile, redirectedReference, mode);
});
}
};
}
Expand Down

0 comments on commit 780f2e7

Please sign in to comment.