fuse-box
Version:
Fuse-Box a bundler that does it right
287 lines (250 loc) • 9.3 kB
text/typescript
import { FuseBox } from "./FuseBox";
import { WorkFlowContext } from "./WorkflowContext";
import { BundleProducer } from "./BundleProducer";
import { FuseProcess } from "../FuseProcess";
import { HotReloadPlugin } from "../plugins/HotReloadPlugin";
import { SocketServer } from "../devServer/SocketServer";
import { File } from "./File";
import * as path from "path";
import { BundleTestRunner } from "../BundleTestRunner";
import { Config } from "../Config";
import { QuantumSplitResolveConfiguration } from "../quantum/plugin/QuantumSplit";
import { BundleAbstraction } from "../quantum/core/BundleAbstraction";
import { PackageAbstraction } from "../quantum/core/PackageAbstraction";
import { EventEmitter } from '../EventEmitter';
import { ExtensionOverrides } from "./ExtensionOverrides";
import { QuantumBit } from "../quantum/plugin/QuantumBit";
export interface HMROptions {
port?: number;
socketURI? : string;
reload?: boolean;
}
export class Bundle {
public context: WorkFlowContext;
public watchRule: string;
public arithmetics: string;
public process: FuseProcess = new FuseProcess(this);
public onDoneCallback: any;
public webIndexPriority = 0;
public generatedCode: Buffer;
public bundleAbstraction: BundleAbstraction;
public packageAbstraction: PackageAbstraction;
public lastChangedFile: string;
public webIndexed = true;
public splitFiles: Map<string, File>;
public quantumBit : QuantumBit;
private errors: string[] = [];
private errorEmitter = new EventEmitter<string>()
private clearErrorEmitter = new EventEmitter<null>()
constructor(public name: string, public fuse: FuseBox, public producer: BundleProducer) {
this.context = fuse.context;
this.context.bundle = this;
// re-assign the parent producer
fuse.producer = producer;
this.setup();
}
public watch(rules?: string): Bundle {
this.watchRule = rules ? rules : "**";
return this;
}
public globals(globals: any): Bundle {
this.context.globals = globals;
return this;
}
public tsConfig(fpath: string): Bundle {
this.context.tsConfig.setConfigFile(fpath);
return this;
}
public shim(shimConfig: any): Bundle {
this.context.shim = shimConfig;
return this;
}
/** Enable HMR in this bundle and inject HMR plugin */
public hmr(opts?: HMROptions): Bundle {
if (!this.producer.hmrAllowed) {
return this;
}
/** Only one is allowed to hava HMR related code */
if (!this.producer.hmrInjected) {
opts = opts || {};
opts.port = this.producer.devServerOptions && this.producer.devServerOptions.port || 4444;
let plugin = HotReloadPlugin({ port: opts.port, uri: opts.socketURI, reload : opts.reload });
this.context.plugins = this.context.plugins || [];
this.context.plugins.push(plugin);
// Should happen only once!
this.producer.hmrInjected = true;
}
/**
* Whenever socket server is initialized
* This will allow use to enable HMR on any bundle within current producer
*/
this.producer.sharedEvents.on("SocketServerReady", (server: SocketServer) => {
this.fuse.context.sourceChangedEmitter.on((info) => {
if (this.fuse.context.isFirstTime() === false) {
this.fuse.context.log.echo(`Source changed for ${info.path}`);
server.send("source-changed", info);
}
});
if (this.context.showErrorsInBrowser) {
const type = "update-bundle-errors",
getData = () => ({
bundleName: this.name,
messages: this.errors
})
this.errorEmitter.on(message => {
server.send("bundle-error", {
bundleName: this.name,
message
})
})
this.clearErrorEmitter.on(() => {
server.send(type, getData())
})
server.server.on("connection", client => {
client.send(JSON.stringify({
type,
data: getData()
}))
})
}
});
return this;
}
public alias(key: any, value: any): Bundle {
this.context.addAlias(key, value);
return this;
}
public split(name: string, filePath: string): Bundle {
this.producer.fuse.context.nameSplit(name, filePath);
return this;
}
/** Override cache option */
public cache(cache: boolean): Bundle {
this.context.useCache = cache;
return this;
}
public splitConfig(opts: QuantumSplitResolveConfiguration): Bundle {
this.producer.fuse.context.configureQuantumSplitResolving(opts);
return this;
}
/** Log */
public log(log: boolean): Bundle {
this.context.doLog = log;
this.context.log.printLog = log;
return this;
}
public extensionOverrides(...overrides: string[]) {
if (!this.context.extensionOverrides) {
this.context.extensionOverrides = new ExtensionOverrides(overrides);
} else {
overrides.forEach((override) => this.context.extensionOverrides.add(override));
}
return this;
}
/**
* Adds a plugin or a chain of plugins
* e.g
* in case of one plugin
* plugin(HTMLPlugin())
* In case of a chain:
*
* plugin("*.html", HTMLPlugin())
* @param args Plugin
*/
public plugin(...args): Bundle {
this.context.plugins = this.context.plugins || [];
this.context.plugins.push(args.length === 1 ? args[0] : args);
return this;
}
/**
* natives({ process : false })
* @param opts
*/
public natives(opts: any): Bundle {
this.context.natives = opts;
return this;
}
public instructions(arithmetics: string): Bundle {
this.arithmetics = arithmetics;
return this;
}
public target(target: string): Bundle {
this.context.target = target;
return this;
}
public sourceMaps(params: any): Bundle {
this.context.setSourceMapsProperty(params);
return this;
}
public test(str: string = "**/*.test.ts", opts: any) {
opts = opts || {};
opts.reporter = opts.reporter || "fuse-test-reporter";
opts.exit = true;
// include test files to the bundle
const clonedOpts = Object.assign({}, this.fuse.opts);
const testBundleFile = path.join(Config.TEMP_FOLDER, "tests", new Date().getTime().toString(), "/$name.js");
clonedOpts.output = testBundleFile;
// adding fuse-test dependency to be bundled
str += ` +fuse-test-runner ${opts.reporter} -ansi`;
const fuse = FuseBox.init(clonedOpts);
fuse.bundle("test")
.instructions(str)
.completed(proc => {
const bundle = require(proc.filePath);
let runner = new BundleTestRunner(bundle, opts);
runner.start();
});
fuse.run();
}
public exec(): Promise<Bundle> {
return new Promise((resolve, reject) => {
this.clearErrors();
this.fuse
.initiateBundle(this.arithmetics || "", () => {
const output = this.fuse.context.output;
this.process.setFilePath(output.lastPrimaryOutput ? output.lastPrimaryOutput.path : output.lastGeneratedFileName);
if (this.onDoneCallback && this.producer.writeBundles === true) {
this.onDoneCallback(this.process);
}
this.printErrors()
return resolve(this);
}).then(source => {
}).catch(e => {
console.error(e);
return reject(reject);
});
return this;
});
}
public completed(fn: (process: FuseProcess) => void): Bundle {
this.onDoneCallback = fn;
return this;
}
private setup() {
// modifying the output name
this.context.output.setName(this.name);
if (this.context.useCache) {
this.context.initCache();
this.context.cache.initialize();
}
}
private clearErrors() {
this.errors = []
this.clearErrorEmitter.emit(null)
}
public addError(message: string) {
this.errors.push(message)
this.errorEmitter.emit(message)
}
public getErrors() {
return this.errors.slice()
}
public printErrors() {
if (this.errors.length && this.fuse.context.showErrors) {
this.fuse.context.log.echoBreak()
this.fuse.context.log.echoBoldRed(`Errors for ${this.name} bundle`)
this.errors.forEach(error => this.fuse.context.log.echoError(error))
this.fuse.context.log.echoBreak()
}
}
}