eserstack Tool — eser/stack on GitHub Install:
pnpm add jsr:@eserstack/bundler
A unified bundler abstraction for Deno with multiple backend support and comprehensive CSS processing utilities.
- Multi-Backend Support: Switch between Rolldown and Deno.bundle backends
- Unified API: Consistent configuration across bundler backends
- CSS Processing: Tailwind CSS, Lightning CSS, CSS Modules
- Font Optimization: Google Fonts self-hosting with preload hints
- Code Splitting: Advanced chunking strategies for optimal loading
- Plugin System: Extensible with resolve, load, and transform hooks
import { createBundler } from "@eserstack/bundler";
import {
createTailwindRoot,
optimizeGoogleFonts,
processCssModule,
} from "@eserstack/bundler/css";import { createBundler } from "@eserstack/bundler";
// Create bundler (Rolldown is default)
const bundler = createBundler("rolldown");
const result = await bundler.bundle({
entrypoints: {
main: "./src/main.ts",
worker: "./src/worker.ts",
},
outputDir: "./dist",
format: "esm",
platform: "browser",
codeSplitting: true,
minify: true,
sourcemap: true,
});
if (result.success) {
console.log(`Bundle size: ${result.totalSize} bytes`);
for (const [path, output] of result.outputs) {
console.log(` ${path}: ${output.size} bytes`);
}
}import { createBundler } from "@eserstack/bundler";
// Use native Deno.bundle API
const bundler = createBundler("deno-bundler");
const result = await bundler.bundle({
entrypoints: { app: "./src/app.tsx" },
outputDir: "./dist",
format: "esm",
platform: "browser",
codeSplitting: true,
minify: false,
sourcemap: "inline",
});Rolldown is a Rust-based bundler that's 10-30x faster than Rollup with Rollup-compatible plugin API.
import {
createRolldownBackend,
createRolldownWithPreset,
RolldownPresets,
} from "@eserstack/bundler";
// Basic usage
const bundler = createRolldownBackend();
// With advanced chunking
const bundler = createRolldownBackend({
treeshake: true,
advancedChunks: {
minSize: 20000,
groups: [
{ name: "vendor", test: /node_modules/, priority: 10 },
{ name: "react", test: /react|react-dom/, priority: 20 },
],
},
});
// Using presets
const bundler = createRolldownWithPreset("react");
// Available presets: "default", "react", "library", "ssr", "performance"Uses the native Deno.bundle() API (requires --unstable-bundle flag).
import { createDenoBundlerBackend } from "@eserstack/bundler";
const bundler = createDenoBundlerBackend({
buildId: "abc123", // Optional build ID for cache busting
});| Feature | Rolldown | Deno Bundler |
|---|---|---|
| Performance | 10-30x faster | Native Deno |
| Format | ESM, CJS, IIFE | ESM only |
| External Modules | ✓ | ✓ |
| Plugins | ✓ (Rollup-compatible) | ✗ |
| Code Splitting | ✓ (Advanced) | ✓ |
| Watch Mode | ✓ | ✓ |
| Sourcemaps | ✓ | ✓ |
| basePath Rewrite | ✓ | ✓ |
| Dependencies | npm:rolldown | None (native) |
When to use Rolldown:
- Production builds requiring maximum performance
- Need CJS or IIFE output format
- Advanced code splitting with vendor chunking
- Plugin support (e.g., custom transformations)
When to use Deno Bundler:
- Simple builds without external dependencies
- Native Deno integration preferred
- Fallback when Rolldown is unavailable
Process CSS with Tailwind using @tailwindcss/node (same pattern as
@tailwindcss/vite).
import {
createTailwindRoot,
hasTailwindDirectives,
processCssModule,
} from "@eserstack/bundler/css";
// Create a Tailwind compiler root (reuse across files)
const tailwind = createTailwindRoot({
base: ".",
minify: true,
});
// Process CSS modules with @apply support
const result = await processCssModule("src/Button.module.css", {
tailwind,
generateDts: true,
});
console.log(result.exports); // { button: "button_abc123" }
// Clean up when done
tailwind.dispose();
// Check if CSS contains Tailwind directives
if (hasTailwindDirectives(cssContent)) {
// Process with Tailwind
}Advanced CSS transformation with browser targeting and minification.
import {
BrowserTargetPresets,
browserVersion,
minifyCss,
transformWithLightningCss,
} from "@eserstack/bundler/css";
// Basic transformation
const result = transformWithLightningCss(cssContent, {
filename: "styles.css",
minify: true,
targets: {
chrome: browserVersion(90),
firefox: browserVersion(88),
safari: browserVersion(14),
},
});
console.log(result.code);
// Using presets
const result = transformWithLightningCss(cssContent, {
minify: true,
targets: BrowserTargetPresets.modern(),
});
// Simple minification
const minified = minifyCss(cssContent);Process .module.css files with scoped class names and TypeScript definitions.
import {
buildCssModules,
createTailwindRoot,
generateTypeScriptDefinition,
processCssModule,
} from "@eserstack/bundler/css";
// Process single module (without Tailwind)
const result = await processCssModule("src/Button.module.css", {
generateDts: true,
minify: true,
});
console.log(result.exports);
// { button: "button_abc123", primary: "primary_def456" }
console.log(result.dts);
// declare const styles: { readonly button: string; readonly primary: string; };
// export default styles;
// Build all CSS modules in a directory
const results = await buildCssModules("src", "dist", {
generateDts: true,
});CSS Modules with Tailwind @apply:
// Create Tailwind compiler (reuse across files)
const tailwind = createTailwindRoot({ base: "." });
// Process CSS module with @apply support
const result = await processCssModule("src/Card.module.css", {
tailwind,
generateDts: true,
});
tailwind.dispose();Note: CSS files using @apply need @reference "tailwindcss"; directive
(Tailwind v4 requirement).
Download and self-host Google Fonts for better performance.
import {
generatePreloadHints,
optimizeGoogleFonts,
optimizeMultipleGoogleFonts,
} from "@eserstack/bundler/css";
// Optimize single font
const result = await optimizeGoogleFonts(
"https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700",
{
outputDir: "dist/fonts",
publicPath: "/fonts",
maxPreloadFonts: 2,
},
);
console.log(result.fontFaceCSS); // Rewritten @font-face declarations
console.log(result.preloadHints); // HTML preload link tags
console.log(result.totalSize); // Total bytes downloaded
// Optimize multiple fonts
const result = await optimizeMultipleGoogleFonts(
[
"https://fonts.googleapis.com/css2?family=Roboto:wght@400;700",
"https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600",
],
{ outputDir: "dist/fonts" },
);interface BundlerConfig {
// Named entrypoints (name -> file path)
entrypoints: Record<string, string>;
// Output directory
outputDir: string;
// Output format: "esm" | "cjs" | "iife"
format: "esm" | "cjs" | "iife";
// Target platform: "browser" | "node" | "neutral"
platform: "browser" | "node" | "neutral";
// Enable code splitting
codeSplitting: boolean;
// Minify output
minify: boolean;
// Sourcemap: true | "inline" | "external" | false
sourcemap: boolean | "inline" | "external";
// Optional: Target environments
target?: string[];
// Optional: External modules
external?: string[];
// Optional: Bundler plugins
plugins?: BundlerPlugin[];
// Optional: Base path for URL rewriting
basePath?: string;
}interface BundleResult {
success: boolean;
outputs: Map<string, BundleOutput>;
errors?: BundleError[];
warnings?: BundleWarning[];
metafile?: BundleMetafile;
entrypointManifest?: Record<string, string[]>;
entrypoint?: string;
totalSize?: number;
}Create custom plugins with resolve, load, and transform hooks:
import type { BundlerPlugin } from "@eserstack/bundler";
const myPlugin: BundlerPlugin = {
name: "my-plugin",
setup(build) {
// Resolve hook - customize module resolution
build.onResolve({ filter: /^virtual:/ }, (args) => {
return { path: args.path, namespace: "virtual" };
});
// Load hook - provide module contents
build.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => {
return {
contents: `export default "virtual module: ${args.path}"`,
loader: "js",
};
});
// Transform hook - modify source code
build.onTransform?.({ filter: /\.tsx?$/ }, (args) => {
return {
code: args.code.replace(/DEBUG/g, "false"),
};
});
},
};
const bundler = createBundler("rolldown");
await bundler.bundle({
// ...config
plugins: [myPlugin],
});Pre-configured settings for common use cases:
| Preset | Description |
|---|---|
default |
Sensible defaults for web apps |
react |
React apps with vendor splitting |
library |
Libraries with minimal chunking |
ssr |
Server-side rendering with aggressive split |
performance |
Maximum optimization for production |
import { createRolldownWithPreset, RolldownPresets } from "@eserstack/bundler";
// Use preset directly
const bundler = createRolldownWithPreset("react");
// Customize preset
const bundler = createRolldownWithPreset("react", {
advancedChunks: { minSize: 5000 },
});
// Access preset options
const options = RolldownPresets.performance();Both backends support watch mode for development:
const bundler = createBundler("rolldown");
const watcher = await bundler.watch(
{
entrypoints: { main: "./src/main.ts" },
outputDir: "./dist",
format: "esm",
platform: "browser",
codeSplitting: false,
minify: false,
sourcemap: "inline",
},
(result) => {
if (result.success) {
console.log("Rebuild complete!");
} else {
console.error("Build failed:", result.errors);
}
},
);
// Stop watching
await watcher.stop();Use browserVersion() to create target numbers for Lightning CSS:
import { BrowserTargetPresets, browserVersion } from "@eserstack/bundler/css";
// Manual targets
const targets = {
chrome: browserVersion(100), // Chrome 100
firefox: browserVersion(95), // Firefox 95
safari: browserVersion(15, 4), // Safari 15.4
};
// Or use presets
BrowserTargetPresets.modern(); // Chrome 90+, Firefox 88+, Safari 14+
BrowserTargetPresets.wide(); // Chrome 80+, Firefox 75+, Safari 13+
BrowserTargetPresets.latest(); // Chrome 120+, Firefox 120+, Safari 17+🔗 For further details, visit the eserstack repository.