Skip to content

Commit 6ec54c5

Browse files
authored
feat(core): allow dev_path, dist_dir as array of paths, fixes #1897 (#1926)
* feat(core): allow `dev_path`, `dist_dir` as array of paths, fixes #1897 * fix: clippy
1 parent cb6c807 commit 6ec54c5

23 files changed

Lines changed: 219 additions & 409 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"tauri": patch
3+
"tauri-codegen": patch
4+
"tauri-utils": patch
5+
---
6+
7+
Allow `dev_path` and `dist_dir` to be an array of root files and directories to embed.

core/tauri-codegen/src/context.rs

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::embedded_assets::{AssetOptions, EmbeddedAssets, EmbeddedAssetsError};
66
use proc_macro2::TokenStream;
77
use quote::quote;
88
use std::path::PathBuf;
9-
use tauri_utils::config::{Config, WindowUrl};
9+
use tauri_utils::config::{AppUrl, Config, WindowUrl};
1010

1111
/// Necessary data needed by [`context_codegen`] to generate code for a Tauri application context.
1212
pub struct ContextData {
@@ -24,44 +24,47 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
2424
config_parent,
2525
root,
2626
} = data;
27+
28+
let mut options = AssetOptions::new();
29+
if let Some(csp) = &config.tauri.security.csp {
30+
options = options.csp(csp.clone());
31+
}
32+
2733
let app_url = if dev {
2834
&config.build.dev_path
2935
} else {
3036
&config.build.dist_dir
3137
};
32-
let assets_path = match app_url {
33-
WindowUrl::External(_) => None,
34-
WindowUrl::App(path) => {
35-
if path.components().count() == 0 {
36-
panic!(
37-
"The `{}` configuration cannot be empty",
38-
if dev { "devPath" } else { "distDir" }
39-
)
40-
}
41-
let assets_path = config_parent.join(path);
42-
if !assets_path.exists() {
43-
panic!(
44-
"The `{}` configuration is set to `{:?}` but this path doesn't exist",
45-
if dev { "devPath" } else { "distDir" },
46-
path
47-
)
38+
39+
let assets = match app_url {
40+
AppUrl::Url(url) => match url {
41+
WindowUrl::External(_) => Default::default(),
42+
WindowUrl::App(path) => {
43+
if path.components().count() == 0 {
44+
panic!(
45+
"The `{}` configuration cannot be empty",
46+
if dev { "devPath" } else { "distDir" }
47+
)
48+
}
49+
let assets_path = config_parent.join(path);
50+
if !assets_path.exists() {
51+
panic!(
52+
"The `{}` configuration is set to `{:?}` but this path doesn't exist",
53+
if dev { "devPath" } else { "distDir" },
54+
path
55+
)
56+
}
57+
EmbeddedAssets::new(&assets_path, options)?
4858
}
49-
Some(assets_path)
50-
}
59+
_ => unimplemented!(),
60+
},
61+
AppUrl::Files(files) => EmbeddedAssets::load_paths(
62+
files.iter().map(|p| config_parent.join(p)).collect(),
63+
options,
64+
)?,
5165
_ => unimplemented!(),
5266
};
5367

54-
// generate the assets inside the dist dir into a perfect hash function
55-
let assets = if let Some(assets_path) = assets_path {
56-
let mut options = AssetOptions::new();
57-
if let Some(csp) = &config.tauri.security.csp {
58-
options = options.csp(csp.clone());
59-
}
60-
EmbeddedAssets::new(&assets_path, options)?
61-
} else {
62-
Default::default()
63-
};
64-
6568
// handle default window icons for Windows targets
6669
let default_window_icon = if cfg!(windows) {
6770
let icon_path = config

core/tauri-codegen/src/embedded_assets.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,50 @@ impl EmbeddedAssets {
106106
.map(Self)
107107
}
108108

109+
/// Compress a list of files and directories.
110+
pub fn load_paths(
111+
paths: Vec<PathBuf>,
112+
options: AssetOptions,
113+
) -> Result<Self, EmbeddedAssetsError> {
114+
Ok(Self(
115+
paths
116+
.iter()
117+
.map(|path| {
118+
let is_file = path.is_file();
119+
WalkDir::new(&path)
120+
.follow_links(true)
121+
.into_iter()
122+
.filter_map(|entry| {
123+
match entry {
124+
// we only serve files, not directory listings
125+
Ok(entry) if entry.file_type().is_dir() => None,
126+
127+
// compress all files encountered
128+
Ok(entry) => Some(Self::compress_file(
129+
if is_file {
130+
path.parent().unwrap()
131+
} else {
132+
path
133+
},
134+
entry.path(),
135+
&options,
136+
)),
137+
138+
// pass down error through filter to fail when encountering any error
139+
Err(error) => Some(Err(EmbeddedAssetsError::Walkdir {
140+
path: path.to_path_buf(),
141+
error,
142+
})),
143+
}
144+
})
145+
.collect::<Result<Vec<Asset>, _>>()
146+
})
147+
.flatten()
148+
.flatten()
149+
.collect::<_>(),
150+
))
151+
}
152+
109153
/// Use highest compression level for release, the fastest one for everything else
110154
fn compression_level() -> i32 {
111155
let levels = zstd::compression_level_range();

core/tauri-utils/src/config.rs

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -393,27 +393,40 @@ impl Default for TauriConfig {
393393
}
394394
}
395395

396+
/// The `dev_path` and `dist_dir` options.
397+
#[derive(PartialEq, Debug, Clone, Deserialize)]
398+
#[serde(untagged)]
399+
#[non_exhaustive]
400+
pub enum AppUrl {
401+
/// A url or file path.
402+
Url(WindowUrl),
403+
/// An array of files.
404+
Files(Vec<PathBuf>),
405+
}
406+
396407
/// The Build configuration object.
397408
#[derive(PartialEq, Deserialize, Debug)]
398409
#[serde(rename_all = "camelCase")]
399410
pub struct BuildConfig {
400411
/// the devPath config.
401412
#[serde(default = "default_dev_path")]
402-
pub dev_path: WindowUrl,
413+
pub dev_path: AppUrl,
403414
/// the dist config.
404415
#[serde(default = "default_dist_path")]
405-
pub dist_dir: WindowUrl,
416+
pub dist_dir: AppUrl,
406417
/// Whether we should inject the Tauri API on `window.__TAURI__` or not.
407418
#[serde(default)]
408419
pub with_global_tauri: bool,
409420
}
410421

411-
fn default_dev_path() -> WindowUrl {
412-
WindowUrl::External(Url::parse("http://localhost:8080").unwrap())
422+
fn default_dev_path() -> AppUrl {
423+
AppUrl::Url(WindowUrl::External(
424+
Url::parse("http://localhost:8080").unwrap(),
425+
))
413426
}
414427

415-
fn default_dist_path() -> WindowUrl {
416-
WindowUrl::App("../dist".into())
428+
fn default_dist_path() -> AppUrl {
429+
AppUrl::Url(WindowUrl::App("../dist".into()))
417430
}
418431

419432
impl Default for BuildConfig {
@@ -465,7 +478,7 @@ pub struct PluginConfig(pub HashMap<String, JsonValue>);
465478
/// application using tauri while only parsing it once (in the build script).
466479
#[cfg(feature = "build")]
467480
mod build {
468-
use std::convert::identity;
481+
use std::{convert::identity, path::Path};
469482

470483
use proc_macro2::TokenStream;
471484
use quote::{quote, ToTokens, TokenStreamExt};
@@ -511,6 +524,15 @@ mod build {
511524
quote! { vec![#(#items),*] }
512525
}
513526

527+
/// Create a `PathBuf` constructor `TokenStream`.
528+
///
529+
/// e.g. `"Hello World" -> String::from("Hello World").
530+
/// This takes a `&String` to reduce casting all the `&String` -> `&str` manually.
531+
fn path_buf_lit(s: impl AsRef<Path>) -> TokenStream {
532+
let s = s.as_ref().to_string_lossy().into_owned();
533+
quote! { ::std::path::PathBuf::from(#s) }
534+
}
535+
514536
/// Create a map constructor, mapping keys and values with other `TokenStream`s.
515537
///
516538
/// This function is pretty generic because the types of keys AND values get transformed.
@@ -612,8 +634,8 @@ mod build {
612634

613635
tokens.append_all(match self {
614636
Self::App(path) => {
615-
let path = path.to_string_lossy().to_string();
616-
quote! { #prefix::App(::std::path::PathBuf::from(#path)) }
637+
let path = path_buf_lit(&path);
638+
quote! { #prefix::App(#path) }
617639
}
618640
Self::External(url) => {
619641
let url = url.as_str();
@@ -779,6 +801,22 @@ mod build {
779801
}
780802
}
781803

804+
impl ToTokens for AppUrl {
805+
fn to_tokens(&self, tokens: &mut TokenStream) {
806+
let prefix = quote! { ::tauri::api::config::AppUrl };
807+
808+
tokens.append_all(match self {
809+
Self::Url(url) => {
810+
quote! { #prefix::Url(#url) }
811+
}
812+
Self::Files(files) => {
813+
let files = vec_lit(files, path_buf_lit);
814+
quote! { #prefix::Files(#files) }
815+
}
816+
})
817+
}
818+
}
819+
782820
impl ToTokens for BuildConfig {
783821
fn to_tokens(&self, tokens: &mut TokenStream) {
784822
let dev_path = &self.dev_path;
@@ -810,8 +848,7 @@ mod build {
810848

811849
impl ToTokens for SystemTrayConfig {
812850
fn to_tokens(&self, tokens: &mut TokenStream) {
813-
let icon_path = self.icon_path.to_string_lossy().to_string();
814-
let icon_path = quote! { ::std::path::PathBuf::from(#icon_path) };
851+
let icon_path = path_buf_lit(&self.icon_path);
815852
literal_struct!(tokens, SystemTrayConfig, icon_path);
816853
}
817854
}
@@ -936,8 +973,10 @@ mod test {
936973

937974
// create a build config
938975
let build = BuildConfig {
939-
dev_path: WindowUrl::External(Url::parse("http://localhost:8080").unwrap()),
940-
dist_dir: WindowUrl::App("../dist".into()),
976+
dev_path: AppUrl::Url(WindowUrl::External(
977+
Url::parse("http://localhost:8080").unwrap(),
978+
)),
979+
dist_dir: AppUrl::Url(WindowUrl::App("../dist".into())),
941980
with_global_tauri: false,
942981
};
943982

@@ -948,7 +987,9 @@ mod test {
948987
assert_eq!(d_updater, tauri.updater);
949988
assert_eq!(
950989
d_path,
951-
WindowUrl::External(Url::parse("http://localhost:8080").unwrap())
990+
AppUrl::Url(WindowUrl::External(
991+
Url::parse("http://localhost:8080").unwrap()
992+
))
952993
);
953994
assert_eq!(d_title, tauri.windows[0].title);
954995
assert_eq!(d_windows, tauri.windows);

core/tauri/src/manager.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use crate::{
99
api::{
1010
assets::Assets,
11-
config::{Config, WindowUrl},
11+
config::{AppUrl, Config, WindowUrl},
1212
path::{resolve_path, BaseDirectory},
1313
PackageInfo,
1414
},
@@ -282,15 +282,15 @@ impl<P: Params> WindowManager<P> {
282282
#[cfg(dev)]
283283
fn get_url(&self) -> String {
284284
match &self.inner.config.build.dev_path {
285-
WindowUrl::External(url) => url.to_string(),
285+
AppUrl::Url(WindowUrl::External(url)) => url.to_string(),
286286
_ => "tauri://localhost".into(),
287287
}
288288
}
289289

290290
#[cfg(custom_protocol)]
291291
fn get_url(&self) -> String {
292292
match &self.inner.config.build.dist_dir {
293-
WindowUrl::External(url) => url.to_string(),
293+
AppUrl::Url(WindowUrl::External(url)) => url.to_string(),
294294
_ => "tauri://localhost".into(),
295295
}
296296
}

core/tauri/test/fixture/src-tauri/tauri.conf.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"distDir": "../dist",
44
"devPath": "http://localhost:4000"
55
},
6-
"ctx": {},
76
"tauri": {
87
"bundle": {
98
"identifier": "studio.tauri.example",

examples/commands/src-tauri/tauri.conf.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"build": {
3-
"distDir": "../public",
4-
"devPath": "../public",
3+
"distDir": ["../index.html"],
4+
"devPath": ["../index.html"],
55
"beforeDevCommand": "",
66
"beforeBuildCommand": ""
77
},

examples/helloworld/src-tauri/tauri.conf.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"build": {
3-
"distDir": "../public",
4-
"devPath": "../public",
3+
"distDir": ["../index.html"],
4+
"devPath": ["../index.html"],
55
"beforeDevCommand": "",
66
"beforeBuildCommand": ""
77
},

0 commit comments

Comments
 (0)