UNPKG

@alwatr/nano-build

Version:

Build/bundle tools for ECMAScript, TypeScript, and JavaScript libraries. It's easy to use, doesn't require any setup, and adheres to best practices. It has no dependencies and uses esbuild for enhanced performance.

232 lines (206 loc) • 5.93 kB
#!/usr/bin/env node const pc = require('picocolors'); /** * @typedef {import('@alwatr/type-helper')} * @typedef {Omit<import('esbuild').BuildOptions, "mangleProps"> & {cjs?: boolean, mangleProps?: string | RegExp}} BuildOptions */ const {context, build} = require('esbuild'); const {resolve} = require('path'); const {existsSync} = require('fs'); const logger = { banner: (/** @type {string} */ message, /** @type {unknown[]} */ ...args) => console.log(pc.cyan(pc.bold(`${message}`)), ...args), info: (/** @type {string} */ message, /** @type {unknown[]} */ ...args) => console.log(pc.bgCyan(`[i] ${message} `), ...args), success: (/** @type {string} */ message, /** @type {unknown[]} */ ...args) => console.log(pc.bgGreen(`[āœ“] ${message} `), ...args), error: (/** @type {string} */ message, /** @type {unknown[]} */ ...args) => console.error(pc.bgRed(`[x] ${message} `), ...args), warn: (/** @type {string} */ message, /** @type {unknown[]} */ ...args) => console.warn(pc.bgYellow(`[!] ${message} `), ...args), log: console.log, }; const packageJsonPath = resolve(process.cwd(), 'package.json'); if (existsSync(packageJsonPath) === false) { logger.error('`package.json` not found in `%s`', packageJsonPath); process.exit(1); } const packageJson = require(packageJsonPath); logger.banner('\nšŸš€ Alwatr NanoBuild\n'); logger.banner('šŸ“¦ %s v%s\n', packageJson.name, packageJson.version); const devMode = process.env.NODE_ENV !== 'production'; logger.info(`${devMode ? 'Development' : 'Production'} mode`); const watchMode = process.argv.includes('--watch'); if (watchMode) { logger.info('Watch mode enabled'); } /** * @type {BuildOptions} */ const defaultOptions = { entryPoints: ['src/*.ts'], outdir: 'dist', logLevel: 'info', target: 'es2022', bundle: true, minify: true, treeShaking: true, sourcemap: false, sourcesContent: false, charset: 'utf8', legalComments: 'linked', banner: { js: '/* ' + packageJson.name + ' v' + packageJson.version + ' */', }, define: { __package_name__: `'${packageJson.name}'`, __package_version__: `'${packageJson.version}'`, __dev_mode__: devMode.toString(), }, }; /** * @type {BuildOptions} */ const developmentOptions = { sourcemap: true, sourcesContent: true, }; /** * @type {BuildOptions} */ const productionOptions = { dropLabels: ['__dev_mode__'], }; /** * @type {DictionaryOpt<BuildOptions>} */ const presetRecord = { default: {}, module: { entryPoints: ['src/main.ts'], bundle: true, platform: 'node', format: 'esm', minify: false, cjs: true, packages: 'external', sourcemap: true, sourcesContent: true, }, module2: { entryPoints: ['src/*.ts'], bundle: true, platform: 'node', format: 'esm', minify: false, cjs: true, packages: 'external', sourcemap: true, sourcesContent: true, }, module3: { entryPoints: ['src/**/*.ts'], bundle: false, platform: 'node', format: 'esm', minify: false, cjs: true, packages: 'external', sourcemap: true, sourcesContent: true, }, pwa: { entryPoints: ['src/*.ts'], platform: 'browser', format: 'iife', mangleProps: '_$', target: ['chrome85', 'firefox115', 'safari15.6'], ...(devMode ? developmentOptions : productionOptions), }, pmpa: { entryPoints: ['site/_ts/*.ts'], outdir: 'dist/es', platform: 'browser', format: 'iife', mangleProps: '_$', target: ['chrome85', 'firefox115', 'safari15.6'], ...(devMode ? developmentOptions : productionOptions), }, weaver: { entryPoints: ['src/ts/*.ts'], outdir: 'dist/es', platform: 'browser', format: 'iife', mangleProps: '_$', target: ['chrome85', 'firefox115', 'safari15.6'], ...(devMode ? developmentOptions : productionOptions), }, microservice: { entryPoints: ['src/main.ts'], platform: 'node', format: 'esm', mangleProps: '_$', target: 'node22', ...(devMode ? developmentOptions : productionOptions), }, }; function getOptions() { const presetName = process.argv.find((arg) => arg.startsWith('--preset='))?.split('=')[1] ?? 'default'; logger.info('Preset: `%s`', presetName); if (!Object.hasOwn(presetRecord, presetName)) { logger.error('Preset `%s` not found', presetName); process.exit(1); } const presetOptions = presetRecord[presetName]; const options = { ...defaultOptions, ...presetOptions, ...packageJson['nano-build'], ...(devMode ? packageJson['nano-build-development'] : packageJson['nano-build-production']), }; // Remove null fields from esbuildOptions Object.keys(options).forEach((key) => { if (options[key] === null) { delete options[key]; } }); logger.info('Options:', options); if (typeof options.mangleProps === 'string') { options.mangleProps = new RegExp(options.mangleProps); } return options; } /** * Nano build process. * @param {import('esbuild').BuildOptions} options */ async function nanoBuild(options) { // @ts-ignore const alsoCjs = options.format === 'esm' && options.cjs; // @ts-ignore delete options.cjs; if (options.format === 'esm' || options.format === 'cjs') { options.outExtension = { '.js': options.format === 'esm' ? '.mjs' : '.cjs', ...options.outExtension, }; } if (watchMode) { logger.info('Watching for changes...'); const esbuildContext = await context(options); await esbuildContext.watch(); logger.success('Watching for file changes.'); return; } // else logger.info('Building...'); await build(options); if (alsoCjs) { logger.info('Building CJS bundle...'); await build({ ...options, format: 'cjs', outExtension: { ...options.outExtension, '.js': '.cjs', }, }); } logger.success('Build complete.'); } nanoBuild(getOptions());