Skip to content

Instantly share code, notes, and snippets.

@Jessidhia
Created November 4, 2017 07:54
Show Gist options
  • Save Jessidhia/006ed7ad68e249677fe25662e87f7f5b to your computer and use it in GitHub Desktop.
Save Jessidhia/006ed7ad68e249677fe25662e87f7f5b to your computer and use it in GitHub Desktop.
Hack for testing the ESM hook
module.exports = {
presets: ['@babel/react']
}
export { resolve } from './loaderImpl.mjs'
import register from './loaderImpl.mjs'
register({ extensions: ['.jsx'] })
// formatting may be crazy as I didn't use linters
import deepClone from "lodash/cloneDeep";
import escapeRegExp from "lodash/escapeRegExp";
import babylon from 'babylon'
import babel from "@babel/core";
import fs from "fs";
import path from "path";
import util from 'util';
import url from 'url'
const { OptionManager, DEFAULT_EXTENSIONS } = babel;
const { URL } = url
const asyncReadFile = util.promisify(fs.readFile)
const asyncWriteFile = util.promisify(fs.writeFile)
const asyncStat = util.promisify(fs.stat)
const transformOpts = {};
const extensions = new Set()
const FILE_SUFFIX = `.babel.${babel.version}.${babel.getEnv()}.mjs`;
/**
* Write stringified cache to disk.
*
* @param {string} originalURL
* @param {string} code
* @returns {Promise<string>} url of cached file
*/
async function writeCache(originalURL, code) {
const parsed = new URL(originalURL)
const basename = path.basename(parsed.pathname)
// the cached result needs to be colocated in order to preserve relative imports 😿
// possible TODO: traverse Imports and rewrite relative imports
const destination = new URL(`./.${basename}${FILE_SUFFIX}`, parsed)
const { pathname } = destination
await asyncWriteFile(pathname, code)
return destination.toString()
}
function compile(code, filename) {
// merge in base options and resolve all the plugins and presets relative to this file
const opts = new OptionManager().init({
sourceRoot: path.dirname(filename), // sourceRoot can be overwritten
...deepClone(transformOpts),
filename
});
// Bail out ASAP if the file has been ignored.
if (opts === null) return null;
// const env = babel.getEnv(false);
const result = babel.transform(
code, {
...opts
// Do not process config files since has already been done with the OptionManager
// calls above and would introduce duplicates.
babelrc: false,
sourceMaps: "both",
ast: false,
sourceType: "module"
}
);
return result.code;
}
export default function register(opts = {}) {
// Clone to avoid mutating the arguments object with the 'delete's below.
opts = Object.assign({}, opts);
if (opts.extensions) {
for (const ext of opts.extensions) {
extensions.add(ext);
}
}
delete opts.extensions;
Object.assign(transformOpts, opts);
if (!transformOpts.ignore && !transformOpts.only) {
transformOpts.only = [
// Only compile things inside the current working directory.
new RegExp("^" + escapeRegExp(process.cwd()), "i"),
];
transformOpts.ignore = [
// Ignore any node_modules inside the current working directory.
new RegExp(
"^" +
escapeRegExp(process.cwd()) +
"(?:" +
path.sep +
".*)?" +
escapeRegExp(path.sep + "node_modules" + path.sep),
"i",
),
];
}
}
/**
*
* @param {string} specifier
* @param {string} parentURL
* @param {function(string, string): Promise<{ url: string, format: string }>} defaultResolve
* @returns {Promise<{ url: string, format: string }>}
*/
export async function resolve (specifier, parentURL, defaultResolve) {
const resolved = new URL(specifier, parentURL)
if (resolved.protocol !== 'file:') {
return resolved
}
let { pathname } = resolved
try {
await asyncStat(pathname)
} catch (e) {
pathname = new URL((await defaultResolve(specifier, parentURL)).url).pathname
}
if (extensions.has(path.extname(pathname))) {
const code = compile(await asyncReadFile(pathname), pathname)
if (code) {
return { url: await writeCache(resolved, code), format: 'esm' }
}
}
return defaultResolve(specifier, parentURL)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment