create-shiny-react-app
Version:
CLI tool to create new shiny-react applications
122 lines (102 loc) • 3.42 kB
text/typescript
import chokidar from "chokidar";
import * as esbuild from "esbuild";
import tailwindPlugin from "esbuild-plugin-tailwindcss";
import { existsSync, writeFileSync } from "node:fs";
const production = process.argv.includes("--production");
const watch = process.argv.includes("--watch");
const metafile = process.argv.includes("--metafile");
const buildOptions: esbuild.BuildOptions = {
entryPoints: ["srcts/main.tsx"],
bundle: true,
format: "esm",
minify: production,
sourcemap: production ? undefined : "linked",
sourcesContent: true,
alias: {
react: "react",
},
logLevel: "warning",
metafile: metafile,
plugins: [tailwindPlugin()],
};
async function main() {
const buildmap: Record<string, Promise<esbuild.BuildContext>> = {};
// Only add build context if the directory exists
if (existsSync("r")) {
buildmap.r = esbuild.context({
...buildOptions,
outfile: "r/www/main.js",
});
}
if (existsSync("py")) {
buildmap.py = esbuild.context({
...buildOptions,
outfile: "py/www/main.js",
});
if (Object.keys(buildmap).length === 0) {
console.log("No build targets found. Need r/ or py/ directory. Exiting.");
process.exit(0);
}
}
if (watch) {
// Use chokidar for watching instead of esbuild's watch, because esbuild's
// watch mode constantly consumes 15-25% CPU due to polling.
// https://github.com/evanw/esbuild/issues/1527
const contexts = await Promise.all(Object.values(buildmap));
// Initial build
await Promise.all(contexts.map((context) => context.rebuild()));
const watchPaths = ["srcts/", "tailwind.config.js"];
const watcher = chokidar.watch(watchPaths, {
persistent: true,
ignoreInitial: true,
});
let rebuildTimeout: NodeJS.Timeout;
watcher.on("all", (eventName, path) => {
console.log(`${eventName}: ${path}`);
// Debounce rebuilds to avoid rapid successive builds
clearTimeout(rebuildTimeout);
rebuildTimeout = setTimeout(async () => {
try {
await Promise.all(contexts.map((context) => context.rebuild()));
} catch (error) {
console.error("Rebuild failed:", error);
}
}, 100);
});
watcher.on("error", (error) => {
console.error("Watcher error:", error);
});
// Graceful shutdown
process.on("SIGINT", async () => {
console.log("\nShutting down...");
// Close file watcher
await watcher.close();
// Dispose esbuild contexts
await Promise.all(contexts.map((context) => context.dispose()));
process.exit(0);
});
} else {
// Non-watch build
Object.entries(buildmap).forEach(([target, build]) =>
build
.then(async (context: esbuild.BuildContext) => {
console.log(`Building .js bundle for ${target} target...`);
const result = await context.rebuild();
console.log(`✓ Successfully built ${target}/www/main.js`);
if (metafile) {
writeFileSync("esbuild-meta.json", JSON.stringify(result.metafile));
console.log("✓ Successfully wrote esbuild-meta.json");
}
await context.dispose();
})
.catch((e) => {
console.error(`Build failed for ${target} target:`, e);
process.exit(1);
})
);
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
});