# Browser bundles `avril bundle` compiles a module graph into ES modules for browsers, using the Rolldown bundler built into avril. It never runs application code and needs no Node.js. ```sh avril bundle web/main.tsx -o dist/app.js avril bundle web/main.tsx -o dist/app.js --minify --sourcemap \ --target es2019 --define 'process.env.NODE_ENV="production"' ``` `-o` names the entry file exactly. Each literal `import()` becomes a lazily loaded chunk with a hashed name beside it, such as `lazy-D5XVYA0X.js`, unless `--no-splitting` inlines it. Unused exports are tree-shaken. Missing output directories are created and other files in them are left alone. Every output file is generated and checked before any is written, and each is replaced atomically; outputs never replace the bundle's own inputs or follow symlinks. ## Multiple entrypoints Use `--outdir` to bundle several entries together: ```sh avril bundle web/main.tsx web/admin.tsx --outdir dist --sourcemap ``` This writes `dist/main.js` and `dist/admin.js`, with shared dependencies and lazy imports in hashed chunks alongside them. Each entry keeps its exports. Entry filenames use the source basename with its final extension replaced by `.js` (for HTTP entries, the URL path supplies the basename). Duplicate entry filenames are an error, including entries from different directories with the same basename. Data URL entrypoints require `-o`: they have no source filename from which to name an output. Data URL dependencies work with either output mode. `--outdir` also accepts a single entry. It cannot be combined with `-o` / `--out`; `-o` and `--no-splitting` require exactly one entry. Options may appear between entries; `--` makes all remaining arguments entrypoints rather than options. Each entry uses its own module configuration. The first entry determines the project root and default lockfile location, as with a single-entry bundle. ## Options - `--minify`: minify the output. - `--sourcemap`: write a `.map` beside each output file. Sources are relative to the output directory (remote modules keep their URLs), and their original contents are embedded. - `--no-splitting`: emit a single file. - `--target es2015` … `es2025` or `esnext` (the default): the syntax level the output is lowered to. Only syntax is lowered; avril adds no API polyfills. Syntax a target cannot express, such as top-level `await` below ES2022, is an error. - `--define KEY=VALUE`: replace a global or dotted path with a JavaScript expression; repeatable. Quote strings yourself: `--define 'process.env.NODE_ENV="production"'`. Nothing else, including `process.env.NODE_ENV`, is defined implicitly. Package options (`--offline`, `--frozen`, `--lock`, `--vendor`, registries, `--cache-dir`) and permissions work as for `avril run`, and bundling records the same lockfile entries. ## Modules Modules resolve and load exactly as for `avril run`: import maps, scopes, local packages, JSR, HTTP, and npm with the same lockfile, integrity, and permission checks. JS, TS, JSX, TSX, JSON, CommonJS (`.cjs`, `.cts`, and npm CommonJS) are supported. JSX uses each module's own [compiler options](jsx.md). npm packages resolve their browser builds: `exports` and `imports` match the `browser`, `import`, `module`, `default` conditions (`browser`, `require`, `default` for `require()`) instead of `avril` and `node`. The legacy `browser` field applies too: a string replaces the package entry, and an object remaps package files to other package files, and bare specifiers to package files or to declared dependencies. A `false` mapping becomes an empty module, like `module.exports = {}`. `sideEffects` guides tree shaking. These fail with an error instead of producing broken browser code: - `avril:` and `node:` modules, and native addons; - `import()` and `require()` of computed specifiers, `require()` in ES modules, and other uses of a free `require` such as `require.resolve` (`typeof require` is fine); - `new URL('./file', import.meta.url)` asset references; - preserved JSX (`jsx: "preserve"` or `"react-native"`). `import.meta.url` is the URL of the output chunk at runtime, and `import.meta.resolve()` is left to the browser: it resolves against that chunk URL, not through avril's package resolution, and adds nothing to the bundle. CSS, HTML, assets, and workers are not bundled.