char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somsupport data urls1c8ffed

main
4.3 KiB93 linesraw

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.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:

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.

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.

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
49  to the output directory (remote modules keep their URLs), and their original
50  contents are embedded.
51- `--no-splitting`: emit a single file.
52- `--target es2015` … `es2025` or `esnext` (the default): the syntax level the
53  output is lowered to. Only syntax is lowered; avril adds no API polyfills.
54  Syntax a target cannot express, such as top-level `await` below ES2022, is an
55  error.
56- `--define KEY=VALUE`: replace a global or dotted path with a JavaScript
57  expression; 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,
85  and other uses of a free `require` such as `require.resolve` (`typeof
86  require` 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.