UNPKG

@wessberg/cjs-to-esm-transformer

Version:

A Custom Transformer for Typescript that transforms Node-style CommonJS to tree-shakeable ES Modules

1,401 lines (1,384 loc) 120 kB
'use strict'; function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var fs = require('fs'); var fs__default = _interopDefault(fs); var TS = require('typescript'); var util = require('util'); var glob = require('glob'); var path = require('path'); var resolve = require('resolve'); var stringutil = require('@wessberg/stringutil'); var reservedWords = require('reserved-words'); const CONSTANT = { inspectOptions: { colors: true, depth: Infinity, maxArrayLength: Infinity } }; /** * Returns true if the given path represents an external library * * @param path * @returns */ function isExternalLibrary(path) { return !path.startsWith(".") && !path.startsWith("/"); } /** * A map between id's and their results from previous resolving * @type {Map<string, string|undefined>} */ const cache = new Map(); /** * Computes a cache key based on the combination of id and parent * * @param id * @param parent * @return */ function computeCacheKey(id, parent) { return isExternalLibrary(id) ? id : `${parent == null ? "" : `${parent}->`}${id}`; } /** * A function that can resolve an import path * * @param options * @returns */ function resolvePath({ id, parent, prioritizedPackageKeys = ["es2015", "esm2015", "module", "jsnext:main", "main", "browser"], prioritizedExtensions = ["", ".js", ".mjs", ".jsx", ".ts", ".tsx", ".json"], moduleDirectory = "node_modules", fileExists, readFile }) { const cacheKey = computeCacheKey(id, parent); // Attempt to take the resolve result from the cache const cacheResult = cache.get(cacheKey); // If it is a proper path, return it if (cacheResult != null) return cacheResult; // Otherwise, if the cache result isn't strictly equal to 'undefined', it has previously been resolved to a non-existing file if (cacheResult === null) return; if (!isExternalLibrary(id)) { const absolute = path.isAbsolute(id) ? id : path.join(parent == null ? "" : path.dirname(parent), id); const variants = [absolute, path.join(absolute, "index")]; for (const variant of variants) { for (const ext of prioritizedExtensions) { const withExtension = `${variant}${ext}`; if (fileExists(withExtension)) { // Add it to the cache cache.set(cacheKey, withExtension); return withExtension; } } } // Add it to the cache and mark it as unresolvable cache.set(cacheKey, null); return undefined; } // Otherwise, try to resolve it via node module resolution and put it in the cache try { const resolveResult = resolve.sync(id, { extensions: prioritizedExtensions, moduleDirectory, readFileSync: (file, charset) => readFile(file, charset), isFile: file => fileExists(file), packageFilter(pkg) { let property; // Otherwise, or if no key was selected, use the prioritized list of fields and take the first matched one if (property == null) { const packageKeys = Object.keys(pkg); property = prioritizedPackageKeys.find(key => packageKeys.includes(key)); } // If a property was resolved, set the 'main' property to it (resolve will use the main property no matter what) if (property != null) { pkg.main = pkg[property]; } // Return the package return pkg; } }); // Add it to the cache cache.set(cacheKey, resolveResult); // Return it return resolveResult; } catch (ex) { // No file could be resolved. Set it in the cache as unresolvable and return void cache.set(cacheKey, null); // Return undefined¬ return undefined; } } function walkThroughFillerNodes(expression, typescript) { if (typescript.isParenthesizedExpression(expression) || typescript.isAsExpression(expression) || typescript.isTypeAssertion(expression) || typescript.isNonNullExpression(expression) || typescript.isTypeAssertion(expression) || typescript.isExpressionWithTypeArguments(expression)) { return expression.expression; } return expression; } /* eslint-disable */ /** * @file This file is auto-generated. Do not change its contents. */ const BUILT_IN_MODULE = new Set([ "assert", "async_hooks", "buffer", "child_process", "cluster", "console", "constants", "crypto", "dgram", "dns", "domain", "events", "fs", "fs/promises", "http", "http2", "https", "inspector", "module", "net", "os", "path", "perf_hooks", "process", "punycode", "querystring", "readline", "repl", "stream", "string_decoder", "timers", "tls", "trace_events", "tty", "url", "util", "v8", "vm", "worker_threads", "zlib" ]); function isBuiltInModule(moduleName) { return BUILT_IN_MODULE.has(moduleName); } const BUILT_IN_MODULE_MAP = { assert: { namedExports: new Set([]), hasDefaultExport: true }, async_hooks: { namedExports: new Set(["AsyncLocalStorage", "createHook", "executionAsyncId", "triggerAsyncId", "executionAsyncResource", "AsyncResource"]), hasDefaultExport: true }, buffer: { namedExports: new Set(["Buffer", "SlowBuffer", "transcode", "kMaxLength", "kStringMaxLength", "constants", "INSPECT_MAX_BYTES"]), hasDefaultExport: true }, child_process: { namedExports: new Set(["ChildProcess", "exec", "execFile", "execFileSync", "execSync", "fork", "spawn", "spawnSync"]), hasDefaultExport: true }, cluster: { namedExports: new Set(["isWorker", "isMaster", "Worker", "workers", "settings", "SCHED_NONE", "SCHED_RR", "schedulingPolicy", "setupMaster", "fork", "disconnect"]), hasDefaultExport: true }, console: { namedExports: new Set([ "log", "warn", "dir", "time", "timeEnd", "timeLog", "trace", "assert", "clear", "count", "countReset", "group", "groupEnd", "table", "debug", "info", "dirxml", "error", "groupCollapsed", "Console", "profile", "profileEnd", "timeStamp", "context" ]), hasDefaultExport: true }, constants: { namedExports: new Set([ "RTLD_LAZY", "RTLD_NOW", "RTLD_GLOBAL", "RTLD_LOCAL", "E2BIG", "EACCES", "EADDRINUSE", "EADDRNOTAVAIL", "EAFNOSUPPORT", "EAGAIN", "EALREADY", "EBADF", "EBADMSG", "EBUSY", "ECANCELED", "ECHILD", "ECONNABORTED", "ECONNREFUSED", "ECONNRESET", "EDEADLK", "EDESTADDRREQ", "EDOM", "EDQUOT", "EEXIST", "EFAULT", "EFBIG", "EHOSTUNREACH", "EIDRM", "EILSEQ", "EINPROGRESS", "EINTR", "EINVAL", "EIO", "EISCONN", "EISDIR", "ELOOP", "EMFILE", "EMLINK", "EMSGSIZE", "EMULTIHOP", "ENAMETOOLONG", "ENETDOWN", "ENETRESET", "ENETUNREACH", "ENFILE", "ENOBUFS", "ENODATA", "ENODEV", "ENOENT", "ENOEXEC", "ENOLCK", "ENOLINK", "ENOMEM", "ENOMSG", "ENOPROTOOPT", "ENOSPC", "ENOSR", "ENOSTR", "ENOSYS", "ENOTCONN", "ENOTDIR", "ENOTEMPTY", "ENOTSOCK", "ENOTSUP", "ENOTTY", "ENXIO", "EOPNOTSUPP", "EOVERFLOW", "EPERM", "EPIPE", "EPROTO", "EPROTONOSUPPORT", "EPROTOTYPE", "ERANGE", "EROFS", "ESPIPE", "ESRCH", "ESTALE", "ETIME", "ETIMEDOUT", "ETXTBSY", "EWOULDBLOCK", "EXDEV", "PRIORITY_LOW", "PRIORITY_BELOW_NORMAL", "PRIORITY_NORMAL", "PRIORITY_ABOVE_NORMAL", "PRIORITY_HIGH", "PRIORITY_HIGHEST", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGIOT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", "SIGINFO", "SIGSYS", "UV_FS_SYMLINK_DIR", "UV_FS_SYMLINK_JUNCTION", "O_RDONLY", "O_WRONLY", "O_RDWR", "UV_DIRENT_UNKNOWN", "UV_DIRENT_FILE", "UV_DIRENT_DIR", "UV_DIRENT_LINK", "UV_DIRENT_FIFO", "UV_DIRENT_SOCKET", "UV_DIRENT_CHAR", "UV_DIRENT_BLOCK", "S_IFMT", "S_IFREG", "S_IFDIR", "S_IFCHR", "S_IFBLK", "S_IFIFO", "S_IFLNK", "S_IFSOCK", "O_CREAT", "O_EXCL", "UV_FS_O_FILEMAP", "O_NOCTTY", "O_TRUNC", "O_APPEND", "O_DIRECTORY", "O_NOFOLLOW", "O_SYNC", "O_DSYNC", "O_SYMLINK", "O_NONBLOCK", "S_IRWXU", "S_IRUSR", "S_IWUSR", "S_IXUSR", "S_IRWXG", "S_IRGRP", "S_IWGRP", "S_IXGRP", "S_IRWXO", "S_IROTH", "S_IWOTH", "S_IXOTH", "F_OK", "R_OK", "W_OK", "X_OK", "UV_FS_COPYFILE_EXCL", "COPYFILE_EXCL", "UV_FS_COPYFILE_FICLONE", "COPYFILE_FICLONE", "UV_FS_COPYFILE_FICLONE_FORCE", "COPYFILE_FICLONE_FORCE", "OPENSSL_VERSION_NUMBER", "SSL_OP_ALL", "SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION", "SSL_OP_CIPHER_SERVER_PREFERENCE", "SSL_OP_CISCO_ANYCONNECT", "SSL_OP_COOKIE_EXCHANGE", "SSL_OP_CRYPTOPRO_TLSEXT_BUG", "SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS", "SSL_OP_EPHEMERAL_RSA", "SSL_OP_LEGACY_SERVER_CONNECT", "SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER", "SSL_OP_MICROSOFT_SESS_ID_BUG", "SSL_OP_MSIE_SSLV2_RSA_PADDING", "SSL_OP_NETSCAPE_CA_DN_BUG", "SSL_OP_NETSCAPE_CHALLENGE_BUG", "SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG", "SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG", "SSL_OP_NO_COMPRESSION", "SSL_OP_NO_QUERY_MTU", "SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION", "SSL_OP_NO_SSLv2", "SSL_OP_NO_SSLv3", "SSL_OP_NO_TICKET", "SSL_OP_NO_TLSv1", "SSL_OP_NO_TLSv1_1", "SSL_OP_NO_TLSv1_2", "SSL_OP_PKCS1_CHECK_1", "SSL_OP_PKCS1_CHECK_2", "SSL_OP_SINGLE_DH_USE", "SSL_OP_SINGLE_ECDH_USE", "SSL_OP_SSLEAY_080_CLIENT_DH_BUG", "SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG", "SSL_OP_TLS_BLOCK_PADDING_BUG", "SSL_OP_TLS_D5_BUG", "SSL_OP_TLS_ROLLBACK_BUG", "ENGINE_METHOD_RSA", "ENGINE_METHOD_DSA", "ENGINE_METHOD_DH", "ENGINE_METHOD_RAND", "ENGINE_METHOD_EC", "ENGINE_METHOD_CIPHERS", "ENGINE_METHOD_DIGESTS", "ENGINE_METHOD_PKEY_METHS", "ENGINE_METHOD_PKEY_ASN1_METHS", "ENGINE_METHOD_ALL", "ENGINE_METHOD_NONE", "DH_CHECK_P_NOT_SAFE_PRIME", "DH_CHECK_P_NOT_PRIME", "DH_UNABLE_TO_CHECK_GENERATOR", "DH_NOT_SUITABLE_GENERATOR", "ALPN_ENABLED", "RSA_PKCS1_PADDING", "RSA_SSLV23_PADDING", "RSA_NO_PADDING", "RSA_PKCS1_OAEP_PADDING", "RSA_X931_PADDING", "RSA_PKCS1_PSS_PADDING", "RSA_PSS_SALTLEN_DIGEST", "RSA_PSS_SALTLEN_MAX_SIGN", "RSA_PSS_SALTLEN_AUTO", "defaultCoreCipherList", "TLS1_VERSION", "TLS1_1_VERSION", "TLS1_2_VERSION", "TLS1_3_VERSION", "POINT_CONVERSION_COMPRESSED", "POINT_CONVERSION_UNCOMPRESSED", "POINT_CONVERSION_HYBRID" ]), hasDefaultExport: true }, crypto: { namedExports: new Set([ "createCipheriv", "createDecipheriv", "createDiffieHellman", "createDiffieHellmanGroup", "createECDH", "createHash", "createHmac", "createPrivateKey", "createPublicKey", "createSecretKey", "createSign", "createVerify", "diffieHellman", "getCiphers", "getCurves", "getDiffieHellman", "getHashes", "pbkdf2", "pbkdf2Sync", "generateKeyPair", "generateKeyPairSync", "privateDecrypt", "privateEncrypt", "publicDecrypt", "publicEncrypt", "randomBytes", "randomFill", "randomFillSync", "scrypt", "scryptSync", "sign", "setEngine", "timingSafeEqual", "getFips", "setFips", "verify", "Certificate", "Cipher", "Cipheriv", "Decipher", "Decipheriv", "DiffieHellman", "DiffieHellmanGroup", "ECDH", "Hash", "Hmac", "KeyObject", "Sign", "Verify", "constants" ]), hasDefaultExport: true }, dgram: { namedExports: new Set(["createSocket", "Socket"]), hasDefaultExport: true }, dns: { namedExports: new Set([ "lookup", "lookupService", "Resolver", "setServers", "ADDRCONFIG", "ALL", "V4MAPPED", "NODATA", "FORMERR", "SERVFAIL", "NOTFOUND", "NOTIMP", "REFUSED", "BADQUERY", "BADNAME", "BADFAMILY", "BADRESP", "CONNREFUSED", "TIMEOUT", "EOF", "FILE", "NOMEM", "DESTRUCTION", "BADSTR", "BADFLAGS", "NONAME", "BADHINTS", "NOTINITIALIZED", "LOADIPHLPAPI", "ADDRGETNETWORKPARAMS", "CANCELLED", "getServers", "resolve", "resolve4", "resolve6", "resolveAny", "resolveCname", "resolveMx", "resolveNaptr", "resolveNs", "resolvePtr", "resolveSoa", "resolveSrv", "resolveTxt", "reverse", "promises" ]), hasDefaultExport: true }, domain: { namedExports: new Set(["Domain", "createDomain", "create", "active"]), hasDefaultExport: true }, events: { namedExports: new Set([]), hasDefaultExport: true }, fs: { namedExports: new Set([ "appendFile", "appendFileSync", "access", "accessSync", "chown", "chownSync", "chmod", "chmodSync", "close", "closeSync", "copyFile", "copyFileSync", "createReadStream", "createWriteStream", "exists", "existsSync", "fchown", "fchownSync", "fchmod", "fchmodSync", "fdatasync", "fdatasyncSync", "fstat", "fstatSync", "fsync", "fsyncSync", "ftruncate", "ftruncateSync", "futimes", "futimesSync", "lchown", "lchownSync", "lchmod", "lchmodSync", "link", "linkSync", "lstat", "lstatSync", "mkdir", "mkdirSync", "mkdtemp", "mkdtempSync", "open", "openSync", "opendir", "opendirSync", "readdir", "readdirSync", "read", "readSync", "readv", "readvSync", "readFile", "readFileSync", "readlink", "readlinkSync", "realpath", "realpathSync", "rename", "renameSync", "rmdir", "rmdirSync", "stat", "statSync", "symlink", "symlinkSync", "truncate", "truncateSync", "unwatchFile", "unlink", "unlinkSync", "utimes", "utimesSync", "watch", "watchFile", "writeFile", "writeFileSync", "write", "writeSync", "writev", "writevSync", "Dir", "Dirent", "Stats", "ReadStream", "WriteStream", "FileReadStream", "FileWriteStream", "F_OK", "R_OK", "W_OK", "X_OK", "constants", "promises" ]), hasDefaultExport: true }, "fs/promises": { namedExports: new Set([ "access", "copyFile", "open", "opendir", "rename", "truncate", "rmdir", "mkdir", "readdir", "readlink", "symlink", "lstat", "stat", "link", "unlink", "chmod", "lchmod", "lchown", "chown", "utimes", "realpath", "mkdtemp", "writeFile", "appendFile", "readFile" ]), hasDefaultExport: true }, http: { namedExports: new Set([ "METHODS", "STATUS_CODES", "Agent", "ClientRequest", "IncomingMessage", "OutgoingMessage", "Server", "ServerResponse", "createServer", "validateHeaderName", "validateHeaderValue", "get", "request", "maxHeaderSize", "globalAgent" ]), hasDefaultExport: true }, http2: { namedExports: new Set([ "connect", "constants", "createServer", "createSecureServer", "getDefaultSettings", "getPackedSettings", "getUnpackedSettings", "Http2ServerRequest", "Http2ServerResponse" ]), hasDefaultExport: true }, https: { namedExports: new Set(["Agent", "globalAgent", "Server", "createServer", "get", "request"]), hasDefaultExport: true }, inspector: { namedExports: new Set(["open", "close", "url", "waitForDebugger", "console", "Session"]), hasDefaultExport: true }, module: { namedExports: new Set([]), hasDefaultExport: true }, net: { namedExports: new Set(["connect", "createConnection", "createServer", "isIP", "isIPv4", "isIPv6", "Server", "Socket", "Stream"]), hasDefaultExport: true }, os: { namedExports: new Set([ "arch", "cpus", "endianness", "freemem", "getPriority", "homedir", "hostname", "loadavg", "networkInterfaces", "platform", "release", "setPriority", "tmpdir", "totalmem", "type", "userInfo", "uptime", "version", "constants", "EOL" ]), hasDefaultExport: true }, path: { namedExports: new Set([ "resolve", "normalize", "isAbsolute", "join", "relative", "toNamespacedPath", "dirname", "basename", "extname", "format", "parse", "sep", "delimiter", "win32", "posix" ]), hasDefaultExport: true }, perf_hooks: { namedExports: new Set(["performance", "PerformanceObserver", "monitorEventLoopDelay", "constants"]), hasDefaultExport: true }, process: { namedExports: new Set([ "version", "versions", "arch", "platform", "release", "moduleLoadList", "binding", "domain", "config", "dlopen", "uptime", "reallyExit", "hrtime", "cpuUsage", "resourceUsage", "memoryUsage", "kill", "exit", "openStdin", "getuid", "geteuid", "getgid", "getegid", "getgroups", "allowedNodeEnvironmentFlags", "assert", "features", "setUncaughtExceptionCaptureCallback", "hasUncaughtExceptionCaptureCallback", "emitWarning", "nextTick", "stdout", "stdin", "stderr", "abort", "umask", "chdir", "cwd", "initgroups", "setgroups", "setegid", "seteuid", "setgid", "setuid", "env", "title", "argv", "execArgv", "pid", "ppid", "execPath", "debugPort", "argv0", "mainModule", "emit" ]), hasDefaultExport: true }, punycode: { namedExports: new Set(["version", "ucs2", "decode", "encode", "toASCII", "toUnicode"]), hasDefaultExport: true }, querystring: { namedExports: new Set(["unescapeBuffer", "unescape", "escape", "stringify", "encode", "parse", "decode"]), hasDefaultExport: true }, readline: { namedExports: new Set(["Interface", "clearLine", "clearScreenDown", "createInterface", "cursorTo", "emitKeypressEvents", "moveCursor"]), hasDefaultExport: true }, repl: { namedExports: new Set(["start", "writer", "REPLServer", "REPL_MODE_SLOPPY", "REPL_MODE_STRICT", "Recoverable"]), hasDefaultExport: true }, stream: { namedExports: new Set([]), hasDefaultExport: true }, string_decoder: { namedExports: new Set(["StringDecoder"]), hasDefaultExport: true }, timers: { namedExports: new Set(["setTimeout", "clearTimeout", "setImmediate", "clearImmediate", "setInterval", "clearInterval", "active", "unenroll", "enroll"]), hasDefaultExport: true }, tls: { namedExports: new Set([ "CLIENT_RENEG_LIMIT", "CLIENT_RENEG_WINDOW", "DEFAULT_CIPHERS", "DEFAULT_ECDH_CURVE", "DEFAULT_MIN_VERSION", "DEFAULT_MAX_VERSION", "getCiphers", "rootCertificates", "convertALPNProtocols", "checkServerIdentity", "parseCertString", "createSecureContext", "SecureContext", "TLSSocket", "Server", "createServer", "connect", "createSecurePair" ]), hasDefaultExport: true }, trace_events: { namedExports: new Set(["createTracing", "getEnabledCategories"]), hasDefaultExport: true }, tty: { namedExports: new Set(["isatty", "ReadStream", "WriteStream"]), hasDefaultExport: true }, url: { namedExports: new Set(["Url", "parse", "resolve", "resolveObject", "format", "URL", "URLSearchParams", "domainToASCII", "domainToUnicode", "pathToFileURL", "fileURLToPath"]), hasDefaultExport: true }, util: { namedExports: new Set([ "callbackify", "debuglog", "deprecate", "format", "formatWithOptions", "getSystemErrorName", "inherits", "inspect", "isArray", "isBoolean", "isBuffer", "isDeepStrictEqual", "isNull", "isNullOrUndefined", "isNumber", "isString", "isSymbol", "isUndefined", "isRegExp", "isObject", "isDate", "isError", "isFunction", "isPrimitive", "log", "promisify", "TextDecoder", "TextEncoder", "types" ]), hasDefaultExport: true }, v8: { namedExports: new Set([ "cachedDataVersionTag", "getHeapSnapshot", "getHeapStatistics", "getHeapSpaceStatistics", "getHeapCodeStatistics", "setFlagsFromString", "Serializer", "Deserializer", "DefaultSerializer", "DefaultDeserializer", "deserialize", "serialize", "writeHeapSnapshot" ]), hasDefaultExport: true }, vm: { namedExports: new Set(["Script", "createContext", "createScript", "runInContext", "runInNewContext", "runInThisContext", "isContext", "compileFunction", "measureMemory"]), hasDefaultExport: true }, worker_threads: { namedExports: new Set([ "isMainThread", "MessagePort", "MessageChannel", "moveMessagePortToContext", "receiveMessageOnPort", "resourceLimits", "threadId", "SHARE_ENV", "Worker", "parentPort", "workerData" ]), hasDefaultExport: true }, zlib: { namedExports: new Set([ "Deflate", "Inflate", "Gzip", "Gunzip", "DeflateRaw", "InflateRaw", "Unzip", "BrotliCompress", "BrotliDecompress", "deflate", "deflateSync", "gzip", "gzipSync", "deflateRaw", "deflateRawSync", "unzip", "unzipSync", "inflate", "inflateSync", "gunzip", "gunzipSync", "inflateRaw", "inflateRawSync", "brotliCompress", "brotliCompressSync", "brotliDecompress", "brotliDecompressSync", "createDeflate", "createInflate", "createDeflateRaw", "createInflateRaw", "createGzip", "createGunzip", "createUnzip", "createBrotliCompress", "createBrotliDecompress", "constants", "codes" ]), hasDefaultExport: true } }; /** * Checks if the CallExpression represents a require call (e.g.: 'require(...)') */ function isRequireCall(inputExpression, sourceFile, context) { const { typescript } = context; const callExpression = walkThroughFillerNodes(inputExpression, typescript); if (!typescript.isCallExpression(callExpression)) return { match: false }; const expression = walkThroughFillerNodes(callExpression.expression, typescript); if (!typescript.isIdentifier(expression) || expression.text !== "require") return { match: false }; // Take the first argument, if there is any const [firstArgument] = callExpression.arguments; if (firstArgument == null) return { match: false }; const moduleSpecifier = typescript.isStringLiteralLike(firstArgument) ? firstArgument.text : undefined; const resolvedModuleSpecifier = moduleSpecifier == null ? undefined : resolvePath({ id: moduleSpecifier, parent: path.normalize(sourceFile.fileName), readFile: context.readFile, fileExists: context.fileExists }); const resolvedModuleSpecifierText = resolvedModuleSpecifier == null || isBuiltInModule(resolvedModuleSpecifier) ? undefined : context.readFile(resolvedModuleSpecifier); if (moduleSpecifier == null || resolvedModuleSpecifier == null || resolvedModuleSpecifierText == null) { return { match: true, moduleSpecifier, resolvedModuleSpecifier: undefined, resolvedModuleSpecifierText: undefined }; } else { return { match: true, moduleSpecifier, resolvedModuleSpecifier: path.normalize(resolvedModuleSpecifier), resolvedModuleSpecifierText }; } } function findNodeUp(from, nodeCb, breakWhen) { let current = from; while (current.parent != null) { current = current.parent; if (breakWhen != null && breakWhen(current)) return undefined; if (nodeCb(current)) return current; } return undefined; } /** * Returns true if the given Node is a Statement * Uses an internal non-exposed Typescript helper to decide whether or not the Node is an Expression */ function isStatement(node, typescript) { return typescript.isStatementButNotDeclaration(node); } /** * Returns true if the given Node is a Declaration * Uses an internal non-exposed Typescript helper to decide whether or not the Node is an Expression */ function isDeclaration(node, typescript) { return typescript.isDeclaration(node); } /** * Returns true if the given Node is a Statement is a Declaration */ function isStatementOrDeclaration(node, typescript) { return isStatement(node, typescript) || isDeclaration(node, typescript); } /** * Generates a proper name based on the given module specifier * * @param moduleSpecifier * @return */ function generateNameFromModuleSpecifier(moduleSpecifier) { const { name } = path.parse(moduleSpecifier); return stringutil.camelCase(name); } /** * Tries to get or potentially parse module exports based on the given data in the given context * @param data * @param context */ function getModuleExportsFromRequireDataInContext(data, context) { if (!data.match) return undefined; const { typescript } = context; // Otherwise, spread out the things we know about the require call const { moduleSpecifier, resolvedModuleSpecifierText, resolvedModuleSpecifier } = data; // If no module specifier could be determined, remove the CallExpression from the SourceFile if (moduleSpecifier == null) { return undefined; } // If we've been able to resolve a module as well as its contents, // Check it for exports so that we know more about its internals, for example whether or not it has any named exports, etc let moduleExports; // If no module specifier could be resolved, it may be a built in module - an we may know about its module exports already if (resolvedModuleSpecifier == null && isBuiltInModule(moduleSpecifier)) { moduleExports = BUILT_IN_MODULE_MAP[moduleSpecifier]; } // Otherwise, if we could resolve a module, try to get the exports for it else if (resolvedModuleSpecifier != null) { // Try to get the ModuleExports for the resolved module, if we know them already moduleExports = context.getModuleExportsForPath(resolvedModuleSpecifier); // If that wasn't possible, generate a new SourceFile and parse it if (moduleExports == null && resolvedModuleSpecifierText != null) { moduleExports = transformSourceFile(typescript.createSourceFile(resolvedModuleSpecifier, resolvedModuleSpecifierText, typescript.ScriptTarget.ESNext, true, typescript.ScriptKind.TS), { baseVisitorContext: { ...context, onlyExports: true } }, context.transformationContext).exports; } } return moduleExports; } function shouldDebug(debug, sourceFile) { if (debug == null) return false; if (typeof debug === "boolean") return debug; if (sourceFile == null) return true; if (typeof debug === "string") return sourceFile.fileName === debug; else return debug(sourceFile.fileName); } /** * Visits the given CallExpression * * @param options * @returns */ function visitCallExpression({ node, childContinuation, sourceFile, context }) { if (context.onlyExports) { return childContinuation(node); } // Check if the node represents a require(...) call. const requireData = isRequireCall(node, sourceFile, context); const { typescript } = context; // If it doesn't proceed without applying any transformations if (!requireData.match) { return childContinuation(node); } // Otherwise, spread out the things we know about the require call const { moduleSpecifier } = requireData; // If no module specifier could be determined, remove the CallExpression from the SourceFile if (moduleSpecifier == null) { return undefined; } // If we've been able to resolve a module as well as its contents, // Check it for exports so that we know more about its internals, for example whether or not it has any named exports, etc const moduleExports = getModuleExportsFromRequireDataInContext(requireData, context); // Find the first ExpressionStatement going up from the Node, breaking if part of a BinaryExpression, CallExpression, or a NewExpression const expressionStatementParent = findNodeUp(node, typescript.isExpressionStatement, currentNode => typescript.isBinaryExpression(currentNode) || typescript.isCallExpression(currentNode) || typescript.isNewExpression(currentNode)); // If we don't know anything about the exports of the module, or if it doesn't export any named exports, // there's really not much we can do in terms of using the context of the CallExpression to import the maximally // minimal subset of the module. In these cases, the only thing that can be done is to import the default // export and maybe return an identifier for it depending on whether or not the CallExpression is part of an ExpressionStatement if (moduleExports == null || moduleExports.namedExports.size === 0 || (expressionStatementParent != null && !moduleExports.hasDefaultExport)) { // If part of an ExpressionStatement, simply return the module without any name or other bindings if (expressionStatementParent != null) { // Only add the import if there isn't already an import within the SourceFile of the entire module without any bindings if (!context.isModuleSpecifierImportedWithoutLocals(moduleSpecifier)) { context.addImport(typescript.createImportDeclaration(undefined, undefined, undefined, typescript.createStringLiteral(moduleSpecifier))); } // Drop this CallExpression return undefined; } // Otherwise, we need to give the module a name and replace the CallExpression with an identifier for it else { // If the default export is already imported, get the local binding name for it and create an identifier for it // rather than generating a new unnecessary import if (context.hasLocalForDefaultImportFromModule(moduleSpecifier)) { const local = context.getLocalForDefaultImportFromModule(moduleSpecifier); return typescript.createIdentifier(local); } else { const identifier = typescript.createIdentifier(context.getFreeIdentifier(generateNameFromModuleSpecifier(moduleSpecifier))); context.addImport(typescript.createImportDeclaration(undefined, undefined, typescript.createImportClause(identifier, undefined), typescript.createStringLiteral(moduleSpecifier))); // Replace the CallExpression by the identifier return identifier; } } } // Otherwise, we know that we want to add an import instead of the CallExpression, but depending on the context of the CallExpression, we may // or may not import specific Named Exports, the Default Export, or the entire namespace. // Find the first Element- or PropertyAccessExpression that wraps the require(...) call, whatever it is. // That means that if it is wrapped in 'require(...)["foo"].bar', then the ElementAccessExpression will be matched first const elementOrPropertyAccessExpressionParent = findNodeUp(node, child => typescript.isElementAccessExpression(child) || typescript.isPropertyAccessExpression(child), nextNode => isStatementOrDeclaration(nextNode, typescript)); if (elementOrPropertyAccessExpressionParent != null) { // Try to evaluate the name or argument expression, depending on the kind of node let rightValue; // If it is a PropertyAccessExpression, the name will always be an identifier if (typescript.isPropertyAccessExpression(elementOrPropertyAccessExpressionParent)) { rightValue = elementOrPropertyAccessExpressionParent.name.text; } else { // Otherwise, the argument may be any kind of expression. Try to evaluate it to a string literal if possible if (typescript.isStringLiteralLike(elementOrPropertyAccessExpressionParent.argumentExpression)) { rightValue = elementOrPropertyAccessExpressionParent.argumentExpression.text; } } // The argumentExpression or name matched a string, use that as a candidate for a lookup binding if (rightValue != null) { // If the module doesn't include a named export with a name matching the right value, // we should instead import the default export if it has any (otherwise we'll use a Namespace import) and replace the CallExpression with an identifier for it if (!moduleExports.namedExports.has(rightValue)) { let identifier; // If the default export is already imported, get the local binding name for it and create an identifier for it // rather than generating a new unnecessary import if (moduleExports.hasDefaultExport && context.hasLocalForDefaultImportFromModule(moduleSpecifier)) { identifier = typescript.createIdentifier(context.getLocalForDefaultImportFromModule(moduleSpecifier)); } // If the namespace is already imported, get the local binding name for it and create an identifier for it // rather than generating a new unnecessary import else if (!moduleExports.hasDefaultExport && context.hasLocalForNamespaceImportFromModule(moduleSpecifier)) { identifier = typescript.createIdentifier(context.getLocalForNamespaceImportFromModule(moduleSpecifier)); } else { identifier = typescript.createIdentifier(context.getFreeIdentifier(generateNameFromModuleSpecifier(moduleSpecifier))); context.addImport(typescript.createImportDeclaration(undefined, undefined, moduleExports.hasDefaultExport ? // Import the default if it has any (or if we don't know if it has) typescript.createImportClause(identifier, undefined) : // Otherwise, import the entire namespace typescript.createImportClause(undefined, typescript.createNamespaceImport(identifier)), typescript.createStringLiteral(moduleSpecifier))); } // Replace the CallExpression by an ObjectLiteral that can be accessed by the wrapping Element- or PropertyAccessExpression return typescript.createObjectLiteral([ identifier.text !== rightValue ? typescript.createPropertyAssignment(rightValue, typescript.createIdentifier(identifier.text)) : typescript.createShorthandPropertyAssignment(typescript.createIdentifier(identifier.text)) ]); } // Otherwise, use the right value as the ImportSpecifier for a new import. // Depending on the placement of the CallExpression, we may or may not need to // replace it with an identifier or remove it entirely in favor of the ImportDeclaration else { // The property to import will be equal to the right value const importBindingPropertyName = rightValue; let importBindingName; // If the default export is already imported, get the local binding name for it and create an identifier for it // rather than generating a new unnecessary import if (context.hasLocalForNamedImportPropertyNameFromModule(importBindingPropertyName, moduleSpecifier)) { importBindingName = context.getLocalForNamedImportPropertyNameFromModule(importBindingPropertyName, moduleSpecifier); } // If the namespace is already imported, get the local binding name for it and create an identifier for it // rather than generating a new unnecessary import else if (!moduleExports.hasDefaultExport && context.hasLocalForNamespaceImportFromModule(moduleSpecifier)) { importBindingName = context.getLocalForNamespaceImportFromModule(moduleSpecifier); } else { // If that binding isn't free within the context, import it as another local name importBindingName = context.isIdentifierFree(importBindingPropertyName) ? importBindingPropertyName : context.getFreeIdentifier(importBindingPropertyName); context.addImport(typescript.createImportDeclaration(undefined, undefined, typescript.createImportClause(undefined, typescript.createNamedImports([ importBindingPropertyName === importBindingName ? // If the property name is free within the context, don't alias the import typescript.createImportSpecifier(undefined, typescript.createIdentifier(importBindingPropertyName)) : // Otherwise, import it aliased by another name that is free within the context typescript.createImportSpecifier(typescript.createIdentifier(importBindingPropertyName), typescript.createIdentifier(importBindingName)) ])), typescript.createStringLiteral(moduleSpecifier))); } // If the 'require(...)[<something>]' or 'require(...).<something>' expression is part of an ExpressionStatement // and isn't part of another expression such as a BinaryExpression, only preserve the import. // Otherwise leave an ObjectLiteral that can be accessed by the wrapping Element- or PropertyAccessExpression if (expressionStatementParent == null) { return typescript.createObjectLiteral([ importBindingName !== rightValue ? typescript.createPropertyAssignment(rightValue, typescript.createIdentifier(importBindingName)) : typescript.createShorthandPropertyAssignment(typescript.createIdentifier(importBindingName)) ]); } else { return undefined; } } } } // If no lookup binding candidate has been determined, it may be determined based on the parent VariableDeclaration, // if there is any. // Find the first VariableDeclaration that holds the require(...) call, if any. // For example, 'const foo = require(...)' would match the VariableDeclaration for 'foo' const variableDeclarationParent = findNodeUp(node, typescript.isVariableDeclaration, nextNode => isStatement(nextNode, typescript)); if (variableDeclarationParent != null) { // If the VariableDeclaration is simply bound to a name, it doesn't tell us anything interesting. // Simply add an import for the default export - if it has any (otherwise we'll import the entire namespace), and // replace this CallExpression by an identifier for it if (typescript.isIdentifier(variableDeclarationParent.name)) { // If the default export is already imported, get the local binding name for it and create an identifier for it // rather than generating a new unnecessary import if (moduleExports.hasDefaultExport && context.hasLocalForDefaultImportFromModule(moduleSpecifier)) { const local = context.getLocalForDefaultImportFromModule(moduleSpecifier); return typescript.createIdentifier(local); } // If the namespace is already imported, get the local binding name for it and create an identifier for it // rather than generating a new unnecessary import else if (!moduleExports.hasDefaultExport && context.hasLocalForNamespaceImportFromModule(moduleSpecifier)) { const local = context.getLocalForNamespaceImportFromModule(moduleSpecifier); return typescript.createIdentifier(local); } // Otherwise proceed as planned else { const identifier = typescript.createIdentifier(context.getFreeIdentifier(generateNameFromModuleSpecifier(moduleSpecifier))); context.addImport(typescript.createImportDeclaration(undefined, undefined, moduleExports.hasDefaultExport ? // Import the default if it has any (or if we don't know if it has) typescript.createImportClause(identifier, undefined) : // Otherwise, import the entire namespace typescript.createImportClause(undefined, typescript.createNamespaceImport(identifier)), typescript.createStringLiteral(moduleSpecifier))); return identifier; } } // If the VariableDeclaration is a BindingPattern, it may mimic destructuring specific named exports. // For example, 'const {foo, bar} = require("./bar")' could import the named export bindings 'foo' and 'bar' from the module './bar'. // However, if as much as a single one of these elements don't directly match a named export, opt out of this behavior and instead // import the default export (if it has any, otherwise import the entire namespace). else if (typescript.isObjectBindingPattern(variableDeclarationParent.name)) { const importSpecifiers = []; const skippedImportSpecifiers = []; // Check each of the BindingElements for (const element of variableDeclarationParent.name.elements) { // If the property name isn't given, the name will always be an Identifier if (element.propertyName == null && typescript.isIdentifier(element.name)) { // If the module exports contains a named export matching the identifier name, // use that as an ImportSpecifier if (moduleExports.namedExports.has(element.name.text)) { // If the property has already been imported, don't add an import, but instead push to 'skippedImportSpecifiers'.