UNPKG

convex

Version:

Client for the Convex Cloud

170 lines (159 loc) 5.03 kB
import path from "path"; import chalk from "chalk"; import esbuild from "esbuild"; import { Filesystem } from "./fs.js"; export { nodeFs, RecordingFs } from "./fs.js"; export type { Filesystem } from "./fs.js"; export function* walkDir( fs: Filesystem, dirPath: string ): Generator<string, void, void> { for (const dirEntry of fs.listDir(dirPath)) { const childPath = path.join(dirPath, dirEntry.name); if (dirEntry.isDirectory()) { yield* walkDir(fs, childPath); } else if (dirEntry.isFile()) { yield childPath; } } } export interface Bundle { path: string; source: string; sourceMap?: string; } export class BundleError extends Error {} type EsBuildResult = esbuild.BuildResult & { outputFiles: esbuild.OutputFile[]; }; async function doEsbuild( fs: Filesystem, dir: string, entryPoints: string[], generateSourceMaps: boolean ): Promise<EsBuildResult> { try { const result = await esbuild.build({ entryPoints, bundle: true, format: "esm", target: "esnext", outdir: "out", outbase: dir, write: false, sourcemap: generateSourceMaps, splitting: true, chunkNames: "_deps/[hash]", treeShaking: true, minify: false, metafile: true, }); for (const [relPath, input] of Object.entries(result.metafile!.inputs)) { // TODO: esbuild outputs paths prefixed with "(disabled)"" when bundling our internal // udf-system package. The files do actually exist locally, though. if (relPath.indexOf("(disabled):") !== -1) { continue; } const absPath = path.resolve(relPath); const st = fs.stat(absPath); if (st.size !== input.bytes) { throw new Error( `Bundled file ${absPath} changed right after esbuild invocation` ); } fs.registerPath(absPath, st); } return result; } catch (err) { throw new BundleError(`esbuild failed: ${(err as any).toString()}`); } } export async function bundle( fs: Filesystem, dir: string, entryPoints: string[], generateSourceMaps: boolean ): Promise<Bundle[]> { const result = await doEsbuild(fs, dir, entryPoints, generateSourceMaps); if (result.errors.length) { for (const error of result.errors) { console.log(chalk.red(`esbuild error: ${error.text}`)); } throw new BundleError("esbuild failed"); } for (const warning of result.warnings) { console.log(chalk.yellow(`esbuild warning: ${warning.text}`)); } const sourceMaps = new Map(); const modules: Bundle[] = []; for (const outputFile of result.outputFiles) { const relPath = path.relative(path.normalize("out"), outputFile.path); if (path.extname(relPath) === ".map") { sourceMaps.set(relPath, outputFile.text); continue; } const posixRelPath = relPath.split(path.sep).join(path.posix.sep); modules.push({ path: posixRelPath, source: outputFile.text }); } for (const module of modules) { const sourceMapPath = module.path + ".map"; const sourceMap = sourceMaps.get(sourceMapPath); if (sourceMap) { module.sourceMap = sourceMap; } } return modules; } export async function bundleAll( fs: Filesystem, dir: string, generateSourceMaps: boolean, verbose: boolean ): Promise<Bundle[]> { const entries = await entryPoints(fs, dir, verbose); return bundle(fs, dir, entries, generateSourceMaps); } export async function bundleSchema(fs: Filesystem, dir: string) { return bundle(fs, dir, [path.resolve(dir, "schema.ts")], true); } export async function entryPoints( fs: Filesystem, dir: string, verbose: boolean ): Promise<string[]> { const entryPoints = []; for (const fpath of walkDir(fs, dir)) { const relPath = path.relative(dir, fpath); const base = path.parse(fpath).base; const log = (line: string) => { if (verbose) { console.log(line); } }; if (relPath.startsWith("_deps" + path.sep)) { throw new Error( `The path "${fpath}" is within the "_deps" directory, which is reserved for dependencies. Please move your code to another directory.` ); } else if (relPath.startsWith("_generated" + path.sep)) { log(chalk.yellow(`Skipping ${fpath}`)); } else if (base.startsWith(".")) { log(chalk.yellow(`Skipping dotfile ${fpath}`)); } else if (base === "README.md") { log(chalk.yellow(`Skipping ${fpath}`)); } else if (base === "_generated.ts") { log(chalk.yellow(`Skipping ${fpath}`)); } else if (base === "schema.ts") { log(chalk.yellow(`Skipping ${fpath}`)); } else if (base.includes(".test.")) { log(chalk.yellow(`Skipping ${fpath}`)); } else if (base === "tsconfig.json") { log(chalk.yellow(`Skipping ${fpath}`)); } else if (relPath.includes(" ")) { log(chalk.yellow(`Skipping ${relPath} because it contains a space`)); } else { log(chalk.green(`Preparing ${fpath}`)); entryPoints.push(fpath); } } return entryPoints; }