Skip to content

Instantly share code, notes, and snippets.

@jasononeil
Last active June 5, 2017 23:33
Show Gist options
  • Save jasononeil/4decc74a0e0e83841f74d62a7a7bd5ec to your computer and use it in GitHub Desktop.
Save jasononeil/4decc74a0e0e83841f74d62a7a7bd5ec to your computer and use it in GitHub Desktop.
A webpack loader that compiles (and watches) a hxml file, including the resulting JS in the webpack bundle.
const fs = require('fs');
const path = require('path');
const loaderUtils = require('loader-utils');
const exec = require('child_process').exec;
module.exports = function(hxmlContent) {
if (this.cacheable) {
this.cacheable();
}
const cb = this.async();
let jsOutputFile = null;
let haxeBuildInfoFile = '_tmp_haxe_build_info.json';
let args = [];
// Add args that are specific to hxml-loader
if (this.debug) {
args.push('-debug');
}
args.push('--macro');
args.push(`"WebpackLoaderUtil.outputJson('${haxeBuildInfoFile}')"`);
// Add args from hxml file
for (let line of hxmlContent.split('\n')) {
line = line.trim();
if (line === '' || line.substr(0, 1) === '#') {
continue;
}
let space = line.indexOf(' ');
let name = space > -1 ? line.substr(0, space) : line;
args.push(name);
if (name === '--next') {
var err = `${this
.resourcePath} included a "--next" line, hxml-loader only supports a single JS build per hxml file.`;
return cb(err);
}
if (space > -1) {
let value = line.substr(space + 1).trim();
args.push(value);
if (name === '-js') {
jsOutputFile = value;
}
}
}
if (!jsOutputFile) {
var err = `${this
.resourcePath} did not include a "-js" line, hxml-loader only supports a single JS build per hxml file.`;
return cb(err);
}
// Execute the Haxe build.
exec(`haxe ${args.join(' ')}`, (err, stdout, stderr) => {
if (stdout) {
console.log(stdout);
}
if (stderr) {
console.error(stderr);
}
if (err) {
return cb(err);
}
// TODO: use promises here to avoid callback crazyness.
// Read the Haxe build info so we can register dependencies.
fs.readFile(haxeBuildInfoFile, (err, json) => {
if (err) return cb(err);
var data = JSON.parse(json);
for (let file of data.modules) {
var filePath = path.resolve(file);
this.addDependency(filePath);
}
// Delete the temporary build info file.
fs.unlink(haxeBuildInfoFile, err => {
if (err) return cb(err);
// Read the resulting JS file.
fs.readFile(jsOutputFile, (err, data) => {
if (err) return cb(err);
return cb(null, data);
});
});
});
});
};
import haxe.macro.Context;
import haxe.macro.Type;
import haxe.macro.Expr;
import sys.io.File;
class WebpackLoaderUtil {
public static function outputJson(outputFile:String) {
Context.onGenerate(function (types:Array<Type>) {
var allFilesUsed = [];
for (type in types) {
addFilesForType(type, allFilesUsed);
}
var data = {
modules: allFilesUsed
};
var json = haxe.Json.stringify(data);
File.saveContent(outputFile, json);
});
}
static function addFilesForType(type:Type, files:Array<String>) {
switch type {
case TMono(_.get() => t):
addFilesForType(t, files);
case TEnum(_.get() => et, params):
addFileFromPosition(et.pos, files);
case TInst(_.get() => ct, params):
addFileFromPosition(ct.pos, files);
case TType(_.get() => td, params):
addFileFromPosition(td.pos, files);
case TFun(args, ret):
// No files to add.
case TAnonymous(_.get() => a):
// No files to add.
case TDynamic(dynamicParam):
if (dynamicParam != null) {
addFilesForType(dynamicParam, files);
}
case TLazy(fn):
addFilesForType(fn(), files);
case TAbstract(_.get() => a, params):
addFileFromPosition(a.pos, files);
}
}
static function addFileFromPosition(p:Position, files:Array<String>) {
var info = Context.getPosInfos(p);
if (files.indexOf(info.file) < 0) {
files.push(info.file);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment