@qooxdoo/framework
Version:
The JS Framework for Coders
245 lines (225 loc) • 7.9 kB
JavaScript
/* ************************************************************************
*
* qooxdoo-compiler - node.js based replacement for the Qooxdoo python
* toolchain
*
* https://github.com/qooxdoo/qooxdoo-compiler
*
* Copyright:
* 2022 Derrell Lipman
*
* License:
* MIT: https://opensource.org/licenses/MIT
*
* This software is provided under the same licensing terms as Qooxdoo,
* please see the LICENSE file in the Qooxdoo project's top-level directory
* for details.
*
* Authors:
* * Derrell Lipman (@derrell)
*
* ************************************************************************/
const hash = require("object-hash");
/**
*
*/
qx.Class.define("qx.tool.compiler.targets.meta.Browserify", {
extend: qx.tool.compiler.targets.meta.AbstractJavascriptMeta,
construct(appMeta) {
super(appMeta, `${appMeta.getApplicationRoot()}commonjs-browserify.js`);
this.setNeedsWriteToDisk(true);
},
members: {
__commonjsModules: null,
__references: null,
__getCommonjsModules() {
if (this.__commonjsModules === null) {
let commonjsModules = new Set();
let references = {};
const db = this.getAppMeta().getAnalyser().getDatabase();
const localModules =
this.getAppMeta().getApplication().getLocalModules() || {};
// Get a Set of unique `require`d CommonJS module names from
// all classes
for (let className in db.classInfo) {
let classInfo = db.classInfo[className];
if (classInfo.commonjsModules) {
Object.keys(classInfo.commonjsModules).forEach(moduleName => {
// Ignore this found `require()` if its a local modules
if (!(moduleName in localModules)) {
// Add this module name to the set of module names
commonjsModules.add(moduleName);
}
// Add the list of references from which this module was require()d
if (!references[moduleName]) {
references[moduleName] = new Set();
}
references[moduleName].add([
...classInfo.commonjsModules[moduleName]
]);
});
}
}
this.__commonjsModules = [...commonjsModules];
this.__references = references;
}
return {
commonjsModules: this.__commonjsModules,
references: this.__references
};
},
/**
* @Override
*/
async writeToDisk() {
const localModules = this.getAppMeta().getApplication().getLocalModules();
let db = this.getAppMeta().getAnalyser().getDatabase();
const { commonjsModules } = this.__getCommonjsModules();
let modules = [];
let modulesInfo = {};
let doIt = !!!(await qx.tool.utils.files.Utils.safeStat(
this.getFilename()
));
// Include any dynamically determined `require()`d modules
if (commonjsModules.length > 0) {
modules.push(commonjsModules);
}
// Include any local modules specified for the application
// in compile.json
if (localModules) {
modulesInfo.localModules = {};
for (let requireName in localModules) {
modules.push(requireName);
let stat = await qx.tool.utils.files.Utils.safeStat(
localModules[requireName]
);
modulesInfo.localModules[requireName] = stat.mtime.getTime();
doIt ||=
modulesInfo.localModules[requireName] >
(db?.modulesInfo?.localModules[requireName] || 0);
}
}
modulesInfo.modulesHash = hash(modules);
doIt ||= modulesInfo.modulesHash !== (db?.modulesInfo?.modulesHash || "");
if (doIt) {
db.modulesInfo = modulesInfo;
await this.getAppMeta().getAnalyser().saveDatabase();
}
this.setNeedsWriteToDisk(doIt);
return super.writeToDisk();
},
/**
* @Override
*/
async writeSourceCodeToStream(ws) {
// If there are any CommonJS modules required to be bundled, or
// any local modules specified for the application in
// compile.json, browserify them
if (
this.getAppMeta().getEnvironmentValue("qx.compiler.applicationType") ==
"browser"
) {
const localModules = this.getAppMeta()
.getApplication()
.getLocalModules();
const { commonjsModules, references } = this.__getCommonjsModules();
if (commonjsModules.length > 0 || localModules) {
await this.__browserify(
commonjsModules,
references,
localModules,
ws
);
}
}
await new Promise(resolve => {
ws.write("\n", resolve);
});
},
__browserify(commonjsModules, references, localModules, ws) {
const babelify = require("babelify");
const preset = require("@babel/preset-env");
const browserify = require("browserify");
const builtins = require("browserify/lib/builtins.js");
// For some reason, `process` is not require()able, but `_process` is.
// Make them equivalent.
builtins.process = builtins._process;
return new Promise((resolve, reject) => {
const options = {
builtins: builtins,
ignoreMissing: true,
insertGlobals: true,
detectGlobals: true
};
qx.lang.Object.mergeWith(
options,
this.getAppMeta().getAnalyser().getBrowserifyConfig()?.options || {},
false
);
let b = browserify([], options);
b._mdeps.on("missing", (id, parent) => {
let message = [];
message.push(`ERROR: could not locate require()d module: "${id}"`);
message.push(" required from:");
try {
[...references[id]].forEach(refs => {
refs.forEach(ref => {
message.push(` ${ref}`);
});
});
} catch (e) {
message.push(` <compile.json:application.localModules'>`);
}
qx.tool.compiler.Console.error(message.join("\n"));
});
// Include any dynamically determined `require()`d modules
if (commonjsModules.length > 0) {
b.require(commonjsModules);
}
// Include any local modules specified for the application
// in compile.json
if (localModules) {
for (let requireName in localModules) {
b.require(localModules[requireName], { expose: requireName });
}
}
// Ensure ES6 local modules are converted to CommonJS format
b.transform(babelify, {
presets: [preset],
sourceMaps: false,
global: true
});
b.bundle(function (e, output) {
if (e) {
// THIS IS A HACK!
// In case of error dependency walker never returns from
// ```if (self.inputPending > 0) return setTimeout(resolve);```
// because inputPending is not decremented anymore.
// so set it to 0 here.
let d = b.pipeline.get("deps");
d.get(0).inputPending = 0;
qx.tool.compiler.Console.error(
`Unable to browserify - this is probably because a module is being require()'d which is not compatible with Browserify:\n${e.message}`
);
setTimeout(() => {
this.emit("end");
});
return;
}
// in case of end event output can not be writen.
// so catch the error and ignore it
try {
ws.write(output);
} catch (err) {}
resolve(null);
});
});
},
/**
* @Override
*/
async getSourceMap() {
return null;
}
}
});