moduloze
Version:
Convert CommonJS (CJS) modules to UMD and ESM formats
510 lines (461 loc) • 13.8 kB
JavaScript
;
var path = require("path");
var fs = require("fs");
var dotenv = require("dotenv");
var micromatch = require("micromatch");
var minimist = require("minimist");
var mkdirp = require("mkdirp");
var recursiveReadDir = require("recursive-readdir-sync");
var terser = require("terser");
var programVersion;
var { build, bundleUMD, umdIndex, esmIndex, defaultLibConfig, } = require("./index.js");
var {
expandHomeDir,
rootRelativePath,
qualifyDepPaths,
isDirectory,
checkPath,
generateName,
} = require("./helpers.js");
dotenv.config();
var params = minimist(process.argv.slice(2),{
boolean: [ "help","version","build-esm","build-umd","recursive","minify", ],
string: [ "config","from","to","bundle-umd","prepend", ],
alias: {
"config": "c",
"recursive": "r",
"build-esm": "e",
"build-umd": "u",
"bundle-umd": "b",
"dep-map": "m",
"minify": "n",
"prepend": "p",
},
default: {
help: false,
version: false,
recursive: false,
"build-esm": false,
"build-umd": false,
minify: false,
prepend: "",
},
});
var RC_SPECIFIED = Boolean(params.config || process.env.RCPATH);
var DEPMAPPATH_SPECIFIED = Boolean(params["dep-map"] || process.env.DEPMAPPATH);
var RCPATH = resolvePath(params.config || process.env.RCPATH || "./.mzrc");
var DEPMAPPATH = resolvePath(params["dep-map"] || process.env.DEPMAPPATH || "./package.json");
// initial CLI-config before reading from rc
var config = defaultCLIConfig();
module.exports = CLI;
module.exports.CLI = CLI;
// ******************************
async function CLI(version = "0.0.0?") {
programVersion = version;
if (!loadConfig()) {
return;
}
// populate known-dependencies map from configuration (if any)
var knownDeps = {};
if (config.depMap) {
let fromDirStr = resolvePath(config.from);
fromDirStr = (
isDirectory(fromDirStr) ?
fromDirStr :
path.dirname(fromDirStr)
);
knownDeps = qualifyDepPaths(config.depMap,fromDirStr);
}
// add discovered input files to known-dependencies map
var inputFiles = getInputFiles();
for (let [ , rootRelativePath, ] of inputFiles) {
if (!(rootRelativePath in knownDeps)) {
knownDeps[rootRelativePath] = generateName();
}
}
var umdBuilds = [];
var esmBuilds = [];
try {
// build each file (for each format)
for (let [ basePath, relativePath, ] of inputFiles) {
let code = fs.readFileSync(path.join(basePath,relativePath),"utf-8");
let res;
try {
res = build(config,relativePath,code,knownDeps);
// save updated depMap
knownDeps = res.esm ? res.esm.depMap : res.umd.depMap;
}
catch (err) {
throw new Error(`${err.toString()} (${ path.join(basePath,relativePath) }) `);
}
// save UMD build (for bundling and/or catch-all generation)?
if (
res.umd &&
(
config.bundleUMDPath ||
config.generateIndex
)
) {
umdBuilds.push(res.umd);
}
// save ESM build (for catch-all generation)?
if (res.esm && config.generateIndex) {
esmBuilds.push(res.esm);
}
// process each output format
for (let format of [ "esm", "umd", ]) {
if (res[format]) {
let outputPath = path.join(config.to,format,res[format].pathStr);
let outputDir = path.dirname(outputPath);
if (mkdir(outputDir) !== true) {
throw new Error(`Output directory (${ outputDir }) could not be created.`);
}
let contents = await processContents(res[format].code,path.basename(outputPath));
try {
fs.writeFileSync(outputPath,contents,"utf-8");
}
catch (err) {
throw new Error(`Output file (${ outputPath }) could not be created.`);
}
}
}
}
// generate the catch-all index for each build format
if (config.generateIndex) {
if (config.buildUMD) {
let indexBuild = umdIndex(config,umdBuilds,knownDeps);
umdBuilds.push(indexBuild);
let outputPath = path.join(config.to,"umd",indexBuild.pathStr);
let contents = await processContents(indexBuild.code,path.basename(outputPath));
try {
fs.writeFileSync(outputPath,contents,"utf-8");
}
catch (err) {
throw new Error(`Generated index (${ outputPath }) could not be created.`);
}
}
if (config.buildESM) {
let indexBuild = esmIndex(config,esmBuilds,knownDeps);
esmBuilds.push(indexBuild);
let outputPath = path.join(config.to,"esm",indexBuild.pathStr);
let contents = await processContents(indexBuild.code,path.basename(outputPath));
try {
fs.writeFileSync(outputPath,contents,"utf-8");
}
catch (err) {
throw new Error(`Generated index (${ outputPath }) could not be created.`);
}
}
}
// need to bundle all the UMDs together?
if (config.bundleUMDPath && umdBuilds.length > 0) {
let res = bundleUMD(config,umdBuilds);
let contents = await processContents(res.code,path.basename(config.bundleUMDPath));
try {
fs.writeFileSync(config.bundleUMDPath,contents,"utf-8");
}
catch (err) {
throw new Error(`UMD bundle (${ config.bundleUMDPath }) could not be created.`);
}
}
// copy any skipped files?
if (config.copyFiles && config.copyFiles.length > 0) {
for (let filePathStr of config.copyFiles) {
let fromPathStr = path.resolve(config.from,filePathStr);
let relativePathStr = rootRelativePath(config.from,fromPathStr);
let contents = fs.readFileSync(fromPathStr);
for (let format of [ "esm", "umd", ]) {
if (
(format == "esm" && config.buildESM) ||
(format == "umd" && config.buildUMD)
) {
let toPathStr = path.resolve(path.join(config.to,format),relativePathStr);
let toDir = path.dirname(toPathStr);
if (mkdir(toDir) !== true) {
throw new Error(`While copying skipped file (${ toPathStr }), directory (${ toDir }) could not be created.`);
}
fs.writeFileSync(toPathStr,contents);
}
}
}
}
}
catch (err) {
return showError(err);
}
}
async function processContents(contents,filename) {
if (params.minify) {
let result = await terser.minify(contents,{
mangle: {
keep_fnames: true,
},
compress: {
keep_fnames: true,
},
output: {
comments: /^!/,
},
});
if (!(result && result.code)) {
if (result.error) throw result.error;
else throw result;
}
contents = result.code;
}
if (params.prepend != "") {
let prepend = params.prepend.replace(/#FILENAME#/g,filename);
contents = `${prepend}${contents}`;
}
return contents;
}
function loadConfig() {
var cfg;
try {
cfg = fs.readFileSync(RCPATH,"utf-8");
}
catch (err) {
// no config found/available
cfg = "";
}
try {
cfg = JSON.parse(cfg);
}
catch (err) {
// config was invalid
if (RC_SPECIFIED) {
return showError(`Invalid config: ${RCPATH}`,/*includeHelp=*/true);
}
}
// merge in default configs for core and lib
config = defaultLibConfig(defaultCLIConfig(cfg));
return checkArgsAndConfig();
}
function checkArgsAndConfig() {
// user asking for help output?
if (params.help) {
printHelp();
return;
}
if (params.version) {
printVersion();
return;
}
// must build at least one format
if (!(
config.buildESM || config.buildUMD
)) {
return showError("Must select at least one output format (ESM or UMD).",/*includeHelp=*/true);
}
// from path invalid?
if (!checkPath(config.from)) {
return showError(`Input directory (${ config.from }) is missing or inaccessible.`);
}
// to path invalid?
if (!checkPath(config.to)) {
// should we create the default output target directory?
if (/\.mz-build$/.test(config.to)) {
// double-check the path was created?
if (mkdir(config.to) !== true) {
return showError(`Default output directory (${ config.to }) could not be created.`);
}
}
else {
return showError(`Output directory (${ config.to }) is missing or inaccessible.`);
}
if (config.buildESM) {
let esmPath = path.join(config.to,"esm");
if (!checkPath(esmPath)) {
if (mkdir(esmPath) !== true) {
return showError(`Output directory (${ esmPath }) could not be created.`);
}
}
}
if (config.buildUMD) {
let umdPath = path.join(config.to,"umd");
if (!checkPath(umdPath)) {
if (mkdir(umdPath) !== true) {
return showError(`Output directory (${ umdPath }) could not be created.`);
}
}
}
}
// dependency map unspecified?
if (!config.depMap) {
// path is invalid?
if (!checkPath(config.depMapPath)) {
// missing/invalid path specified for dependency map?
if (DEPMAPPATH_SPECIFIED) {
return showError(`Dependency map (${ config.depMapPath }) is missing or inaccessible.`);
}
// UMD build format (requires a dependency-map)?
else if (config.buildUMD) {
return showError("UMD build format requires dependency map.");
}
// otherwise ESM build assumed
else {
config.depMap = {};
}
}
// otherwise, load UMD dependency map from path
else {
let json;
try {
json = JSON.parse(fs.readFileSync(config.depMapPath,"utf-8"));
// need to find config in a package.json?
if (/package\.json$/.test(config.depMapPath)) {
json = json["mz-dependencies"];
if (
// "mz-config" key is missing or not an object?
(!json || typeof json != "object") &&
// UMD build format (requires a dependency-map)?
config.buildUMD
) {
throw true;
}
}
}
catch (err) {
return showError(`Invalid/missing dependency map (${ config.depMapPath }).`);
}
config.depMap = json;
}
}
return true;
}
function getInputFiles() {
var files;
// scan the directory for input files?
if (isDirectory(config.from)) {
if (config.recursive) {
try {
files = recursiveReadDir(config.from);
}
catch (err) {
return showError(`Failed scanning for input files (${ config.from })`);
return;
}
}
else {
files =
fs.readdirSync(config.from)
.filter(function skipDirs(pathStr){
return !isDirectory(pathStr);
})
}
}
// otherwise, assume only a single input file
else {
files = [ config.from, ];
config.from = path.dirname(config.from);
}
// any skip patterns to remove?
if (Array.isArray(config.skip) && config.skip.length > 0) {
files = files.filter(function skipFiles(pathStr){
var res = micromatch(pathStr,config.skip).length == 0;
if (
// skipping file?
!res &&
// should copy it?
config.copyOnSkip
) {
config.copyFiles = config.copyFiles || [];
config.copyFiles.push(pathStr);
}
return res;
});
}
// split all paths into base and relative
files = files.map(function fixPaths(pathStr){
return [
path.resolve(config.from),
rootRelativePath(config.from,pathStr),
];
});
return files;
}
function printHelp() {
console.log("moduloze usage:");
console.log(" mz {OPTIONS}");
console.log("");
console.log("--help print this help");
console.log("--version print version info");
console.log("--config={PATH}, -c path to load config");
console.log(` [${ RCPATH }]`);
console.log("--from={PATH} scan directory for input file(s)");
console.log(` [${ config.from }]`);
console.log("--to={PATH} target directory for output file(s)");
console.log(` [${ config.to }]`);
console.log("--dep-map={PATH}, -m dependency map file");
console.log(` [${ DEPMAPPATH }]`);
console.log("--recursive, -r scan recursively for input files");
console.log(` [${ config.recursive }]`);
console.log("--build-esm, -e build ES-Modules format from input file(s)");
console.log(` [${ config.buildESM }]`);
console.log("--build-umd, -u build UMD format from input file(s)");
console.log(` [${ config.buildUMD }]`);
console.log("--bundle-umd={PATH}, -b include UMD bundle");
console.log(` [${ config.bundleUMDPath || "./umd/bundle.js" }]`);
console.log("--minify, -n minify output files");
console.log(` [${ config.minify }]`);
console.log("--prepend={TEXT}, -p prepend TEXT to each file");
console.log(` [${ config.prepend }]`);
console.log("");
}
function printVersion() {
console.log(`v${ programVersion }`);
}
function showError(err,includeHelp = false) {
console.error(err.toString());
if (includeHelp) {
console.log("");
printHelp();
}
process.exit(1);
}
function defaultCLIConfig({
from = process.env.FROMPATH,
to = process.env.TOPATH,
depMap,
bundleUMDPath,
skip = [],
copyOnSkip = false,
copyFiles = [],
recursive,
buildESM,
buildUMD,
generateIndex = false,
minify = false,
prepend = "",
...other
} = {}) {
// params override configs
from = resolvePath(params.from || from || "./");
to = resolvePath(params.to || to || "./.mz-build");
var depMapPath = DEPMAPPATH;
bundleUMDPath =
("bundle-umd" in params || bundleUMDPath || "UMDBUNDLEPATH" in process.env) ?
resolvePath((params["bundle-umd"] || bundleUMDPath || process.env.UMDBUNDLEPATH || "./umd/bundle.js"),to) :
false;
recursive = Boolean(params.recursive || recursive);
buildESM = Boolean(params["build-esm"] || buildESM);
buildUMD = Boolean(params["build-umd"] || buildUMD);
return {
from, to, recursive, buildESM, buildUMD, skip,
copyOnSkip, copyFiles, depMap, depMapPath, bundleUMDPath,
generateIndex, minify, prepend, ...other,
};
}
function resolvePath(pathStr,basePath = process.cwd()) {
pathStr = expandHomeDir(pathStr);
return path.resolve(basePath,pathStr);
}
function mkdir(pathStr) {
try {
mkdirp.sync(pathStr);
return true;
}
catch (err) {
return err;
}
}