cleanjs
Version:
Replaces all the require(file.js) and export statements in file with the actual file cotent, and compiles them into one single perfect JavaScript file for the whole project.
180 lines • 6.89 kB
JavaScript
/**-------------------------------------------------------------------------
* Created by shadrack on 28/11/16.
* Name : Perfect
* Purpose : Clean and include all require(files) in one single file js file
*------------------------------------------------------------------------*/
"use strict";
const fs = require("fs");
const path = require("path");
/**
* CLi line content padding
* @param size
* @param chars
* @param buffer
* @param l
* @param r
* @param favour
* @return {string}
*/
function pad(size, chars, buffer = 2, l = " ", r = " ", favour = "r") {
let p = Math.floor((size - (chars.length + buffer)) / 2);
let pl = `${l}`.repeat(p);
let pr = `${r}`.repeat(p);
let dif;
if ((dif = (size - `${pl} ${chars} ${pr}`.length))) {
if (favour === "r") {
pr = pr + r.repeat(dif);
}
else if (favour === "l") {
pl = pl + l.repeat(dif);
}
}
return `${pl} ${chars} ${pr}`;
}
class FileX {
constructor() {
this.url = "";
this.name = "";
this.destination = "";
this.require = "";
this.data = "";
}
}
//noinspection JSUnusedGlobalSymbols
class Clean {
//noinspection JSUnusedGlobalSymbols
/**
* Perfect Constructor
* @param from : string Path of the file or entry file to a module file
* @param to : string
* @param options
*/
constructor(from, to, options = {removeSourceMapComment: false, optimizeStrictMode: true}) {
this.options = options;
this.rawFile = path.resolve(from);
this.files = [];
this.dirNames = [];
this.clanFile = path.resolve(to);
this.regex = /((const|let|var)\s*)(\w+[0-9]*)(\s*=\s*)(require\s*\(")(.*\/*\w+[0-9]*)("\s*\)\s*;)/g;
this.mapTree();
this.parseModuleTree();
}
/**
* Maps the whole file finds all require statements and creates
* an optimal map of file-dependant linked list
*/
mapTree() {
this.files.push({name: "base", url: this.rawFile, destination: "-base|7777|@/201020", data: ""});
for (let ii = 0; ii < this.files.length; ii++) {
let file = this.files[ii];
let data = fs.readFileSync(file.url, "utf-8");
if (file.data) {
this.files[file.data].data = "";
}
file.data = data;
let m;
while ((m = this.regex.exec(data)) !== null) {
let fileX = new FileX();
fileX.url = path.resolve(path.dirname(file.url), m[6] + ".js");
fileX.name = m[3];
fileX.destination = file.url;
fileX.require = m[0];
for (let i in this.files) {
if (this.files.hasOwnProperty(i)) {
let fileY = this.files[i];
if (fileY.url === fileX.url) {
fileX.data = i;
break;
}
}
}
this.files.push(fileX);
}
}
this.files.reverse();
}
/**
* Parses the linked list created by Perfect.mapTree() and
* finally flattens the file into a single clean file
*/
parseModuleTree() {
for (let i = 0; i < this.files.length; i++) {
let fileX = this.files[i];
if (fileX.destination === "-base|7777|@/201020") {
fs.writeFileSync(this.clanFile, fileX.data, {encoding: "utf-8", mode: 0o666, flag: 'w'});
return;
}
else {
let destination;
for (let i in this.files) {
if (this.files.hasOwnProperty(i)) {
let fileY = this.files[i];
if (fileY.url === fileX.destination) {
destination = i;
break;
}
}
}
if (this.options.optimizeStrictMode) {
fileX.data = fileX.data.replace(/"use strict";/g, "");
}
this.files[destination].data = this.files[destination].data.replace(fileX.require, fileX.data);
this.files[destination].data = this.files[destination].data.replace(new RegExp(`${fileX.name}.`, 'g'), "");
this.files[destination].data = this.files[destination].data.replace(/(exports.\w+\s*=\s*\w+\s*;)/g, '');
if (this.options.removeSourceMapComment) {
this.files[destination].data = this.files[destination].data.replace(/(\/\/#\s*sourceMappingURL=\w*.js.map)/g, '');
}
}
}
}
//noinspection JSUnusedGlobalSymbols
/**
* Watches all the files of the dependency files and ushers in
* a new perfection each time any of the raw dependency files
* changes
*/
watch() {
let urls = this.files.map((file) => {
return file.url;
});
console.log("");
console.log("-".repeat(100));
let title = "watching and perfecting";
console.log(pad(100, title.toLocaleUpperCase(), 2, "-", "-"));
console.log("-".repeat(100));
urls.forEach((url, index) => {
let dirName = path.dirname(url);
if (!this.dirNames.includes(dirName)) {
let relPath = `(${index}). ` + path.relative(".", dirName);
console.log(`${relPath + ".".repeat(100 - relPath.length)}`);
this.dirNames.push(dirName);
}
});
console.log("-".repeat(100), "\n\n");
this.dirNames.forEach((dirName) => {
fs.watch(dirName, {encoding: "utf-8"}, (eventType, filename) => {
if (filename && path.extname(filename) === ".js" && filename !== path.basename(this.clanFile)) {
let d = new Date();
let h = (d.getHours() > 10) ? d.getHours() : "0" + d.getHours();
let m = (d.getMinutes() > 10) ? d.getMinutes() : "0" + d.getMinutes();
console.log(`${pad(20, filename, 2, "-")} `, ` ${eventType}ed @ ${h}:${m}:${d.getSeconds()}:${d.getMilliseconds()}`, ` perfected `, this.clanFile);
new Clean(this.rawFile, this.clanFile, this.options);
}
});
});
if (process.platform === "win32") {
let rl = require("readline").createInterface({
input: process.stdin,
output: process.stdout
});
rl.on("SIGINT", function () {
process.emit("SIGINT");
});
}
process.on("SIGINT", function () {
process.exit();
});
}
}
exports.Clean = Clean;
//# sourceMappingURL=clean.js.map