Skip to content

Commit

Permalink
fix: Restore @babel/register strategy.
Browse files Browse the repository at this point in the history
  • Loading branch information
darkobits committed Jul 9, 2023
1 parent a6e4a41 commit 858a8a6
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 15 deletions.
18 changes: 17 additions & 1 deletion src/lib/configuration/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import merge from 'deepmerge';

import validators from 'etc/validators';
import { babelStrategy } from 'lib/configuration/strategies/babel';
import { babelRegisterStrategy } from 'lib/configuration/strategies/babel-register';
import { esbuildStrategy } from 'lib/configuration/strategies/esbuild';
import log from 'lib/log';
import { getPackageInfo } from 'lib/package';
Expand Down Expand Up @@ -76,7 +77,7 @@ async function ecmaScriptLoader(filePath: string /* , content: string */) {


/**
* Strategy 3: Babel
* Strategy 3: babel
*/
try {
const result = await babelStrategy(filePath, pkgInfo);
Expand All @@ -87,6 +88,21 @@ async function ecmaScriptLoader(filePath: string /* , content: string */) {
}


/**
* Strategy 3: babel/register
*
* This strategy will (likely) only need to be used when a configuration file
* relies on other files that also need to be transpiled.
*/
try {
const result = await babelRegisterStrategy(filePath, pkgInfo);
log.verbose(prefix, 'Used strategy:', log.chalk.bold('babel/register'));
return getDefaultExport(result);
} catch (err: any) {
errors.push(new Error(`${prefix} Failed to load file with ${log.chalk.bold('babel/register')}: ${err}`));
}


if (errors.length > 0) throw new AggregateError(errors, 'All parsing strategies failed.');
}

Expand Down
125 changes: 125 additions & 0 deletions src/lib/configuration/strategies/babel-register.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import path from 'path';

import fs from 'fs-extra';
import resolvePkg from 'resolve-pkg';
import * as tsConfck from 'tsconfck';

import log from 'lib/log';

import type { PackageInfo } from 'lib/package';


/**
* Creates a temporary module in the nearest node_modules folder that loads
* @babel/register, then loads the provided configuration file, and returns the
* results.
*/
export async function babelRegisterStrategy(filePath: string, pkgInfo: PackageInfo) {
const prefix = log.prefix('strategy:babel-register');

try {
if (!pkgInfo.root) throw new Error('Unable to determine host package root directory.');

const babelRegisterPath = resolvePkg('@babel/register', { cwd: pkgInfo.root });
if (!babelRegisterPath) throw new Error('Unable to resolve path to @babel/register.');

const babelPresetEnvPath = resolvePkg('@babel/preset-env', { cwd: pkgInfo.root });
if (!babelPresetEnvPath) throw new Error('Unable to resolve path to @babel/preset-env.');

const babelPresetTypeScriptPath = resolvePkg('@babel/preset-typescript', { cwd: pkgInfo.root });
if (!babelPresetTypeScriptPath) throw new Error('Unable to resolve path to @babel/preset-typescript.');

const babelPluginModuleResolverTsConfigPath = resolvePkg('babel-plugin-module-resolver-tsconfig', { cwd: pkgInfo.root });
if (!babelPluginModuleResolverTsConfigPath) throw new Error('Unable to resolve path to babel-plugin-module-resolver-tsconfig.');

const tsConfigFilePath = await tsConfck.find(filePath);

const wrapperWithTsConfig = `
const { setModuleResolverPluginForTsConfig } = require('${babelPluginModuleResolverTsConfigPath}');
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.cts', '.cjs', '.mjs', '.mts'];
require('${babelRegisterPath}')({
extensions,
// Treat files that contain import statements as modules and require statements as CommonJS.
sourceType: 'unambiguous',
// Let's Babel transpile files that may be above process.cwd(), which are ignored when using the
// default settings.
// See: https://github.com/babel/babel/issues/8321
ignore: [/node_modules/],
// Apply the minimum amount of transforms required to make code compatible with the local Node
// installation.
targets: { node: 'current' },
presets: [
['${babelPresetEnvPath}', {
// Tell Babel to not transpile dynamic import statements into require() calls as this is the
// mechanism by which CommonJS can import ESM.
exclude: ['proposal-dynamic-import']
}],
'${babelPresetTypeScriptPath}'
],
plugins: [
// If the project has set up path mappings using tsconfig.json, this plugin will allow those
// path specifiers to work as expected.
setModuleResolverPluginForTsConfig({
tsconfigPath: '${tsConfigFilePath}',
extensions
})
]
});
const configExport = require('${filePath}');
module.exports = configExport.default ?? configExport;
`;

const wrapperWithoutTsConfig = `
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.cts', '.cjs', '.mjs', '.mts'];
require('${babelRegisterPath}')({
extensions,
// Treat files that contain import statements as modules and require statements as CommonJS.
sourceType: 'unambiguous',
// Let's Babel transpile files that may be above process.cwd(), which are ignored when using the
// default settings.
// See: https://github.com/babel/babel/issues/8321
ignore: [/node_modules/],
// Apply the minimum amount of transforms required to make code compatible with the local Node
// installation.
targets: { node: 'current' },
presets: [
['${babelPresetEnvPath}', {
// Tell Babel to not transpile dynamic import statements into require() calls as this is the
// mechanism by which CommonJS can import ESM.
exclude: ['proposal-dynamic-import']
}]
]
});
const configExport = require('${filePath}');
module.exports = configExport.default ?? configExport;
`;

if (tsConfigFilePath) {
log.silly(prefix, `Loaded tsconfig.json from: ${log.chalk.green(tsConfigFilePath)}`);
}

const tempDir = path.resolve(pkgInfo.root, 'node_modules', '.saffron-config');
await fs.ensureDir(tempDir);
const loaderPath = path.resolve(tempDir, 'loader.cjs');
await fs.writeFile(
loaderPath,
tsConfigFilePath
? wrapperWithTsConfig
: wrapperWithoutTsConfig
);
const result = await import(loaderPath);
await fs.remove(tempDir);

return result.default ?? result;
} catch (err: any) {
throw new Error(
`${log.chalk.red(`[${prefix}] Failed to import() configuration file ${filePath}:`)} ${err.message}`,
{ cause: err }
);
}
}
29 changes: 15 additions & 14 deletions src/lib/configuration/strategies/babel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,12 @@ const EXT_MAP: Record<string, string> = {
};


const extensions = [
'.ts',
'.tsx',
'.js',
'.jsx',
'.cts',
'.cjs',
'.mjs',
'.mts'
];


/**
* WIP
* Uses Babel to transpile the file at `filePath` by creating a temporary
* file in the same directory, then attempts to dynamically import it. An
* output format and extension are chosen based on the host project's
* "type" setting that are the least likely to produce errors. Once imported,
* the temporary file is removed.
*/
export async function babelStrategy(filePath: string, pkgInfo: PackageInfo) {
const prefix = log.prefix('strategy:babel');
Expand Down Expand Up @@ -100,7 +92,16 @@ export async function babelStrategy(filePath: string, pkgInfo: PackageInfo) {
log.verbose(prefix, `Using TypeScript configuration: ${log.chalk.green(tsConfigFilePath)}`);
transformOptions.plugins?.push(setModuleResolverPluginForTsConfig({
tsconfigPath: tsConfigFilePath,
extensions
extensions: [
'.ts',
'.tsx',
'.js',
'.jsx',
'.cts',
'.cjs',
'.mjs',
'.mts'
]
}));
}

Expand Down

0 comments on commit 858a8a6

Please sign in to comment.