Skip to content

Instantly share code, notes, and snippets.

@wolfadex
Created March 24, 2021 01:48
Show Gist options
  • Save wolfadex/5a2c2e02c7aa623621fab887cc32823d to your computer and use it in GitHub Desktop.
Save wolfadex/5a2c2e02c7aa623621fab887cc32823d to your computer and use it in GitHub Desktop.
Experimental Elm node loader
import { URL, pathToFileURL } from "url";
import fs from "fs";
import spawn from "cross-spawn";
import { track } from "temp";
import _ from "lodash";
const temp = track();
const baseURL = pathToFileURL(`${process.cwd()}/`).href;
// Elm files end in .elm.
const extensionsRegex = /\.elm$/;
export function resolve(specifier, context, defaultResolve) {
const { parentURL = baseURL } = context;
// Node.js normally errors on unknown file extensions, so return a URL for
// specifiers ending in the Elm file extensions.
if (extensionsRegex.test(specifier)) {
return {
url: new URL(specifier, parentURL).href,
};
}
// Let Node.js handle all other specifiers.
return defaultResolve(specifier, context, defaultResolve);
}
export function getFormat(url, context, defaultGetFormat) {
// Now that we patched resolve to let Elm URLs through, we need to
// tell Node.js what format such URLs should be interpreted as. For the
// purposes of this loader, all Elm URLs are ES modules.
if (extensionsRegex.test(url)) {
return {
format: "module",
};
}
// Let Node.js handle all other URLs.
return defaultGetFormat(url, context, defaultGetFormat);
}
export function transformSource(source, context, defaultTransformSource) {
const { url, format } = context;
if (extensionsRegex.test(url)) {
return {
source: compileToStringSync(url.replace("file://", "")),
};
}
// Let Node.js handle all other sources.
return defaultTransformSource(source, context, defaultTransformSource);
}
// Copied from https://github.com/rtfeldman/node-elm-compiler
function compileToStringSync(sources, options = {}) {
const file = temp.openSync({ suffix: ".js" });
options.output = file.path;
compileSync(sources, options);
const initialOutput = fs.readFileSync(file.path, { encoding: "utf8" });
const deIIFE = initialOutput
.replace("(function(scope){", "function init(scope){")
.replace(";}(this));", ";}");
const result = `${deIIFE}
const moduleScope = {};
init(moduleScope);
export default moduleScope.Elm;`;
return result;
}
const elmBinaryName = "elm";
function compileSync(sources, options) {
var optionsWithDefaults = prepareOptions(
options,
options.spawn || spawn.sync
);
var pathToElm = options.pathToElm || elmBinaryName;
try {
return runCompiler(sources, optionsWithDefaults, pathToElm);
} catch (err) {
throw compilerErrorToString(err, pathToElm);
}
}
function compilerErrorToString(err, pathToElm) {
if (typeof err === "object" && typeof err.code === "string") {
switch (err.code) {
case "ENOENT":
return (
'Could not find Elm compiler "' + pathToElm + '". Is it installed?'
);
case "EACCES":
return (
'Elm compiler "' +
pathToElm +
'" did not have permission to run. Do you need to give it executable permissions?'
);
default:
return (
'Error attempting to run Elm compiler "' + pathToElm + '":\n' + err
);
}
} else if (typeof err === "object" && typeof err.message === "string") {
return JSON.stringify(err.message);
} else {
return (
"Exception thrown when attempting to run Elm compiler " +
JSON.stringify(pathToElm)
);
}
}
const defaultOptions = {
spawn: spawn,
cwd: undefined,
pathToElm: undefined,
help: undefined,
output: undefined,
report: undefined,
debug: undefined,
verbose: false,
processOpts: undefined,
docs: undefined,
optimize: undefined,
};
function prepareOptions(options, spawnFn) {
return _.defaults({ spawn: spawnFn }, options, defaultOptions);
}
function runCompiler(sources, options, pathToElm) {
if (typeof options.spawn !== "function") {
throw (
"options.spawn was a(n) " +
typeof options.spawn +
" instead of a function."
);
}
var processArgs = prepareProcessArgs(sources, options);
var processOpts = prepareProcessOpts(options);
if (options.verbose) {
console.log(["Running", pathToElm].concat(processArgs).join(" "));
}
return options.spawn(pathToElm, processArgs, processOpts);
}
function prepareProcessOpts(options) {
var env = _.merge({ LANG: "en_US.UTF-8" }, process.env);
return _.merge(
{ env: env, stdio: "inherit", cwd: options.cwd },
options.processOpts
);
}
function prepareProcessArgs(sources, options) {
var preparedSources = prepareSources(sources);
var compilerArgs = compilerArgsFromOptions(options);
return ["make"].concat(
preparedSources ? preparedSources.concat(compilerArgs) : compilerArgs
);
}
function prepareSources(sources) {
if (!(sources instanceof Array || typeof sources === "string")) {
throw "compile() received neither an Array nor a String for its sources argument.";
}
return typeof sources === "string" ? [sources] : sources;
}
const supportedOptions = _.keys(defaultOptions);
// Converts an object of key/value pairs to an array of arguments suitable
// to be passed to child_process.spawn for elm-make.
function compilerArgsFromOptions(options) {
return _.flatten(
_.map(options, function (value, opt) {
if (value) {
switch (opt) {
case "help":
return ["--help"];
case "output":
return ["--output", value];
case "report":
return ["--report", value];
case "debug":
return ["--debug"];
case "docs":
return ["--docs", value];
case "optimize":
return ["--optimize"];
case "runtimeOptions":
return [].concat(["+RTS"], value, ["-RTS"]);
default:
if (supportedOptions.indexOf(opt) === -1) {
if (opt === "yes") {
throw new Error(
"node-elm-compiler received the `yes` option, but that was removed in Elm 0.19. Try re-running without passing the `yes` option."
);
} else if (opt === "warn") {
throw new Error(
"node-elm-compiler received the `warn` option, but that was removed in Elm 0.19. Try re-running without passing the `warn` option."
);
} else if (opt === "pathToMake") {
throw new Error(
"node-elm-compiler received the `pathToMake` option, but that was renamed to `pathToElm` in Elm 0.19. Try re-running after renaming the parameter to `pathToElm`."
);
} else {
throw new Error(
"node-elm-compiler was given an unrecognized Elm compiler option: " +
opt
);
}
}
return [];
}
} else {
return [];
}
})
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment