mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-01 10:01:07 +02:00
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
@@ -15,6 +15,7 @@ use tauri_codegen::{context_codegen, ContextData};
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "codegen")))]
|
||||
#[derive(Debug)]
|
||||
pub struct CodegenContext {
|
||||
dev: bool,
|
||||
config_path: PathBuf,
|
||||
out_file: PathBuf,
|
||||
}
|
||||
@@ -22,6 +23,7 @@ pub struct CodegenContext {
|
||||
impl Default for CodegenContext {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dev: false,
|
||||
config_path: PathBuf::from("tauri.conf.json"),
|
||||
out_file: PathBuf::from("tauri-build-context.rs"),
|
||||
}
|
||||
@@ -60,6 +62,13 @@ impl CodegenContext {
|
||||
self
|
||||
}
|
||||
|
||||
/// Run the codegen in a `dev` context, meaning that Tauri is using a dev server or local file for development purposes,
|
||||
/// usually with the `tauri dev` CLI command.
|
||||
pub fn dev(mut self) -> Self {
|
||||
self.dev = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Generate the code and write it to the output file - returning the path it was saved to.
|
||||
///
|
||||
/// Unless you are doing something special with this builder, you don't need to do anything with
|
||||
@@ -80,6 +89,7 @@ impl CodegenContext {
|
||||
pub fn try_build(self) -> Result<PathBuf> {
|
||||
let (config, config_parent) = tauri_codegen::get_config(&self.config_path)?;
|
||||
let code = context_codegen(ContextData {
|
||||
dev: self.dev,
|
||||
config,
|
||||
config_parent,
|
||||
// it's very hard to have a build script for unit tests, so assume this is always called from
|
||||
|
||||
@@ -10,6 +10,7 @@ description = "code generation meant to be consumed inside of `tauri` through `t
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
blake3 = { version = "0.3", features = ["rayon"] }
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
@@ -17,4 +18,4 @@ serde_json = "1"
|
||||
tauri-api = { path = "../../tauri-api", features = ["build"] }
|
||||
thiserror = "1"
|
||||
walkdir = "2"
|
||||
zstd = "0.6"
|
||||
zstd = "0.7"
|
||||
|
||||
@@ -6,6 +6,7 @@ use tauri_api::config::Config;
|
||||
|
||||
/// Necessary data needed by [`codegen_context`] to generate code for a Tauri application context.
|
||||
pub struct ContextData {
|
||||
pub dev: bool,
|
||||
pub config: Config,
|
||||
pub config_parent: PathBuf,
|
||||
pub context_path: TokenStream,
|
||||
@@ -14,15 +15,28 @@ pub struct ContextData {
|
||||
/// Build an `AsTauriContext` implementation for including in application code.
|
||||
pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsError> {
|
||||
let ContextData {
|
||||
mut config,
|
||||
dev,
|
||||
config,
|
||||
config_parent,
|
||||
context_path,
|
||||
} = data;
|
||||
let dist_dir = config_parent.join(&config.build.dist_dir);
|
||||
config.build.dist_dir = dist_dir.to_string_lossy().to_string();
|
||||
let assets_path = if dev {
|
||||
// if dev_path is a dev server, we don't have any assets to embed
|
||||
if config.build.dev_path.starts_with("http") {
|
||||
None
|
||||
} else {
|
||||
Some(config_parent.join(&config.build.dev_path))
|
||||
}
|
||||
} else {
|
||||
Some(config_parent.join(&config.build.dist_dir))
|
||||
};
|
||||
|
||||
// generate the assets inside the dist dir into a perfect hash function
|
||||
let assets = EmbeddedAssets::new(&dist_dir)?;
|
||||
let assets = if let Some(assets_path) = assets_path {
|
||||
EmbeddedAssets::new(&assets_path)?
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
// handle default window icons for Windows targets
|
||||
let default_window_icon = if cfg!(windows) {
|
||||
|
||||
@@ -2,17 +2,21 @@ use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env::var,
|
||||
fs::File,
|
||||
io::BufReader,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tauri_api::assets::AssetKey;
|
||||
use thiserror::Error;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
/// The subdirectory inside the target directory we want to place assets.
|
||||
const TARGET_PATH: &str = "tauri-codegen-assets";
|
||||
|
||||
/// The minimum size needed for the hasher to use multiple threads.
|
||||
const MULTI_HASH_SIZE_LIMIT: usize = 131_072; // 128KiB
|
||||
|
||||
/// (key, (original filepath, compressed bytes))
|
||||
type Asset = (AssetKey, (String, Vec<u8>));
|
||||
type Asset = (AssetKey, (PathBuf, PathBuf));
|
||||
|
||||
/// All possible errors while reading and compressing an [`EmbeddedAssets`] directory
|
||||
#[derive(Debug, Error)]
|
||||
@@ -37,6 +41,9 @@ pub enum EmbeddedAssetsError {
|
||||
path: PathBuf,
|
||||
error: walkdir::Error,
|
||||
},
|
||||
|
||||
#[error("OUT_DIR env var is not set, do you have a build script?")]
|
||||
OutDir,
|
||||
}
|
||||
|
||||
/// Represent a directory of assets that are compressed and embedded.
|
||||
@@ -48,7 +55,8 @@ pub enum EmbeddedAssetsError {
|
||||
/// The assets are compressed during this runtime, and can only be represented as a [`TokenStream`]
|
||||
/// through [`ToTokens`]. The generated code is meant to be injected into an application to include
|
||||
/// the compressed assets in that application's binary.
|
||||
pub struct EmbeddedAssets(HashMap<AssetKey, (String, Vec<u8>)>);
|
||||
#[derive(Default)]
|
||||
pub struct EmbeddedAssets(HashMap<AssetKey, (PathBuf, PathBuf)>);
|
||||
|
||||
impl EmbeddedAssets {
|
||||
/// Compress a directory of assets, ready to be generated into a [`tauri_api::assets::Assets`].
|
||||
@@ -75,29 +83,64 @@ impl EmbeddedAssets {
|
||||
|
||||
/// Use highest compression level for release, the fastest one for everything else
|
||||
fn compression_level() -> i32 {
|
||||
match var("PROFILE").as_ref().map(String::as_str) {
|
||||
Ok("release") => 22,
|
||||
_ => -5,
|
||||
let levels = zstd::compression_level_range();
|
||||
if cfg!(debug_assertions) {
|
||||
*levels.start()
|
||||
} else {
|
||||
*levels.end()
|
||||
}
|
||||
}
|
||||
|
||||
/// Compress a file and spit out the information in a [`HashMap`] friendly form.
|
||||
fn compress_file(prefix: &Path, path: &Path) -> Result<Asset, EmbeddedAssetsError> {
|
||||
let reader =
|
||||
File::open(&path)
|
||||
.map(BufReader::new)
|
||||
.map_err(|error| EmbeddedAssetsError::AssetRead {
|
||||
let input = std::fs::read(path).map_err(|error| EmbeddedAssetsError::AssetRead {
|
||||
path: path.to_owned(),
|
||||
error,
|
||||
})?;
|
||||
|
||||
// we must canonicalize the base of our paths to allow long paths on windows
|
||||
let out_dir = std::env::var("OUT_DIR")
|
||||
.map_err(|_| EmbeddedAssetsError::OutDir)
|
||||
.map(PathBuf::from)
|
||||
.and_then(|p| p.canonicalize().map_err(|_| EmbeddedAssetsError::OutDir))
|
||||
.map(|p| p.join(TARGET_PATH))?;
|
||||
|
||||
// make sure that our output directory is created
|
||||
std::fs::create_dir_all(&out_dir).map_err(|_| EmbeddedAssetsError::OutDir)?;
|
||||
|
||||
// get a hash of the input - allows for caching existing files
|
||||
let hash = {
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
if input.len() < MULTI_HASH_SIZE_LIMIT {
|
||||
hasher.update(&input);
|
||||
} else {
|
||||
hasher.update_with_join::<blake3::join::RayonJoin>(&input);
|
||||
}
|
||||
hasher.finalize().to_hex()
|
||||
};
|
||||
|
||||
// use the content hash to determine filename, keep extensions that exist
|
||||
let out_path = if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
|
||||
out_dir.join(format!("{}.{}", hash, ext))
|
||||
} else {
|
||||
out_dir.join(hash.to_string())
|
||||
};
|
||||
|
||||
// only compress and write to the file if it doesn't already exist.
|
||||
if !out_path.exists() {
|
||||
let out_file = File::create(&out_path).map_err(|error| EmbeddedAssetsError::AssetWrite {
|
||||
path: out_path.clone(),
|
||||
error,
|
||||
})?;
|
||||
|
||||
// entirely write input to the output file path with compression
|
||||
zstd::stream::copy_encode(&*input, out_file, Self::compression_level()).map_err(|error| {
|
||||
EmbeddedAssetsError::AssetWrite {
|
||||
path: path.to_owned(),
|
||||
error,
|
||||
})?;
|
||||
|
||||
// entirely read compressed asset into bytes
|
||||
let bytes = zstd::encode_all(reader, Self::compression_level()).map_err(|error| {
|
||||
EmbeddedAssetsError::AssetWrite {
|
||||
path: path.to_owned(),
|
||||
error,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
// get a key to the asset path without the asset directory prefix
|
||||
let key = path
|
||||
@@ -108,20 +151,22 @@ impl EmbeddedAssets {
|
||||
path: path.to_owned(),
|
||||
})?;
|
||||
|
||||
Ok((key, (path.display().to_string(), bytes)))
|
||||
Ok((key, (path.into(), out_path)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for EmbeddedAssets {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let mut map = TokenStream::new();
|
||||
for (key, (original, bytes)) in &self.0 {
|
||||
for (key, (input, output)) in &self.0 {
|
||||
let key: &str = key.as_ref();
|
||||
let input = input.display().to_string();
|
||||
let output = output.display().to_string();
|
||||
|
||||
// add original asset as a compiler dependency, rely on dead code elimination to clean it up
|
||||
map.append_all(quote!(#key => {
|
||||
const _: &[u8] = include_bytes!(#original);
|
||||
&[#(#bytes),*]
|
||||
const _: &[u8] = include_bytes!(#input);
|
||||
include_bytes!(#output)
|
||||
},));
|
||||
}
|
||||
|
||||
|
||||
3
examples/api/src-tauri/.cargo/config
Normal file
3
examples/api/src-tauri/.cargo/config
Normal file
@@ -0,0 +1,3 @@
|
||||
# using lld linker can make the compile time 30% of the default linker time.
|
||||
[build]
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"build": {
|
||||
"distDir": "../public",
|
||||
"devPath": "http://localhost:5000",
|
||||
"devPath": "../public",
|
||||
"beforeDevCommand": "yarn dev",
|
||||
"beforeBuildCommand": "yarn build"
|
||||
},
|
||||
@@ -24,7 +24,11 @@
|
||||
"name": "theme",
|
||||
"takesValue": true,
|
||||
"description": "App theme",
|
||||
"possibleValues": ["light", "dark", "system"]
|
||||
"possibleValues": [
|
||||
"light",
|
||||
"dark",
|
||||
"system"
|
||||
]
|
||||
},
|
||||
{
|
||||
"short": "v",
|
||||
@@ -77,4 +81,4 @@
|
||||
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
examples/helloworld/src-tauri/.cargo/config
Normal file
3
examples/helloworld/src-tauri/.cargo/config
Normal file
@@ -0,0 +1,3 @@
|
||||
# using lld linker can make the compile time 30% of the default linker time.
|
||||
[build]
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
|
||||
3
examples/multiwindow/src-tauri/.cargo/config
Normal file
3
examples/multiwindow/src-tauri/.cargo/config
Normal file
@@ -0,0 +1,3 @@
|
||||
# using lld linker can make the compile time 30% of the default linker time.
|
||||
[build]
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
|
||||
@@ -17,3 +17,6 @@ proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = { version = "1", features = [ "full" ] }
|
||||
tauri-codegen = { path = "../core/tauri-codegen" }
|
||||
|
||||
[features]
|
||||
custom-protocol = []
|
||||
|
||||
@@ -72,6 +72,7 @@ pub(crate) fn generate_context(context: ContextItems) -> TokenStream {
|
||||
let context = get_config(&context.config_file)
|
||||
.map_err(|e| e.to_string())
|
||||
.map(|(config, config_parent)| ContextData {
|
||||
dev: cfg!(not(feature = "custom-protocol")),
|
||||
config,
|
||||
config_parent,
|
||||
context_path: context.context_path.to_token_stream(),
|
||||
|
||||
@@ -14,7 +14,7 @@ serde_json = "1.0"
|
||||
sysinfo = "0.10"
|
||||
thiserror = "1.0.19"
|
||||
phf = { version = "0.8", features = ["macros"] }
|
||||
zstd = "0.6"
|
||||
zstd = "0.7"
|
||||
|
||||
# build feature only
|
||||
proc-macro2 = { version = "1.0", optional = true }
|
||||
|
||||
@@ -43,7 +43,7 @@ serde = { version = "1.0", features = [ "derive" ] }
|
||||
|
||||
[features]
|
||||
cli = [ "tauri-api/cli" ]
|
||||
custom-protocol = [ ]
|
||||
custom-protocol = [ "tauri-macros/custom-protocol" ]
|
||||
api-all = [ "tauri-api/notification", "tauri-api/global-shortcut", "updater" ]
|
||||
updater = [ "tauri-updater" ]
|
||||
|
||||
|
||||
@@ -106,19 +106,7 @@ where
|
||||
if self.inner.config.build.dev_path.starts_with("http") {
|
||||
self.inner.config.build.dev_path.clone()
|
||||
} else {
|
||||
let path = "index.html";
|
||||
format!(
|
||||
"data:text/html;base64,{}",
|
||||
base64::encode(
|
||||
self
|
||||
.inner
|
||||
.assets
|
||||
.get(&path)
|
||||
.ok_or_else(|| crate::Error::AssetNotFound(path.to_string()))
|
||||
.map(Cow::into_owned)
|
||||
.expect("Unable to find `index.html` under your devPath folder")
|
||||
)
|
||||
)
|
||||
format!("tauri://{}", self.inner.config.tauri.bundle.identifier)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user