Skip to content

Commit

Permalink
refactor: Refactor TypeScript loader.
Browse files Browse the repository at this point in the history
  • Loading branch information
darkobits committed Jan 25, 2023
1 parent 2b897c3 commit 4170c9d
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 92 deletions.
76 changes: 5 additions & 71 deletions src/lib/configuration.ts
Original file line number Diff line number Diff line change
@@ -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';


/**
Expand All @@ -87,10 +21,10 @@ export default async function loadConfiguration<C>(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',
Expand Down
79 changes: 58 additions & 21 deletions src/lib/typescript-loader.ts
Original file line number Diff line number Diff line change
@@ -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<T extends object>(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
);
}
}

0 comments on commit 4170c9d

Please sign in to comment.