Decides which modules an application actually needs.
What was impossible before, and why
The build prepended dist/runtime.min.js — one pre-bundled blob — to every
application. Nothing about that arrangement admits the question "does this
application use the trace recorder?", because by the time the compiler ran,
the recorder had already been fused into a single artifact. Shaking is not a
feature that was missing; it was unaskable.
With the runtime consumed as modules the question is answerable, and this file answers it.
The rule, and its one deliberate departure from the specification
Two kinds of edge are treated differently, which is the whole mechanism:
-
A plain
importalways executes its target. That is what ES modules do, and the runtime depends on it:reactive/proxyHandler.jscallssetPathResolver(getPropertyPath)at its top level, and that call has to happen. No analysis here will ever drop a module that something imports. -
A re-export edge is followed only for the names that are needed. A barrel is a routing table, and
export { startRecording } from './trace/recorder.js'in a barrel nobody asksstartRecordingof is a route to nowhere. Strictly, the specification says that re-export executesrecorder.js; every bundler departs from it here, because otherwise no barrel is ever shakeable andavenx-core/runtimeis a barrel.
The departure is bounded rather than blanket. A re-export target is still
kept when it has a top-level side effect of its own — an expression statement
rather than a declaration — or when its package declares itself effectful
through sideEffects in package.json. Exactly one module in the Avenx
runtime has such a statement, and it is reached by a plain import anyway.
What this does not do
It does not remove unused declarations inside a module that is kept. Statement-level elimination needs a real identifier analysis, and a bundler that guesses at that miscompiles code rather than shrinking it. The honest consequence is stated in the build's own reporting: what shaking removes here is whole modules, and the fixed cost of a module that something imports is its whole source.
- Source:
Members
(inner, constant) ALL :string
The wildcard standing for "every export of this module is needed".
Type:
- string
- Source:
Methods
(static) hasTopLevelEffects(module) → {boolean}
Whether a module runs code at its top level beyond declaring things.
An expression statement at module scope is a side effect: it happens when the
module is evaluated and nothing else will make it happen. A declaration is
not, even when its initialiser calls something — that is the standard
assumption every bundler makes, and abandoning it would keep every module
that ever writes const x = new Thing().
Parameters:
| Name | Type | Description |
|---|---|---|
module |
object | A graph module. |
- Source:
Returns:
True when the module must run if it is reached at all.
- Type
- boolean
(static) shake(options) → {Set:.<string:>}
Works out which modules the bundle must contain.
Parameters:
- Source:
Returns:
Module ids to emit.
(inner) packageSideEffects(file, cache) → {boolean|null}
Reads the sideEffects declaration of the package a module belongs to.
"sideEffects": false is the convention a package uses to say its modules
can be dropped when unused. It is honoured for third-party packages, where
this analysis has no other way to know.
Parameters:
| Name | Type | Description |
|---|---|---|
file |
string | Absolute module path. |
cache |
Map:.<string:, (boolean:|null:)> | Per-directory answers. |
- Source:
Returns:
False when the package declares itself pure, true when it declares effects, null when it says nothing.
- Type
- boolean | null