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