UNPKG

bob-the-bundler

Version:
225 lines (224 loc) • 8.95 kB
import { spawn } from 'child_process'; import { dirname, join, resolve } from 'path'; import { DepGraph } from 'dependency-graph'; import fse from 'fs-extra'; import fs from 'fs-extra'; import { globby } from 'globby'; import pLimit from 'p-limit'; import { build as tsup } from 'tsup'; import ncc from '@vercel/ncc'; import { createCommand } from '../command.js'; export const distDir = 'dist'; export const runifyCommand = createCommand(api => { const { reporter } = api; return { command: 'runify', describe: 'Runify', builder(yargs) { return yargs.options({ tag: { describe: 'Run only for following tags', array: true, type: 'string', }, }); }, async handler({ tag }) { const [rootPackageJSONPath] = await globby('package.json', { cwd: process.cwd(), absolute: true, }); const rootPackageJSON = await fse.readJSON(rootPackageJSONPath); const isSinglePackage = Array.isArray(rootPackageJSON.workspaces) === false; if (isSinglePackage) { return runify(join(process.cwd(), 'package.json'), reporter); } const limit = pLimit(1); const packageJsonFiles = await globby('packages/**/package.json', { cwd: process.cwd(), absolute: true, ignore: ['**/node_modules/**', `**/${distDir}/**`], }); const packageJsons = await Promise.all(packageJsonFiles.map(packagePath => fs.readJSON(packagePath))); const depGraph = new DepGraph(); packageJsons.forEach((pkg, i) => { var _a, _b; depGraph.addNode(pkg.name, { path: packageJsonFiles[i], tags: (_b = (_a = pkg.buildOptions) === null || _a === void 0 ? void 0 : _a.tags) !== null && _b !== void 0 ? _b : [], }); }); packageJsons.forEach(pkg => { var _a, _b, _c; [ ...Object.keys((_a = pkg.dependencies) !== null && _a !== void 0 ? _a : {}), ...Object.keys((_b = pkg.peerDependencies) !== null && _b !== void 0 ? _b : {}), ...Object.keys((_c = pkg.devDependencies) !== null && _c !== void 0 ? _c : {}), ] .filter(dep => depGraph.hasNode(dep)) .forEach(dep => { depGraph.addDependency(pkg.name, dep); }); }); const ordered = depGraph.overallOrder(); await Promise.all(ordered.map(name => { const data = depGraph.getNodeData(name); if (tag) { if (!data.tags.some(t => tag.includes(t))) { return; } } return limit(() => runify(depGraph.getNodeData(name).path, reporter)); })); }, }; }); async function runify(packagePath, reporter) { var _a, _b, _c, _d, _e, _f; const cwd = packagePath.replace('/package.json', ''); const pkg = await readPackageJson(cwd); const buildOptions = pkg.buildOptions || {}; if (!buildOptions.runify) { return; } if (isNext(pkg)) { const additionalRequire = (_d = (_c = (_b = (_a = pkg === null || pkg === void 0 ? void 0 : pkg.buildOptions) === null || _a === void 0 ? void 0 : _a.runify) === null || _b === void 0 ? void 0 : _b.next) === null || _c === void 0 ? void 0 : _c.header) !== null && _d !== void 0 ? _d : null; await buildNext(cwd, additionalRequire); await rewritePackageJson(pkg, cwd, newPkg => ({ ...newPkg, dependencies: pkg.dependencies, type: 'commonjs', })); } else { await compile(cwd, (_e = buildOptions.bin) !== null && _e !== void 0 ? _e : 'src/index.ts', buildOptions, Object.keys((_f = pkg.dependencies) !== null && _f !== void 0 ? _f : {}), pkg.type === 'module'); await rewritePackageJson(pkg, cwd); } reporter.success(`Built ${pkg.name}`); } export async function readPackageJson(baseDir) { return JSON.parse(await fs.readFile(resolve(baseDir, 'package.json'), { encoding: 'utf-8', })); } async function rewritePackageJson(pkg, cwd, modify) { let filename = 'index.js'; // only tsup keep the file name if (pkg.buildOptions.bin && pkg.buildOptions.tsup) { const bits = pkg.buildOptions.bin.split('/'); const lastBit = bits[bits.length - 1]; filename = lastBit.replace('.ts', '.js'); } let newPkg = { bin: filename, }; const fields = ['name', 'version', 'description', 'registry', 'repository', 'type']; fields.forEach(field => { if (typeof pkg[field] !== 'undefined') { newPkg[field] = pkg[field]; } }); if (modify) { newPkg = modify(newPkg); } await fs.writeFile(join(cwd, 'dist/package.json'), JSON.stringify(newPkg, null, 2), { encoding: 'utf-8', }); } function isNext(pkg) { var _a, _b; return ((_a = pkg === null || pkg === void 0 ? void 0 : pkg.dependencies) === null || _a === void 0 ? void 0 : _a.next) || ((_b = pkg === null || pkg === void 0 ? void 0 : pkg.devDependencies) === null || _b === void 0 ? void 0 : _b.next); } async function buildNext(cwd, additionalRequire) { await new Promise((resolve, reject) => { const child = spawn('next', ['build'], { stdio: 'inherit', cwd, }); child.on('exit', code => (code ? reject(code) : resolve(code))); child.on('error', reject); }); await fs.mkdirp(join(cwd, 'dist')); if (additionalRequire) { await tsup({ entryPoints: [join(cwd, additionalRequire)], outDir: join(cwd, 'dist'), target: 'node18', format: ['cjs'], splitting: false, skipNodeModulesBundle: true, }); } await Promise.all([ fs.copy(join(cwd, '.next'), join(cwd, 'dist/.next'), { filter(src) { // copy without webpack cache (it's 900mb...) return src.includes('cache/webpack') === false; }, }), fs.copy(join(cwd, 'public'), join(cwd, 'dist/public')), fs.writeFile(join(cwd, 'dist/index.js'), [ `#!/usr/bin/env node`, `process.on('SIGTERM', () => process.exit(0))`, `process.on('SIGINT', () => process.exit(0))`, additionalRequire ? `require('${additionalRequire.replace('.ts', '')}')` : ``, ` require('next/dist/server/lib/start-server').startServer({ dir: __dirname, hostname: '0.0.0.0', port: parseInt(process.env.PORT), conf: {}, }).then(async (app)=>{ const appUrl = 'http://' + app.hostname + ':' + app.port; console.log('started server on '+ app.hostname + ':' + app.port + ', url: ' + appUrl); await app.prepare(); }).catch((err)=>{ console.error(err); process.exit(1); }) `, ].join('\n')), ]); } async function compile(cwd, entryPoint, buildOptions, dependencies, useEsm = false) { if (buildOptions.tsup) { const out = join(cwd, 'dist'); await tsup({ entryPoints: [join(cwd, entryPoint)], outDir: out, target: 'node18', format: [useEsm ? 'esm' : 'cjs'], splitting: false, sourcemap: true, clean: true, shims: true, skipNodeModulesBundle: false, noExternal: dependencies, external: buildOptions.external, banner: buildOptions.banner ? { js: buildOptions.banner.includes('.js') || buildOptions.banner.includes('.mjs') ? await fs.readFile(join(cwd, buildOptions.banner), 'utf-8') : buildOptions.banner, } : {}, }); return; } const { code, map, assets } = await ncc(join(cwd, entryPoint), { cache: false, sourceMap: true, }); await fs.mkdirp(join(cwd, 'dist')); await Promise.all([ fs.writeFile(join(cwd, 'dist/index.js'), code, 'utf8'), fs.writeFile(join(cwd, 'dist/index.js.map'), map, 'utf8'), ...Object.keys(assets).map(async (filepath) => { if (filepath.endsWith('package.json')) { return; } await fs.ensureDir(dirname(join(cwd, 'dist', filepath)), {}); await fs.writeFile(join(cwd, 'dist', filepath), assets[filepath].source); }), ]); }