rollup-plugin-ts
Version:
A TypeScript Rollup plugin that bundles declarations, respects Browserslists, and enables seamless integration with transpilers such as babel and swc
1,119 lines (1,093 loc) • 446 kB
JavaScript
'use strict';
const path = require('crosspath');
const module$1 = require('module');
const color = require('ansi-colors');
const util = require('util');
const browserslistGenerator = require('browserslist-generator');
const crypto = require('crypto');
const TSModule = require('typescript');
const browserslistModule = require('browserslist');
const pluginutils = require('@rollup/pluginutils');
const stringutil = require('@wessberg/stringutil');
const compatfactory = require('compatfactory');
const tsCloneNode = require('ts-clone-node');
const MagicString = require('magic-string');
function _interopNamespaceDefault(e) {
const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
if (e) {
for (const k in e) {
if (k !== 'default') {
const d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: () => e[k]
});
}
}
}
n.default = e;
return Object.freeze(n);
}
const SOURCE_MAP_EXTENSION = ".map";
const TS_EXTENSION = ".ts";
const TSX_EXTENSION = ".tsx";
const MTS_EXTENSION = ".mts";
const MTSX_EXTENSION = ".mtsx";
const CTS_EXTENSION = ".cts";
const CTSX_EXTENSION = ".ctsx";
const JS_EXTENSION = ".js";
const JS_MAP_EXTENSION = `.js.map`;
const JSX_EXTENSION = ".jsx";
const JSON_EXTENSION = ".json";
const MJS_EXTENSION = ".mjs";
const MJS_MAP_EXTENSION = ".mjs.map";
const MJSX_EXTENSION = ".mjsx";
const MJSX_MAP_EXTENSION = ".mjsx.map";
const CJS_EXTENSION = ".cjs";
const CJS_MAP_EXTENSION = ".cjs.map";
const CJSX_EXTENSION = ".cjsx";
const CJSX_MAP_EXTENSION = ".cjsx.map";
const D_TS_EXTENSION = `.d.ts`;
const D_TS_MAP_EXTENSION = `.d.ts.map`;
const D_CTS_EXTENSION = `.d.cts`;
const D_CTS_MAP_EXTENSION = `.d.cts.map`;
const D_MTS_EXTENSION = `.d.mts`;
const D_MTS_MAP_EXTENSION = `.d.mts.map`;
const TSBUILDINFO_EXTENSION = `.tsbuildinfo`;
const ROLLUP_PLUGIN_MULTI_ENTRY_LEGACY = "\0rollup-plugin-multi-entry:entry-point";
const ROLLUP_PLUGIN_VIRTUAL_PREFIX = `\0virtual:`;
const AMBIENT_EXTENSIONS = new Set([D_TS_EXTENSION, D_TS_MAP_EXTENSION, D_MTS_EXTENSION, D_MTS_MAP_EXTENSION, D_CTS_EXTENSION, D_CTS_MAP_EXTENSION]);
const KNOWN_EXTENSIONS = new Set([
...AMBIENT_EXTENSIONS,
JS_MAP_EXTENSION,
TS_EXTENSION,
MTS_EXTENSION,
MTSX_EXTENSION,
CTS_EXTENSION,
CTSX_EXTENSION,
TSX_EXTENSION,
JS_EXTENSION,
JSX_EXTENSION,
JSON_EXTENSION,
MJS_EXTENSION,
MJS_MAP_EXTENSION,
MJSX_EXTENSION,
MJSX_MAP_EXTENSION,
CJS_EXTENSION,
CJS_MAP_EXTENSION,
CJSX_EXTENSION,
CJSX_MAP_EXTENSION,
TSBUILDINFO_EXTENSION
]);
const DEFAULT_TSCONFIG_FILE_NAME = "tsconfig.json";
const NODE_MODULES = "node_modules";
const NODE_MODULES_MATCH_PATH = `/${NODE_MODULES}/`;
const SOURCE_MAP_COMMENT = "//# sourceMappingURL";
const SOURCE_MAP_COMMENT_REGEXP = /\/\/# sourceMappingURL=(\S*)/;
const TSLIB_NAME = `tslib${D_TS_EXTENSION}`;
const BABEL_RUNTIME_PREFIX_1 = "@babel/runtime/";
const BABEL_RUNTIME_PREFIX_2 = "babel-runtime/";
const SWC_HELPERS_PREFIX = "@swc/helpers";
const REGENERATOR_RUNTIME_NAME_1 = `${BABEL_RUNTIME_PREFIX_1}regenerator/index.js`;
const REGENERATOR_RUNTIME_NAME_2 = `${BABEL_RUNTIME_PREFIX_2}regenerator/index.js`;
const REGENERATOR_RUNTIME_NAME_3 = `regenerator-runtime/runtime.js`;
const REGENERATOR_RUNTIME_VIRTUAL_SRC = `${ROLLUP_PLUGIN_VIRTUAL_PREFIX}regenerator-runtime`;
const BABEL_REQUIRE_RUNTIME_HELPER_ESM_REGEXP_1 = new RegExp(`(require\\(["'\`])(${BABEL_RUNTIME_PREFIX_1}helpers/esm/[^"'\`]*)["'\`]\\)`);
const BABEL_REQUIRE_RUNTIME_HELPER_ESM_REGEXP_2 = new RegExp(`(require\\(["'\`])(${BABEL_RUNTIME_PREFIX_2}helpers/esm/[^"'\`]*)["'\`]\\)`);
const BABEL_IMPORT_RUNTIME_HELPER_CJS_REGEXP_1 = new RegExp(`(import\\s+\\w+\\s+from\\s+["'\`])(${BABEL_RUNTIME_PREFIX_1}helpers/[^"'/\`]*)["'\`]`);
const BABEL_IMPORT_RUNTIME_HELPER_CJS_REGEXP_2 = new RegExp(`(import\\s+\\w+\\s+from\\s+["'\`])(${BABEL_RUNTIME_PREFIX_2}helpers/[^"'/\`]*)["'\`]`);
const BABEL_IMPORT_RUNTIME_HELPER_CJS_REGEXP_3 = new RegExp(`(import\\s+["'\`])(${BABEL_RUNTIME_PREFIX_1}helpers/[^"'/\`]*)["'\`]`);
const BABEL_IMPORT_RUNTIME_HELPER_CJS_REGEXP_4 = new RegExp(`(import\\s+["'\`])(${BABEL_RUNTIME_PREFIX_2}helpers/[^"'/\`]*)["'\`]`);
const BABEL_MINIFICATION_BLACKLIST_PRESET_NAMES = [];
const BABEL_MINIFICATION_BLACKLIST_PLUGIN_NAMES = ["@babel/plugin-transform-runtime", "babel-plugin-transform-runtime"];
const BABEL_MINIFY_PRESET_NAMES = ["babel-preset-minify"];
const BABEL_MINIFY_PLUGIN_NAMES = [
"babel-plugin-transform-minify-booleans",
"babel-plugin-minify-builtins",
"babel-plugin-transform-inline-consecutive-adds",
"babel-plugin-minify-dead-code-elimination",
"babel-plugin-minify-constant-folding",
"babel-plugin-minify-flip-comparisons",
"babel-plugin-minify-guarded-expressions",
"babel-plugin-minify-infinity",
"babel-plugin-minify-mangle-names",
"babel-plugin-transform-member-expression-literals",
"babel-plugin-transform-merge-sibling-variables",
"babel-plugin-minify-numeric-literals",
"babel-plugin-transform-property-literals",
"babel-plugin-transform-regexp-constructors",
"babel-plugin-transform-remove-console",
"babel-plugin-transform-remove-debugger",
"babel-plugin-transform-remove-undefined",
"babel-plugin-minify-replace",
"babel-plugin-minify-simplify",
"babel-plugin-transform-simplify-comparison-operators",
"babel-plugin-minify-type-constructors",
"babel-plugin-transform-undefined-to-void"
];
const FORCED_SWC_MODULE_OPTIONS = {
type: "es6"
};
const FORCED_SWC_JSC_OPTIONS = {
externalHelpers: true
};
const FORCED_BABEL_PRESET_ENV_OPTIONS = {
modules: false
};
const FORCED_BABEL_YEARLY_PRESET_OPTIONS = {
...FORCED_BABEL_PRESET_ENV_OPTIONS
};
const FORCED_BABEL_PLUGIN_TRANSFORM_RUNTIME_OPTIONS = {
helpers: true,
regenerator: true,
// eslint-disable-next-line @typescript-eslint/naming-convention
useESModules: true
};
/**
* Ensures that the given item is in fact an array
*/
function ensureArray(item) {
return Array.isArray(item) ? item : [item];
}
// Until import.meta.resolve becomes stable, we'll have to do this instead
const resolveModule = (id, from = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href))) => module$1.createRequire(from).resolve(id);
function isTypeScriptLib(p) {
return p.startsWith(`lib.`) && p.endsWith(D_TS_EXTENSION);
}
function removeSearchPathFromFilename(p) {
if (p.includes(`?`)) {
return p.slice(0, p.indexOf(`?`));
}
return p;
}
/**
* Gets the extension of the given file
*/
function getExtension(file) {
if (file.endsWith(D_TS_EXTENSION))
return D_TS_EXTENSION;
else if (file.endsWith(D_TS_MAP_EXTENSION))
return D_TS_MAP_EXTENSION;
return path.extname(file);
}
/**
* Returns true if the given path represents an external library
*/
function isExternalLibrary(p) {
return (!p.startsWith(".") && !p.startsWith("/")) || p.includes(NODE_MODULES_MATCH_PATH);
}
/**
* Returns true if the given id represents tslib
*/
function isTslib(p) {
return p === "tslib" || path.normalize(p).endsWith(`/tslib/${TSLIB_NAME}`) || path.normalize(p).endsWith("/tslib/tslib.es6.js") || path.normalize(p).endsWith("/tslib/tslib.js");
}
/**
* Returns true if the given path represents a Babel helper
*/
function isBabelHelper(p) {
return includesBabelEsmHelper(p) || isBabelCjsHelper(p);
}
function isRegeneratorRuntime(p) {
return p.endsWith(REGENERATOR_RUNTIME_NAME_1) || p.endsWith(REGENERATOR_RUNTIME_NAME_2) || p.endsWith(REGENERATOR_RUNTIME_NAME_3) || p === REGENERATOR_RUNTIME_VIRTUAL_SRC;
}
/**
* Returns true if the given path represents a swc helper
*/
function isSwcHelper(p) {
return path.normalize(p).includes(`${SWC_HELPERS_PREFIX}`);
}
/**
* Returns true if the given path represents a Babel ESM helper
*/
function includesBabelEsmHelper(p) {
return path.normalize(p).includes(`${BABEL_RUNTIME_PREFIX_1}helpers/esm`) || path.normalize(p).includes(`${BABEL_RUNTIME_PREFIX_2}helpers/esm`);
}
/**
* Returns true if the given path represents a Babel CJS helper
*/
function isBabelCjsHelper(p) {
return !includesBabelEsmHelper(p) && (path.normalize(p).includes(`${BABEL_RUNTIME_PREFIX_1}helpers`) || path.normalize(p).includes(`${BABEL_RUNTIME_PREFIX_2}helpers`));
}
/**
* Returns true if the given path represents @babel/preset-env
*/
function isBabelPresetEnv(p) {
return path.normalize(p).includes("@babel/preset-env") || path.normalize(p).includes("babel-preset-env");
}
/**
* Returns true if the given path represents @babel/preset-typescript
*/
function isBabelPresetTypescript(p) {
return path.normalize(p).includes("@babel/preset-typescript");
}
/**
* Returns true if the given path is the name of the entry module or @rollup/plugin-multi-entry
*/
function isMultiEntryModule(p, multiEntryModuleName) {
const normalized = path.normalize(p);
return normalized === ROLLUP_PLUGIN_MULTI_ENTRY_LEGACY || (multiEntryModuleName != null && normalized === multiEntryModuleName);
}
/**
* Returns true if the given path represents @babel/preset-es[2015|2016|2017]
*/
function isYearlyBabelPreset(p) {
return path.normalize(p).includes("@babel/preset-es") || path.normalize(p).includes("babel-preset-es");
}
/**
* Returns true if the given path represents @babel/plugin-transform-runtime
*/
function isBabelPluginTransformRuntime(p) {
return path.normalize(p).includes("@babel/plugin-transform-runtime") || path.normalize(p).includes("babel-plugin-transform-runtime");
}
function somePathsAreRelated(paths, matchPath) {
for (const p of paths) {
if (pathsAreRelated(p, matchPath))
return true;
}
return false;
}
function pathsAreRelated(a, b) {
if (a === b)
return true;
// A node_modules folder may contain one or more nested node_modules
if (a.includes(NODE_MODULES) || b.includes(NODE_MODULES)) {
const firstPathFromNodeModules = a.includes(NODE_MODULES) ? a.slice(a.indexOf(NODE_MODULES)) : a;
const secondPathFromNodeModules = b.includes(NODE_MODULES) ? b.slice(b.indexOf(NODE_MODULES)) : b;
if (firstPathFromNodeModules.includes(secondPathFromNodeModules))
return true;
if (secondPathFromNodeModules.includes(firstPathFromNodeModules))
return true;
}
return false;
}
/**
* Strips the extension from a file
*/
function stripKnownExtension(file) {
let currentExtname;
for (const extName of KNOWN_EXTENSIONS) {
if (file.endsWith(extName)) {
currentExtname = extName;
break;
}
}
if (currentExtname == null)
return file;
return file.slice(0, file.lastIndexOf(currentExtname));
}
/**
* Sets the given extension for the given file
*/
function setExtension(file, extension) {
return path.normalize(`${stripKnownExtension(file)}${extension}`);
}
/**
* Ensure that the given path has a leading "."
*/
function ensureHasLeadingDotAndPosix(p, externalGuard = true) {
if (externalGuard && isExternalLibrary(p))
return p;
const posixPath = path.normalize(p);
if (posixPath.startsWith("."))
return posixPath;
if (posixPath.startsWith("/"))
return `.${posixPath}`;
return `./${posixPath}`;
}
/**
* Ensures that the given path is relative
*/
function ensureRelative(root, p) {
// If the path is already relative, simply return it
if (!path.isAbsolute(p)) {
return path.normalize(p);
}
// Otherwise, construct a relative path from the root
return path.relative(root, p);
}
/**
* Ensures that the given path is absolute
*/
function ensureAbsolute(root, p) {
// If the path is already absolute, simply return it
if (path.isAbsolute(p)) {
return path.normalize(p);
}
// Otherwise, construct an absolute path from the root
return path.join(root, p);
}
/**
* Checks the id from the given importer with respect to the given externalOption provided to Rollup
*/
function isExternal(id, importer, externalOption) {
var _a;
if (externalOption == null)
return false;
if (externalOption === true)
return true;
if (externalOption === false)
return false;
if (typeof externalOption === "function")
return (_a = externalOption(id, importer, true)) !== null && _a !== void 0 ? _a : false;
const ids = new Set();
const matchers = [];
for (const value of ensureArray(externalOption)) {
if (value instanceof RegExp) {
matchers.push(value);
}
else {
ids.add(value);
}
}
return ids.has(id) || matchers.some(matcher => matcher.test(id));
}
/**
* On TypeScript versions 4.5 and 4.6, nodenext is available as a Compiler Option, but selecting it
* will throw.
*/
function allowsNodeNextModuleResolution(typescript) {
// If 'Node16' module resolution is available, NodeNext is allowed to be selected as a ModuleResolutionKind.
// This happened in v4.7
return typescript.ModuleResolutionKind.Node16 != null;
}
function finalizeParsedCommandLine({ cwd, typescript, parsedCommandLineResult: { originalCompilerOptions, parsedCommandLine, tsconfigPath } }) {
var _a, _b;
// Typescript should always be able to emit - otherwise we cannot transform source files.
// That is, unless 'allowImportingTsExtensions' is true, in which case noEmit *must* be truthy.
parsedCommandLine.options.noEmit = Boolean(parsedCommandLine.options.allowImportingTsExtensions);
/**
* If 'Classic' Module resolution is requested, replace that one with 'NodeNext' instead.
* If anything else is requested, leave it as it is
*/
if (parsedCommandLine.options.moduleResolution === typescript.ModuleResolutionKind.Classic) {
parsedCommandLine.options.moduleResolution =
typescript.ModuleResolutionKind.NodeNext != null && allowsNodeNextModuleResolution(typescript)
? typescript.ModuleResolutionKind.NodeNext
: // eslint-disable-next-line deprecation/deprecation
(_b = (_a = typescript.ModuleResolutionKind.Node16) !== null && _a !== void 0 ? _a : typescript.ModuleResolutionKind.Node10) !== null && _b !== void 0 ? _b : typescript.ModuleResolutionKind.NodeJs;
}
// Declarations may be generated, but not as part of the Builder/Incremental program which is used during the transform, renderChunk, and generateBundle phases, so a nice optimization can be to instruct TypeScript not to generate them.
// The raw CompilerOptions will be preserved and used in the last compilation phase to generate declarations if needed.
// However, when 'composite' is true or when incremental compilation is active, declarations must be emitted for buildInfo to work, so under such circumstances this optimization must be skipped.
const canApplySkipDeclarationsOptimization = !Boolean(parsedCommandLine.options.incremental) &&
!Boolean(parsedCommandLine.options.composite) &&
parsedCommandLine.options.tsBuildInfoFile == null &&
(parsedCommandLine.projectReferences == null || parsedCommandLine.projectReferences.length < 1);
if (canApplySkipDeclarationsOptimization) {
parsedCommandLine.options.declaration = false;
parsedCommandLine.options.declarationMap = false;
parsedCommandLine.options.declarationDir = undefined;
}
// Ensure that at tsBuildInfoFile exists if 'composite' or 'incremental' is true
if (parsedCommandLine.options.incremental === true || parsedCommandLine.options.composite === true) {
if (parsedCommandLine.options.tsBuildInfoFile != null) {
parsedCommandLine.options.tsBuildInfoFile = ensureAbsolute(cwd, parsedCommandLine.options.tsBuildInfoFile);
}
// Otherwise, use the _actual_ outDir/outFile from the resolved tsconfig to build the path to the .tsbuildinfo file since TypeScript should be able to actually
// resolve the file from the path pointed to by the user
else {
let tsBuildInfoAbsolutePath;
// Use outDir as the base directory
if (originalCompilerOptions.outDir != null) {
tsBuildInfoAbsolutePath = path.join(ensureAbsolute(cwd, originalCompilerOptions.outDir), `${path.parse(tsconfigPath).name}${TSBUILDINFO_EXTENSION}`);
}
// Otherwise, use outFile but replace the extension
else if (originalCompilerOptions.outFile != null) {
tsBuildInfoAbsolutePath = ensureAbsolute(cwd, setExtension(originalCompilerOptions.outFile, TSBUILDINFO_EXTENSION));
}
// Otherwise, use 'cwd' as the directory for the .tsbuildinfo file
else {
tsBuildInfoAbsolutePath = path.join(ensureAbsolute(cwd, `${path.parse(tsconfigPath).name}${TSBUILDINFO_EXTENSION}`));
}
parsedCommandLine.options.tsBuildInfoFile = tsBuildInfoAbsolutePath;
}
}
return parsedCommandLine;
}
function shouldDebugSourceFile(debug, { fileName, text }) {
if (typeof debug === "boolean")
return debug;
return Boolean(debug({
kind: "transformer",
fileName,
text
}));
}
function shouldDebugMetrics(debug, sourceFile) {
if (typeof debug === "boolean")
return debug;
return Boolean(debug({
kind: "metrics",
...(sourceFile == null ? {} : { fileName: sourceFile.fileName })
}));
}
function shouldDebugEmit(debug, fileName, text, outputPathKind) {
if (typeof debug === "boolean")
return debug;
return Boolean(debug({
kind: "emit",
fileKind: outputPathKind,
fileName,
text
}));
}
function shouldDebugTsconfig(debug) {
if (typeof debug === "boolean")
return debug;
return Boolean(debug({
kind: "tsconfig"
}));
}
function getFormattedDateTimePrefix() {
const currentDate = new Date();
const currentDateTime = `(${currentDate.getHours().toString().padStart(2, "0")}:${currentDate.getMinutes().toString().padStart(2, "0")}:${currentDate
.getSeconds()
.toString()
.padStart(2, "0")})`;
return `${color.gray(currentDateTime)} `;
}
function inspect(item, depth = 4) {
console.log(util.inspect(item, { colors: true, depth, maxArrayLength: 1000 }));
}
function logTsconfig(config) {
console.log(`${getFormattedDateTimePrefix()}${color.red(`tsconfig`)}`);
inspect(config);
}
/**
* Returns true if the given tsconfig is a ParsedCommandLine
*/
function isParsedCommandLine(tsconfig) {
return tsconfig != null && typeof tsconfig !== "string" && typeof tsconfig !== "function" && "options" in tsconfig && !("hook" in tsconfig);
}
/**
* Returns true if the given tsconfig are raw, JSON-serializable CompilerOptions
*/
function isRawCompilerOptions(tsconfig) {
return tsconfig != null && typeof tsconfig !== "string" && typeof tsconfig !== "function" && !("options" in tsconfig) && !("hook" in tsconfig);
}
/**
* Returns true if the given tsconfig is in fact a function that receives resolved CompilerOptions that can be extended
*/
function isTsConfigResolver(tsconfig) {
return tsconfig != null && typeof tsconfig === "function";
}
/**
* Returns true if the given tsconfig is in fact an object that provides a filename for a tsconfig,
* as well as a 'hook' function that receives resolved CompilerOptions that can be extended
*/
function isTsConfigResolverWithFileName(tsconfig) {
return tsconfig != null && typeof tsconfig !== "string" && typeof tsconfig !== "function" && !("options" in tsconfig) && "hook" in tsconfig;
}
/**
* Returns true if the given tsconfig are CompilerOptions
*/
function isCompilerOptions(tsconfig) {
return (tsconfig != null &&
typeof tsconfig !== "string" &&
typeof tsconfig !== "function" &&
!("options" in tsconfig) &&
!("hook" in tsconfig) &&
(("module" in tsconfig && typeof tsconfig.module === "number") ||
("target" in tsconfig && typeof tsconfig.target === "number") ||
("jsx" in tsconfig && typeof tsconfig.jsx === "number") ||
("moduleResolution" in tsconfig && typeof tsconfig.moduleResolution === "number") ||
("newLine" in tsconfig && typeof tsconfig.newLine === "number")));
}
/**
* Gets a ParsedCommandLine based on the given options
*/
function getParsedCommandLine(options) {
const { cwd, tsconfig, fileSystem, forcedCompilerOptions = {}, typescript } = options;
const hasProvidedTsconfig = tsconfig != null;
let originalCompilerOptions;
let parsedCommandLine;
let tsconfigPath = ensureAbsolute(cwd, DEFAULT_TSCONFIG_FILE_NAME);
// If the given tsconfig is already a ParsedCommandLine, use that one, but apply the forced CompilerOptions
if (isParsedCommandLine(tsconfig)) {
originalCompilerOptions = tsconfig.options;
tsconfig.options = { ...tsconfig.options, ...forcedCompilerOptions };
parsedCommandLine = tsconfig;
}
// If the user provided CompilerOptions directly, use those to build a ParsedCommandLine
else if (isCompilerOptions(tsconfig)) {
originalCompilerOptions = typescript.parseJsonConfigFileContent({}, fileSystem, cwd, tsconfig).options;
parsedCommandLine = typescript.parseJsonConfigFileContent({}, fileSystem, cwd, {
...tsconfig,
...forcedCompilerOptions
});
}
// If the user provided JSON-serializable ("raw") CompilerOptions directly, use those to build a ParsedCommandLine
else if (isRawCompilerOptions(tsconfig)) {
originalCompilerOptions = typescript.parseJsonConfigFileContent({ compilerOptions: tsconfig }, fileSystem, cwd).options;
parsedCommandLine = typescript.parseJsonConfigFileContent({ compilerOptions: tsconfig }, fileSystem, cwd, forcedCompilerOptions);
}
// Otherwise, attempt to resolve it and parse it
else {
tsconfigPath = ensureAbsolute(cwd, isTsConfigResolverWithFileName(tsconfig) ? tsconfig.fileName : tsconfig != null && !isTsConfigResolver(tsconfig) ? tsconfig : DEFAULT_TSCONFIG_FILE_NAME);
// If the file exists, read the tsconfig on that location
let tsconfigContent = fileSystem.readFile(tsconfigPath);
// Otherwise, if the user hasn't provided any tsconfig at all, start from an empty one (and only use the forced options)
if (tsconfigContent == null && !hasProvidedTsconfig) {
tsconfigContent = "";
}
// Finally, if the user has provided a file that doesn't exist, throw
else if (tsconfigContent == null) {
throw new ReferenceError(`The given tsconfig: '${tsconfigPath}' doesn't exist!`);
}
const tsconfigJson = typescript.parseConfigFileTextToJson(tsconfigPath, tsconfigContent).config;
const basePath = path.native.dirname(tsconfigPath);
originalCompilerOptions = typescript.parseJsonConfigFileContent(tsconfigJson, fileSystem, basePath, {}, tsconfigPath).options;
parsedCommandLine = typescript.parseJsonConfigFileContent(tsconfigJson, fileSystem, basePath, forcedCompilerOptions, tsconfigPath);
// If an extension hook has been provided. Make sure to still apply the forced CompilerOptions
if (isTsConfigResolver(tsconfig)) {
originalCompilerOptions = { ...tsconfig(originalCompilerOptions) };
parsedCommandLine.options = { ...tsconfig(parsedCommandLine.options), ...forcedCompilerOptions };
}
else if (isTsConfigResolverWithFileName(tsconfig)) {
// If an extension hook has been provided through the 'hook' property. Make sure to still apply the forced CompilerOptions
originalCompilerOptions = { ...tsconfig.hook(originalCompilerOptions) };
parsedCommandLine.options = { ...tsconfig.hook(parsedCommandLine.options), ...forcedCompilerOptions };
}
}
// Ensure that the parsed command line, as well as the original CompilerOptions has a base URL
if (parsedCommandLine.options.baseUrl == null) {
parsedCommandLine.options.baseUrl = ".";
}
if (originalCompilerOptions.baseUrl == null) {
originalCompilerOptions.baseUrl = ".";
}
// Remove all non-declaration files from the default file names since these will be handled separately by Rollup.
// Also filter out all files that is matched by the include/exclude globs provided as plugin options
parsedCommandLine.fileNames = parsedCommandLine.fileNames.filter(file => file.endsWith(D_TS_EXTENSION) && options.filter(file));
const parsedCommandLineResult = {
parsedCommandLine,
originalCompilerOptions,
tsconfigPath
};
// On some TypeScript versions such as 3.0.0, the 'composite' feature
// require that a specific configFilePath exists on the CompilerOptions,
// so make sure a path is always set.
if (parsedCommandLine.options.configFilePath == null) {
parsedCommandLine.options.configFilePath = tsconfigPath;
}
// Finalize the parsed command line
finalizeParsedCommandLine({ ...options, parsedCommandLineResult });
if (shouldDebugTsconfig(options.pluginOptions.debug)) {
logTsconfig(parsedCommandLine);
}
return parsedCommandLineResult;
}
/**
* Gets the ScriptTarget to use from the given Browserslist
*/
function getScriptTargetFromBrowserslist(browserslist, typescript) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3;
switch (browserslistGenerator.getAppropriateEcmaVersionForBrowserslist(browserslist)) {
case "es3":
return typescript.ScriptTarget.ES3;
case "es5":
return typescript.ScriptTarget.ES5;
case "es2015":
return typescript.ScriptTarget.ES2015;
// Support older TypeScript versions that may not supported ES2016 as a ScriptTarget with nullish coalescing
case "es2016":
return (_a = typescript.ScriptTarget.ES2016) !== null && _a !== void 0 ? _a : typescript.ScriptTarget.ES2015;
// Support older TypeScript versions that may not supported ES2017 as a ScriptTarget with nullish coalescing
case "es2017":
return (_c = (_b = typescript.ScriptTarget.ES2017) !== null && _b !== void 0 ? _b : typescript.ScriptTarget.ES2016) !== null && _c !== void 0 ? _c : typescript.ScriptTarget.ES2015;
// Support older TypeScript versions that may not supported ES2018 as a ScriptTarget with nullish coalescing
case "es2018":
return (_f = (_e = (_d = typescript.ScriptTarget.ES2018) !== null && _d !== void 0 ? _d : typescript.ScriptTarget.ES2017) !== null && _e !== void 0 ? _e : typescript.ScriptTarget.ES2016) !== null && _f !== void 0 ? _f : typescript.ScriptTarget.ES2015;
// Support older TypeScript versions that may not supported ES2019 as a ScriptTarget with nullish coalescing
case "es2019":
return (_k = (_j = (_h = (_g = typescript.ScriptTarget.ES2019) !== null && _g !== void 0 ? _g : typescript.ScriptTarget.ES2018) !== null && _h !== void 0 ? _h : typescript.ScriptTarget.ES2017) !== null && _j !== void 0 ? _j : typescript.ScriptTarget.ES2016) !== null && _k !== void 0 ? _k : typescript.ScriptTarget.ES2015;
// Support older TypeScript versions that may not supported ES2020 as a ScriptTarget with nullish coalescing
case "es2020":
return ((_q = (_p = (_o = (_m = (_l = typescript.ScriptTarget.ES2020) !== null && _l !== void 0 ? _l : typescript.ScriptTarget.ES2019) !== null && _m !== void 0 ? _m : typescript.ScriptTarget.ES2018) !== null && _o !== void 0 ? _o : typescript.ScriptTarget.ES2017) !== null && _p !== void 0 ? _p : typescript.ScriptTarget.ES2016) !== null && _q !== void 0 ? _q : typescript.ScriptTarget.ES2015);
// Support older TypeScript versions that may not supported ES2021 as a ScriptTarget with nullish coalescing
case "es2021":
return ((_w = (_v = (_u = (_t = (_s = (_r = typescript.ScriptTarget.ES2021) !== null && _r !== void 0 ? _r : typescript.ScriptTarget.ES2020) !== null && _s !== void 0 ? _s : typescript.ScriptTarget.ES2019) !== null && _t !== void 0 ? _t : typescript.ScriptTarget.ES2018) !== null && _u !== void 0 ? _u : typescript.ScriptTarget.ES2017) !== null && _v !== void 0 ? _v : typescript.ScriptTarget.ES2016) !== null && _w !== void 0 ? _w : typescript.ScriptTarget.ES2015);
case "es2022":
case "es2023":
return ((_3 = (_2 = (_1 = (_0 = (_z = (_y = (_x = typescript.ScriptTarget.ES2022) !== null && _x !== void 0 ? _x : typescript.ScriptTarget.ES2021) !== null && _y !== void 0 ? _y : typescript.ScriptTarget.ES2020) !== null && _z !== void 0 ? _z : typescript.ScriptTarget.ES2019) !== null && _0 !== void 0 ? _0 : typescript.ScriptTarget.ES2018) !== null && _1 !== void 0 ? _1 : typescript.ScriptTarget.ES2017) !== null && _2 !== void 0 ? _2 : typescript.ScriptTarget.ES2016) !== null && _3 !== void 0 ? _3 : typescript.ScriptTarget.ES2015);
}
}
/**
* Gets the EcmaVersion that represents the given ScriptTarget
*/
function getEcmaVersionForScriptTarget(scriptTarget, typescript) {
switch (scriptTarget) {
case typescript.ScriptTarget.ES3:
return "es3";
case typescript.ScriptTarget.ES5:
return "es5";
case typescript.ScriptTarget.ES2015:
return "es2015";
case typescript.ScriptTarget.ES2016:
return "es2016";
case typescript.ScriptTarget.ES2017:
return "es2017";
case typescript.ScriptTarget.ES2018:
return "es2018";
case typescript.ScriptTarget.ES2019:
return "es2019";
case typescript.ScriptTarget.ES2020:
return "es2020";
case typescript.ScriptTarget.ES2021:
return "es2021";
case typescript.ScriptTarget.ES2022:
return "es2022";
case typescript.ScriptTarget.ESNext:
case typescript.ScriptTarget.Latest:
case typescript.ScriptTarget.JSON:
return "es2023";
}
}
/**
* Generates a random hash
*/
function generateRandomHash({ length = 8, key } = {}) {
return key == null ? crypto.randomBytes(length / 2).toString("hex") : crypto.createHmac("sha1", key).digest("hex").slice(0, length);
}
function generateRandomIntegerHash(options, offset = 1000000) {
const str = generateRandomHash(options);
let result = 0;
for (let i = 0; i < str.length; i++) {
result = result + str.charCodeAt(i);
}
return result + offset;
}
/**
* Gets the destination directory to use based on the given Rollup output options
*/
function getOutDir(cwd, options) {
let outDir;
if (options == null) {
// Generate a random output directory. The idea is that this will never match any existing files on disk.
// The reason being that Typescript may erroneously think that input files may be overwritten if 'allowJs' is true
// and 'outDir' is '.'
outDir = path.join(cwd, generateRandomHash());
}
else if (options.dir != null) {
outDir = options.dir;
}
else if (options.file != null) {
outDir = path.dirname(options.file);
}
else {
outDir = cwd;
}
// Return the relative output directory. Default to "." if it should be equal to cwd
const relativeToCwd = ensureRelative(cwd, outDir);
return relativeToCwd === "" ? "." : relativeToCwd;
}
/**
* Gets normalized PluginOptions based on the given ones
*/
function getPluginOptions(options) {
// Destructure the options and provide default
const { browserslist, transpiler = "typescript", typescript = TSModule, cwd = path.normalize(process.cwd()), tsconfig, transformers, include = [], exclude = [], transpileOnly = false, debug = false, fileSystem = typescript.sys, babelConfig = {}, swcConfig = {}, hook = {} } = options;
return {
typescript: typescript,
transpiler,
browserslist,
cwd: ensureAbsolute(process.cwd(), cwd),
exclude,
include,
transformers,
tsconfig,
babelConfig,
swcConfig,
transpileOnly,
debug,
fileSystem,
hook
};
}
function getTranspilerOptions(transpiler) {
if (typeof transpiler === "string") {
return {
typescriptSyntax: transpiler,
otherSyntax: transpiler
};
}
return transpiler;
}
function isUsingTranspiler(transpiler, options) {
return options.typescriptSyntax === transpiler || options.otherSyntax === transpiler;
}
/**
* Gets the ModuleKind to force
*/
function getForcedModuleKindOption({ pluginOptions }) {
// Under these circumstances, TypeScript is a client of Rollup, and Rollup only understands ESM.
// Rollup, not TypeScript, is the decider of which module system(s) to target based on the Rollup configuration.
// Because of this, TypeScript will always be instructed to emit ESM.
return { module: pluginOptions.typescript.ModuleKind.ESNext };
}
/**
* Gets the ScriptTarget to force
*/
function getForcedScriptTargetOption({ pluginOptions, browserslist }) {
// If anything else than TypeScript should perform the rest of the transpilation after stripping TypeScript syntax, always target the latest ECMAScript version and let the other transpiler take care of the rest
if (getTranspilerOptions(pluginOptions.transpiler).otherSyntax !== "typescript") {
return { target: pluginOptions.typescript.ScriptTarget.ESNext };
}
// If a Browserslist is provided, and if Typescript should perform the transpilation, decide the appropriate ECMAScript version based on the Browserslist.
else if (browserslist != null && browserslist !== false) {
return { target: getScriptTargetFromBrowserslist(browserslist, pluginOptions.typescript) };
}
// Otherwise, don't force the 'target' option
return {};
}
/**
* Decide whether or not to force import helpers
*/
function getForcedImportHelpersOption({ pluginOptions }) {
// If TypeScript is being used, which uses tslib, helpers should *always* be imported.
// We don't want them to be duplicated multiple times within generated chunks.
// When other transpilers are being used in some shape of form, they'll have similar enforced options
if (isUsingTranspiler("typescript", getTranspilerOptions(pluginOptions.transpiler))) {
return { importHelpers: true };
}
// Otherwise, don't force the 'importHelpers' option
return {};
}
/**
* Retrieves the CompilerOptions that will be forced
*/
function getForcedCompilerOptions(options) {
return {
...getForcedModuleKindOption(options),
...getForcedScriptTargetOption(options),
...getForcedImportHelpersOption(options),
outDir: getOutDir(options.pluginOptions.cwd),
// Rollup, not Typescript, is the decider of where to put files
outFile: undefined,
// Always generate SourceMaps. Rollup will then decide if it wants to use them or not
sourceMap: true,
// Never use inline source maps. Let Rollup inline the returned SourceMap if it can and if sourcemaps should be emitted in the OutputOptions,
inlineSourceMap: false,
// Since we never use inline source maps, inline sources aren't supported
inlineSources: false,
// Typescript should always be able to emit - otherwise we cannot transform source files
noEmitOnError: false,
// Typescript should always be able to emit other things than declarations - otherwise we cannot transform source files
emitDeclarationOnly: false,
// Typescript should always be able to emit helpers - since we force 'importHelpers'
noEmitHelpers: false,
// Typescript should always be able to resolve things - otherwise compilation will break
noResolve: false,
// Typescript should never watch files. That is the job of Rollup
watch: false,
// Typescript should never watch files. That is the job of Rollup
preserveWatchOutput: false,
skipLibCheck: true
};
}
/**
* Returns true if the given OutputFile represents code
*/
function isCodeOutputFile({ name }) {
const extension = getExtension(name);
return [SOURCE_MAP_EXTENSION, D_TS_EXTENSION, D_TS_MAP_EXTENSION].every(otherExtension => extension !== otherExtension);
}
/**
* Returns true if the given OutputFile represents a SourceMap
*/
function isMapOutputFile({ name }) {
const extension = getExtension(name);
return [SOURCE_MAP_EXTENSION, D_TS_MAP_EXTENSION].some(otherExtension => extension === otherExtension);
}
/**
* Gets a SourceDescription from the given EmitOutput
*/
function getSourceDescriptionFromEmitOutput(output) {
const code = output.outputFiles.find(isCodeOutputFile);
if (code == null)
return undefined;
const map = output.outputFiles.find(isMapOutputFile);
// Remove the SourceMap comment from the code if it is given. Rollup is the decider of whether or not to emit SourceMaps and if they should be inlined
const inlinedSourcemapIndex = code.text.indexOf(`\n${SOURCE_MAP_COMMENT}`);
if (inlinedSourcemapIndex >= 0) {
code.text = code.text.slice(0, inlinedSourcemapIndex);
}
return {
code: code.text,
...(map == null ? {} : { map: map.text })
};
}
/**
* Gets diagnostics for the given fileName
*/
function emitDiagnostics({ host, context, pluginOptions }) {
const typescript = host.getTypescript();
let diagnostics = host.getDiagnostics();
// If there is a hook for diagnostics, call it assign the result of calling it to the local variable 'diagnostics'
if (pluginOptions.hook.diagnostics != null) {
diagnostics = pluginOptions.hook.diagnostics(diagnostics);
}
// Don't proceed if the hook returned null or undefined
if (diagnostics == null)
return;
diagnostics.forEach((diagnostic) => {
const message = typescript.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
const position = diagnostic.start == null || diagnostic.file == null ? undefined : diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
// Color-format the diagnostics
const colorFormatted = typescript.formatDiagnosticsWithColorAndContext([diagnostic], host);
// Provide a normalized error code
const code = `${diagnostic.scope == null ? "TS" : diagnostic.scope}${diagnostic.code}`;
// Provide an empty Stack. There's nothing useful in seeing the internals of this Plugin in the reported error
const stack = "";
// Isolate the frame
const newLine = host.getNewLine();
let frame = colorFormatted.slice(colorFormatted.indexOf(message) + message.length);
// Remove the trailing newline from the frame if it has one
if (frame.startsWith(newLine)) {
frame = frame.slice(frame.indexOf(newLine) + newLine.length);
}
switch (diagnostic.category) {
case typescript.DiagnosticCategory.Error:
context.error({
frame,
code,
name: code,
stack,
...(diagnostic.length == null ? {} : { length: diagnostic.length }),
...(diagnostic.file == null && position == null
? {}
: {
loc: {
...(diagnostic.file == null ? {} : { file: diagnostic.file.fileName }),
...(position == null ? {} : { line: position.line + 1 }),
...(position == null ? {} : { column: position.character + 1 })
}
}),
...(diagnostic.file == null ? {} : { pos: diagnostic.file.pos }),
message
});
break;
case typescript.DiagnosticCategory.Warning:
case typescript.DiagnosticCategory.Message:
case typescript.DiagnosticCategory.Suggestion:
context.warn({
frame,
code,
name: code,
...(diagnostic.length == null ? {} : { length: diagnostic.length }),
loc: {
...(diagnostic.file == null ? {} : { file: diagnostic.file.fileName }),
...(position == null ? {} : { line: position.line + 1 }),
...(position == null ? {} : { column: position.character + 1 })
},
...(diagnostic.file == null ? {} : { pos: diagnostic.file.pos }),
message
});
break;
}
});
}
/**
* Gets the extensions that are supported by Typescript, depending on whether or not to allow JS and JSON
*/
function getSupportedExtensions(allowJs, allowJson, typescript) {
// If the TypeScript version supports Node16 as a module resolution target,
// it also supports some additional formats such as .mts, .cts, .cjs, .d.cts, .mjs, .d.mts, .cjsx, and .mjsx
if (typescript.ModuleResolutionKind.Node16 != null) {
return new Set([
TS_EXTENSION,
MTS_EXTENSION,
MTSX_EXTENSION,
CTS_EXTENSION,
CTSX_EXTENSION,
TSX_EXTENSION,
D_TS_EXTENSION,
D_CTS_EXTENSION,
D_MTS_EXTENSION,
...(allowJs ? [JS_EXTENSION, JSX_EXTENSION, MJS_EXTENSION, MJSX_EXTENSION, CJS_EXTENSION, CJSX_EXTENSION] : []),
...(allowJson ? [JSON_EXTENSION] : [])
]);
}
return new Set([
TS_EXTENSION,
TSX_EXTENSION,
D_TS_EXTENSION,
...(allowJs ? [JS_EXTENSION, JSX_EXTENSION] : []),
...(allowJson ? [JSON_EXTENSION] : [])
]);
}
/**
* Returns true if the given asset is an OutputChunk
*/
function isOutputChunk(thing) {
return thing.type === "chunk";
}
/**
* Takes all filenames that has been included in the given bundle
*/
function takeBundledFilesNames(bundle) {
const bundledFilenames = new Set();
Object.values(bundle).forEach(value => {
if (isOutputChunk(value)) {
Object.keys(value.modules).forEach(fileName => bundledFilenames.add(path.normalize(fileName)));
}
else if ("fileName" in value) {
bundledFilenames.add(path.normalize(value.fileName));
}
});
return bundledFilenames;
}
/**
* Returns true if the given browserslist is raw input for a Browserslist
*/
function isBrowserslistInput(browserslist) {
return typeof browserslist === "string" || Array.isArray(browserslist);
}
/**
* Returns true if the given browserslist is an IBrowserslistQueryConfig
*/
function isBrowserslistQueryConfig(browserslist) {
return browserslist != null && !isBrowserslistInput(browserslist) && browserslist !== false && "query" in browserslist && browserslist.query != null;
}
/**
* Returns true if the given browserslist is an IBrowserslistPathConfig
*/
function isBrowserslistPathConfig(browserslist) {
return browserslist != null && !isBrowserslistInput(browserslist) && browserslist !== false && "path" in browserslist && browserslist.path != null;
}
/**
* Gets a Browserslist based on the given options
*/
function getBrowserslist({ browserslist, cwd, fileSystem }) {
// If a Browserslist is provided directly from the options, use that
if (browserslist != null) {
// If the Browserslist is equal to false, it should never be used. Return undefined
if (browserslist === false) {
return false;
}
// If the Browserslist is some raw input queries, use them directly
else if (isBrowserslistInput(browserslist)) {
return browserslistGenerator.normalizeBrowserslist(ensureArray(browserslist));
}
// If the Browserslist is a config with raw query options, use them directly
else if (isBrowserslistQueryConfig(browserslist)) {
return browserslistGenerator.normalizeBrowserslist(ensureArray(browserslist.query));
}
// If the Browserslist is a config with a path, attempt to resolve the Browserslist from that property
else if (isBrowserslistPathConfig(browserslist)) {
const browserslistPath = ensureAbsolute(cwd, browserslist.path);
const errorMessage = `The given path for a Browserslist: '${browserslistPath}' could not be resolved from '${cwd}'`;
if (!fileSystem.fileExists(path.native.normalize(browserslistPath))) {
throw new ReferenceError(errorMessage);
}
else {
// Read the config
const match = browserslistModule.readConfig(browserslistPath);
if (match == null) {
throw new ReferenceError(errorMessage);
}
else {
return match.defaults;
}
}
}
// The config object could not be validated. Return undefined
else {
return undefined;
}
}
// Otherwise, try to locate a Browserslist
else {
const config = browserslistModule.findConfig(cwd);
return config == null ? undefined : config.defaults;
}
}
/**
* A Cache over resolved modules
*/
class ResolveCache {
constructor(options) {
this.options = options;
/**
* A memory-persistent cache of resolved modules for files over time
*/
this.RESOLVE_CACHE = new Map();
}
/**
* Gets the resolved path for an id from a parent
*/
getFromCache(id, parent) {
const parentMap = this.RESOLVE_CACHE.get(parent);
if (parentMap == null)
return undefined;
return parentMap.get(id);
}
/**
* Deletes the entry matching the given parent
*/
delete(parent) {
return this.RESOLVE_CACHE.delete(parent);
}
clear() {
this.RESOLVE_CACHE.clear();
}
/**
* Sets the given resolved module in the resolve cache
*/
setInCache(result, id, parent) {
let parentMap = this.RESOLVE_CACHE.get(parent);
if (parentMap == null) {
parentMap = new Map();
this.RESOLVE_CACHE.set(parent, parentMap);
}
parentMap.set(id, result);
return result;
}
/**
* Resolves a module name, including internal helpers such as tslib, even if they aren't included in the language service
*/
resolveModuleName(typescript, moduleName, containingFile, compilerOptions, host, cache, redirectedReference) {
// Default to using Typescript's resolver directly
return typescript.resolveModuleName(moduleName, containingFile, compilerOptions, host, cache, redirectedReference);
}
/**
* Gets a cached module result for the given file from the given parent and returns it if it exists already.
* If not, it will compute it, update the cache, and then return it
*/
get(options) {
const { id, parent, moduleResolutionHost } = options;
let cacheResult = this.getFromCache(id, parent);
const typescript = moduleResolutionHost.getTypescript();
const compilerOptions = moduleResolutionHost.getCompilationSettings();
const cwd = moduleResolutionHost.getCwd();
const nonAmbientSupportedExtensions = moduleResolutionHost.getSupportedNonAmbientExtensions();
if (cacheResult != null) {
return cacheResult;
}
// Resolve the file via Typescript, either through classic or node module resolution
const { resolvedModule } = this.resolveModuleName(typescript, id, path.normalize(parent), compilerOptions, moduleResolutionHost);
// If it could not be resolved, the cache result will be equal to null
if (resolvedModule == null) {
cacheResult = null;
}
// Otherwise, proceed
else {
// Make sure that the path is absolute from the cwd
resolvedModule.resolvedFileName = path.includeDriveLetter(path.normalize(ensureAbsolute(cwd, resolvedModule.resolvedFileName)));
if (resolvedModule.resolvedFileName.endsWith(D_TS_EXTENSION)) {
resolvedModule.resolvedAmbientFileName = resolvedModule.resolvedFileName;
resolvedModule.resolvedFileName = undefined;
resolvedModule.extension = D_TS_EXTENSION;
if (isTslib(id)) {
// Sometimes the drive letter is omitted by TypeScript on Windows here, which can lead to problems
const candidate = path.includeDriveLetter(path.normalize(setExtension(resolvedModule.resolvedAmbientFileName, `.es6${JS_EXTENSION}`)));
if (this.options.fileSystem.fileExists(path.native.normalize(candidate))) {
resolvedModule.resolvedFileName = candidate;
}
}
// Don't go and attempt to resolve sources for external libraries
else if (resolvedModule.isExternalLibraryImport == null || !resolvedModule.isExternalLibraryImport) {
// Try to determine the resolved file name.
for (const extension of nonAmbientSupportedExtensions) {
const candidate = path.normalize(setExtension(resolvedModule.resolvedAmbientFileName, extension