Skip to main content

fallow_output/
entry_weight.rs

1//! Startup import weight carried by `fallow list --entry-weight`.
2//!
3//! The unit is source bytes on disk. The value includes types and comments and
4//! ignores tree shaking and bundler chunks, so it is not a bundle size. It is a
5//! repeatable count that goes down when an import moves behind `import()`.
6//!
7//! The analysis is syntactic. An import without the `type` keyword counts as
8//! eager, even when it brings in only types that TypeScript removes. Thus
9//! `eager_bytes` can be higher than the code that really loads.
10
11use serde::{Deserialize, Serialize};
12
13/// Unit of every byte count in [`EntryWeightListing`].
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
16#[serde(rename_all = "snake_case")]
17pub enum EntryWeightUnit {
18    /// On-disk bytes of project source files, before any build step.
19    SourceBytes,
20}
21
22/// `entry_weight` block of `fallow list --entry-weight --format json`.
23#[derive(Debug, Clone, Serialize)]
24#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
25pub struct EntryWeightListing {
26    /// Unit of every byte count in this block.
27    pub unit: EntryWeightUnit,
28    /// Number of entries in `entries`.
29    pub entry_count: usize,
30    /// One row per runtime entry point, heaviest `eager_bytes` first. A
31    /// declaration file (`.d.ts`) is not a row, because nothing loads it.
32    pub entries: Vec<EntryWeightOutput>,
33    /// Comparison with a saved regression baseline; present when a baseline
34    /// with entry weights was loaded.
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub regression: Option<EntryWeightRegression>,
37}
38
39/// Startup import weight of one runtime entry point.
40#[derive(Debug, Clone, Serialize)]
41#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
42pub struct EntryWeightOutput {
43    /// Entry file, relative to the analysed root.
44    pub path: String,
45    /// What declared the entry point, e.g. a plugin or `package.json main`.
46    pub source: String,
47    /// Project modules that load before the entry runs, the entry included.
48    /// A static import counts unless it uses the `type` keyword. An import
49    /// of only types without `import type` still counts, although TypeScript
50    /// removes it.
51    pub eager_modules: usize,
52    /// Source bytes of `eager_modules`.
53    pub eager_bytes: u64,
54    /// The part of `eager_bytes` that stylesheets (CSS, Sass, Less)
55    /// contribute.
56    pub eager_css_bytes: u64,
57    /// Project modules that load only on demand through `import()` or a lazy
58    /// glob or template pattern.
59    pub deferred_modules: usize,
60    /// Source bytes of `deferred_modules`.
61    pub deferred_bytes: u64,
62    /// Project modules that only a `new URL(..., import.meta.url)` reference
63    /// (for example a worker URL), a webpack worker loader request (for
64    /// example `worker-loader!./work.js`), `child_process.fork`, a pino
65    /// transport or a `module.register` hook reaches. They do not load on the thread of
66    /// the entry.
67    pub out_of_thread_modules: usize,
68    /// Source bytes of `out_of_thread_modules`.
69    pub out_of_thread_bytes: u64,
70    /// Number of packages in `eager_packages`.
71    pub eager_package_count: usize,
72    /// Packages that eager modules import statically, sorted by name.
73    /// Platform built-ins such as `node:fs` are not listed. Package bytes are
74    /// not measured.
75    pub eager_packages: Vec<EagerPackageOutput>,
76    /// Single imports that each keep a part of the eager modules eager,
77    /// heaviest first. This is evidence for a review, not a fix: a lazy load of
78    /// code that the first screen needs can make startup slower.
79    pub dominating_imports: Vec<DominatingImportOutput>,
80}
81
82/// One package on the eager path of an entry.
83#[derive(Debug, Clone, Serialize)]
84#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
85pub struct EagerPackageOutput {
86    /// Package name, e.g. `lodash` or `@scope/pkg`.
87    pub name: String,
88    /// Specifiers as written, e.g. `lodash/debounce`, sorted.
89    pub specifiers: Vec<String>,
90}
91
92/// One import that alone keeps a subtree of the eager modules eager.
93#[derive(Debug, Clone, Serialize)]
94#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
95pub struct DominatingImportOutput {
96    /// File that contains the import, relative to the analysed root.
97    pub importer: String,
98    /// 1-based line of the import binding; absent when the edge has no
99    /// binding span, such as an `export ... from` re-export or an eager glob
100    /// match.
101    #[serde(default, skip_serializing_if = "Option::is_none")]
102    pub line: Option<u32>,
103    /// Imported file, relative to the analysed root.
104    pub target: String,
105    /// Source bytes that leave the eager modules if this import becomes an
106    /// `import()`.
107    pub exclusive_bytes: u64,
108    /// Modules that leave the eager modules if this import becomes an
109    /// `import()`.
110    pub exclusive_modules: usize,
111}
112
113/// Comparison of the current entry weights with a regression baseline.
114#[derive(Debug, Clone, Serialize)]
115#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
116pub struct EntryWeightRegression {
117    /// Allowed growth of `eager_bytes` per entry, as spelled: `"5%"` or a
118    /// byte count such as `"1024"`.
119    pub tolerance: String,
120    /// Whether `--fail-on-regression` makes an exceeded entry fail the run.
121    /// Without it the comparison is report-only.
122    pub enforced: bool,
123    /// Whether at least one entry grew more than the tolerance allows.
124    pub exceeded: bool,
125    /// One row per entry in the baseline or in the current run, sorted by path.
126    pub entries: Vec<EntryWeightDelta>,
127}
128
129/// Change of one entry against the regression baseline.
130#[derive(Debug, Clone, Serialize)]
131#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
132pub struct EntryWeightDelta {
133    /// Entry file, relative to the analysed root.
134    pub path: String,
135    /// Baseline `eager_bytes`; absent for an entry that is new in this run.
136    #[serde(default, skip_serializing_if = "Option::is_none")]
137    pub baseline_eager_bytes: Option<u64>,
138    /// Current `eager_bytes`; absent for an entry that is gone in this run.
139    #[serde(default, skip_serializing_if = "Option::is_none")]
140    pub current_eager_bytes: Option<u64>,
141    /// Baseline `eager_modules`; absent for an entry that is new in this run.
142    #[serde(default, skip_serializing_if = "Option::is_none")]
143    pub baseline_eager_modules: Option<usize>,
144    /// Current `eager_modules`; absent for an entry that is gone in this run.
145    #[serde(default, skip_serializing_if = "Option::is_none")]
146    pub current_eager_modules: Option<usize>,
147    /// Packages on the eager path now that the baseline did not have.
148    #[serde(default, skip_serializing_if = "Vec::is_empty")]
149    pub new_eager_packages: Vec<String>,
150    /// Whether the growth of `eager_bytes` is larger than the tolerance.
151    pub exceeded: bool,
152}