char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
1c8ffed
main
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.
avril bundle web/main.tsx-o dist/app.jsavril 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:
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.mapbeside 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…es2025oresnext(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-levelawaitbelow 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, includingprocess.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.
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:andnode:modules, and native addons;import()andrequire()of computed specifiers,require()in ES modules, and other uses of a freerequiresuch asrequire.resolve(typeof requireis 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.
1# Browser bundles 2 3`avril bundle` compiles a module graph into ES modules for browsers, using the 4Rolldown bundler built into avril. It never runs application code and needs no 5Node.js. 6 7``` sh 8avril bundle web/main.tsx -o dist/app.js 9avril bundle web/main.tsx -o dist/app.js --minify --sourcemap \ 10--target es2019 --define 'process.env.NODE_ENV="production"' 11``` 12 13`-o` names the entry file exactly. Each literal `import()` becomes a lazily 14loaded chunk with a hashed name beside it, such as `lazy-D5XVYA0X.js`, unless 15`--no-splitting` inlines it. Unused exports are tree-shaken. Missing output 16directories are created and other files in them are left alone. Every output 17file is generated and checked before any is written, and each is replaced 18atomically; outputs never replace the bundle's own inputs or follow symlinks. 19 20## Multiple entrypoints 21 22Use `--outdir` to bundle several entries together: 23 24``` sh 25avril bundle web/main.tsx web/admin.tsx --outdir dist --sourcemap 26``` 27 28This writes `dist/main.js` and `dist/admin.js`, with shared dependencies and 29lazy imports in hashed chunks alongside them. Each entry keeps its exports. 30Entry filenames use the source basename with its final extension replaced by 31`.js` (for HTTP entries, the URL path supplies the basename). Duplicate entry 32filenames are an error, including entries from different directories with the 33same basename. 34 35Data URL entrypoints require `-o`: they have no source filename from which to 36name an output. Data URL dependencies work with either output mode. 37 38`--outdir` also accepts a single entry. It cannot be combined with `-o` / `--out`; 39`-o` and `--no-splitting` require exactly one entry. Options may appear between 40entries; `--` makes all remaining arguments entrypoints rather than options. 41 42Each entry uses its own module configuration. The first entry determines the 43project root and default lockfile location, as with a single-entry bundle. 44 45## Options 46 47- `--minify`: minify the output. 48- `--sourcemap`: write a `.map` beside each output file. Sources are relative 49to the output directory (remote modules keep their URLs), and their original 50contents are embedded. 51- `--no-splitting`: emit a single file. 52- `--target es2015` … `es2025` or `esnext` (the default): the syntax level the 53output is lowered to. Only syntax is lowered; avril adds no API polyfills. 54Syntax a target cannot express, such as top-level `await` below ES2022, is an 55error. 56- `--define KEY=VALUE`: replace a global or dotted path with a JavaScript 57expression; repeatable. Quote strings yourself: 58`--define 'process.env.NODE_ENV="production"'`. Nothing else, including 59`process.env.NODE_ENV`, is defined implicitly. 60 61Package options (`--offline`, `--frozen`, `--lock`, `--vendor`, registries, 62`--cache-dir`) and permissions work as for `avril run`, and bundling records 63the same lockfile entries. 64 65## Modules 66 67Modules resolve and load exactly as for `avril run`: import maps, scopes, local 68packages, JSR, HTTP, and npm with the same lockfile, integrity, and permission 69checks. JS, TS, JSX, TSX, JSON, CommonJS (`.cjs`, `.cts`, and npm CommonJS) 70are supported. JSX uses each module's own [compiler options](jsx.md). 71 72npm packages resolve their browser builds: `exports` and `imports` match the 73`browser`, `import`, `module`, `default` conditions (`browser`, `require`, 74`default` for `require()`) instead of `avril` and `node`. The legacy 75`browser` field applies too: a string replaces the package entry, and an object 76remaps package files to other package files, and bare specifiers to package 77files or to declared dependencies. A `false` mapping becomes an empty module, 78like `module.exports = {}`. 79`sideEffects` guides tree shaking. 80 81These fail with an error instead of producing broken browser code: 82 83- `avril:` and `node:` modules, and native addons; 84- `import()` and `require()` of computed specifiers, `require()` in ES modules, 85and other uses of a free `require` such as `require.resolve` (`typeof 86require` is fine); 87- `new URL('./file', import.meta.url)` asset references; 88- preserved JSX (`jsx: "preserve"` or `"react-native"`). 89 90`import.meta.url` is the URL of the output chunk at runtime, and 91`import.meta.resolve()` is left to the browser: it resolves against that chunk 92URL, not through avril's package resolution, and adds nothing to the bundle. 93CSS, HTML, assets, and workers are not bundled.