fuse-box
Version:
Fuse-Box a bundler that does it right
179 lines (159 loc) • 6.74 kB
text/typescript
import { each } from "realm-utils";
import { Bundle } from "../../core/Bundle";
import { ensureUserPath, uglify } from "../../Utils";
import { QuantumCore } from "./QuantumCore";
import * as fs from "fs";
export class BundleWriter {
private bundles = new Map<string, Bundle>();
constructor(public core: QuantumCore) { }
private getUglifyJSOptions(): any {
const mainOptions: any = {
};
return {
...this.core.opts.shouldUglify() || {},
...mainOptions
}
}
private createBundle(name: string, code?: string): Bundle {
let bundle = new Bundle(name, this.core.producer.fuse.copy(), this.core.producer);
if (code) {
bundle.generatedCode = new Buffer(code);
}
this.bundles.set(bundle.name, bundle);
return bundle;
}
private addShims() {
const producer = this.core.producer;
// check for shims
if (producer.fuse.context.shim) {
const shims = [];
for (let name in producer.fuse.context.shim) {
let item = producer.fuse.context.shim[name];
if (item.source) {
let shimPath = ensureUserPath(item.source);
if (!fs.existsSync(shimPath)) {
console.warn(`Shim erro: Not found: ${shimPath}`);
} else {
shims.push(fs.readFileSync(shimPath).toString())
}
}
}
if (shims.length) {
this.createBundle("shims.js", shims.join("\n"));
}
}
}
private uglifyBundle(bundle: Bundle) {
this.core.log.echoInfo(`Uglifying ${bundle.name}...`);
const result = uglify(bundle.generatedCode, this.getUglifyJSOptions());
if (result.error) {
this.core.log
.echoBoldRed(` → Error during uglifying ${bundle.name}`)
.error(result.error);
throw result.error;
}
bundle.generatedCode = result.code;
this.core.log.echoInfo(`Done Uglifying ${bundle.name}`)
this.core.log.echoGzip(result.code);
}
public process() {
const producer = this.core.producer;
const bundleManifest: any = {};
this.addShims();
producer.bundles.forEach(bundle => {
this.bundles.set(bundle.name, bundle)
});
if (this.core.opts.isContained() && producer.bundles.size > 1) {
this.core.opts.throwContainedAPIError();
}
// create api bundle (should be the last)
let apiName2bake = this.core.opts.shouldBakeApiIntoBundle()
if (!apiName2bake) {
this.createBundle("api.js");
}
producer.bundles = this.bundles;
const splitConfig = this.core.context.quantumSplitConfig;
let splitFileOptions: any;
if (splitConfig) {
splitFileOptions = {
c: { b: splitConfig.resolveOptions.browser || "./", "s": splitConfig.resolveOptions.server || "./" },
i: {}
};
this.core.api.setBundleMapping(splitFileOptions);
}
let index = 1;
const writeBundle = (bundle: Bundle) => {
return bundle.context.output.writeCurrent(bundle.generatedCode).then(output => {
bundleManifest[bundle.name] = {
fileName: output.filename,
hash: output.hash,
absPath: output.path,
webIndexed: !bundle.quantumItem,
relativePath: output.relativePath
};
// if this bundle belongs to splitting
// we need to remember the generated file name and store
// and then pass to the API
if (bundle.quantumItem) {
splitFileOptions.i[bundle.quantumItem.name] = [output.relativePath, bundle.quantumItem.entryId];
}
});
}
return each(producer.bundles, (bundle: Bundle) => {
if (bundle.name === "api.js") {
// has to be the highest priority
// assuming that u user won't make more than 1000 bundles...
bundle.webIndexPriority = 1000;
if (this.core.opts.isContained()) {
this.core.opts.throwContainedAPIError();
}
bundle.generatedCode = new Buffer(this.core.api.render());
} else {
bundle.webIndexPriority = 1000 - index;
}
// if the api wants to be baked it, we have to skip generation now
if (apiName2bake !== bundle.name) {
if (this.core.opts.shouldUglify()) {
this.uglifyBundle(bundle);
}
index++;
return writeBundle(bundle);
}
}).then(() => {
if (apiName2bake) {
let targetBundle = producer.bundles.get(apiName2bake);
if (!targetBundle) {
this.core.log.echoBoldRed(` → Error. Can't find bundle name ${targetBundle}`);
} else {
const generatedAPIBundle = this.core.api.render();
if (this.core.opts.isContained()) {
targetBundle.generatedCode = new Buffer(targetBundle.generatedCode.toString().replace("/*$$CONTAINED_API_PLACEHOLDER$$*/", generatedAPIBundle.toString()));
} else {
targetBundle.generatedCode = new Buffer(generatedAPIBundle + "\n" + targetBundle.generatedCode);
}
if (this.core.opts.shouldUglify()) {
this.uglifyBundle(targetBundle);
}
}
return writeBundle(targetBundle);
}
}).then(() => {
const manifestPath = this.core.opts.getManifestFilePath();
if (manifestPath) {
this.core.producer.fuse.context.output.write(manifestPath,
JSON.stringify(bundleManifest, null, 2), true);
}
if (this.core.opts.webIndexPlugin) {
return this.core.opts.webIndexPlugin.producerEnd(producer)
}
}).then(() => {
// calling completed()
this.core.producer.bundles.forEach(bundle => {
if (bundle.onDoneCallback) {
bundle.process.setFilePath(bundle.fuse.context.output.lastWrittenPath);
bundle.onDoneCallback(bundle.process);
}
})
})
}
}