@hypernym/bundler
Version:
ESM & TS module bundler.
205 lines (195 loc) • 5.33 kB
JavaScript
import { createArgs } from "@hypernym/args";
import process, { cwd } from "node:process";
import { cyan, dim } from "@hypernym/colors";
import { isAbsolute, resolve } from "node:path";
import { exists, read, write } from "@hypernym/utils/fs";
import { build } from "rolldown";
import { build as build$1 } from "../build/index.mjs";
//#region src/bin/meta.ts
const name = `Hyperbundler`;
const version = `0.21.0`;
//#endregion
//#region src/utils/logger.ts
const cl = console.log;
const separator = `|`;
const logger = {
info: (...args) => {
cl(name, dim(separator), ...args);
},
error: (...args) => {
cl();
cl(name, dim(separator), ...args);
cl();
},
exit: (message) => {
cl();
cl(name, dim(separator), message);
cl();
return process.exit();
}
};
//#endregion
//#region src/utils/error.ts
function error(err) {
logger.error("Something went wrong...");
console.error(err);
return process.exit();
}
//#endregion
//#region src/utils/format-ms.ts
function formatMs(ms) {
const s = 1e3;
const m = s * 60;
const h = m * 60;
const msAbs = Math.abs(ms);
if (msAbs >= h) return `${(ms / h).toFixed(2)}h`;
if (msAbs >= m) return `${(ms / m).toFixed(2)}m`;
if (msAbs >= s) return `${(ms / s).toFixed(2)}s`;
return `${ms}ms`;
}
//#endregion
//#region src/utils/format-bytes.ts
function formatBytes(bytes) {
const decimals = 2;
const units = [
"B",
"KB",
"MB",
"GB",
"TB"
];
if (bytes === 0) return `0 B`;
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${units[i]}`;
}
//#endregion
//#region src/config.ts
/**
* List of global default patterns for external module identifiers.
*
* @example
*
* ```ts
* import { externals } from '@hypernym/bundler'
*
* export default defineConfig({
* entries: [
* {
* input: './src/index.ts',
* externals: [...externals, 'id', /regexp/]
* },
* ]
* })
* ```
*/
const externals = [
/^node:/,
/^@types/,
/^@rollup/,
/^@rolldown/,
/^@hypernym/,
/^rollup/,
/^rolldown/
];
//#endregion
//#region src/bin/loader.ts
async function getTSConfigPath(cwd$1, filePath = "tsconfig.json") {
const tsconfigPath = resolve(cwd$1, filePath);
const tsconfigFile = await exists(tsconfigPath);
if (tsconfigFile) return tsconfigPath;
}
async function loadConfig(filePath, defaults) {
const cwd$1 = defaults.cwd;
const result = await build({
input: resolve(cwd$1, filePath),
write: false,
external: (id) => !(isAbsolute(id) || /^(\.|@\/|~\/)/.test(id)),
tsconfig: defaults.tsconfig,
output: { format: "esm" }
});
const tempConfig = resolve(cwd$1, "node_modules/.hypernym/bundler/config.mjs");
await write(tempConfig, result.output[0].code);
const config = (await import(tempConfig)).default;
const options = {
...defaults,
...config
};
return {
options,
path: filePath
};
}
async function createConfigLoader(args) {
const cwdir = args.cwd && args.cwd.trim() !== "" ? resolve(args.cwd) : cwd();
const tsconfig = await getTSConfigPath(cwdir, args.tsconfig);
const pkgPath = resolve(cwdir, "package.json");
const pkgFile = await read(pkgPath).catch(error);
const { dependencies } = JSON.parse(pkgFile);
const defaults = {
cwd: cwdir,
tsconfig,
externals: [...Object.keys(dependencies || {}), ...externals],
entries: []
};
const warnMessage = `Missing required configuration. To start bundling, add the ${cyan(`'bundler.config.{js,mjs,ts,mts}'`)} file to the project's root.`;
if (args.config) {
const path = resolve(args.config);
const isConfig = await exists(path);
if (isConfig) return await loadConfig(path, defaults);
else return logger.exit(warnMessage);
}
const configName = "bundler.config";
const configExts = [
".ts",
".mts",
".mjs",
".js"
];
for (const ext of configExts) {
const path = resolve(cwdir, `${configName}${ext}`);
const isConfig = await exists(path);
if (isConfig) return await loadConfig(path, defaults);
}
return logger.exit(warnMessage);
}
//#endregion
//#region src/bin/builder.ts
async function createBuilder(config) {
const { options, path: configPath } = config;
const { hooks } = options;
const cl$1 = console.log;
await hooks?.["bundle:start"]?.(options);
cl$1();
logger.info(dim(`v${version}`));
cl$1("Config", dim(configPath));
cl$1();
cl$1("Processing specified entries...");
cl$1();
await build$1(options).then((stats) => {
const entriesLength = options.entries.length;
const totalEntries = `${entriesLength} ${entriesLength > 1 ? "entries" : "entry"}`;
const filesLength = stats.files.length;
const totalFiles = `${stats.files.length} file${filesLength > 1 ? "s" : ""}`;
const buildTime = formatMs(stats.buildTime);
const buildSize = formatBytes(stats.size);
cl$1();
cl$1("Stats:", dim(`${totalEntries}, ${totalFiles}, ${buildSize}, ${buildTime}`));
cl$1();
cl$1("All entries successfully processed.");
cl$1("Bundle is optimized and ready for production.");
cl$1();
}).catch(error);
await hooks?.["bundle:end"]?.(options);
}
//#endregion
//#region src/bin/index.ts
async function main() {
const args = createArgs({ alias: { config: "c" } });
const config = await createConfigLoader(args);
await createBuilder(config);
}
main().catch(error);
//#endregion