@plugjs/tsrun
Version:
A simple TypeScript runner --------------------------
8 lines (7 loc) • 17.7 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../src/loader-commonjs.cts", "../src/loader-shared.ts"],
"sourcesContent": ["// NodeJS dependencies\nimport _module from 'node:module'\nimport _path from 'node:path'\n\nimport {\n CJS,\n ESM,\n esbTranpile,\n logMessage,\n moduleType,\n throwError,\n} from './loader-shared'\n\n/* ========================================================================== *\n * CJS VERSION *\n * ========================================================================== */\n\n/** The extension handler type, loading CJS modules */\ntype ExtensionHandler = (module: NodeJS.Module, filename: string) => void\n\n/* Add the `_compile(...)` method to NodeJS' `Module` interface */\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace NodeJS {\n interface Module {\n _compile: (contents: string, filename: string) => void\n }\n }\n}\n\n/**\n * Add the `_extensions[...]` and `resolveFilename(...)` members to the\n * definition of `node:module`.\n */\ndeclare module 'node:module' {\n const _extensions: Record<`.${string}`, ExtensionHandler>\n function _resolveFilename(\n request: string,\n parent: _module | undefined,\n isMain: boolean,\n options?: any,\n ): string\n}\n\n/* ========================================================================== */\n\nconst _type = moduleType(CJS)\n\nconst loader: ExtensionHandler = (module, filename): void => {\n logMessage(CJS, `Attempting to load \"${filename}\"`)\n\n /* Figure our the extension (\".ts\" or \".cts\")... */\n const ext = _path.extname(filename)\n\n /* If the file is a \".ts\", we need to figure out the default type */\n if (ext === '.ts') {\n /* If the _default_ module type is CJS then load as such! */\n if (_type === ESM) {\n throwError(CJS, `Must use import to load ES Module: ${filename}`, { code: 'ERR_REQUIRE_ESM' })\n }\n } else if (ext !== '.cts') {\n throwError(CJS, `Unsupported filename \"${filename}\"`)\n }\n\n const source = esbTranpile(filename, CJS)\n\n /* Let node do its thing, but wrap any error it throws */\n try {\n module._compile(source, filename)\n } catch (cause) {\n console.error(`Error compiling module \"${filename}\"`, cause)\n }\n}\n\n/**\n * Replace _module._resolveFilename with our own.\n *\n * This is a _HACK BEYOND REDEMPTION_ and I'm ashamed of even _thinking_ about\n * it, but, well, it makes things work.\n *\n * TypeScript allows us to import \"./foo.js\", and internally resolves this to\n * \"./foo.ts\" (yeah, nice, right?) and while we normally wouldn't want to deal\n * with this kind of stuff, the \"node16\" module resolution mode _forces_ us to\n * use this syntax.\n *\n * And we _need_ the \"node16\" module resolution to properly consume \"export\n * conditions\" from other packages. Since ESBuild's plugins only work in async\n * mode, changing those import statements on the fly is out of the question, so\n * we need to hack our way into Node's own resolver.\n *\n * See my post: https://twitter.com/ianosh/status/1559484168685379590\n * ESBuild related fix: https://github.com/evanw/esbuild/commit/0cdc005e3d1c765a084f206741bc4bff78e30ec4\n */\nconst _oldResolveFilename = _module._resolveFilename\n_module._resolveFilename = function(\n request: string,\n parent: _module | undefined,\n ...args: [ isMain: boolean, options: any ]\n): any {\n try {\n /* First call the old _resolveFilename to see what Node thinks */\n return _oldResolveFilename.call(this, request, parent, ...args)\n } catch (error: any) {\n /* If the error was anything but \"MODULE_NOT_FOUND\" bail out */\n if (error.code !== 'MODULE_NOT_FOUND') throw error\n\n /* Check if the \"request\" ends with \".js\", \".mjs\" or \".cjs\" */\n const match = request.match(/(.*)(\\.[mc]?js$)/)\n\n /*\n * If the file matches our extension, _and_ we have a parent, we simply\n * try with a new extension (e.g. \".js\" becomes \".ts\")...\n */\n if (parent && match) {\n const [ , name, ext ] = match\n const tsrequest = name + ext!.replace('js', 'ts')\n try {\n const result = _oldResolveFilename.call(this, tsrequest, parent, ...args)\n logMessage(CJS, `Resolution for \"${request}\" intercepted as \"${tsrequest}`)\n return result\n } catch {\n throw error // throw the _original_ error in this case\n }\n }\n\n /* We have no parent, or we don't match our extension, throw! */\n throw error\n }\n}\n\n/* ========================================================================== */\n\n/* Remember to load our loader for .TS/.CTS as CommonJS modules */\n_module._extensions['.ts'] = _module._extensions['.cts'] = loader\nlogMessage(CJS, 'TypeScript loader for CommonJS loaded')\n", "/* eslint-disable @typescript-eslint/no-unsafe-function-type */\n/* ========================================================================== *\n * HACK BEYOND REDEMPTION: TRANSPILE .ts FILES (the esm loader) *\n * -------------------------------------------------------------------------- *\n * Use ESBuild to quickly transpile TypeScript files into JavaScript. *\n * *\n * The plan as it stands is as follows: *\n * - `.mts` files always get transpiled to ESM modules *\n * - `.cts` files always get transpiled to CJS modules *\n * - `.ts` files are treanspiled according to what's in `package.json` *\n * *\n * Additionally, when transpiling to ESM modules, we can't rely on the magic *\n * that Node's `require(...)` call uses to figure out which file to import. *\n * We need to _actually verify_ on disk what's the correct file to import. *\n * *\n * This is a single module, only available as ESM, and it will _both_ behave *\n * as a NodeJS' loader, _and_ inject the CJS extension handlers (hack) found *\n * in the `_extensions` of `node:module` (same as `require.extensions`). *\n * ========================================================================== */\n\n// NodeJS dependencies\nimport _fs from 'node:fs'\nimport _path from 'node:path'\nimport _util from 'node:util'\n\n// ESBuild is the only external dependency\nimport _esbuild from 'esbuild'\n\n/* ========================================================================== *\n * CONSTANTS AND TYPES *\n * ========================================================================== */\n\n/** Supported types from `package.json` */\nexport type Type = 'commonjs' | 'module'\n/** Constant identifying a `commonjs` module */\nexport const CJS = 'commonjs' as const\n/** Constant identifying an ESM `module` */\nexport const ESM = 'module' as const\n\n/* ========================================================================== *\n * DEBUGGING AND ERRORS *\n * ========================================================================== */\n\n/** Setup debugging */\nconst _debugLog = _util.debuglog('plug:ts-loader')\nconst _debug = _debugLog.enabled\n\n/** Emit some logs if `DEBUG_TS_LOADER` is set to `true` */\nexport function logMessage(mode: Type, arg: string, ...args: any []): void {\n if (! _debug) return\n\n const t = mode === ESM ? 'esm' : mode === CJS ? 'cjs' : '---'\n _debugLog(`[${t}] ${arg}`, ...args)\n}\n\n/** Fail miserably */\nexport function throwError(\n mode: Type,\n message: string,\n options: { start?: Function, code?: string, cause?: any } = {},\n): never {\n const t = mode === ESM ? 'esm' : mode === CJS ? 'cjs' : '---'\n const prefix = `[ts-loader|${t}|pid=${process.pid}]`\n\n const { start = throwError, ...extra } = options\n const error = new Error(`${prefix} ${message}`)\n Error.captureStackTrace(error, start)\n Object.assign(error, extra)\n\n throw error\n}\n\n/* ========================================================================== *\n * MODULE TYPES AND FORCING TYPE *\n * ========================================================================== */\n\n/**\n * Determine the current module type to transpile .TS files as looking at the\n * `__TS_LOADER_FORCE_TYPE` environment variable (used by PlugJS and CLI) or,\n * if unspecified, looking at the `type` field in the `package.json` file.\n */\nexport function moduleType(mode: Type): Type {\n if (process.env.__TS_LOADER_FORCE_TYPE) {\n const type = process.env.__TS_LOADER_FORCE_TYPE\n if ((type === CJS) || (type === ESM)) {\n logMessage(mode, `Forcing type to \"${type}\" from environment`)\n return type\n } else {\n throwError(mode, `Invalid type \"${process.env.__TS_LOADER_FORCE_TYPE}\"`)\n }\n }\n\n const _findType = (directory: string): Type => {\n const packageFile = _path.join(directory, 'package.json')\n try {\n const packageData = _fs.readFileSync(packageFile, 'utf-8')\n const packageJson = JSON.parse(packageData)\n const packageType = packageJson.type\n switch (packageType) {\n case undefined:\n logMessage(mode, `File \"${packageFile}\" does not declare a default type`)\n return CJS\n\n case CJS:\n case ESM:\n logMessage(mode, `File \"${packageFile}\" declares type as \"${CJS}\"`)\n return packageType\n\n default:\n logMessage(mode, `File \"${packageFile}\" specifies unknown type \"${packageType}\"`)\n return CJS\n }\n } catch (cause: any) {\n if ((cause.code !== 'ENOENT') && (cause.code !== 'EISDIR')) {\n throwError(mode, `Unable to read or parse \"${packageFile}\"`, { cause, start: _findType })\n }\n }\n\n const parent = _path.dirname(directory)\n if (directory !== parent) return _findType(directory)\n\n logMessage(mode, `Type defaulted to \"${CJS}\"`)\n return CJS\n }\n\n return _findType(process.cwd())\n}\n\n/* ========================================================================== *\n * ESBUILD HELPERS *\n * ========================================================================== */\n\n/**\n * Take an ESBuild `BuildResult` or `BuildFailure` (they both have arrays\n * of `Message` in both `warnings` and `errors`), format them and print them\n * out nicely. Then fail if any error was detected.\n */\nfunction _esbReport(\n kind: 'error' | 'warning',\n messages: _esbuild.Message[] = [],\n): void {\n const output = process.stderr\n const options = { color: !!output.isTTY, terminalWidth: output.columns || 80 }\n\n const array = _esbuild.formatMessagesSync(messages, { kind, ...options })\n array.forEach((message) => output.write(`${message}\\n`))\n}\n\n/**\n * Transpile with ESBuild\n */\nexport function esbTranpile(filename: string, type: Type): string {\n logMessage(type, `Transpiling \"${filename}\" as \"${type}\"`)\n\n const [ format, __fileurl ] = type === ESM ?\n [ 'esm', 'import.meta.url' ] as const :\n [ 'cjs', '__filename' ] as const\n\n /* ESbuild options */\n const options: _esbuild.TransformOptions = {\n sourcefile: filename, // the original filename we're parsing\n format, // what are we actually transpiling to???\n loader: 'ts', // the format is always \"typescript\"\n sourcemap: 'inline', // always inline source maps\n sourcesContent: false, // do not include sources content in sourcemap\n platform: 'node', // d'oh! :-)\n minifyWhitespace: true, // https://github.com/evanw/esbuild/releases/tag/v0.16.14\n logLevel: 'silent', // catching those in our _esbReport below\n target: `node${process.versions['node']}`, // target _this_ version\n define: { __fileurl }, // from \"globals.d.ts\"\n }\n\n /* Emit a line on the console when loading in debug mode */\n if (_debug) {\n if (format === 'esm') {\n options.banner = `;(await import('node:util')).debuglog('plug:ts-loader')('[esm] Loaded \"%s\"', ${__fileurl});`\n } else if (format === 'cjs') {\n options.banner = `;require('node:util').debuglog('plug:ts-loader')('[cjs] Loaded \"%s\"', ${__fileurl});`\n }\n }\n\n /* Transpile our TypeScript file into some JavaScript stuff */\n let result\n try {\n const source = _fs.readFileSync(filename, 'utf-8')\n result = _esbuild.transformSync(source, options)\n } catch (cause: any) {\n _esbReport('error', (cause as _esbuild.TransformFailure).errors)\n _esbReport('warning', (cause as _esbuild.TransformFailure).warnings)\n throwError(type, `ESBuild error transpiling \"${filename}\"`, { cause, start: esbTranpile })\n }\n\n /* Log transpile warnings if debugging */\n if (_debug) _esbReport('warning', result.warnings)\n\n /* Done! */\n return result.code\n}\n\n\n/* ========================================================================== *\n * UTILITIES *\n * ========================================================================== */\n\n/* Returns a boolean indicating whether the specified file exists or not */\nexport function isFile(path: string): boolean {\n try {\n return _fs.statSync(path).isFile()\n } catch {\n return false\n }\n}\n\n/* Returns a boolean indicating whether the specified directory exists or not */\nexport function isDirectory(path: string): boolean {\n try {\n return _fs.statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n"],
"mappings": "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",
"names": ["import_node_path", "_util", "_path", "_fs", "_esbuild", "module", "_path", "_module"]
}