UNPKG

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,111 lines (1,087 loc) 445 kB
import path from 'crosspath'; import { createRequire } from 'module'; import color from 'ansi-colors'; import { inspect as inspect$1 } from 'util'; import { getAppropriateEcmaVersionForBrowserslist, normalizeBrowserslist, browsersWithSupportForEcmaVersion } from 'browserslist-generator'; import { randomBytes, createHmac } from 'crypto'; import TSModule from 'typescript'; import browserslistModule from 'browserslist'; import { createFilter } from '@rollup/pluginutils'; import { camelCase, matchAll } from '@wessberg/stringutil'; import { ensureNodeFactory } from 'compatfactory'; import { preserveNode, cloneNode, setParentNodes } from 'ts-clone-node'; import MagicString from 'magic-string'; 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 = import.meta.url) => 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(inspect$1(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 (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 ? randomBytes(length / 2).toString("hex") : 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 normalizeBrowserslist(ensureArray(browserslist)); } // If the Browserslist is a config with raw query options, use them directly else if (isBrowserslistQueryConfig(browserslist)) { return 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)); if (this.options.fileSystem.fileExists(path.native.normalize(candidate))) { resolvedModule.resolvedFileName = candidate; break; } } } } else { resolvedModule.resolvedAmbientFileName = undefined; const candidate = path.normalize(setExtension(resolvedModule.resolvedFileName, D_TS_EXTENSION)); if (this.options.fileSystem.fileExi