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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
use crate::config::{
models::ConfigModel,
types::{BaseUrl, Minify},
};
use schemars::JsonSchema;
use serde::{de, Deserialize, Deserializer, Serialize};
use std::{
collections::HashMap,
fmt::{Display, Formatter},
marker::PhantomData,
path::PathBuf,
str::FromStr,
};
/// Config options for the build system.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
pub struct Build {
/// The index HTML file to drive the bundling process
#[serde(default = "default::target")]
pub target: PathBuf,
/// The name of the output HTML file.
///
/// If not set, the file is named "index.html"
#[serde(default = "default::html_output")]
pub html_output: String,
/// Build in release mode [default: false]
#[serde(default)]
pub release: bool,
/// Cargo profile to use.
///
/// Overrides the default chosen by cargo. Ignored if the 'index.html' has one configured.
#[serde(default)]
pub cargo_profile: Option<String>,
/// The output dir for all final assets
#[serde(default = "default::dist")]
pub dist: PathBuf,
/// Run without accessing the network
#[serde(default)]
pub offline: bool,
/// Require Cargo.lock and cache are up to date
#[serde(default)]
pub frozen: bool,
/// Require Cargo.lock is up to date
#[serde(default)]
pub locked: bool,
/// The public URL from which assets are to be served
#[serde(default)]
pub public_url: BaseUrl,
/// Don't add a trailing slash to the public URL if it is missing
#[serde(default)]
pub public_url_no_trailing_slash_fix: bool,
/// Build without default features
#[serde(default)]
pub no_default_features: bool,
/// Build with all features
#[serde(default)]
pub all_features: bool,
/// A comma-separated list of features to activate, must not be used with all-features
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[serde(deserialize_with = "string_or_vec")]
#[schemars(schema_with = "schema::features")]
pub features: Vec<String>,
/// Whether to include hash values in the output file names
#[serde(default = "default::filehash")]
pub filehash: bool,
/// Whether to build an example.
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub example: Option<String>,
/// Optional pattern for the app loader script [default: None]
///
/// Patterns should include the sequences `{base}`, `{wasm}`, and `{js}` in order to
/// properly load the application. Other sequences may be included corresponding
/// to key/value pairs provided in `pattern_params`.
///
/// These values can only be provided via config file.
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern_script: Option<String>,
/// Whether to inject scripts into your index file.
///
/// These values can only be provided via config file.
#[serde(default = "default::inject_scripts")]
pub inject_scripts: bool,
/// Optional pattern for the app preload element [default: None]
///
/// Patterns should include the sequences `{base}`, `{wasm}`, and `{js}` in order to
/// properly preload the application. Other sequences may be included corresponding
/// to key/value pairs provided in `pattern_params`.
///
/// These values can only be provided via config file.
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern_preload: Option<String>,
/// Optional replacement parameters corresponding to the patterns provided in
/// `pattern_script` and `pattern_preload`.
///
/// When a pattern is being replaced with its corresponding value from this map, if the value
/// is prefixed with the symbol `@`, then the value is expected to be a file path, and the
/// pattern will be replaced with the contents of the target file. This allows insertion of
/// some big JSON state or even HTML files as a part of the `index.html` build.
///
/// Trunk will automatically insert the `base`, `wasm` and `js` key/values into this map. In
/// order for the app to be loaded properly, the patterns `{base}`, `{wasm}` and `{js}` should
/// be used in `pattern_script` and `pattern_preload`.
///
/// These values can only be provided via config file.
#[serde(default)]
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub pattern_params: HashMap<String, String>,
/// When desired, set a custom root certificate chain (same format as Cargo's config.toml http.cainfo)
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub root_certificate: Option<String>,
/// Allows request to ignore certificate validation errors.
///
/// Can be useful when behind a corporate proxy.
#[serde(default)]
pub accept_invalid_certs: bool,
/// Control minification.
#[serde(default)]
pub minify: Minify,
/// Allows disabling sub-resource integrity (SRI)
#[serde(default)]
pub no_sri: bool,
/// Ignore error's related to self-closing script elements, and instead issue a warning.
///
/// Since this issue can cause the HTML output to be truncated, only enable this in case you
/// are sure it is caused due to a false positive.
#[serde(default)]
pub allow_self_closing_script: bool,
/// Create 'nonce' attributes with a placeholder.
#[serde(default)]
pub create_nonce: bool,
/// The placeholder which is used in the 'nonce' attribute.
#[serde(default = "default::nonce_placeholder")]
pub nonce_placeholder: String,
}
fn string_or_vec<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
where
T: Deserialize<'de> + FromStr,
T::Err: Display,
D: Deserializer<'de>,
{
struct StringOrVec<T>(PhantomData<fn() -> T>);
impl<'de, T> de::Visitor<'de> for StringOrVec<T>
where
T: FromStr,
T::Err: Display,
{
type Value = Vec<T>;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("string of vec")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(vec![T::from_str(v).map_err(de::Error::custom)?])
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
let mut vec = Vec::new();
while let Some(element) = seq.next_element::<String>()? {
let value = T::from_str(&element).map_err(de::Error::custom)?;
vec.push(value);
}
Ok(vec)
}
}
deserializer.deserialize_any(StringOrVec(PhantomData))
}
impl Default for Build {
fn default() -> Self {
Self {
target: default::target(),
html_output: default::html_output(),
release: false,
cargo_profile: None,
dist: default::dist(),
offline: false,
frozen: false,
locked: false,
public_url: Default::default(),
public_url_no_trailing_slash_fix: false,
no_default_features: false,
all_features: false,
features: vec![],
example: None,
filehash: default::filehash(),
pattern_script: None,
inject_scripts: default::inject_scripts(),
pattern_preload: None,
pattern_params: Default::default(),
root_certificate: None,
accept_invalid_certs: false,
minify: Default::default(),
no_sri: false,
allow_self_closing_script: false,
create_nonce: false,
nonce_placeholder: default::nonce_placeholder(),
}
}
}
mod default {
use crate::config::DIST_DIR;
use std::path::PathBuf;
pub fn dist() -> PathBuf {
DIST_DIR.into()
}
pub fn target() -> PathBuf {
"index.html".into()
}
pub fn html_output() -> String {
"index.html".into()
}
pub const fn filehash() -> bool {
true
}
pub const fn inject_scripts() -> bool {
true
}
pub fn nonce_placeholder() -> String {
"{{__TRUNK NONCE__}}".to_string()
}
}
mod schema {
use schemars::schema::{SchemaObject, SubschemaValidation};
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
pub fn features(gen: &mut SchemaGenerator) -> Schema {
let schema = SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
one_of: Some(vec![
String::json_schema(gen),
Vec::<String>::json_schema(gen),
]),
..Default::default()
})),
..Default::default()
};
schema.into()
}
}
impl ConfigModel for Build {}