I'm working on the plugin at https://github.com/ccapndave/fuse-box-elm-plugin/blob/master/src/index.ts
Let's assume that the entry point to my application is app.ts
.
fuse.bundle("app")
.watch()
.instructions("> js/app.ts")
.hmr();
import * as Elm from "../elm/src/Main.elm"
import Model exposing (Model)
import Update exposing (update, subscriptions, Msg(..))
import View exposing (view)
So elm-make
will compile Model.elm
, Update.elm
and View.elm
into its output automatically. I can get a list of the absolute
paths of all dependencies using a find-elm-dependencies
npm module.
The goal is to make HMR work, so that if I edit View.elm
, elm-make
will run on Main.elm
, and then inject the code into the browser.
Here is my (failed) attempt. This does actually make HMR work (I think), but it runs elm-make
for Main.elm
, Model.elm
, Update.elm
and View.elm
instead of just running it for Main.elm
. I sort of copied the relevant parts from BabelPlugin.ts without properly
understanding them.
//import { File, WorkFlowContext, Plugin } from "fuse-box";
import { File, WorkFlowContext, Plugin } from "../../fuse-box/.dev";
import { ChildProcess } from "child_process";
import { resolve } from "path";
import { readFile } from "fs";
import { tmpName } from "tmp";
import * as spawn from "cross-spawn";
import { findAllDependencies } from "find-elm-dependencies"
const tmp = () =>
new Promise((resolve, reject) =>
tmpName((err, path) => (err ? reject(err) : resolve(path)))
);
export interface ElmPluginOptions {
warn?: boolean;
debug?: boolean;
}
export class ElmPluginClass implements Plugin {
// Match Elm files
public test: RegExp = /\.elm$/;
public context: WorkFlowContext;
public options: ElmPluginOptions;
constructor(options: ElmPluginOptions = {}) {
this.options = { ...options };
}
public init(context: WorkFlowContext): void {
this.context = context;
context.allowExtension(".elm");
}
public getElmMakePath(): string {
try {
return resolve("node_modules/.bin/elm-make");
} catch (_) {}
return "elm-make";
}
public async transform(file: File): Promise<any> {
if (this.context.useCache) {
if (file.loadFromCache()) {
return;
}
}
file.loadContents();
// Get the path to elm-make
const elmMakePath: string = this.getElmMakePath();
// Create temporary JS file
const tmpFilename: string = `${await tmp()}.js`;
return new Promise((resolve, reject) => {
// Construct the arguments for elm-make
const args = [
"--yes",
"--output",
tmpFilename,
this.options.warn ? "--warn" : null,
this.options.debug ? "--debug" : null,
file.absPath
].filter(x => x !== null);
const proc: ChildProcess = spawn(elmMakePath, args, { stdio: "inherit" });
proc.on("error", (err: NodeJS.ErrnoException) => {
if (err.code === "ENOENT") {
reject(
`Could not find Elm compiler @ "${elmMakePath}"
\nHave you installed elm yet? If not, please install "elm" via npm`
);
} else if (err.code === "EACCES") {
reject(
`Elm compiler @ "${elmMakePath}" did not have permission to run
\nYou may need give it executable permissions`
);
} else {
reject(
`Error attempting to run Elm compiler @ "${elmMakePath}" \n ${err}`
);
}
});
proc.on("close", (code: Number) => {
if (code === 0) {
readFile(tmpFilename, (err: NodeJS.ErrnoException, data: Buffer) => {
if (err) {
reject(err);
} else {
file.contents = data.toString();
findAllDependencies(file.absPath)
.then((paths: string[]) => {
file.analysis.dependencies = paths;
if (this.context.useCache) {
this.context.emitJavascriptHotReload(file);
this.context.cache.writeStaticCache(file, file.sourceMap);
}
resolve(file);
})
.catch((err: string) => {
reject(err);
});
resolve(file);
}
});
} else {
reject("Failed to compile Elm.");
}
});
});
}
}
export const ElmPlugin = (options?: ElmPluginOptions) =>
new ElmPluginClass(options);
Ok, let's break it down;
what's
paths
, can you give me an example?