Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(typescript): support 4.7 nodenext #1194

Merged
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 || '{,**/}*.?(c|m)ts?(x)', exclude, {
perrin4869 marked this conversation as resolved.
Show resolved Hide resolved
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