From 4170c9dde857ec83785f0e2b2424d72a051e810d Mon Sep 17 00:00:00 2001 From: Joshua Date: Tue, 24 Jan 2023 23:38:32 -0800 Subject: [PATCH] refactor: Refactor TypeScript loader. --- src/lib/configuration.ts | 76 +++------------------------------- src/lib/typescript-loader.ts | 79 ++++++++++++++++++++++++++---------- 2 files changed, 63 insertions(+), 92 deletions(-) diff --git a/src/lib/configuration.ts b/src/lib/configuration.ts index 8270bc3..7315483 100644 --- a/src/lib/configuration.ts +++ b/src/lib/configuration.ts @@ -1,75 +1,9 @@ -import path from 'path'; - import { cosmiconfig } from 'cosmiconfig'; import merge, { } from 'deepmerge'; -import fs from 'fs-extra'; -import { packageDirectory } from 'pkg-dir'; -import resolvePkg from 'resolve-pkg'; import { SaffronCosmiconfigOptions, SaffronCosmiconfigResult } from 'etc/types'; import validators from 'etc/validators'; -import log from 'lib/log'; -// import TypeScriptLoader from 'lib/typescript-loader'; - - -/** - * @private - * - * Creates a temporary module in the nearest node_modules folder that loads - * tsconfig-paths and ts-node, executes the provided contents, and returns the - * result. The node_modules folder is used to ensure the nearest configuration - * file is loaded. - */ -async function withTsNode(cwd: string, contents: string) { - const pkgDir = await packageDirectory({ cwd: path.dirname(cwd) }); - if (!pkgDir) throw new Error('[withTsNode] Unable to compute package directory.'); - - const tsConfigPathsPath = resolvePkg('tsconfig-paths'); - const tsNodePath = resolvePkg('ts-node'); - - const wrapper = ` - require('${tsConfigPathsPath}/register'); - require('${tsNodePath}').register({ - esm: true, - transpileOnly: true - }); - ${contents} - `; - - const tempDir = path.resolve(pkgDir, 'node_modules'); - const loaderPath = path.resolve(tempDir, '.saffron-loader.js'); - await fs.ensureDir(tempDir); - await fs.writeFile(loaderPath, wrapper); - const result = await import(loaderPath); - void fs.remove(loaderPath); - return result; -} - - -/** - * Cosmiconfig custom loader that supports ESM syntax. It allows host - * applications to be written in ESM or CJS, and for the consumers of those - * applications to write configuration files as ESM or CJS. - */ -async function configurationLoader(filePath: string) { - try { - const config = await withTsNode(filePath, ` - // module.exports = import("${filePath}?nonce=1").then(result => { - // return result?.default ? result.default : result; - // }); - const config = require("${filePath}"); - module.exports = config.default ?? config; - `); - log.verbose(log.prefix('parseConfiguration'), log.chalk.green.bold('Loaded configuration using ts-node + import().')); - return config?.default ?? config; - } catch (err: any) { - log.error( - log.prefix('parseConfiguration'), - 'Failed to load configuration file with ts-node + import():', - err.message - ); - } -} +import typescriptLoader from 'lib/typescript-loader'; /** @@ -87,10 +21,10 @@ export default async function loadConfiguration(options: SaffronCosmiconfigOp loaders: { // [Aug 2022] This loader is not working at the moment. // '.ts': TypeScriptLoader, - '.ts': configurationLoader, - '.js': configurationLoader, - '.mjs': configurationLoader, - '.cjs': configurationLoader + '.ts': typescriptLoader, + '.js': typescriptLoader, + '.mjs': typescriptLoader, + '.cjs': typescriptLoader }, searchPlaces: [ 'package.json', diff --git a/src/lib/typescript-loader.ts b/src/lib/typescript-loader.ts index 38c6db2..f68e046 100644 --- a/src/lib/typescript-loader.ts +++ b/src/lib/typescript-loader.ts @@ -1,31 +1,68 @@ -import _TypeScriptLoader from '@endemolshinegroup/cosmiconfig-typescript-loader'; +import path from 'path'; + +import fs from 'fs-extra'; +import { packageDirectory } from 'pkg-dir'; +import resolvePkg from 'resolve-pkg'; + +import log from 'lib/log'; /** - * This is needed because while we transpile to ESM, we still transpile to CJS - * for testing in Jest, and certain modules will import different values based - * on these strategies, so we have to "find" the package's true default export - * at runtime in a way that works in both ESM and CJS. + * @private * - * This is essentially a replacement for Babel's _interopRequireDefault helper - * which is not used / added to transpiled code when transpiling to ESM. - * - * TODO: Move to separate package. + * Creates a temporary module in the nearest node_modules folder that loads + * ts-node and tsconfig-paths, then loads the provided configuration file, and + * returns the results. */ -export function getDefaultExport(value: T): T { - try { - let result = value; +async function withTsNode(filePath: string) { + const pkgDir = await packageDirectory({ + cwd: path.dirname(filePath) + }); - while (Reflect.has(result, 'default')) { - // @ts-expect-error - TODO: This file may likely be removed in the future. - result = Reflect.get(result, 'default'); - } + if (!pkgDir) throw new Error('[withTsNode] Unable to compute package directory.'); - return result; - } catch { - return value; - } + const tsNodePath = resolvePkg('ts-node'); + const tsConfigPathsPath = resolvePkg('tsconfig-paths'); + + const wrapper = ` + require('${tsNodePath}').register({ + compilerOptions: { + module: 'commonjs' + } + }); + + require('${tsConfigPathsPath}/register'); + + const config = require("${filePath}"); + module.exports = config.default ?? config; + `; + + const tempDir = path.resolve(pkgDir, 'node_modules', '.saffron-config'); + await fs.ensureDir(tempDir); + const loaderPath = path.resolve(tempDir, 'loader.js'); + await fs.writeFile(loaderPath, wrapper); + const result = await import(loaderPath); + void fs.remove(loaderPath); + + return result; } -export default getDefaultExport(_TypeScriptLoader); +/** + * Cosmiconfig custom loader that supports ESM syntax. It allows host + * applications to be written in ESM or CJS, and for the consumers of those + * applications to write configuration files as ESM or CJS. + */ +export default async function configurationLoader(filePath: string) { + try { + const config = await withTsNode(filePath); + log.verbose(log.prefix('parseConfiguration'), log.chalk.green.bold('Loaded configuration using ts-node + import().')); + return config?.default ?? config; + } catch (err: any) { + log.error( + log.prefix('parseConfiguration'), + 'Failed to load configuration file with ts-node + import():', + err.message + ); + } +}