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}