Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 102 additions & 57 deletions lib/node-loader-babel.js
Original file line number Diff line number Diff line change
@@ -1,57 +1,102 @@
import babel from "@babel/core";
import path from "path";
import urlModule from "url";

const { loadOptionsAsync, transformAsync } = babel;

function isBabelConfigFile(filename) {
const basename = path.basename(filename);
return (
basename === ".babelrc.js" ||
basename === ".babelrc.mjs" ||
basename === "babel.config.js" ||
basename === "babel.config.mjs" ||
basename === ".babelrc" ||
basename === ".babelrc.cjs" ||
basename === "babel.config.cjs"
);
}

export async function load(url, context, defaultLoad) {
if (useLoader(url)) {
const { source } = await defaultLoad(url, context, defaultLoad);

const filename = urlModule.fileURLToPath(url);
// Babel config files can themselves be ES modules,
// but we cannot transform those since doing so would cause an infinite loop.
if (isBabelConfigFile(filename)) {
return {
source,
format: /\.(c|m)?js$/.test(filename) ? "module" : "json",
};
}

const options = await loadOptionsAsync({
sourceType: "module",
root: process.cwd(),
rootMode: "root",
filename: filename,
configFile: true,
});

const transformed = await transformAsync(source, options);

return {
source: transformed.code,
// Maybe a shaky assumption
// TODO: look at babel config to see whether it will output ESM/CJS or other formats
format: "module",
};
} else {
return defaultLoad(url, context, defaultLoad);
}
}

function useLoader(url) {
return !/node_modules/.test(url) && !/node:/.test(url);
}
import process from 'process';
import path from 'path';
import {fileURLToPath} from 'url';
import {loadOptionsAsync, transformAsync} from '@babel/core';

const BABEL_FORMATS_TRANSFORMED = new Set(['module']);

const BABEL_CONFIG_FILES = new Set([
'.babelrc.js',
'.babelrc.mjs',
'babel.config.js',
'babel.config.mjs',
'.babelrc',
'.babelrc.cjs',
'babel.config.cjs',
]);

const anyURLToPathOrUndefined = (url) => {
try {
return fileURLToPath(url);
} catch (error) {
if (error instanceof TypeError && error.code === 'ERR_INVALID_URL_SCHEME') {
return undefined;
}

throw error;
}
};

const isBabelConfigFile = (filename) => {
const basename = path.basename(filename);
return BABEL_CONFIG_FILES.has(basename);
};

const getSourceType = (format) => {
switch (format) {
case 'module':
return 'module';
case 'commonjs':
return 'script';
default:
return 'unambiguous';
}
};

const prepare = async (url, context, defaultLoad) => {
const original = await defaultLoad(url, context, defaultLoad);

const noop = () => ({
transform: false,
original,
});

if (
/node_modules/.test(url) ||
/node:/.test(url) ||
!BABEL_FORMATS_TRANSFORMED.has(original.format)
) {
return noop();
}

const filename = anyURLToPathOrUndefined(url);

// Babel config files can themselves be ES modules,
// but transforming those could require more than one pass.
if (isBabelConfigFile(filename)) return noop();

return {
transform: true,
original,
options: {
filename,
},
};
};

const transformed = async ({format, source}, {filename}) => {
const options = await loadOptionsAsync({
sourceType: getSourceType(format),
root: process.cwd(),
rootMode: 'root',
filename,
configFile: true,
});

const result = await transformAsync(source, options);

return {
source: result.code,
// TODO: look at babel config to see whether it will output ESM/CJS or other formats
format,
};
};

export const load = async (url, context, defaultLoad) => {
const {transform, original, options} = await prepare(
url,
context,
defaultLoad,
);
return transform ? transformed(original, options) : original;
};