From 6ec54c53b504eec3873d326b1a45e450227d46ed Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Mon, 31 May 2021 11:42:10 -0300 Subject: [PATCH] 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 --- .changes/dev-path-dist-dir-array.md | 7 + core/tauri-codegen/src/context.rs | 63 ++-- core/tauri-codegen/src/embedded_assets.rs | 44 +++ core/tauri-utils/src/config.rs | 69 +++- core/tauri/src/manager.rs | 6 +- .../test/fixture/src-tauri/tauri.conf.json | 1 - examples/commands/{public => }/index.html | 0 examples/commands/src-tauri/tauri.conf.json | 4 +- examples/helloworld/{public => }/index.html | 0 examples/helloworld/src-tauri/tauri.conf.json | 4 +- examples/multiwindow/{dist => }/index.html | 0 .../multiwindow/src-tauri/tauri.conf.json | 6 +- examples/params/{public => }/index.html | 0 examples/params/src-tauri/tauri.conf.json | 4 +- examples/state/{public => }/index.html | 0 examples/state/src-tauri/tauri.conf.json | 4 +- examples/updater/{public => }/index.html | 0 examples/updater/public/__tauri.js | 329 ------------------ examples/updater/src-tauri/tauri.conf.json | 4 +- tooling/cli.rs/config_definition.rs | 33 +- tooling/cli.rs/schema.json | 30 +- tooling/cli.rs/src/build.rs | 16 +- tooling/cli.rs/src/info.rs | 4 +- 23 files changed, 219 insertions(+), 409 deletions(-) create mode 100644 .changes/dev-path-dist-dir-array.md rename examples/commands/{public => }/index.html (100%) rename examples/helloworld/{public => }/index.html (100%) rename examples/multiwindow/{dist => }/index.html (100%) rename examples/params/{public => }/index.html (100%) rename examples/state/{public => }/index.html (100%) rename examples/updater/{public => }/index.html (100%) delete mode 100644 examples/updater/public/__tauri.js diff --git a/.changes/dev-path-dist-dir-array.md b/.changes/dev-path-dist-dir-array.md new file mode 100644 index 00000000000..1ad11730a00 --- /dev/null +++ b/.changes/dev-path-dist-dir-array.md @@ -0,0 +1,7 @@ +--- +"tauri": patch +"tauri-codegen": patch +"tauri-utils": patch +--- + +Allow `dev_path` and `dist_dir` to be an array of root files and directories to embed. diff --git a/core/tauri-codegen/src/context.rs b/core/tauri-codegen/src/context.rs index 76f02a02332..75a07de0c97 100644 --- a/core/tauri-codegen/src/context.rs +++ b/core/tauri-codegen/src/context.rs @@ -6,7 +6,7 @@ use crate::embedded_assets::{AssetOptions, EmbeddedAssets, EmbeddedAssetsError}; use proc_macro2::TokenStream; use quote::quote; use std::path::PathBuf; -use tauri_utils::config::{Config, WindowUrl}; +use tauri_utils::config::{AppUrl, Config, WindowUrl}; /// Necessary data needed by [`context_codegen`] to generate code for a Tauri application context. pub struct ContextData { @@ -24,44 +24,47 @@ pub fn context_codegen(data: ContextData) -> Result None, - WindowUrl::App(path) => { - if path.components().count() == 0 { - panic!( - "The `{}` configuration cannot be empty", - if dev { "devPath" } else { "distDir" } - ) - } - let assets_path = config_parent.join(path); - if !assets_path.exists() { - panic!( - "The `{}` configuration is set to `{:?}` but this path doesn't exist", - if dev { "devPath" } else { "distDir" }, - path - ) + + let assets = match app_url { + AppUrl::Url(url) => match url { + WindowUrl::External(_) => Default::default(), + WindowUrl::App(path) => { + if path.components().count() == 0 { + panic!( + "The `{}` configuration cannot be empty", + if dev { "devPath" } else { "distDir" } + ) + } + let assets_path = config_parent.join(path); + if !assets_path.exists() { + panic!( + "The `{}` configuration is set to `{:?}` but this path doesn't exist", + if dev { "devPath" } else { "distDir" }, + path + ) + } + EmbeddedAssets::new(&assets_path, options)? } - Some(assets_path) - } + _ => unimplemented!(), + }, + AppUrl::Files(files) => EmbeddedAssets::load_paths( + files.iter().map(|p| config_parent.join(p)).collect(), + options, + )?, _ => unimplemented!(), }; - // generate the assets inside the dist dir into a perfect hash function - let assets = if let Some(assets_path) = assets_path { - let mut options = AssetOptions::new(); - if let Some(csp) = &config.tauri.security.csp { - options = options.csp(csp.clone()); - } - EmbeddedAssets::new(&assets_path, options)? - } else { - Default::default() - }; - // handle default window icons for Windows targets let default_window_icon = if cfg!(windows) { let icon_path = config diff --git a/core/tauri-codegen/src/embedded_assets.rs b/core/tauri-codegen/src/embedded_assets.rs index 38ad8f5807c..e87fbdebc70 100644 --- a/core/tauri-codegen/src/embedded_assets.rs +++ b/core/tauri-codegen/src/embedded_assets.rs @@ -106,6 +106,50 @@ impl EmbeddedAssets { .map(Self) } + /// Compress a list of files and directories. + pub fn load_paths( + paths: Vec, + options: AssetOptions, + ) -> Result { + Ok(Self( + paths + .iter() + .map(|path| { + let is_file = path.is_file(); + WalkDir::new(&path) + .follow_links(true) + .into_iter() + .filter_map(|entry| { + match entry { + // we only serve files, not directory listings + Ok(entry) if entry.file_type().is_dir() => None, + + // compress all files encountered + Ok(entry) => Some(Self::compress_file( + if is_file { + path.parent().unwrap() + } else { + path + }, + entry.path(), + &options, + )), + + // pass down error through filter to fail when encountering any error + Err(error) => Some(Err(EmbeddedAssetsError::Walkdir { + path: path.to_path_buf(), + error, + })), + } + }) + .collect::, _>>() + }) + .flatten() + .flatten() + .collect::<_>(), + )) + } + /// Use highest compression level for release, the fastest one for everything else fn compression_level() -> i32 { let levels = zstd::compression_level_range(); diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 6f385a38cbb..59f50d696d7 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -393,27 +393,40 @@ impl Default for TauriConfig { } } +/// The `dev_path` and `dist_dir` options. +#[derive(PartialEq, Debug, Clone, Deserialize)] +#[serde(untagged)] +#[non_exhaustive] +pub enum AppUrl { + /// A url or file path. + Url(WindowUrl), + /// An array of files. + Files(Vec), +} + /// The Build configuration object. #[derive(PartialEq, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct BuildConfig { /// the devPath config. #[serde(default = "default_dev_path")] - pub dev_path: WindowUrl, + pub dev_path: AppUrl, /// the dist config. #[serde(default = "default_dist_path")] - pub dist_dir: WindowUrl, + pub dist_dir: AppUrl, /// Whether we should inject the Tauri API on `window.__TAURI__` or not. #[serde(default)] pub with_global_tauri: bool, } -fn default_dev_path() -> WindowUrl { - WindowUrl::External(Url::parse("http://localhost:8080").unwrap()) +fn default_dev_path() -> AppUrl { + AppUrl::Url(WindowUrl::External( + Url::parse("http://localhost:8080").unwrap(), + )) } -fn default_dist_path() -> WindowUrl { - WindowUrl::App("../dist".into()) +fn default_dist_path() -> AppUrl { + AppUrl::Url(WindowUrl::App("../dist".into())) } impl Default for BuildConfig { @@ -465,7 +478,7 @@ pub struct PluginConfig(pub HashMap); /// application using tauri while only parsing it once (in the build script). #[cfg(feature = "build")] mod build { - use std::convert::identity; + use std::{convert::identity, path::Path}; use proc_macro2::TokenStream; use quote::{quote, ToTokens, TokenStreamExt}; @@ -511,6 +524,15 @@ mod build { quote! { vec![#(#items),*] } } + /// Create a `PathBuf` constructor `TokenStream`. + /// + /// e.g. `"Hello World" -> String::from("Hello World"). + /// This takes a `&String` to reduce casting all the `&String` -> `&str` manually. + fn path_buf_lit(s: impl AsRef) -> TokenStream { + let s = s.as_ref().to_string_lossy().into_owned(); + quote! { ::std::path::PathBuf::from(#s) } + } + /// Create a map constructor, mapping keys and values with other `TokenStream`s. /// /// This function is pretty generic because the types of keys AND values get transformed. @@ -612,8 +634,8 @@ mod build { tokens.append_all(match self { Self::App(path) => { - let path = path.to_string_lossy().to_string(); - quote! { #prefix::App(::std::path::PathBuf::from(#path)) } + let path = path_buf_lit(&path); + quote! { #prefix::App(#path) } } Self::External(url) => { let url = url.as_str(); @@ -779,6 +801,22 @@ mod build { } } + impl ToTokens for AppUrl { + fn to_tokens(&self, tokens: &mut TokenStream) { + let prefix = quote! { ::tauri::api::config::AppUrl }; + + tokens.append_all(match self { + Self::Url(url) => { + quote! { #prefix::Url(#url) } + } + Self::Files(files) => { + let files = vec_lit(files, path_buf_lit); + quote! { #prefix::Files(#files) } + } + }) + } + } + impl ToTokens for BuildConfig { fn to_tokens(&self, tokens: &mut TokenStream) { let dev_path = &self.dev_path; @@ -810,8 +848,7 @@ mod build { impl ToTokens for SystemTrayConfig { fn to_tokens(&self, tokens: &mut TokenStream) { - let icon_path = self.icon_path.to_string_lossy().to_string(); - let icon_path = quote! { ::std::path::PathBuf::from(#icon_path) }; + let icon_path = path_buf_lit(&self.icon_path); literal_struct!(tokens, SystemTrayConfig, icon_path); } } @@ -936,8 +973,10 @@ mod test { // create a build config let build = BuildConfig { - dev_path: WindowUrl::External(Url::parse("http://localhost:8080").unwrap()), - dist_dir: WindowUrl::App("../dist".into()), + dev_path: AppUrl::Url(WindowUrl::External( + Url::parse("http://localhost:8080").unwrap(), + )), + dist_dir: AppUrl::Url(WindowUrl::App("../dist".into())), with_global_tauri: false, }; @@ -948,7 +987,9 @@ mod test { assert_eq!(d_updater, tauri.updater); assert_eq!( d_path, - WindowUrl::External(Url::parse("http://localhost:8080").unwrap()) + AppUrl::Url(WindowUrl::External( + Url::parse("http://localhost:8080").unwrap() + )) ); assert_eq!(d_title, tauri.windows[0].title); assert_eq!(d_windows, tauri.windows); diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 8552d10b26f..3af7418742a 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -8,7 +8,7 @@ use crate::{ api::{ assets::Assets, - config::{Config, WindowUrl}, + config::{AppUrl, Config, WindowUrl}, path::{resolve_path, BaseDirectory}, PackageInfo, }, @@ -282,7 +282,7 @@ impl WindowManager

{ #[cfg(dev)] fn get_url(&self) -> String { match &self.inner.config.build.dev_path { - WindowUrl::External(url) => url.to_string(), + AppUrl::Url(WindowUrl::External(url)) => url.to_string(), _ => "tauri://localhost".into(), } } @@ -290,7 +290,7 @@ impl WindowManager

{ #[cfg(custom_protocol)] fn get_url(&self) -> String { match &self.inner.config.build.dist_dir { - WindowUrl::External(url) => url.to_string(), + AppUrl::Url(WindowUrl::External(url)) => url.to_string(), _ => "tauri://localhost".into(), } } diff --git a/core/tauri/test/fixture/src-tauri/tauri.conf.json b/core/tauri/test/fixture/src-tauri/tauri.conf.json index 8b3d175198e..092ffa9b999 100644 --- a/core/tauri/test/fixture/src-tauri/tauri.conf.json +++ b/core/tauri/test/fixture/src-tauri/tauri.conf.json @@ -3,7 +3,6 @@ "distDir": "../dist", "devPath": "http://localhost:4000" }, - "ctx": {}, "tauri": { "bundle": { "identifier": "studio.tauri.example", diff --git a/examples/commands/public/index.html b/examples/commands/index.html similarity index 100% rename from examples/commands/public/index.html rename to examples/commands/index.html diff --git a/examples/commands/src-tauri/tauri.conf.json b/examples/commands/src-tauri/tauri.conf.json index cae9dd732fa..588d5e26430 100644 --- a/examples/commands/src-tauri/tauri.conf.json +++ b/examples/commands/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "build": { - "distDir": "../public", - "devPath": "../public", + "distDir": ["../index.html"], + "devPath": ["../index.html"], "beforeDevCommand": "", "beforeBuildCommand": "" }, diff --git a/examples/helloworld/public/index.html b/examples/helloworld/index.html similarity index 100% rename from examples/helloworld/public/index.html rename to examples/helloworld/index.html diff --git a/examples/helloworld/src-tauri/tauri.conf.json b/examples/helloworld/src-tauri/tauri.conf.json index 52a51ef78f8..d43bffaed6a 100644 --- a/examples/helloworld/src-tauri/tauri.conf.json +++ b/examples/helloworld/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "build": { - "distDir": "../public", - "devPath": "../public", + "distDir": ["../index.html"], + "devPath": ["../index.html"], "beforeDevCommand": "", "beforeBuildCommand": "" }, diff --git a/examples/multiwindow/dist/index.html b/examples/multiwindow/index.html similarity index 100% rename from examples/multiwindow/dist/index.html rename to examples/multiwindow/index.html diff --git a/examples/multiwindow/src-tauri/tauri.conf.json b/examples/multiwindow/src-tauri/tauri.conf.json index 59d6f1aa694..90d9e52bc67 100644 --- a/examples/multiwindow/src-tauri/tauri.conf.json +++ b/examples/multiwindow/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "build": { - "distDir": "../dist", - "devPath": "../dist", + "distDir": ["../index.html"], + "devPath": ["../index.html"], "withGlobalTauri": true }, "tauri": { @@ -45,4 +45,4 @@ "active": false } } -} +} \ No newline at end of file diff --git a/examples/params/public/index.html b/examples/params/index.html similarity index 100% rename from examples/params/public/index.html rename to examples/params/index.html diff --git a/examples/params/src-tauri/tauri.conf.json b/examples/params/src-tauri/tauri.conf.json index cae9dd732fa..588d5e26430 100644 --- a/examples/params/src-tauri/tauri.conf.json +++ b/examples/params/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "build": { - "distDir": "../public", - "devPath": "../public", + "distDir": ["../index.html"], + "devPath": ["../index.html"], "beforeDevCommand": "", "beforeBuildCommand": "" }, diff --git a/examples/state/public/index.html b/examples/state/index.html similarity index 100% rename from examples/state/public/index.html rename to examples/state/index.html diff --git a/examples/state/src-tauri/tauri.conf.json b/examples/state/src-tauri/tauri.conf.json index cae9dd732fa..588d5e26430 100644 --- a/examples/state/src-tauri/tauri.conf.json +++ b/examples/state/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "build": { - "distDir": "../public", - "devPath": "../public", + "distDir": ["../index.html"], + "devPath": ["../index.html"], "beforeDevCommand": "", "beforeBuildCommand": "" }, diff --git a/examples/updater/public/index.html b/examples/updater/index.html similarity index 100% rename from examples/updater/public/index.html rename to examples/updater/index.html diff --git a/examples/updater/public/__tauri.js b/examples/updater/public/__tauri.js deleted file mode 100644 index 2dbd701dfaf..00000000000 --- a/examples/updater/public/__tauri.js +++ /dev/null @@ -1,329 +0,0 @@ -// polyfills -if (!String.prototype.startsWith) { - String.prototype.startsWith = function (searchString, position) { - position = position || 0; - return this.substr(position, searchString.length) === searchString; - }; -} - -(function () { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - - var uid = function () { - return ( - s4() + - s4() + - "-" + - s4() + - "-" + - s4() + - "-" + - s4() + - "-" + - s4() + - s4() + - s4() - ); - }; - - function ownKeys(object, enumerableOnly) { - var keys = Object.keys(object); - if (Object.getOwnPropertySymbols) { - var symbols = Object.getOwnPropertySymbols(object); - if (enumerableOnly) - symbols = symbols.filter(function (sym) { - return Object.getOwnPropertyDescriptor(object, sym).enumerable; - }); - keys.push.apply(keys, symbols); - } - return keys; - } - - function _objectSpread(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i] != null ? arguments[i] : {}; - if (i % 2) { - ownKeys(source, true).forEach(function (key) { - _defineProperty(target, key, source[key]); - }); - } else if (Object.getOwnPropertyDescriptors) { - Object.defineProperties( - target, - Object.getOwnPropertyDescriptors(source) - ); - } else { - ownKeys(source).forEach(function (key) { - Object.defineProperty( - target, - key, - Object.getOwnPropertyDescriptor(source, key) - ); - }); - } - } - return target; - } - - function _defineProperty(obj, key, value) { - if (key in obj) { - Object.defineProperty(obj, key, { - value: value, - enumerable: true, - configurable: true, - writable: true, - }); - } else { - obj[key] = value; - } - return obj; - } - - if (!window.__TAURI__) { - window.__TAURI__ = {}; - } - - window.__TAURI__.transformCallback = function transformCallback( - callback, - once - ) { - var identifier = uid(); - - window[identifier] = function (result) { - if (once) { - delete window[identifier]; - } - - return callback && callback(result); - }; - - return identifier; - }; - - window.__TAURI__.invoke = function invoke(cmd, args = {}) { - var _this = this; - - return new Promise(function (resolve, reject) { - var callback = _this.transformCallback(function (r) { - resolve(r); - delete window[error]; - }, true); - var error = _this.transformCallback(function (e) { - reject(e); - delete window[callback]; - }, true); - - if (typeof cmd === "string") { - args.cmd = cmd; - } else if (typeof cmd === "object") { - args = cmd; - } else { - return reject(new Error("Invalid argument type.")); - } - - if (window.rpc) { - window.rpc.notify( - cmd, - _objectSpread( - { - callback: callback, - error: error, - }, - args - ) - ); - } else { - window.addEventListener("DOMContentLoaded", function () { - window.rpc.notify( - cmd, - _objectSpread( - { - callback: callback, - error: error, - }, - args - ) - ); - }); - } - }); - }; - - // open links with the Tauri API - function __openLinks() { - document.querySelector("body").addEventListener( - "click", - function (e) { - var target = e.target; - while (target != null) { - if ( - target.matches ? target.matches("a") : target.msMatchesSelector("a") - ) { - if ( - target.href && - target.href.startsWith("http") && - target.target === "_blank" - ) { - window.__TAURI__.invoke('tauri', { - __tauriModule: "Shell", - message: { - cmd: "open", - uri: target.href, - }, - }); - e.preventDefault(); - } - break; - } - target = target.parentElement; - } - }, - true - ); - } - - if ( - document.readyState === "complete" || - document.readyState === "interactive" - ) { - __openLinks(); - } else { - window.addEventListener( - "DOMContentLoaded", - function () { - __openLinks(); - }, - true - ); - } - - window.__TAURI__.invoke('tauri', { - __tauriModule: "Event", - message: { - cmd: "listen", - event: "tauri://window-created", - handler: window.__TAURI__.transformCallback(function (event) { - if (event.payload) { - var windowLabel = event.payload.label; - window.__TAURI__.__windows.push({ label: windowLabel }); - } - }), - }, - }); - - let permissionSettable = false; - let permissionValue = "default"; - - function isPermissionGranted() { - if (window.Notification.permission !== "default") { - return Promise.resolve(window.Notification.permission === "granted"); - } - return window.__TAURI__.invoke('tauri', { - __tauriModule: "Notification", - message: { - cmd: "isNotificationPermissionGranted", - }, - }); - } - - function setNotificationPermission(value) { - permissionSettable = true; - window.Notification.permission = value; - permissionSettable = false; - } - - function requestPermission() { - return window.__TAURI__ - .invoke('tauri', { - __tauriModule: "Notification", - mainThread: true, - message: { - cmd: "requestNotificationPermission", - }, - }) - .then(function (permission) { - setNotificationPermission(permission); - return permission; - }); - } - - function sendNotification(options) { - if (typeof options === "object") { - Object.freeze(options); - } - - isPermissionGranted().then(function (permission) { - if (permission) { - return window.__TAURI__.invoke('tauri', { - __tauriModule: "Notification", - message: { - cmd: "notification", - options: - typeof options === "string" - ? { - title: options, - } - : options, - }, - }); - } - }); - } - - window.Notification = function (title, options) { - var opts = options || {}; - sendNotification( - Object.assign(opts, { - title: title, - }) - ); - }; - - window.Notification.requestPermission = requestPermission; - - Object.defineProperty(window.Notification, "permission", { - enumerable: true, - get: function () { - return permissionValue; - }, - set: function (v) { - if (!permissionSettable) { - throw new Error("Readonly property"); - } - permissionValue = v; - }, - }); - - isPermissionGranted().then(function (response) { - if (response === null) { - setNotificationPermission("default"); - } else { - setNotificationPermission(response ? "granted" : "denied"); - } - }); - - window.alert = function (message) { - window.__TAURI__.invoke('tauri', { - __tauriModule: "Dialog", - mainThread: true, - message: { - cmd: "messageDialog", - message: message, - }, - }); - }; - - window.confirm = function (message) { - return window.__TAURI__.invoke('tauri', { - __tauriModule: "Dialog", - mainThread: true, - message: { - cmd: "askDialog", - message: message, - }, - }); - }; -})(); diff --git a/examples/updater/src-tauri/tauri.conf.json b/examples/updater/src-tauri/tauri.conf.json index 133d3799e07..95734e51f8b 100644 --- a/examples/updater/src-tauri/tauri.conf.json +++ b/examples/updater/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "build": { - "distDir": "../public", - "devPath": "../public", + "distDir": ["../index.html"], + "devPath": ["../index.html"], "beforeDevCommand": "", "beforeBuildCommand": "" }, diff --git a/tooling/cli.rs/config_definition.rs b/tooling/cli.rs/config_definition.rs index 03573f51173..f153276e83d 100644 --- a/tooling/cli.rs/config_definition.rs +++ b/tooling/cli.rs/config_definition.rs @@ -590,6 +590,25 @@ fn default_dialog() -> Option { Some(true) } +/// The `dev_path` and `dist_dir` options. +#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, JsonSchema)] +#[serde(untagged, deny_unknown_fields)] +pub enum AppUrl { + /// The app's external URL, or the path to the directory containing the app assets. + Url(String), + /// An array of files to embed on the app. + Files(Vec), +} + +impl std::fmt::Display for AppUrl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Url(url) => write!(f, "{}", url), + Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()), + } + } +} + /// The Build configuration object. #[skip_serializing_none] #[derive(Debug, PartialEq, Clone, Deserialize, Serialize, JsonSchema)] @@ -597,12 +616,12 @@ fn default_dialog() -> Option { pub struct BuildConfig { /// The binary used to build and run the application. pub runner: Option, - /// the app's dev server URL, or the path to the directory containing an index.html file + /// The path or URL to use on development. #[serde(default = "default_dev_path")] - pub dev_path: String, + pub dev_path: AppUrl, /// the path to the app's dist dir. This path must contain your index.html file. #[serde(default = "default_dist_dir")] - pub dist_dir: String, + pub dist_dir: AppUrl, /// a shell command to run before `tauri dev` kicks in pub before_dev_command: Option, /// a shell command to run before `tauri build` kicks in @@ -614,12 +633,12 @@ pub struct BuildConfig { pub with_global_tauri: bool, } -fn default_dev_path() -> String { - "".to_string() +fn default_dev_path() -> AppUrl { + AppUrl::Url("".to_string()) } -fn default_dist_dir() -> String { - "../dist".to_string() +fn default_dist_dir() -> AppUrl { + AppUrl::Url("../dist".to_string()) } type JsonObject = HashMap; diff --git a/tooling/cli.rs/schema.json b/tooling/cli.rs/schema.json index 9978dae6258..9ef9fdbe6fa 100644 --- a/tooling/cli.rs/schema.json +++ b/tooling/cli.rs/schema.json @@ -206,6 +206,22 @@ }, "additionalProperties": false }, + "AppUrl": { + "description": "The `dev_path` and `dist_dir` options.", + "anyOf": [ + { + "description": "The app's external URL, or the path to the directory containing the app assets.", + "type": "string" + }, + { + "description": "An array of files to embed on the app.", + "type": "array", + "items": { + "type": "string" + } + } + ] + }, "BuildConfig": { "description": "The Build configuration object.", "type": "object", @@ -225,14 +241,22 @@ ] }, "devPath": { - "description": "the app's dev server URL, or the path to the directory containing an index.html file", + "description": "The path or URL to use on development.", "default": "", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/AppUrl" + } + ] }, "distDir": { "description": "the path to the app's dist dir. This path must contain your index.html file.", "default": "../dist", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/AppUrl" + } + ] }, "features": { "description": "features passed to `cargo` commands", diff --git a/tooling/cli.rs/src/build.rs b/tooling/cli.rs/src/build.rs index 89b35b367f9..5a540d70b77 100644 --- a/tooling/cli.rs/src/build.rs +++ b/tooling/cli.rs/src/build.rs @@ -7,7 +7,7 @@ use tauri_bundler::bundle::{bundle_project, PackageType, SettingsBuilder}; use crate::helpers::{ app_paths::{app_dir, tauri_dir}, - config::get as get_config, + config::{get as get_config, AppUrl}, execute_with_output, manifest::rewrite_manifest, updater_signature::sign_file_from_env_variables, @@ -103,12 +103,14 @@ impl Build { } } - let web_asset_path = PathBuf::from(&config_.build.dist_dir); - if !web_asset_path.exists() { - return Err(anyhow::anyhow!( - "Unable to find your web assets, did you forget to build your web app? Your distDir is set to \"{:?}\".", - web_asset_path - )); + if let AppUrl::Url(url) = &config_.build.dist_dir { + let web_asset_path = PathBuf::from(url); + if !web_asset_path.exists() { + return Err(anyhow::anyhow!( + "Unable to find your web assets, did you forget to build your web app? Your distDir is set to \"{:?}\".", + web_asset_path + )); + } } let runner_from_config = config_.build.runner.clone(); diff --git a/tooling/cli.rs/src/info.rs b/tooling/cli.rs/src/info.rs index 95dea8b5010..7b0ac779d01 100644 --- a/tooling/cli.rs/src/info.rs +++ b/tooling/cli.rs/src/info.rs @@ -545,10 +545,10 @@ impl Info { }) .display(); InfoBlock::new("distDir") - .value(config.build.dist_dir.clone()) + .value(config.build.dist_dir.to_string()) .display(); InfoBlock::new("devPath") - .value(config.build.dev_path.clone()) + .value(config.build.dev_path.to_string()) .display(); } if let Ok(package_json) = read_to_string(app_dir.join("package.json")) {