mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-27 13:52:47 -07:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89ad91a055 | |||
| d810047ddd | |||
| 19c73f7747 | |||
| 2f2d772882 | |||
| 32e300f730 | |||
| 2f31d14194 | |||
| 980aa0d07f | |||
| 07351587f3 | |||
| 3027b828a8 | |||
| eef2cd26e0 | |||
| 8190915e3a | |||
| dd157d6ef9 | |||
| 58f4ecb260 | |||
| ab441d1fa7 | |||
| 7538031261 | |||
| 18f66ff79b | |||
| b48f7244b7 |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tuono"
|
||||
version = "0.19.4"
|
||||
version = "0.19.7"
|
||||
edition = "2024"
|
||||
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
||||
description = "Superfast React fullstack framework"
|
||||
@@ -24,6 +24,7 @@ tracing-subscriber = {version = "0.3.19", features = ["env-filter"]}
|
||||
miette = "7.2.0"
|
||||
|
||||
colored = "2.1.0"
|
||||
once_cell = "1.19.0"
|
||||
watchexec = "5.0.0"
|
||||
watchexec-signals = "4.0.0"
|
||||
watchexec-events = "4.0.0"
|
||||
@@ -36,7 +37,7 @@ reqwest = { version = "0.12.4", features = ["blocking", "json"] }
|
||||
serde_json = "1.0"
|
||||
fs_extra = "1.3.0"
|
||||
http = "1.1.0"
|
||||
tuono_internal = {path = "../tuono_internal", version = "0.19.4"}
|
||||
tuono_internal = {path = "../tuono_internal", version = "0.19.7"}
|
||||
spinners = "4.1.1"
|
||||
console = "0.15.10"
|
||||
convert_case = "0.8.0"
|
||||
|
||||
@@ -253,7 +253,6 @@ impl App {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -19,6 +22,12 @@ use crate::source_builder::SourceBuilder;
|
||||
use console::Term;
|
||||
use spinners::{Spinner, Spinners};
|
||||
|
||||
fn is_css_module(file_name: Cow<str>) -> bool {
|
||||
static RE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^.*\.module\.(css|scss|sass|less|styl|stylus)$").unwrap());
|
||||
RE.is_match(&file_name)
|
||||
}
|
||||
|
||||
fn ssr_reload_needed(path: &Path) -> bool {
|
||||
let file_name_starts_with_env = path
|
||||
.file_name()
|
||||
@@ -27,7 +36,12 @@ fn ssr_reload_needed(path: &Path) -> bool {
|
||||
|
||||
let file_path = path.to_string_lossy();
|
||||
|
||||
file_name_starts_with_env || file_path.ends_with("sx") || file_path.ends_with("mdx")
|
||||
file_name_starts_with_env
|
||||
|| file_path.ends_with("sx")
|
||||
|| file_path.ends_with("mdx")
|
||||
// When a CSS module is modified
|
||||
// also the class names get modified.
|
||||
|| is_css_module(file_path)
|
||||
}
|
||||
|
||||
#[allow(
|
||||
|
||||
@@ -102,7 +102,7 @@ fn read_http_methods_from_file(path: &String) -> Vec<Method> {
|
||||
// Extract just the element surrounded by the phrantesist.
|
||||
.replace("tuono_lib::api(", "")
|
||||
.replace(")]", "");
|
||||
Method::from_str(http_method.as_str()).unwrap_or(Method::GET)
|
||||
Method::from_str(http_method.to_uppercase().as_str()).unwrap_or(Method::GET)
|
||||
})
|
||||
.collect::<Vec<Method>>()
|
||||
}
|
||||
@@ -283,7 +283,6 @@ impl Route {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -107,6 +107,41 @@ fn it_successfully_create_multiple_api_for_the_same_file() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn it_successfully_import_mixed_case_routes() {
|
||||
let temp_tuono_project = TempTuonoProject::new();
|
||||
|
||||
for method in ["get", "post", "put", "delete", "patch"] {
|
||||
temp_tuono_project.add_file_with_content(
|
||||
&format!("./src/routes/api/{}_lower.rs", method),
|
||||
&format!(r"#[tuono_lib::api({})]", method),
|
||||
);
|
||||
temp_tuono_project.add_file_with_content(
|
||||
&format!("./src/routes/api/{}_upper.rs", method),
|
||||
&format!(r"#[tuono_lib::api({})]", method.to_uppercase()),
|
||||
);
|
||||
}
|
||||
|
||||
let mut test_tuono_build = Command::cargo_bin("tuono").unwrap();
|
||||
test_tuono_build
|
||||
.arg("build")
|
||||
.arg("--no-js-emit")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let temp_main_rs_path = temp_tuono_project.path().join(".tuono/main.rs");
|
||||
|
||||
let temp_main_rs_content =
|
||||
fs::read_to_string(&temp_main_rs_path).expect("Failed to read '.tuono/main.rs' content.");
|
||||
|
||||
for method in ["get", "post", "put", "delete", "patch"] {
|
||||
let expected = format!(r#"use tuono_lib::axum::routing::{};"#, method);
|
||||
let imports = temp_main_rs_content.match_indices(&expected);
|
||||
assert_eq!(imports.count(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn it_successfully_create_catch_all_routes() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tuono_internal"
|
||||
version = "0.19.4"
|
||||
version = "0.19.7"
|
||||
edition = "2024"
|
||||
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
||||
description = "Superfast React fullstack framework"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tuono_lib"
|
||||
version = "0.19.4"
|
||||
version = "0.19.7"
|
||||
edition = "2024"
|
||||
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
||||
description = "Superfast React fullstack framework"
|
||||
@@ -17,7 +17,7 @@ include = [
|
||||
|
||||
|
||||
[dependencies]
|
||||
ssr_rs = "0.8.2"
|
||||
ssr_rs = "0.8.3"
|
||||
axum = {version = "0.8.1", features = ["json", "ws"]}
|
||||
axum-extra = {version = "0.10.0", features = ["cookie"]}
|
||||
tokio = { version = "1.37.0", features = ["full"] }
|
||||
@@ -32,8 +32,8 @@ either = "1.13.0"
|
||||
tower-http = {version = "0.6.0", features = ["fs"]}
|
||||
colored = "3.0.0"
|
||||
|
||||
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.19.4"}
|
||||
tuono_internal = {path = "../tuono_internal", version = "0.19.4"}
|
||||
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.19.7"}
|
||||
tuono_internal = {path = "../tuono_internal", version = "0.19.7"}
|
||||
# Match the same version used by axum
|
||||
tokio-tungstenite = "0.26.0"
|
||||
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
||||
|
||||
+478
-130
@@ -1,4 +1,6 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use once_cell::sync::OnceCell;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
@@ -7,167 +9,513 @@ use std::path::PathBuf;
|
||||
|
||||
const VITE_MANIFEST_PATH: &str = "./out/client/.vite/manifest.json";
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BundleInfo {
|
||||
/// TODO: Add also the import field and load the dynamic
|
||||
/// values in the payload bundles.
|
||||
pub file: String,
|
||||
#[serde(default = "default_css_vector")]
|
||||
pub css: Vec<String>,
|
||||
fn has_dynamic_path(pathname: &str) -> bool {
|
||||
static RE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"\[(.*?)\]").expect("Invalid regex for dynamic path detection"));
|
||||
RE.is_match(pathname)
|
||||
}
|
||||
|
||||
fn default_css_vector() -> Vec<String> {
|
||||
/// ViteManifest is the mapping between the vite output bundled files
|
||||
/// and the originals.
|
||||
/// Vite doc: https://vitejs.dev/config/build-options.html#build-manifest
|
||||
pub type ViteManifest = HashMap<String, BundleInfo>;
|
||||
|
||||
fn empty_vector() -> Vec<String> {
|
||||
Vec::with_capacity(0)
|
||||
}
|
||||
|
||||
/// Manifest is the mapping between the vite output bundled files
|
||||
/// and the originals.
|
||||
/// Vite doc: https://vitejs.dev/config/build-options.html#build-manifest
|
||||
pub static MANIFEST: OnceCell<HashMap<String, BundleInfo>> = OnceCell::new();
|
||||
|
||||
pub fn load_manifest() {
|
||||
let file = File::open(PathBuf::from(VITE_MANIFEST_PATH)).unwrap();
|
||||
let reader = BufReader::new(file);
|
||||
let manifest = serde_json::from_reader(reader).unwrap();
|
||||
let _ = MANIFEST.set(remap_manifest_keys(manifest));
|
||||
/// Interface representing the bundle information
|
||||
/// as they are in the vite manifest.json.
|
||||
///
|
||||
/// Used for deserialization
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BundleInfo {
|
||||
pub file: String,
|
||||
#[serde(default = "empty_vector")]
|
||||
pub css: Vec<String>,
|
||||
#[serde(default = "empty_vector")]
|
||||
pub imports: Vec<String>,
|
||||
// TODO: Add also dynamic imports
|
||||
}
|
||||
|
||||
fn remap_manifest_keys(manifest: HashMap<String, BundleInfo>) -> HashMap<String, BundleInfo> {
|
||||
let mut new_hashmap = HashMap::new();
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct RouteBundle {
|
||||
pub css_files: Vec<String>,
|
||||
pub js_files: Vec<String>,
|
||||
}
|
||||
|
||||
manifest.keys().for_each(|key| {
|
||||
let new_key = key
|
||||
.replace("../src/routes", "")
|
||||
.replace(".tsx", "")
|
||||
.replace(".jsx", "")
|
||||
.replace("index", "");
|
||||
#[derive(Debug)]
|
||||
pub struct Manifest {
|
||||
/// The mapping between the route and the bundle
|
||||
bundles: HashMap<String, RouteBundle>,
|
||||
}
|
||||
|
||||
new_hashmap.insert(new_key, manifest.get(key).unwrap().clone());
|
||||
});
|
||||
fn clean_route_path(path: String) -> String {
|
||||
let path = path
|
||||
.replace("../src/routes", "")
|
||||
.replace(".tsx", "")
|
||||
.replace(".mdx", "")
|
||||
.replace(".md", "")
|
||||
.replace(".jsx", "");
|
||||
|
||||
new_hashmap
|
||||
if path == "/index" {
|
||||
return "/".to_string();
|
||||
}
|
||||
|
||||
path.replace("/index", "")
|
||||
}
|
||||
|
||||
impl From<ViteManifest> for Manifest {
|
||||
fn from(manifest: ViteManifest) -> Self {
|
||||
let mut bundles = HashMap::new();
|
||||
let client_main = manifest
|
||||
.get("client-main.tsx")
|
||||
// client-main.tsx is the entry point and always exists
|
||||
.expect("client-main.tsx not found in the manifest")
|
||||
.clone();
|
||||
|
||||
for (key, bundle) in &manifest {
|
||||
if key.contains("__layout") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if key == "client-main.tsx" {
|
||||
bundles.insert(
|
||||
"client-main".to_string(),
|
||||
RouteBundle {
|
||||
css_files: bundle.css.clone(),
|
||||
js_files: vec![bundle.file.clone()],
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let route = clean_route_path(key.clone());
|
||||
|
||||
// Skip components/utils files
|
||||
if !route.starts_with("/") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let css_files = [bundle.css.clone(), client_main.css.clone()].concat();
|
||||
let js_files = vec![bundle.file.clone(), client_main.file.clone()];
|
||||
|
||||
let mut route_bundle = RouteBundle {
|
||||
css_files,
|
||||
js_files,
|
||||
};
|
||||
|
||||
// the imports bundle always contains at least the client-main
|
||||
if bundle.imports.len() > 1 {
|
||||
for import in &bundle.imports {
|
||||
if import == "client-main.tsx" {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(import_bundle) = manifest.get(import) {
|
||||
route_bundle.js_files.push(import_bundle.file.clone());
|
||||
route_bundle.css_files.extend(import_bundle.css.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bundles.insert(route, route_bundle);
|
||||
}
|
||||
|
||||
// Add __layout imports
|
||||
for (key, layout_bundle) in &manifest {
|
||||
let route = clean_route_path(key.clone());
|
||||
if route.contains("__layout") {
|
||||
let path_included_in_layout = route.replace("__layout", "");
|
||||
|
||||
let mut layout_css_files: Vec<String> = Vec::new();
|
||||
let mut layout_js_files: Vec<String> = Vec::new();
|
||||
|
||||
for import in &layout_bundle.imports {
|
||||
if import == "client-main.tsx" {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(import_bundle) = manifest.get(import) {
|
||||
layout_js_files.push(import_bundle.file.clone());
|
||||
layout_css_files.extend(import_bundle.css.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for (key, route_bundles) in &mut bundles {
|
||||
if key.starts_with(path_included_in_layout.as_str()) {
|
||||
route_bundles.js_files.push(layout_bundle.file.clone());
|
||||
route_bundles.css_files.extend(layout_bundle.css.clone());
|
||||
route_bundles.js_files.extend(layout_js_files.clone());
|
||||
route_bundles.css_files.extend(layout_css_files.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Manifest { bundles }
|
||||
}
|
||||
}
|
||||
|
||||
impl Manifest {
|
||||
/// This method adds the route specific bundles to the server
|
||||
/// side rendered HTML.
|
||||
///
|
||||
/// The same matching algorithm is implemented on the client side in
|
||||
/// this file (packages/tuono/src/router/components/Matches.ts).
|
||||
///
|
||||
/// Optimizations should occour on both.
|
||||
pub fn get_bundle_from_pathname(&self, pathname: &str) -> RouteBundle {
|
||||
// Exact match
|
||||
if let Some(bundle) = self.bundles.get(pathname) {
|
||||
return bundle.clone();
|
||||
}
|
||||
|
||||
let dynamic_routes = self
|
||||
.bundles
|
||||
.keys()
|
||||
.filter(|path| has_dynamic_path(path))
|
||||
.collect::<Vec<&String>>();
|
||||
|
||||
if !dynamic_routes.is_empty() {
|
||||
let path_segments = pathname
|
||||
.split('/')
|
||||
.filter(|path| !path.is_empty())
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
'_dynamic_routes_loop: for dyn_route in dynamic_routes.iter() {
|
||||
let dyn_route_segments = dyn_route
|
||||
.split('/')
|
||||
.filter(|path| !path.is_empty())
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
let mut route_segments_collector: Vec<&str> = Vec::new();
|
||||
|
||||
for i in 0..dyn_route_segments.len() {
|
||||
// Catch all dynamic route
|
||||
if dyn_route_segments[i].starts_with("[...") {
|
||||
route_segments_collector.push(dyn_route_segments[i]);
|
||||
|
||||
let manifest_key = route_segments_collector.join("/");
|
||||
|
||||
let route_data = self.bundles.get(&format!("/{manifest_key}"));
|
||||
|
||||
if let Some(data) = route_data {
|
||||
return data.clone();
|
||||
}
|
||||
break '_dynamic_routes_loop;
|
||||
}
|
||||
if path_segments.len() == i {
|
||||
break;
|
||||
}
|
||||
if dyn_route_segments[i] == path_segments[i]
|
||||
|| has_dynamic_path(dyn_route_segments[i])
|
||||
{
|
||||
route_segments_collector.push(dyn_route_segments[i])
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if route_segments_collector.len() == path_segments.len() {
|
||||
let manifest_key = route_segments_collector.join("/");
|
||||
|
||||
let route_data = self.bundles.get(&format!("/{manifest_key}"));
|
||||
if let Some(data) = route_data {
|
||||
return data.clone();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No dynamic routes, return the client main bundle
|
||||
if let Some(bundle) = self.bundles.get("client-main") {
|
||||
return bundle.clone();
|
||||
}
|
||||
|
||||
// This should never happen because client-main always exists
|
||||
RouteBundle::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub static MANIFEST: OnceCell<Manifest> = OnceCell::new();
|
||||
|
||||
/// Load the vite manifest from the file system and set it in the MANIFEST
|
||||
/// static variable.
|
||||
pub fn load_manifest() -> std::io::Result<()> {
|
||||
let file = File::open(PathBuf::from(VITE_MANIFEST_PATH))?;
|
||||
let reader = BufReader::new(file);
|
||||
let manifest: ViteManifest = serde_json::from_reader(reader)?;
|
||||
MANIFEST
|
||||
.set(Manifest::from(manifest))
|
||||
.map_err(|_| std::io::Error::other("Failed to set the manifest"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// This manifest is an example of a complex vite manifest.json
|
||||
// It includes dynamic routes, static routes, catch all routes, nested
|
||||
// __layout and shared components.
|
||||
const MANIFEST_EXAMPLE: &str = r#"{
|
||||
"../src/routes/about.tsx": {
|
||||
"file": "assets/about-C3UqHfGb.js",
|
||||
"name": "about",
|
||||
"src": "../src/routes/about.tsx",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"client-main.tsx",
|
||||
"_FileWithCssOnly.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/about-DUhMJ_Ze.css"
|
||||
]
|
||||
},
|
||||
"_FileWithCssOnly.js": {
|
||||
"file": "assets/FileWithCssOnly.js",
|
||||
"name": "FileWithCssOnly",
|
||||
"imports": [
|
||||
"client-main.tsx"
|
||||
],
|
||||
"css": [
|
||||
"assets/FileWithCssOnly.css"
|
||||
]
|
||||
},
|
||||
"../src/routes/catch_all/[...slug].tsx": {
|
||||
"file": "assets/_...slug_-CpJyPnPj.js",
|
||||
"name": "_...slug_",
|
||||
"src": "../src/routes/catch_all/[...slug].tsx",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"client-main.tsx"
|
||||
],
|
||||
"css": [
|
||||
"assets/_..-CipbPoTl.css"
|
||||
]
|
||||
},
|
||||
"../src/routes/index.tsx": {
|
||||
"file": "assets/index-B3tnHOzi.js",
|
||||
"name": "index",
|
||||
"src": "../src/routes/index.tsx",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"client-main.tsx"
|
||||
],
|
||||
"css": [
|
||||
"assets/index-CynfArjF.css"
|
||||
]
|
||||
},
|
||||
"../src/routes/pokemons/[pokemon]/[type].tsx": {
|
||||
"file": "assets/_type_-B-sJOcVJ.js",
|
||||
"name": "_type_",
|
||||
"src": "../src/routes/pokemons/[pokemon]/[type].tsx",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"client-main.tsx",
|
||||
"_PokemonView-jNGFFO0j.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/_type_-B8vgxybx.css"
|
||||
]
|
||||
},
|
||||
"../src/routes/pokemons/[pokemon]/index.tsx": {
|
||||
"file": "assets/index-ByRBj7WK.js",
|
||||
"name": "index",
|
||||
"src": "../src/routes/pokemons/[pokemon]/index.tsx",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"client-main.tsx",
|
||||
"_PokemonView-jNGFFO0j.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/index-CM86zKWq.css"
|
||||
]
|
||||
},
|
||||
"../src/routes/pokemons/__layout.tsx": {
|
||||
"file": "assets/__layout-2v3JiSeL.js",
|
||||
"name": "__layout",
|
||||
"src": "../src/routes/pokemons/__layout.tsx",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"client-main.tsx"
|
||||
],
|
||||
"css": [
|
||||
"assets/__layout-CXGGqNw5.css"
|
||||
]
|
||||
},
|
||||
"_PokemonView-BcJZaQaO.css": {
|
||||
"file": "assets/PokemonView-BcJZaQaO.css",
|
||||
"src": "_PokemonView-BcJZaQaO.css"
|
||||
},
|
||||
"_PokemonView-jNGFFO0j.js": {
|
||||
"file": "assets/PokemonView-jNGFFO0j.js",
|
||||
"name": "PokemonView",
|
||||
"imports": [
|
||||
"client-main.tsx"
|
||||
],
|
||||
"css": [
|
||||
"assets/PokemonView-BcJZaQaO.css"
|
||||
]
|
||||
},
|
||||
"client-main.tsx": {
|
||||
"file": "assets/client-main-DOdr9gvl.js",
|
||||
"name": "client-main",
|
||||
"src": "client-main.tsx",
|
||||
"isEntry": true,
|
||||
"dynamicImports": [
|
||||
"../src/routes/pokemons/__layout.tsx",
|
||||
"../src/routes/about.tsx",
|
||||
"../src/routes/index.tsx",
|
||||
"../src/routes/catch_all/[...slug].tsx",
|
||||
"../src/routes/pokemons/[pokemon]/[type].tsx",
|
||||
"../src/routes/pokemons/[pokemon]/index.tsx"
|
||||
],
|
||||
"css": [
|
||||
"assets/client-main-BS7N-NIa.css"
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
#[test]
|
||||
fn correctly_parse_the_manifest_json() {
|
||||
let manifest_example = r#"{
|
||||
"../src/routes/index.tsx": {
|
||||
"file": "assets/index.js",
|
||||
"name": "index",
|
||||
"src": "../src/routes/index.tsx",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"client-main.tsx"
|
||||
],
|
||||
"css": [
|
||||
"assets/index.css"
|
||||
]
|
||||
},
|
||||
"meta-tags-lib": {
|
||||
"file": "assets/meta-lib.js",
|
||||
"name": "meta-tags-lib",
|
||||
"imports": [
|
||||
"client-main.tsx"
|
||||
]
|
||||
},
|
||||
"client-main.tsx": {
|
||||
"file": "assets/client-main.js",
|
||||
"name": "client-main",
|
||||
"src": "client-main.tsx",
|
||||
"isEntry": true,
|
||||
"dynamicImports": [
|
||||
"../src/routes/index.tsx",
|
||||
"../src/routes/pokemons/[pokemon].tsx"
|
||||
],
|
||||
"css": [
|
||||
"assets/client-main.css"
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
fn it_correctly_cleans_the_route_path() {
|
||||
let cleaned_path = clean_route_path("../src/routes/index.tsx".to_string());
|
||||
assert_eq!(cleaned_path, "/");
|
||||
|
||||
let parsed_manifest =
|
||||
serde_json::from_str::<HashMap<String, BundleInfo>>(manifest_example).unwrap();
|
||||
let cleaned_path =
|
||||
clean_route_path("../src/routes/pokemons/[pokemon]/index.tsx".to_string());
|
||||
assert_eq!(cleaned_path, "/pokemons/[pokemon]");
|
||||
|
||||
let mut result = HashMap::new();
|
||||
result.insert(
|
||||
"../src/routes/index.tsx".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/index.js".to_string(),
|
||||
css: vec!["assets/index.css".to_string()],
|
||||
},
|
||||
);
|
||||
result.insert(
|
||||
"client-main.tsx".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/client-main.js".to_string(),
|
||||
css: vec!["assets/client-main.css".to_string()],
|
||||
},
|
||||
);
|
||||
let cleaned_path = clean_route_path("../src/routes/pokemons/__layout.tsx".to_string());
|
||||
assert_eq!(cleaned_path, "/pokemons/__layout");
|
||||
|
||||
result.insert(
|
||||
"meta-tags-lib".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/meta-lib.js".to_string(),
|
||||
css: Vec::new(),
|
||||
},
|
||||
);
|
||||
let cleaned_path =
|
||||
clean_route_path("../src/routes/pokemons/[pokemon]/[type].mdx".to_string());
|
||||
assert_eq!(cleaned_path, "/pokemons/[pokemon]/[type]");
|
||||
|
||||
assert_eq!(parsed_manifest, result);
|
||||
let cleaned_path = clean_route_path("../src/routes/about.md".to_string());
|
||||
assert_eq!(cleaned_path, "/about");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_correctly_remap_the_manifest() {
|
||||
let mut parsed_manifest: HashMap<String, BundleInfo> = HashMap::new();
|
||||
parsed_manifest.insert(
|
||||
"../src/routes/index.tsx".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/index.js".to_string(),
|
||||
css: vec!["assets/index.css".to_string()],
|
||||
},
|
||||
);
|
||||
parsed_manifest.insert(
|
||||
"../src/routes/about.jsx".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/about.js".to_string(),
|
||||
css: vec!["assets/about.css".to_string()],
|
||||
},
|
||||
);
|
||||
parsed_manifest.insert(
|
||||
"../src/routes/posts/[post].tsx".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/posts/[post].js".to_string(),
|
||||
css: vec!["assets/posts/[post].css".to_string()],
|
||||
},
|
||||
);
|
||||
parsed_manifest.insert(
|
||||
"client-main.tsx".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/main.js".to_string(),
|
||||
css: vec!["assets/main.css".to_string()],
|
||||
},
|
||||
);
|
||||
fn correctly_parse_the_manifest_json() {
|
||||
let parsed_manifest = serde_json::from_str::<ViteManifest>(MANIFEST_EXAMPLE).unwrap();
|
||||
|
||||
let remapped = remap_manifest_keys(parsed_manifest);
|
||||
let manifest = Manifest::from(parsed_manifest);
|
||||
assert_eq!(manifest.bundles.len(), 6);
|
||||
let index_route = manifest.get_bundle_from_pathname("/");
|
||||
|
||||
assert_eq!(
|
||||
remapped.get("/").unwrap().file,
|
||||
"assets/index.js".to_string()
|
||||
index_route.css_files,
|
||||
vec![
|
||||
"assets/index-CynfArjF.css",
|
||||
"assets/client-main-BS7N-NIa.css"
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
index_route.js_files,
|
||||
vec!["assets/index-B3tnHOzi.js", "assets/client-main-DOdr9gvl.js"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_load_the_correct_single_dyn_path() {
|
||||
let parsed_manifest = serde_json::from_str::<ViteManifest>(MANIFEST_EXAMPLE).unwrap();
|
||||
|
||||
let manifest = Manifest::from(parsed_manifest);
|
||||
let nested_route = manifest.get_bundle_from_pathname("/pokemons/ditto");
|
||||
|
||||
assert_eq!(
|
||||
nested_route.css_files,
|
||||
vec![
|
||||
"assets/index-CM86zKWq.css",
|
||||
"assets/client-main-BS7N-NIa.css",
|
||||
"assets/PokemonView-BcJZaQaO.css",
|
||||
"assets/__layout-CXGGqNw5.css"
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
remapped.get("/about").unwrap().file,
|
||||
"assets/about.js".to_string()
|
||||
nested_route.js_files,
|
||||
vec![
|
||||
"assets/index-ByRBj7WK.js",
|
||||
"assets/client-main-DOdr9gvl.js",
|
||||
"assets/PokemonView-jNGFFO0j.js",
|
||||
"assets/__layout-2v3JiSeL.js"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_load_the_correct_nested_dyn_path_bundles() {
|
||||
let parsed_manifest = serde_json::from_str::<ViteManifest>(MANIFEST_EXAMPLE).unwrap();
|
||||
|
||||
let manifest = Manifest::from(parsed_manifest);
|
||||
let route = manifest.get_bundle_from_pathname("/pokemons/charizard/fire");
|
||||
|
||||
assert_eq!(
|
||||
route.css_files,
|
||||
vec![
|
||||
"assets/_type_-B8vgxybx.css",
|
||||
"assets/client-main-BS7N-NIa.css",
|
||||
"assets/PokemonView-BcJZaQaO.css",
|
||||
"assets/__layout-CXGGqNw5.css"
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
route.js_files,
|
||||
vec![
|
||||
"assets/_type_-B-sJOcVJ.js",
|
||||
"assets/client-main-DOdr9gvl.js",
|
||||
"assets/PokemonView-jNGFFO0j.js",
|
||||
"assets/__layout-2v3JiSeL.js"
|
||||
]
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn should_load_the_correct_catch_all_bundles() {
|
||||
let parsed_manifest = serde_json::from_str::<ViteManifest>(MANIFEST_EXAMPLE).unwrap();
|
||||
|
||||
let manifest = Manifest::from(parsed_manifest);
|
||||
let route = manifest.get_bundle_from_pathname("/catch_all/some/random/path");
|
||||
|
||||
assert_eq!(
|
||||
route.css_files,
|
||||
vec!["assets/_..-CipbPoTl.css", "assets/client-main-BS7N-NIa.css"]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
route.js_files,
|
||||
vec![
|
||||
"assets/_...slug_-CpJyPnPj.js",
|
||||
"assets/client-main-DOdr9gvl.js"
|
||||
]
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn should_load_the_defined_path_bundles() {
|
||||
let parsed_manifest = serde_json::from_str::<ViteManifest>(MANIFEST_EXAMPLE).unwrap();
|
||||
|
||||
let manifest = Manifest::from(parsed_manifest);
|
||||
let route = manifest.get_bundle_from_pathname("/about");
|
||||
assert_eq!(
|
||||
route.css_files,
|
||||
vec![
|
||||
"assets/about-DUhMJ_Ze.css",
|
||||
"assets/client-main-BS7N-NIa.css",
|
||||
"assets/FileWithCssOnly.css"
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
remapped.get("/posts/[post]").unwrap().file,
|
||||
"assets/posts/[post].js".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
remapped.get("client-main").unwrap().file,
|
||||
"assets/main.js".to_string()
|
||||
route.js_files,
|
||||
vec![
|
||||
"assets/about-C3UqHfGb.js",
|
||||
"assets/client-main-DOdr9gvl.js",
|
||||
"assets/FileWithCssOnly.js"
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+66
-251
@@ -2,17 +2,11 @@ use crate::config::GLOBAL_CONFIG;
|
||||
use crate::manifest::MANIFEST;
|
||||
use crate::mode::{GLOBAL_MODE, Mode};
|
||||
use erased_serde::Serialize;
|
||||
use regex::Regex;
|
||||
use serde::Serialize as SerdeSerialize;
|
||||
use tuono_internal::config::ServerConfig;
|
||||
|
||||
use crate::request::{Location, Request};
|
||||
|
||||
fn has_dynamic_path(route: &str) -> bool {
|
||||
let regex = Regex::new(r"\[(.*?)\]").expect("Failed to create the regex");
|
||||
regex.is_match(route)
|
||||
}
|
||||
|
||||
#[derive(SerdeSerialize)]
|
||||
/// This is the payload sent to the client for hydration
|
||||
pub struct Payload<'a> {
|
||||
@@ -20,9 +14,9 @@ pub struct Payload<'a> {
|
||||
data: &'a dyn Serialize,
|
||||
mode: Mode,
|
||||
#[serde(rename(serialize = "jsBundles"))]
|
||||
js_bundles: Option<Vec<&'a String>>,
|
||||
js_bundles: Option<Vec<String>>,
|
||||
#[serde(rename(serialize = "cssBundles"))]
|
||||
css_bundles: Option<Vec<&'a String>>,
|
||||
css_bundles: Option<Vec<String>>,
|
||||
#[serde(rename(serialize = "devServerConfig"))]
|
||||
dev_server_config: Option<&'a ServerConfig>,
|
||||
}
|
||||
@@ -58,102 +52,11 @@ impl<'a> Payload<'a> {
|
||||
serde_json::to_string(&self)
|
||||
}
|
||||
|
||||
/// This method adds the route specific bundles to the server
|
||||
/// side rendered HTML.
|
||||
///
|
||||
/// The same matching algorithm is implemented on the client side in
|
||||
/// this file (packages/tuono/src/router/components/Matches.ts).
|
||||
///
|
||||
/// Optimizations should occour on both.
|
||||
fn add_bundle_sources(&mut self) {
|
||||
// Manifest should always be loaded. The load happen before starting
|
||||
// the server.
|
||||
let manifest = MANIFEST.get().expect("Failed to load manifest");
|
||||
|
||||
// The main bundle should always exist.
|
||||
// The extension should be tsx even with JS only projects.
|
||||
let main_bundle = manifest
|
||||
.get("client-main")
|
||||
.expect("Failed to get client-main bundle");
|
||||
|
||||
let mut js_bundles_sources = vec![&main_bundle.file];
|
||||
let mut css_bundles_sources = main_bundle.css.iter().collect::<Vec<&String>>();
|
||||
|
||||
let pathname = &self.location.pathname();
|
||||
|
||||
let bundle_data = manifest.get(*pathname);
|
||||
|
||||
if let Some(data) = bundle_data {
|
||||
js_bundles_sources.push(&data.file);
|
||||
|
||||
data.css
|
||||
.iter()
|
||||
.for_each(|source| css_bundles_sources.push(source))
|
||||
} else {
|
||||
let dynamic_routes = manifest
|
||||
.keys()
|
||||
.filter(|path| has_dynamic_path(path))
|
||||
.collect::<Vec<&String>>();
|
||||
|
||||
if !dynamic_routes.is_empty() {
|
||||
let path_segments = pathname
|
||||
.split('/')
|
||||
.filter(|path| !path.is_empty())
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
'_dynamic_routes_loop: for dyn_route in dynamic_routes.iter() {
|
||||
let dyn_route_segments = dyn_route
|
||||
.split('/')
|
||||
.filter(|path| !path.is_empty())
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
let mut route_segments_collector: Vec<&str> = Vec::new();
|
||||
|
||||
for i in 0..dyn_route_segments.len() {
|
||||
if dyn_route_segments[i].starts_with("[...") {
|
||||
route_segments_collector.push(dyn_route_segments[i]);
|
||||
|
||||
let manifest_key = route_segments_collector.join("/");
|
||||
|
||||
let route_data = manifest.get(&format!("/{manifest_key}"));
|
||||
if let Some(data) = route_data {
|
||||
js_bundles_sources.push(&data.file);
|
||||
data.css
|
||||
.iter()
|
||||
.for_each(|source| css_bundles_sources.push(source))
|
||||
}
|
||||
break '_dynamic_routes_loop;
|
||||
}
|
||||
if path_segments.len() == i {
|
||||
break;
|
||||
}
|
||||
if dyn_route_segments[i] == path_segments[i]
|
||||
|| has_dynamic_path(dyn_route_segments[i])
|
||||
{
|
||||
route_segments_collector.push(dyn_route_segments[i])
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if route_segments_collector.len() == path_segments.len() {
|
||||
let manifest_key = route_segments_collector.join("/");
|
||||
|
||||
let route_data = manifest.get(&format!("/{manifest_key}"));
|
||||
if let Some(data) = route_data {
|
||||
js_bundles_sources.push(&data.file);
|
||||
data.css
|
||||
.iter()
|
||||
.for_each(|source| css_bundles_sources.push(source))
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.js_bundles = Some(js_bundles_sources);
|
||||
self.css_bundles = Some(css_bundles_sources);
|
||||
let manifest = MANIFEST.get().expect("Manifest not loaded");
|
||||
let bundles = manifest.get_bundle_from_pathname(self.location.pathname());
|
||||
self.js_bundles = Some(bundles.js_files);
|
||||
self.css_bundles = Some(bundles.css_files);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,69 +64,66 @@ impl<'a> Payload<'a> {
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::manifest::ViteManifest;
|
||||
use axum::http::Uri;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::manifest::BundleInfo;
|
||||
const MANIFEST_EXAMPLE: &str = r#"{
|
||||
"../src/routes/index.tsx": {
|
||||
"file": "assets/index-D-yFyCZo.js",
|
||||
"name": "index",
|
||||
"src": "../src/routes/index.tsx",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"client-main.tsx"
|
||||
],
|
||||
"css": [
|
||||
"assets/index-CynfArjF.css"
|
||||
]
|
||||
},
|
||||
"../src/routes/pokemons/[pokemon].tsx": {
|
||||
"file": "assets/_pokemon_-DlFInatQ.js",
|
||||
"name": "_pokemon_",
|
||||
"src": "../src/routes/pokemons/[pokemon].tsx",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"client-main.tsx"
|
||||
],
|
||||
"css": [
|
||||
"assets/_pokemon_-BcJZaQaO.css"
|
||||
]
|
||||
},
|
||||
"../src/routes/pokemons/__layout.tsx": {
|
||||
"file": "assets/__layout-BFnT3M7X.js",
|
||||
"name": "__layout",
|
||||
"src": "../src/routes/pokemons/__layout.tsx",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"client-main.tsx"
|
||||
],
|
||||
"css": [
|
||||
"assets/__layout-CXGGqNw5.css"
|
||||
]
|
||||
},
|
||||
"client-main.tsx": {
|
||||
"file": "assets/client-main-B9g1NVV7.js",
|
||||
"name": "client-main",
|
||||
"src": "client-main.tsx",
|
||||
"isEntry": true,
|
||||
"dynamicImports": [
|
||||
"../src/routes/pokemons/__layout.tsx",
|
||||
"../src/routes/index.tsx",
|
||||
"../src/routes/pokemons/[pokemon].tsx"
|
||||
],
|
||||
"css": [
|
||||
"assets/client-main-BS7N-NIa.css"
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
fn prepare_payload(uri: Option<&str>, mode: Mode) -> Payload {
|
||||
let mut manifest_mock = HashMap::new();
|
||||
manifest_mock.insert(
|
||||
"client-main".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/bundled-file.js".to_string(),
|
||||
css: vec!["assets/bundled-file.css".to_string()],
|
||||
},
|
||||
);
|
||||
manifest_mock.insert(
|
||||
"/".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/index.js".to_string(),
|
||||
|
||||
css: vec!["assets/index.css".to_string()],
|
||||
},
|
||||
);
|
||||
manifest_mock.insert(
|
||||
"/posts/[post]".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/posts/[post].js".to_string(),
|
||||
|
||||
css: vec!["assets/posts/[post].css".to_string()],
|
||||
},
|
||||
);
|
||||
manifest_mock.insert(
|
||||
"/posts/[post]/[comment]".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/posts/[post]/[comment].js".to_string(),
|
||||
|
||||
css: vec!["assets/posts/[post]/[comment].css".to_string()],
|
||||
},
|
||||
);
|
||||
manifest_mock.insert(
|
||||
"/pokemons/[...catch_all]".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/catch_all.js".to_string(),
|
||||
css: vec!["assets/catch_all.css".to_string()],
|
||||
},
|
||||
);
|
||||
|
||||
manifest_mock.insert(
|
||||
"/posts/custom-post".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/custom-post.js".to_string(),
|
||||
|
||||
css: vec!["assets/custom-post.css".to_string()],
|
||||
},
|
||||
);
|
||||
manifest_mock.insert(
|
||||
"/about".to_string(),
|
||||
BundleInfo {
|
||||
file: "assets/about.js".to_string(),
|
||||
|
||||
css: vec!["assets/about.css".to_string()],
|
||||
},
|
||||
);
|
||||
MANIFEST.get_or_init(|| manifest_mock);
|
||||
let manifest_mock = serde_json::from_str::<ViteManifest>(MANIFEST_EXAMPLE)
|
||||
.expect("Failed to parse the manifest example");
|
||||
MANIFEST.get_or_init(|| manifest_mock.into());
|
||||
|
||||
let uri = uri
|
||||
.unwrap_or("http://localhost:3000/")
|
||||
@@ -250,15 +150,15 @@ mod tests {
|
||||
assert_eq!(
|
||||
payload.js_bundles,
|
||||
Some(vec![
|
||||
&"assets/bundled-file.js".to_string(),
|
||||
&"assets/index.js".to_string()
|
||||
"assets/index-D-yFyCZo.js".to_string(),
|
||||
"assets/client-main-B9g1NVV7.js".to_string()
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
payload.css_bundles,
|
||||
Some(vec![
|
||||
&"assets/bundled-file.css".to_string(),
|
||||
&"assets/index.css".to_string()
|
||||
"assets/index-CynfArjF.css".to_string(),
|
||||
"assets/client-main-BS7N-NIa.css".to_string()
|
||||
])
|
||||
);
|
||||
}
|
||||
@@ -270,89 +170,4 @@ mod tests {
|
||||
assert!(payload.js_bundles.is_none());
|
||||
assert!(payload.css_bundles.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_load_the_correct_single_dyn_path_bundles() {
|
||||
let mut payload = prepare_payload(Some("http://localhost:3000/posts/a-post"), Mode::Prod);
|
||||
let _ = payload.client_payload();
|
||||
assert_eq!(
|
||||
payload.js_bundles,
|
||||
Some(vec![
|
||||
&"assets/bundled-file.js".to_string(),
|
||||
&"assets/posts/[post].js".to_string()
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
payload.css_bundles,
|
||||
Some(vec![
|
||||
&"assets/bundled-file.css".to_string(),
|
||||
&"assets/posts/[post].css".to_string()
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_load_the_correct_nested_dyn_path_bundles() {
|
||||
let mut payload = prepare_payload(
|
||||
Some("http://localhost:3000/posts/a-post/a-comment"),
|
||||
Mode::Prod,
|
||||
);
|
||||
let _ = payload.client_payload();
|
||||
assert_eq!(
|
||||
payload.js_bundles,
|
||||
Some(vec![
|
||||
&"assets/bundled-file.js".to_string(),
|
||||
&"assets/posts/[post]/[comment].js".to_string()
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
payload.css_bundles,
|
||||
Some(vec![
|
||||
&"assets/bundled-file.css".to_string(),
|
||||
&"assets/posts/[post]/[comment].css".to_string()
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_load_the_correct_catch_all_bundles() {
|
||||
let mut payload = prepare_payload(
|
||||
Some("http://localhost:3000/pokemons/a-poke/a-poke"),
|
||||
Mode::Prod,
|
||||
);
|
||||
let _ = payload.client_payload();
|
||||
assert_eq!(
|
||||
payload.js_bundles,
|
||||
Some(vec![
|
||||
&"assets/bundled-file.js".to_string(),
|
||||
&"assets/catch_all.js".to_string()
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
payload.css_bundles,
|
||||
Some(vec![
|
||||
&"assets/bundled-file.css".to_string(),
|
||||
&"assets/catch_all.css".to_string()
|
||||
])
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn should_load_the_defined_path_bundles() {
|
||||
let mut payload = prepare_payload(Some("http://localhost:3000/about"), Mode::Prod);
|
||||
let _ = payload.client_payload();
|
||||
assert_eq!(
|
||||
payload.js_bundles,
|
||||
Some(vec![
|
||||
&"assets/bundled-file.js".to_string(),
|
||||
&"assets/about.js".to_string()
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
payload.css_bundles,
|
||||
Some(vec![
|
||||
&"assets/bundled-file.css".to_string(),
|
||||
&"assets/about.css".to_string()
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,9 @@ impl Server {
|
||||
let _ = GLOBAL_CONFIG.set(config.clone());
|
||||
|
||||
if mode == Mode::Prod {
|
||||
load_manifest()
|
||||
if let Err(err) = load_manifest() {
|
||||
tuono_println!("Failed to load vite manifest: {}", err.to_string().red());
|
||||
}
|
||||
}
|
||||
|
||||
let server_address = format!("{}:{}", config.server.host, config.server.port);
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
use crate::config::GLOBAL_CONFIG;
|
||||
use axum::body::Body;
|
||||
use axum::extract::Path;
|
||||
use axum::extract::{Path, Query};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use axum::http::{HeaderName, HeaderValue};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use reqwest::Client;
|
||||
|
||||
pub async fn vite_reverse_proxy(Path(path): Path<String>) -> impl IntoResponse {
|
||||
pub async fn vite_reverse_proxy(
|
||||
Path(path): Path<String>,
|
||||
query: Query<HashMap<String, String>>,
|
||||
) -> impl IntoResponse {
|
||||
let client = Client::new();
|
||||
|
||||
let config = GLOBAL_CONFIG
|
||||
@@ -19,7 +23,24 @@ pub async fn vite_reverse_proxy(Path(path): Path<String>) -> impl IntoResponse {
|
||||
config.server.port + 1
|
||||
);
|
||||
|
||||
match client.get(format!("{vite_url}/{path}")).send().await {
|
||||
let query_string = query
|
||||
.0
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{}={}", k, v))
|
||||
.collect::<Vec<_>>()
|
||||
.join("&");
|
||||
|
||||
let query_string = if query_string.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("?{}", query_string)
|
||||
};
|
||||
|
||||
match client
|
||||
.get(format!("{vite_url}/{path}{query_string}"))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(res) => {
|
||||
let mut response_builder = Response::builder().status(res.status().as_u16());
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tuono_lib_macros"
|
||||
version = "0.19.4"
|
||||
version = "0.19.7"
|
||||
edition = "2024"
|
||||
description = "Superfast React fullstack framework"
|
||||
homepage = "https://tuono.dev"
|
||||
|
||||
+1
-2
@@ -31,7 +31,7 @@ const tuonoEslintConfig = tseslint.config(
|
||||
// #endregion shared
|
||||
|
||||
// #region package-specific
|
||||
'packages/tuono-fs-router-vite-plugin/tests/generator/**',
|
||||
'packages/tuono-react-vite-plugin/tests/generator/**',
|
||||
|
||||
'packages/tuono-lazy-fn-vite-plugin/tests/sources/**',
|
||||
|
||||
@@ -69,7 +69,6 @@ const tuonoEslintConfig = tseslint.config(
|
||||
{
|
||||
files: [REACT_FILES_MATCH],
|
||||
plugins: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
'react-hooks': eslintPluginReactHooks,
|
||||
},
|
||||
rules: {
|
||||
|
||||
@@ -1 +1 @@
|
||||
@import 'tailwindcss';
|
||||
@import 'tailwindcss' source('../..');
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"eslint-plugin-react-hooks": "5.2.0",
|
||||
"prettier": "3.5.3",
|
||||
"turbo": "2.5.0",
|
||||
"turbo": "2.5.3",
|
||||
"typescript": "5.7.3",
|
||||
"typescript-eslint": "8.29.1"
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { normalize } from 'node:path'
|
||||
|
||||
import type { Plugin } from 'vite'
|
||||
|
||||
import { routeGenerator } from './generator'
|
||||
|
||||
const ROUTES_DIRECTORY_PATH = './src/routes'
|
||||
|
||||
let lock = false
|
||||
|
||||
export function TuonoFsRouterPlugin(): Plugin {
|
||||
const generate = async (): Promise<void> => {
|
||||
if (lock) return
|
||||
lock = true
|
||||
|
||||
try {
|
||||
await routeGenerator()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
lock = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleFile = async (file: string): Promise<void> => {
|
||||
const filePath = normalize(file)
|
||||
|
||||
if (filePath.startsWith(ROUTES_DIRECTORY_PATH)) {
|
||||
await generate()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'vite-plugin-tuono-fs-router',
|
||||
configResolved: async (): Promise<void> => {
|
||||
await generate()
|
||||
},
|
||||
watchChange: async (
|
||||
file: string,
|
||||
context: { event: string },
|
||||
): Promise<void> => {
|
||||
if (['create', 'update', 'delete'].includes(context.event)) {
|
||||
await handleFile(file)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tuono-fs-router-vite-plugin",
|
||||
"version": "0.19.4",
|
||||
"name": "tuono-react-vite-plugin",
|
||||
"version": "0.19.7",
|
||||
"description": "Plugin for the tuono's file system router. Tuono is the react/rust fullstack framework",
|
||||
"homepage": "https://tuono.dev",
|
||||
"scripts": {
|
||||
@@ -16,7 +16,7 @@
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tuono-labs/tuono.git",
|
||||
"directory": "packages/tuono-fs-router-vite-plugin"
|
||||
"directory": "packages/tuono-react-vite-plugin"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Valerio Ageno",
|
||||
@@ -46,6 +46,6 @@
|
||||
"devDependencies": {
|
||||
"@types/babel__core": "7.20.5",
|
||||
"vite-config": "workspace:*",
|
||||
"vitest": "3.1.1"
|
||||
"vitest": "3.1.4"
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
import type { RouteNode } from '../types'
|
||||
|
||||
import { spaces } from './utils'
|
||||
import type { RouteNode } from './types'
|
||||
|
||||
export function buildRouteConfig(nodes: Array<RouteNode>, depth = 1): string {
|
||||
const children = nodes.map((node) => {
|
||||
+4
-2
@@ -3,6 +3,8 @@ import path from 'path'
|
||||
|
||||
import { format } from 'prettier'
|
||||
|
||||
import type { Config, RouteNode } from '../types'
|
||||
|
||||
import { buildRouteConfig } from './build-route-config'
|
||||
import { hasParentRoute } from './has-parent-route'
|
||||
import {
|
||||
@@ -17,7 +19,6 @@ import {
|
||||
removeUnderscores,
|
||||
} from './utils'
|
||||
|
||||
import type { Config, RouteNode } from './types'
|
||||
import {
|
||||
ROUTES_FOLDER,
|
||||
LAYOUT_PATH_ID,
|
||||
@@ -26,7 +27,7 @@ import {
|
||||
} from './constants'
|
||||
|
||||
import { sortRouteNodes } from './sort-route-nodes'
|
||||
import isDefaultExported from './utils/is-default-exported'
|
||||
import isDefaultExported from './is-default-exported'
|
||||
|
||||
let latestTask = 0
|
||||
|
||||
@@ -206,6 +207,7 @@ export async function routeGenerator(
|
||||
rustHandlersNodes.includes(node.path || '')
|
||||
? 'hasHandler: true'
|
||||
: '',
|
||||
`filePath: '${node.path || '/'}'`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(',')}
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
import type { RouteNode } from '../types'
|
||||
|
||||
import { LAYOUT_PATH_ID } from './constants'
|
||||
import { multiSortBy } from './utils'
|
||||
import type { RouteNode } from './types'
|
||||
|
||||
export function hasParentRoute(
|
||||
routes: Array<RouteNode>,
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import type { RouteNode } from './types'
|
||||
import type { RouteNode } from '../types'
|
||||
|
||||
import { multiSortBy } from './utils'
|
||||
import { LAYOUT_PATH_ID } from './constants'
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { RouteNode } from './types'
|
||||
import type { RouteNode } from '../types'
|
||||
|
||||
export function removeExt(d: string, keepExtension: boolean = false): string {
|
||||
return keepExtension ? d : d.substring(0, d.lastIndexOf('.')) || d
|
||||
@@ -0,0 +1 @@
|
||||
export { TuonoReactPlugin } from './plugin'
|
||||
@@ -0,0 +1,83 @@
|
||||
import { normalize } from 'node:path'
|
||||
|
||||
import type { Plugin, ViteDevServer } from 'vite'
|
||||
|
||||
import { routeGenerator } from './fs-routing/generator'
|
||||
import { getStylesForComponentId, isCssModulesFile } from './styles'
|
||||
|
||||
const CRITICAL_CSS_PATH = '/vite-server/tuono_internal__critical_css'
|
||||
|
||||
const ROUTES_DIRECTORY_PATH = './src/routes'
|
||||
|
||||
let lock = false
|
||||
|
||||
export function TuonoReactPlugin(): Plugin {
|
||||
const generate = async (): Promise<void> => {
|
||||
if (lock) return
|
||||
lock = true
|
||||
|
||||
try {
|
||||
await routeGenerator()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
lock = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleFile = async (file: string): Promise<void> => {
|
||||
const filePath = normalize(file)
|
||||
|
||||
if (filePath.startsWith(ROUTES_DIRECTORY_PATH)) {
|
||||
await generate()
|
||||
}
|
||||
}
|
||||
|
||||
// This manifest is used to store the CSS modules contents in dev mode
|
||||
// { [filePath]: cssContent }
|
||||
const cssModulesManifest: Record<string, string> = {}
|
||||
|
||||
return {
|
||||
name: 'vite-plugin-tuono-react',
|
||||
configResolved: async (): Promise<void> => {
|
||||
await generate()
|
||||
},
|
||||
watchChange: async (
|
||||
file: string,
|
||||
context: { event: string },
|
||||
): Promise<void> => {
|
||||
if (['create', 'update', 'delete'].includes(context.event)) {
|
||||
await handleFile(file)
|
||||
}
|
||||
},
|
||||
transform: (code, id): void => {
|
||||
if (isCssModulesFile(id)) {
|
||||
cssModulesManifest[id] = code
|
||||
}
|
||||
},
|
||||
configureServer: (server: ViteDevServer): void => {
|
||||
// Using middlewares in order to take advantage of async requests out of
|
||||
// the box
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
server.middlewares.use(async (req, res, next): Promise<void> => {
|
||||
const url = new URL(req.url || '', `http://${req.headers.host || ''}`)
|
||||
|
||||
// Give the request handler access to the critical CSS in dev to avoid a
|
||||
// flash of unstyled content since Vite injects CSS file contents via JS
|
||||
if (url.pathname === CRITICAL_CSS_PATH) {
|
||||
const componentId = url.searchParams.get('componentId')
|
||||
const css = await getStylesForComponentId(
|
||||
server,
|
||||
componentId,
|
||||
cssModulesManifest,
|
||||
)
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/css' })
|
||||
res.end(css)
|
||||
return
|
||||
}
|
||||
next()
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* This module is strongly inspired by the remix project.
|
||||
*
|
||||
* source: https://github.com/remix-run/remix/blob/main/packages/remix-dev/vite/styles.ts
|
||||
*/
|
||||
import path from 'path'
|
||||
|
||||
import type { ModuleNode, ViteDevServer } from 'vite'
|
||||
|
||||
const isCssFile = (file: string): boolean => cssFileRegExp.test(file)
|
||||
|
||||
const cssFileRegExp =
|
||||
/\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/
|
||||
|
||||
const cssModulesRegExp = new RegExp(`\\.module${cssFileRegExp.source}`)
|
||||
|
||||
const routesFolder = path.relative(process.cwd(), 'src/routes')
|
||||
|
||||
const injectQuery = (url: string, query: string): string =>
|
||||
url.includes('?') ? url.replace('?', `?${query}&`) : `${url}?${query}`
|
||||
|
||||
export const isCssModulesFile = (file: string): boolean =>
|
||||
cssModulesRegExp.test(file)
|
||||
|
||||
const cssUrlParamsWithoutSideEffects = ['url', 'inline', 'raw', 'inline-css']
|
||||
|
||||
const isCssUrlWithoutSideEffects = (url: string): boolean => {
|
||||
const queryString = url.split('?')[1]
|
||||
|
||||
if (!queryString) {
|
||||
return false
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(queryString)
|
||||
for (const paramWithoutSideEffects of cssUrlParamsWithoutSideEffects) {
|
||||
if (
|
||||
// Parameter is blank and not explicitly set, i.e. "?url", not "?url="
|
||||
params.get(paramWithoutSideEffects) === '' &&
|
||||
!url.includes(`?${paramWithoutSideEffects}=`) &&
|
||||
!url.includes(`&${paramWithoutSideEffects}=`)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const normalizePath = (modulePath: string): string => {
|
||||
return modulePath.startsWith('node_modules')
|
||||
? path.join(process.cwd(), modulePath)
|
||||
: modulePath
|
||||
}
|
||||
|
||||
export const getStylesForModule = async (
|
||||
viteDevServer: ViteDevServer,
|
||||
moduleUrl: string,
|
||||
/**
|
||||
* All the CSS modules are preloaded and saved in this manifest
|
||||
*/
|
||||
cssModulesManifest: Record<string, string>,
|
||||
): Promise<string | undefined> => {
|
||||
const styles: Record<string, string> = {}
|
||||
const deps: Set<ModuleNode> = new Set()
|
||||
|
||||
const moduleFilePath = normalizePath(moduleUrl)
|
||||
try {
|
||||
let node: ModuleNode | undefined =
|
||||
await viteDevServer.moduleGraph.getModuleByUrl(moduleFilePath)
|
||||
|
||||
// If the module is only present in the client module graph, the module
|
||||
// won't have been found on the first request to the server. If so, we
|
||||
// request the module so it's in the module graph, then try again.
|
||||
if (!node) {
|
||||
try {
|
||||
await viteDevServer.transformRequest(moduleFilePath)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
node = await viteDevServer.moduleGraph.getModuleByUrl(moduleFilePath)
|
||||
}
|
||||
|
||||
if (!node) {
|
||||
console.error(`Could not resolve module for file: ${moduleFilePath}`)
|
||||
return
|
||||
}
|
||||
await findNodeDependencies(viteDevServer, node, deps)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
for (const dep of deps) {
|
||||
if (
|
||||
dep.file &&
|
||||
isCssFile(dep.file) &&
|
||||
!isCssUrlWithoutSideEffects(dep.url) // Ignore styles that resolved as URLs, inline or raw. These shouldn't get injected.
|
||||
) {
|
||||
try {
|
||||
const css = isCssModulesFile(dep.file)
|
||||
? cssModulesManifest[dep.file]
|
||||
: ((
|
||||
await viteDevServer.ssrLoadModule(
|
||||
// We need the ?inline query in Vite v6 when loading CSS in SSR
|
||||
// since it does not expose the default export for CSS in a
|
||||
// server environment.
|
||||
injectQuery(normalizePath(dep.file), 'inline'),
|
||||
)
|
||||
).default as string)
|
||||
|
||||
if (css === undefined) {
|
||||
throw new Error()
|
||||
}
|
||||
|
||||
styles[dep.url] = css
|
||||
} catch {
|
||||
// this can happen with dynamically imported modules
|
||||
console.warn(`Could not load ${dep.file}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
Object.entries(styles)
|
||||
.map(([fileName, css]) => [
|
||||
`\n/* ${fileName
|
||||
// Escape comment syntax in file paths
|
||||
.replace(/\/\*/g, '/\\*')
|
||||
.replace(/\*\//g, '*\\/')} */`,
|
||||
css,
|
||||
])
|
||||
.flat()
|
||||
.join('\n') || undefined
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function transform the componentId into a file path.
|
||||
* File extension is not required for the vite.moduleGraph URL search.
|
||||
*/
|
||||
function findFileFromComponentId(id: string): string {
|
||||
if (id.endsWith('/')) {
|
||||
return id + 'index'
|
||||
}
|
||||
|
||||
if (id.includes('__root__')) {
|
||||
return id.replaceAll('__root__', '__layout')
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
export const getStylesForComponentId = async (
|
||||
viteDevServer: ViteDevServer,
|
||||
/**
|
||||
* The route name (should match tuono-router specs)
|
||||
*/
|
||||
componentId: string | null,
|
||||
/**
|
||||
* All the CSS modules are preloaded and saved in this manifest
|
||||
*/
|
||||
cssModulesManifest: Record<string, string>,
|
||||
): Promise<string | undefined> => {
|
||||
const relativeFilePath = path.join(
|
||||
routesFolder,
|
||||
findFileFromComponentId(componentId || ''),
|
||||
)
|
||||
|
||||
const fileUrl = path.join(process.cwd(), relativeFilePath)
|
||||
|
||||
return await getStylesForModule(viteDevServer, fileUrl, cssModulesManifest)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to find all the dependencies of a module node.
|
||||
* The starting node is always a route.
|
||||
*/
|
||||
const findNodeDependencies = async (
|
||||
vite: ViteDevServer,
|
||||
node: ModuleNode,
|
||||
deps: Set<ModuleNode>,
|
||||
): Promise<void> => {
|
||||
// since `ssrTransformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous.
|
||||
// instead of using `await`, we resolve all branches in parallel.
|
||||
const branches: Array<Promise<void>> = []
|
||||
|
||||
async function addFromNode(innerNode: ModuleNode): Promise<void> {
|
||||
if (!deps.has(innerNode)) {
|
||||
deps.add(innerNode)
|
||||
await findNodeDependencies(vite, innerNode, deps)
|
||||
}
|
||||
}
|
||||
|
||||
async function addFromUrl(url: string): Promise<void> {
|
||||
const innerNode = await vite.moduleGraph.getModuleByUrl(url)
|
||||
|
||||
if (innerNode) {
|
||||
await addFromNode(innerNode)
|
||||
}
|
||||
}
|
||||
|
||||
if (node.ssrTransformResult) {
|
||||
if (node.ssrTransformResult.deps) {
|
||||
node.ssrTransformResult.deps.forEach((url) =>
|
||||
branches.push(addFromUrl(url)),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
node.importedModules.forEach((innerNode: ModuleNode) =>
|
||||
branches.push(addFromNode(innerNode)),
|
||||
)
|
||||
}
|
||||
|
||||
await Promise.all(branches)
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@ import fs from 'fs/promises'
|
||||
|
||||
import { describe, it, expect } from 'vitest'
|
||||
|
||||
import { routeGenerator } from '../src/generator'
|
||||
import { routeGenerator } from '../src/fs-routing/generator'
|
||||
|
||||
describe('generator works', async () => {
|
||||
const folderNames = await fs.readdir(`${process.cwd()}/tests/generator`)
|
||||
+2
@@ -21,12 +21,14 @@ const Postscatchall = createRoute({ component: PostscatchallImport })
|
||||
const IndexRoute = Index.update({
|
||||
path: '/',
|
||||
getParentRoute: () => rootRoute,
|
||||
filePath: '/',
|
||||
})
|
||||
|
||||
const PostscatchallRoute = Postscatchall.update({
|
||||
path: '/posts/[...catch_all]',
|
||||
getParentRoute: () => rootRoute,
|
||||
hasHandler: true,
|
||||
filePath: '/posts/[...catch_all]',
|
||||
})
|
||||
|
||||
// Create and export the route tree
|
||||
+2
@@ -21,11 +21,13 @@ const Index = createRoute({ component: IndexImport })
|
||||
const AboutRoute = About.update({
|
||||
path: '/about',
|
||||
getParentRoute: () => rootRoute,
|
||||
filePath: '/about',
|
||||
})
|
||||
|
||||
const IndexRoute = Index.update({
|
||||
path: '/',
|
||||
getParentRoute: () => rootRoute,
|
||||
filePath: '/',
|
||||
})
|
||||
|
||||
// Create and export the route tree
|
||||
+6
@@ -36,31 +36,37 @@ const PostsMyPost = createRoute({ component: PostsMyPostImport })
|
||||
|
||||
const PostslayoutRoute = Postslayout.update({
|
||||
getParentRoute: () => rootRoute,
|
||||
filePath: '/posts/__layout',
|
||||
})
|
||||
|
||||
const AboutRoute = About.update({
|
||||
path: '/about',
|
||||
getParentRoute: () => rootRoute,
|
||||
filePath: '/about',
|
||||
})
|
||||
|
||||
const IndexRoute = Index.update({
|
||||
path: '/',
|
||||
getParentRoute: () => rootRoute,
|
||||
filePath: '/',
|
||||
})
|
||||
|
||||
const PostspostRoute = Postspost.update({
|
||||
path: '/posts/[post]',
|
||||
getParentRoute: () => PostslayoutRoute,
|
||||
filePath: '/posts/[post]',
|
||||
})
|
||||
|
||||
const PostsIndexRoute = PostsIndex.update({
|
||||
path: '/posts',
|
||||
getParentRoute: () => PostslayoutRoute,
|
||||
filePath: '/posts/',
|
||||
})
|
||||
|
||||
const PostsMyPostRoute = PostsMyPost.update({
|
||||
path: '/posts/my-post',
|
||||
getParentRoute: () => PostslayoutRoute,
|
||||
filePath: '/posts/my-post',
|
||||
})
|
||||
|
||||
// Create and export the route tree
|
||||
+3
@@ -25,16 +25,19 @@ const PostsMyPost = createRoute({ component: PostsMyPostImport })
|
||||
const AboutRoute = About.update({
|
||||
path: '/about',
|
||||
getParentRoute: () => rootRoute,
|
||||
filePath: '/about',
|
||||
})
|
||||
|
||||
const IndexRoute = Index.update({
|
||||
path: '/',
|
||||
getParentRoute: () => rootRoute,
|
||||
filePath: '/',
|
||||
})
|
||||
|
||||
const PostsMyPostRoute = PostsMyPost.update({
|
||||
path: '/posts/my-post',
|
||||
getParentRoute: () => rootRoute,
|
||||
filePath: '/posts/my-post',
|
||||
})
|
||||
|
||||
// Create and export the route tree
|
||||
+2
@@ -21,11 +21,13 @@ const Index = createRoute({ component: IndexImport })
|
||||
const AboutRoute = About.update({
|
||||
path: '/about',
|
||||
getParentRoute: () => rootRoute,
|
||||
filePath: '/about',
|
||||
})
|
||||
|
||||
const IndexRoute = Index.update({
|
||||
path: '/',
|
||||
getParentRoute: () => rootRoute,
|
||||
filePath: '/',
|
||||
})
|
||||
|
||||
// Create and export the route tree
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tuono-router",
|
||||
"version": "0.19.4",
|
||||
"version": "0.19.7",
|
||||
"description": "React routing component for the framework tuono. Tuono is the react/rust fullstack framework",
|
||||
"homepage": "https://tuono.dev",
|
||||
"scripts": {
|
||||
@@ -45,12 +45,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@types/react": "19.1.1",
|
||||
"@types/react": "19.1.3",
|
||||
"@vitejs/plugin-react-swc": "3.8.1",
|
||||
"happy-dom": "17.4.4",
|
||||
"happy-dom": "17.4.7",
|
||||
"react": "19.1.0",
|
||||
"vite": "6.1.3",
|
||||
"vite-config": "workspace:*",
|
||||
"vitest": "3.1.1"
|
||||
"vitest": "3.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { JSX } from 'react'
|
||||
|
||||
import type { Mode } from '../types'
|
||||
|
||||
const VITE_PROXY_PATH = '/vite-server'
|
||||
const CRITICAL_CSS_PATH = VITE_PROXY_PATH + '/tuono_internal__critical_css'
|
||||
|
||||
interface CriticalCssProps {
|
||||
routeFilePath?: string
|
||||
mode?: Mode
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the critical CSS for the given route
|
||||
* This is required in order to avoid FOUC during development
|
||||
* since vite does not support CSS injection without JS waterfall
|
||||
*/
|
||||
export function CriticalCss({
|
||||
routeFilePath,
|
||||
mode,
|
||||
}: CriticalCssProps): JSX.Element | null {
|
||||
if (!routeFilePath || mode !== 'Dev') {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<link
|
||||
href={`${CRITICAL_CSS_PATH}?componentId=${routeFilePath}`}
|
||||
precedence="high"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,8 @@ import type { JSX } from 'react'
|
||||
|
||||
import { useRoute } from '../hooks/useRoute'
|
||||
|
||||
import type { Mode } from '../types'
|
||||
|
||||
import { RouteMatch } from './RouteMatch'
|
||||
import { NotFound } from './NotFound'
|
||||
import { useRouterContext } from './RouterContext'
|
||||
@@ -9,16 +11,26 @@ import { useRouterContext } from './RouterContext'
|
||||
interface MatchesProps<TServerPayloadData = unknown> {
|
||||
// user defined props
|
||||
serverInitialData: TServerPayloadData
|
||||
mode?: Mode
|
||||
}
|
||||
|
||||
export function Matches({ serverInitialData }: MatchesProps): JSX.Element {
|
||||
export function Matches({
|
||||
serverInitialData,
|
||||
mode,
|
||||
}: MatchesProps): JSX.Element {
|
||||
const { location } = useRouterContext()
|
||||
|
||||
const route = useRoute(location.pathname)
|
||||
|
||||
if (!route) {
|
||||
return <NotFound />
|
||||
return <NotFound mode={mode} />
|
||||
}
|
||||
|
||||
return <RouteMatch route={route} serverInitialData={serverInitialData} />
|
||||
return (
|
||||
<RouteMatch
|
||||
route={route}
|
||||
mode={mode}
|
||||
serverInitialData={serverInitialData}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,17 +3,25 @@ import type { JSX } from 'react'
|
||||
import { useRouterContext } from '../components/RouterContext'
|
||||
import { ROOT_ROUTE_ID } from '../route'
|
||||
|
||||
import type { Mode } from '../types'
|
||||
|
||||
import { RouteMatch } from './RouteMatch'
|
||||
import { NotFoundDefaultContent } from './NotFoundDefaultContent'
|
||||
import { CriticalCss } from './CriticalCss'
|
||||
|
||||
export function NotFound(): JSX.Element | null {
|
||||
export function NotFound({ mode }: { mode?: Mode }): JSX.Element | null {
|
||||
const { router } = useRouterContext()
|
||||
|
||||
const custom404Route = router.routesById['/404']
|
||||
|
||||
// Check if exists a custom 404 error page
|
||||
if (custom404Route) {
|
||||
return <RouteMatch route={custom404Route} serverInitialData={{}} />
|
||||
return (
|
||||
<>
|
||||
<CriticalCss routeFilePath={custom404Route.filePath} mode={mode} />
|
||||
<RouteMatch route={custom404Route} mode={mode} serverInitialData={{}} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const RootLayout = router.routesById[ROOT_ROUTE_ID]?.component
|
||||
@@ -22,6 +30,7 @@ export function NotFound(): JSX.Element | null {
|
||||
|
||||
return (
|
||||
<RootLayout data={null} isLoading={false}>
|
||||
<CriticalCss routeFilePath="__root__" mode={mode} />
|
||||
<NotFoundDefaultContent />
|
||||
</RootLayout>
|
||||
)
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import type { JSX } from 'react'
|
||||
import { memo, Suspense, useMemo } from 'react'
|
||||
|
||||
import type { Mode } from '../types'
|
||||
import type { Route } from '../route'
|
||||
|
||||
import { useServerPayloadData } from '../hooks/useServerPayloadData'
|
||||
|
||||
import { useRouterContext } from './RouterContext'
|
||||
import { CriticalCss } from './CriticalCss'
|
||||
|
||||
interface RouteMatchProps<TServerPayloadData = unknown> {
|
||||
route: Route
|
||||
// User defined server side props
|
||||
serverInitialData: TServerPayloadData
|
||||
mode?: Mode
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,6 +24,7 @@ interface RouteMatchProps<TServerPayloadData = unknown> {
|
||||
export const RouteMatch = ({
|
||||
route,
|
||||
serverInitialData,
|
||||
mode,
|
||||
}: RouteMatchProps): JSX.Element => {
|
||||
const { data } = useServerPayloadData(route, serverInitialData)
|
||||
const { isTransitioning } = useRouterContext()
|
||||
@@ -35,8 +39,10 @@ export const RouteMatch = ({
|
||||
routes={routes}
|
||||
data={routeData}
|
||||
isLoading={isTransitioning}
|
||||
mode={mode}
|
||||
>
|
||||
<Suspense>
|
||||
<CriticalCss routeFilePath={route.filePath} mode={mode} />
|
||||
<route.component data={routeData} isLoading={isTransitioning} />
|
||||
</Suspense>
|
||||
</TraverseRootComponents>
|
||||
@@ -49,6 +55,7 @@ interface TraverseRootComponentsProps<TData = unknown> {
|
||||
isLoading: boolean
|
||||
children?: React.ReactNode
|
||||
index?: number
|
||||
mode?: Mode
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,18 +70,26 @@ const TraverseRootComponents = memo(
|
||||
data,
|
||||
isLoading,
|
||||
index = 0,
|
||||
mode,
|
||||
children,
|
||||
}: TraverseRootComponentsProps): React.JSX.Element => {
|
||||
if (routes.length > index) {
|
||||
const Parent = (routes[index] as Route).component
|
||||
const route = routes[index] as Route
|
||||
const Parent = route.component
|
||||
|
||||
// Fallback to the route id if the filePath is not defined
|
||||
// as is the case for the root route
|
||||
const routeFilePath = route.filePath || route.id
|
||||
|
||||
return (
|
||||
<Parent data={data} isLoading={isLoading}>
|
||||
<CriticalCss routeFilePath={routeFilePath} mode={mode} />
|
||||
<TraverseRootComponents
|
||||
routes={routes}
|
||||
data={data}
|
||||
isLoading={isLoading}
|
||||
index={index + 1}
|
||||
mode={mode}
|
||||
>
|
||||
{children}
|
||||
</TraverseRootComponents>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { JSX } from 'react'
|
||||
|
||||
import type { ServerInitialLocation } from '../types'
|
||||
import type { ServerInitialLocation, Mode } from '../types'
|
||||
import type { Router } from '../router'
|
||||
|
||||
import { RouterContextProvider } from './RouterContext'
|
||||
@@ -10,19 +10,21 @@ interface RouterProviderProps {
|
||||
router: Router
|
||||
serverInitialLocation: ServerInitialLocation
|
||||
serverInitialData: unknown
|
||||
mode?: Mode
|
||||
}
|
||||
|
||||
export function RouterProvider({
|
||||
router,
|
||||
serverInitialLocation,
|
||||
serverInitialData,
|
||||
mode,
|
||||
}: RouterProviderProps): JSX.Element {
|
||||
return (
|
||||
<RouterContextProvider
|
||||
router={router}
|
||||
serverInitialLocation={serverInitialLocation}
|
||||
>
|
||||
<Matches serverInitialData={serverInitialData} />
|
||||
<Matches serverInitialData={serverInitialData} mode={mode} />
|
||||
</RouterContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ export function sanitizePathname(pathname: string): string {
|
||||
return pathname
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Returns the route that matches the given pathname
|
||||
*
|
||||
* This hook is also implemented on server side to match the bundle
|
||||
* file to load at the first rendering.
|
||||
*
|
||||
|
||||
@@ -6,6 +6,7 @@ interface RouteOptions {
|
||||
isRoot?: boolean
|
||||
getParentRoute?: () => Route
|
||||
path?: string
|
||||
filePath?: string
|
||||
component: RouteComponent
|
||||
hasHandler?: boolean
|
||||
}
|
||||
@@ -19,11 +20,28 @@ export const ROOT_ROUTE_ID = '__root__'
|
||||
export class Route {
|
||||
options: RouteOptions
|
||||
|
||||
/**
|
||||
* The route id is used to identify the route in the router
|
||||
* and is used to match the route with the URL.
|
||||
*
|
||||
* For now is the `path`
|
||||
*/
|
||||
id?: string
|
||||
isRoot: boolean
|
||||
/**
|
||||
* Used for identify the route by matching the URL
|
||||
*/
|
||||
path?: string
|
||||
fullPath!: string
|
||||
|
||||
/**
|
||||
* Utility to identify the route in the file system
|
||||
* Used i.e. for finding the criticalCss to load
|
||||
*
|
||||
* The path does not include the file extension
|
||||
*/
|
||||
filePath?: string
|
||||
|
||||
children?: Array<Route>
|
||||
parentRoute?: Route
|
||||
originalIndex?: number
|
||||
@@ -70,11 +88,10 @@ export class Route {
|
||||
id = joinPaths(['/', id])
|
||||
}
|
||||
|
||||
const fullPath = id === ROOT_ROUTE_ID ? '/' : path
|
||||
|
||||
this.filePath = this.options.filePath
|
||||
this.path = path
|
||||
this.id = id
|
||||
this.fullPath = fullPath || ''
|
||||
this.fullPath = path || ''
|
||||
}
|
||||
|
||||
addChildren(routes: Array<Route>): this {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { ReactNode, ComponentType } from 'react'
|
||||
|
||||
export type Mode = 'Dev' | 'Prod'
|
||||
|
||||
export interface Segment {
|
||||
type: 'pathname' | 'param' | 'wildcard'
|
||||
value: string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tuono",
|
||||
"version": "0.19.4",
|
||||
"version": "0.19.7",
|
||||
"description": "Superfast React fullstack framework",
|
||||
"homepage": "https://tuono.dev",
|
||||
"scripts": {
|
||||
@@ -71,7 +71,7 @@
|
||||
"@rollup/plugin-inject": "^5.0.5",
|
||||
"@vitejs/plugin-react-swc": "^3.8.0",
|
||||
"fast-text-encoding": "^1.0.6",
|
||||
"tuono-fs-router-vite-plugin": "workspace:*",
|
||||
"tuono-react-vite-plugin": "workspace:*",
|
||||
"tuono-router": "workspace:*",
|
||||
"url-search-params-polyfill": "^8.2.5",
|
||||
"vite": "^6.1.1",
|
||||
@@ -81,12 +81,12 @@
|
||||
"@types/babel__core": "7.20.5",
|
||||
"@types/babel__traverse": "7.20.7",
|
||||
"@types/node": "22.14.1",
|
||||
"@types/react": "19.1.1",
|
||||
"@types/react-dom": "19.1.2",
|
||||
"@types/react": "19.1.3",
|
||||
"@types/react-dom": "19.1.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"vite-config": "workspace:*",
|
||||
"vitest": "3.1.1"
|
||||
"vitest": "3.1.4"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"keywords": [
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { InlineConfig, Plugin } from 'vite'
|
||||
import { build, createServer, mergeConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
import inject from '@rollup/plugin-inject'
|
||||
import { TuonoFsRouterPlugin } from 'tuono-fs-router-vite-plugin'
|
||||
import { TuonoReactPlugin } from 'tuono-react-vite-plugin'
|
||||
|
||||
import type { TuonoConfig } from '../config'
|
||||
|
||||
@@ -71,7 +71,7 @@ function createBaseViteConfigFromTuonoConfig(
|
||||
// @ts-expect-error see above comment
|
||||
react({ include: pluginFilesInclude }),
|
||||
|
||||
TuonoFsRouterPlugin(),
|
||||
TuonoReactPlugin(),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import type { JSX } from 'react'
|
||||
|
||||
import type { TuonoConfigServer } from '../config'
|
||||
|
||||
const VITE_PROXY_PATH = '/vite-server'
|
||||
const DEFAULT_SERVER_CONFIG = { host: 'localhost', origin: null, port: 3000 }
|
||||
const VITE_PROXY_PATH = '/vite-server'
|
||||
|
||||
interface DevResourcesProps {
|
||||
devServerConfig?: TuonoConfigServer
|
||||
|
||||
@@ -2,10 +2,13 @@ import type { JSX } from 'react'
|
||||
import { RouterProvider } from 'tuono-router'
|
||||
import type { RouterInstanceType } from 'tuono-router'
|
||||
|
||||
import type { Mode } from '../types'
|
||||
|
||||
import { useTuonoContextServerPayload } from './TuonoContext'
|
||||
|
||||
interface RouterContextProviderWrapperProps {
|
||||
router: RouterInstanceType
|
||||
mode?: Mode
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -17,6 +20,7 @@ interface RouterContextProviderWrapperProps {
|
||||
*/
|
||||
export function RouterContextProviderWrapper({
|
||||
router,
|
||||
mode,
|
||||
}: RouterContextProviderWrapperProps): JSX.Element {
|
||||
const serverPayload = useTuonoContextServerPayload()
|
||||
|
||||
@@ -25,6 +29,7 @@ export function RouterContextProviderWrapper({
|
||||
router={router}
|
||||
serverInitialLocation={serverPayload.location}
|
||||
serverInitialData={serverPayload.data}
|
||||
mode={mode}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ export function TuonoEntryPoint({
|
||||
return (
|
||||
<StrictMode>
|
||||
<TuonoContextProvider serverPayload={serverPayload}>
|
||||
<RouterContextProviderWrapper router={router} />
|
||||
<RouterContextProviderWrapper
|
||||
router={router}
|
||||
mode={serverPayload?.mode}
|
||||
/>
|
||||
</TuonoContextProvider>
|
||||
</StrictMode>
|
||||
)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export type Mode = 'Dev' | 'Prod'
|
||||
@@ -2,6 +2,8 @@ import type { ReactNode } from 'react'
|
||||
|
||||
import type { TuonoConfigServer } from './config'
|
||||
|
||||
export type Mode = 'Dev' | 'Prod'
|
||||
|
||||
/**
|
||||
* Provided by the rust server and used in the ssr env
|
||||
* @see tuono-router {@link ServerInitialLocation}
|
||||
|
||||
Generated
+168
-169
@@ -19,7 +19,7 @@ importers:
|
||||
version: 22.14.1
|
||||
'@vitest/eslint-plugin':
|
||||
specifier: 1.1.42
|
||||
version: 1.1.42(@typescript-eslint/utils@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3)(vitest@3.1.1(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3)))
|
||||
version: 1.1.42(@typescript-eslint/utils@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3)(vitest@3.1.4(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.7)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3)))
|
||||
eslint:
|
||||
specifier: 9.24.0
|
||||
version: 9.24.0(jiti@2.4.2)
|
||||
@@ -39,8 +39,8 @@ importers:
|
||||
specifier: 3.5.3
|
||||
version: 3.5.3
|
||||
turbo:
|
||||
specifier: 2.5.0
|
||||
version: 2.5.0
|
||||
specifier: 2.5.3
|
||||
version: 2.5.3
|
||||
typescript:
|
||||
specifier: 5.7.3
|
||||
version: 5.7.3
|
||||
@@ -80,10 +80,10 @@ importers:
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^19.0.2
|
||||
version: 19.1.0
|
||||
version: 19.1.3
|
||||
'@types/react-dom':
|
||||
specifier: ^19.0.2
|
||||
version: 19.1.1(@types/react@19.1.0)
|
||||
version: 19.1.3(@types/react@19.1.3)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.7.3
|
||||
@@ -102,10 +102,10 @@ importers:
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^19.0.2
|
||||
version: 19.1.0
|
||||
version: 19.1.3
|
||||
'@types/react-dom':
|
||||
specifier: ^19.0.2
|
||||
version: 19.1.1(@types/react@19.1.0)
|
||||
version: 19.1.3(@types/react@19.1.3)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.7.3
|
||||
@@ -124,10 +124,10 @@ importers:
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^19.0.2
|
||||
version: 19.1.0
|
||||
version: 19.1.3
|
||||
'@types/react-dom':
|
||||
specifier: ^19.0.2
|
||||
version: 19.1.1(@types/react@19.1.0)
|
||||
version: 19.1.3(@types/react@19.1.3)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.7.3
|
||||
@@ -136,7 +136,7 @@ importers:
|
||||
dependencies:
|
||||
'@mdx-js/react':
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0(@types/react@19.1.0)(react@19.1.0)
|
||||
version: 3.1.0(@types/react@19.1.3)(react@19.1.0)
|
||||
react:
|
||||
specifier: ^19.0.0
|
||||
version: 19.1.0
|
||||
@@ -152,10 +152,10 @@ importers:
|
||||
version: 3.1.0(acorn@8.14.1)(rollup@4.38.0)
|
||||
'@types/react':
|
||||
specifier: ^19.0.2
|
||||
version: 19.1.0
|
||||
version: 19.1.3
|
||||
'@types/react-dom':
|
||||
specifier: ^19.0.2
|
||||
version: 19.1.1(@types/react@19.1.0)
|
||||
version: 19.1.3(@types/react@19.1.3)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.7.3
|
||||
@@ -180,10 +180,10 @@ importers:
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^19.0.2
|
||||
version: 19.1.0
|
||||
version: 19.1.3
|
||||
'@types/react-dom':
|
||||
specifier: ^19.0.2
|
||||
version: 19.1.1(@types/react@19.1.0)
|
||||
version: 19.1.3(@types/react@19.1.3)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.7.3
|
||||
@@ -208,9 +208,9 @@ importers:
|
||||
fast-text-encoding:
|
||||
specifier: ^1.0.6
|
||||
version: 1.0.6
|
||||
tuono-fs-router-vite-plugin:
|
||||
tuono-react-vite-plugin:
|
||||
specifier: workspace:*
|
||||
version: link:../tuono-fs-router-vite-plugin
|
||||
version: link:../tuono-react-vite-plugin
|
||||
tuono-router:
|
||||
specifier: workspace:*
|
||||
version: link:../tuono-router
|
||||
@@ -234,11 +234,11 @@ importers:
|
||||
specifier: 22.14.1
|
||||
version: 22.14.1
|
||||
'@types/react':
|
||||
specifier: 19.1.1
|
||||
version: 19.1.1
|
||||
specifier: 19.1.3
|
||||
version: 19.1.3
|
||||
'@types/react-dom':
|
||||
specifier: 19.1.2
|
||||
version: 19.1.2(@types/react@19.1.1)
|
||||
specifier: 19.1.3
|
||||
version: 19.1.3(@types/react@19.1.3)
|
||||
react:
|
||||
specifier: 19.1.0
|
||||
version: 19.1.0
|
||||
@@ -249,10 +249,10 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../../devtools/vite-config
|
||||
vitest:
|
||||
specifier: 3.1.1
|
||||
version: 3.1.1(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3))
|
||||
specifier: 3.1.4
|
||||
version: 3.1.4(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.7)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3))
|
||||
|
||||
packages/tuono-fs-router-vite-plugin:
|
||||
packages/tuono-react-vite-plugin:
|
||||
dependencies:
|
||||
'@babel/core':
|
||||
specifier: ^7.24.4
|
||||
@@ -274,8 +274,8 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../../devtools/vite-config
|
||||
vitest:
|
||||
specifier: 3.1.1
|
||||
version: 3.1.1(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3))
|
||||
specifier: 3.1.4
|
||||
version: 3.1.4(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.7)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3))
|
||||
|
||||
packages/tuono-router:
|
||||
dependencies:
|
||||
@@ -285,16 +285,16 @@ importers:
|
||||
devDependencies:
|
||||
'@testing-library/react':
|
||||
specifier: 16.3.0
|
||||
version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.2(@types/react@19.1.1))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.3(@types/react@19.1.3))(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@types/react':
|
||||
specifier: 19.1.1
|
||||
version: 19.1.1
|
||||
specifier: 19.1.3
|
||||
version: 19.1.3
|
||||
'@vitejs/plugin-react-swc':
|
||||
specifier: 3.8.1
|
||||
version: 3.8.1(vite@6.1.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3)))
|
||||
happy-dom:
|
||||
specifier: 17.4.4
|
||||
version: 17.4.4
|
||||
specifier: 17.4.7
|
||||
version: 17.4.7
|
||||
react:
|
||||
specifier: 19.1.0
|
||||
version: 19.1.0
|
||||
@@ -305,8 +305,8 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../../devtools/vite-config
|
||||
vitest:
|
||||
specifier: 3.1.1
|
||||
version: 3.1.1(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3))
|
||||
specifier: 3.1.4
|
||||
version: 3.1.4(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.7)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3))
|
||||
|
||||
packages:
|
||||
|
||||
@@ -314,8 +314,8 @@ packages:
|
||||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@babel/code-frame@7.26.2':
|
||||
resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
|
||||
'@babel/code-frame@7.27.1':
|
||||
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/compat-data@7.26.2':
|
||||
@@ -352,8 +352,8 @@ packages:
|
||||
resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.25.9':
|
||||
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
|
||||
'@babel/helper-validator-identifier@7.27.1':
|
||||
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-option@7.25.9':
|
||||
@@ -381,8 +381,8 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/runtime@7.27.0':
|
||||
resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==}
|
||||
'@babel/runtime@7.27.1':
|
||||
resolution: {integrity: sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/template@7.25.9':
|
||||
@@ -1194,21 +1194,13 @@ packages:
|
||||
'@types/node@22.14.1':
|
||||
resolution: {integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==}
|
||||
|
||||
'@types/react-dom@19.1.1':
|
||||
resolution: {integrity: sha512-jFf/woGTVTjUJsl2O7hcopJ1r0upqoq/vIOoCj0yLh3RIXxWcljlpuZ+vEBRXsymD1jhfeJrlyTy/S1UW+4y1w==}
|
||||
'@types/react-dom@19.1.3':
|
||||
resolution: {integrity: sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==}
|
||||
peerDependencies:
|
||||
'@types/react': ^19.0.0
|
||||
|
||||
'@types/react-dom@19.1.2':
|
||||
resolution: {integrity: sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==}
|
||||
peerDependencies:
|
||||
'@types/react': ^19.0.0
|
||||
|
||||
'@types/react@19.1.0':
|
||||
resolution: {integrity: sha512-UaicktuQI+9UKyA4njtDOGBD/67t8YEBt2xdfqu8+gP9hqPUPsiXlNPcpS2gVdjmis5GKPG3fCxbQLVgxsQZ8w==}
|
||||
|
||||
'@types/react@19.1.1':
|
||||
resolution: {integrity: sha512-ePapxDL7qrgqSF67s0h9m412d9DbXyC1n59O2st+9rjuuamWsZuD2w55rqY12CbzsZ7uVXb5Nw0gEp9Z8MMutQ==}
|
||||
'@types/react@19.1.3':
|
||||
resolution: {integrity: sha512-dLWQ+Z0CkIvK1J8+wrDPwGxEYFA4RAyHoZPxHVGspYmFVnwGSNT24cGIhFJrtfRnWVuW8X7NO52gCXmhkVUWGQ==}
|
||||
|
||||
'@types/unist@2.0.11':
|
||||
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
|
||||
@@ -1357,11 +1349,11 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@vitest/expect@3.1.1':
|
||||
resolution: {integrity: sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==}
|
||||
'@vitest/expect@3.1.4':
|
||||
resolution: {integrity: sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==}
|
||||
|
||||
'@vitest/mocker@3.1.1':
|
||||
resolution: {integrity: sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==}
|
||||
'@vitest/mocker@3.1.4':
|
||||
resolution: {integrity: sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==}
|
||||
peerDependencies:
|
||||
msw: ^2.4.9
|
||||
vite: ^5.0.0 || ^6.0.0
|
||||
@@ -1371,20 +1363,20 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/pretty-format@3.1.1':
|
||||
resolution: {integrity: sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==}
|
||||
'@vitest/pretty-format@3.1.4':
|
||||
resolution: {integrity: sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==}
|
||||
|
||||
'@vitest/runner@3.1.1':
|
||||
resolution: {integrity: sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==}
|
||||
'@vitest/runner@3.1.4':
|
||||
resolution: {integrity: sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==}
|
||||
|
||||
'@vitest/snapshot@3.1.1':
|
||||
resolution: {integrity: sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==}
|
||||
'@vitest/snapshot@3.1.4':
|
||||
resolution: {integrity: sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==}
|
||||
|
||||
'@vitest/spy@3.1.1':
|
||||
resolution: {integrity: sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==}
|
||||
'@vitest/spy@3.1.4':
|
||||
resolution: {integrity: sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==}
|
||||
|
||||
'@vitest/utils@3.1.1':
|
||||
resolution: {integrity: sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==}
|
||||
'@vitest/utils@3.1.4':
|
||||
resolution: {integrity: sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
@@ -1651,8 +1643,8 @@ packages:
|
||||
resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-module-lexer@1.6.0:
|
||||
resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==}
|
||||
es-module-lexer@1.7.0:
|
||||
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||
@@ -1847,6 +1839,14 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fdir@6.4.4:
|
||||
resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
@@ -1938,8 +1938,8 @@ packages:
|
||||
graphemer@1.4.0:
|
||||
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
||||
|
||||
happy-dom@17.4.4:
|
||||
resolution: {integrity: sha512-/Pb0ctk3HTZ5xEL3BZ0hK1AqDSAUuRQitOmROPHhfUYEWpmTImwfD8vFDGADmMAX0JYgbcgxWoLFKtsWhcpuVA==}
|
||||
happy-dom@17.4.7:
|
||||
resolution: {integrity: sha512-NZypxadhCiV5NT4A+Y86aQVVKQ05KDmueja3sz008uJfDRwz028wd0aTiJPwo4RQlvlz0fznkEEBBCHVNWc08g==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
has-bigints@1.0.2:
|
||||
@@ -2587,9 +2587,6 @@ packages:
|
||||
resolution: {integrity: sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
regenerator-runtime@0.14.1:
|
||||
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||
|
||||
regexp.prototype.flags@1.5.3:
|
||||
resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2710,8 +2707,8 @@ packages:
|
||||
stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
|
||||
std-env@3.8.1:
|
||||
resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==}
|
||||
std-env@3.9.0:
|
||||
resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
|
||||
|
||||
string.prototype.matchall@4.0.12:
|
||||
resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
|
||||
@@ -2780,6 +2777,10 @@ packages:
|
||||
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinyglobby@0.2.13:
|
||||
resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinypool@1.0.2:
|
||||
resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
@@ -2814,38 +2815,38 @@ packages:
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
turbo-darwin-64@2.5.0:
|
||||
resolution: {integrity: sha512-fP1hhI9zY8hv0idym3hAaXdPi80TLovmGmgZFocVAykFtOxF+GlfIgM/l4iLAV9ObIO4SUXPVWHeBZQQ+Hpjag==}
|
||||
turbo-darwin-64@2.5.3:
|
||||
resolution: {integrity: sha512-YSItEVBUIvAGPUDpAB9etEmSqZI3T6BHrkBkeSErvICXn3dfqXUfeLx35LfptLDEbrzFUdwYFNmt8QXOwe9yaw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
turbo-darwin-arm64@2.5.0:
|
||||
resolution: {integrity: sha512-p9sYq7kXH7qeJwIQE86cOWv/xNqvow846l6c/qWc26Ib1ci5W7V0sI5thsrP3eH+VA0d+SHalTKg5SQXgNQBWA==}
|
||||
turbo-darwin-arm64@2.5.3:
|
||||
resolution: {integrity: sha512-5PefrwHd42UiZX7YA9m1LPW6x9YJBDErXmsegCkVp+GjmWrADfEOxpFrGQNonH3ZMj77WZB2PVE5Aw3gA+IOhg==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
turbo-linux-64@2.5.0:
|
||||
resolution: {integrity: sha512-1iEln2GWiF3iPPPS1HQJT6ZCFXynJPd89gs9SkggH2EJsj3eRUSVMmMC8y6d7bBbhBFsiGGazwFIYrI12zs6uQ==}
|
||||
turbo-linux-64@2.5.3:
|
||||
resolution: {integrity: sha512-M9xigFgawn5ofTmRzvjjLj3Lqc05O8VHKuOlWNUlnHPUltFquyEeSkpQNkE/vpPdOR14AzxqHbhhxtfS4qvb1w==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
turbo-linux-arm64@2.5.0:
|
||||
resolution: {integrity: sha512-bKBcbvuQHmsX116KcxHJuAcppiiBOfivOObh2O5aXNER6mce7YDDQJy00xQQNp1DhEfcSV2uOsvb3O3nN2cbcA==}
|
||||
turbo-linux-arm64@2.5.3:
|
||||
resolution: {integrity: sha512-auJRbYZ8SGJVqvzTikpg1bsRAsiI9Tk0/SDkA5Xgg0GdiHDH/BOzv1ZjDE2mjmlrO/obr19Dw+39OlMhwLffrw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
turbo-windows-64@2.5.0:
|
||||
resolution: {integrity: sha512-9BCo8oQ7BO7J0K913Czbc3tw8QwLqn2nTe4E47k6aVYkM12ASTScweXPTuaPFP5iYXAT6z5Dsniw704Ixa5eGg==}
|
||||
turbo-windows-64@2.5.3:
|
||||
resolution: {integrity: sha512-arLQYohuHtIEKkmQSCU9vtrKUg+/1TTstWB9VYRSsz+khvg81eX6LYHtXJfH/dK7Ho6ck+JaEh5G+QrE1jEmCQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
turbo-windows-arm64@2.5.0:
|
||||
resolution: {integrity: sha512-OUHCV+ueXa3UzfZ4co/ueIHgeq9B2K48pZwIxKSm5VaLVuv8M13MhM7unukW09g++dpdrrE1w4IOVgxKZ0/exg==}
|
||||
turbo-windows-arm64@2.5.3:
|
||||
resolution: {integrity: sha512-3JPn66HAynJ0gtr6H+hjY4VHpu1RPKcEwGATvGUTmLmYSYBQieVlnGDRMMoYN066YfyPqnNGCfhYbXfH92Cm0g==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
turbo@2.5.0:
|
||||
resolution: {integrity: sha512-PvSRruOsitjy6qdqwIIyolv99+fEn57gP6gn4zhsHTEcCYgXPhv6BAxzAjleS8XKpo+Y582vTTA9nuqYDmbRuA==}
|
||||
turbo@2.5.3:
|
||||
resolution: {integrity: sha512-iHuaNcq5GZZnr3XDZNuu2LSyCzAOPwDuo5Qt+q64DfsTP1i3T2bKfxJhni2ZQxsvAoxRbuUK5QetJki4qc5aYA==}
|
||||
hasBin: true
|
||||
|
||||
type-check@0.4.0:
|
||||
@@ -2954,8 +2955,8 @@ packages:
|
||||
vfile@6.0.3:
|
||||
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
|
||||
|
||||
vite-node@3.1.1:
|
||||
resolution: {integrity: sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==}
|
||||
vite-node@3.1.4:
|
||||
resolution: {integrity: sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==}
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
hasBin: true
|
||||
|
||||
@@ -3004,16 +3005,16 @@ packages:
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
vitest@3.1.1:
|
||||
resolution: {integrity: sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==}
|
||||
vitest@3.1.4:
|
||||
resolution: {integrity: sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==}
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@types/debug': ^4.1.12
|
||||
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
|
||||
'@vitest/browser': 3.1.1
|
||||
'@vitest/ui': 3.1.1
|
||||
'@vitest/browser': 3.1.4
|
||||
'@vitest/ui': 3.1.4
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
peerDependenciesMeta:
|
||||
@@ -3094,9 +3095,9 @@ snapshots:
|
||||
'@jridgewell/gen-mapping': 0.3.8
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@babel/code-frame@7.26.2':
|
||||
'@babel/code-frame@7.27.1':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
js-tokens: 4.0.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
@@ -3105,7 +3106,7 @@ snapshots:
|
||||
'@babel/core@7.26.0':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@babel/code-frame': 7.26.2
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/generator': 7.26.3
|
||||
'@babel/helper-compilation-targets': 7.25.9
|
||||
'@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0)
|
||||
@@ -3149,7 +3150,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/helper-module-imports': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
'@babel/traverse': 7.26.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -3158,7 +3159,7 @@ snapshots:
|
||||
|
||||
'@babel/helper-string-parser@7.25.9': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.25.9': {}
|
||||
'@babel/helper-validator-identifier@7.27.1': {}
|
||||
|
||||
'@babel/helper-validator-option@7.25.9': {}
|
||||
|
||||
@@ -3181,19 +3182,17 @@ snapshots:
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/helper-plugin-utils': 7.25.9
|
||||
|
||||
'@babel/runtime@7.27.0':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
'@babel/runtime@7.27.1': {}
|
||||
|
||||
'@babel/template@7.25.9':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.26.2
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/parser': 7.26.3
|
||||
'@babel/types': 7.26.3
|
||||
|
||||
'@babel/traverse@7.26.4':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.26.2
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/generator': 7.26.3
|
||||
'@babel/parser': 7.26.3
|
||||
'@babel/template': 7.25.9
|
||||
@@ -3206,7 +3205,7 @@ snapshots:
|
||||
'@babel/types@7.26.3':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
|
||||
'@emnapi/core@1.4.0':
|
||||
dependencies:
|
||||
@@ -3407,10 +3406,10 @@ snapshots:
|
||||
- acorn
|
||||
- supports-color
|
||||
|
||||
'@mdx-js/react@3.1.0(@types/react@19.1.0)(react@19.1.0)':
|
||||
'@mdx-js/react@3.1.0(@types/react@19.1.3)(react@19.1.0)':
|
||||
dependencies:
|
||||
'@types/mdx': 2.0.13
|
||||
'@types/react': 19.1.0
|
||||
'@types/react': 19.1.3
|
||||
react: 19.1.0
|
||||
|
||||
'@mdx-js/rollup@3.1.0(acorn@8.14.1)(rollup@4.38.0)':
|
||||
@@ -3738,8 +3737,8 @@ snapshots:
|
||||
|
||||
'@testing-library/dom@10.4.0':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.26.2
|
||||
'@babel/runtime': 7.27.0
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/runtime': 7.27.1
|
||||
'@types/aria-query': 5.0.4
|
||||
aria-query: 5.3.0
|
||||
chalk: 4.1.2
|
||||
@@ -3747,15 +3746,15 @@ snapshots:
|
||||
lz-string: 1.5.0
|
||||
pretty-format: 27.5.1
|
||||
|
||||
'@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.2(@types/react@19.1.1))(@types/react@19.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
'@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.3(@types/react@19.1.3))(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.0
|
||||
'@babel/runtime': 7.27.1
|
||||
'@testing-library/dom': 10.4.0
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.1
|
||||
'@types/react-dom': 19.1.2(@types/react@19.1.1)
|
||||
'@types/react': 19.1.3
|
||||
'@types/react-dom': 19.1.3(@types/react@19.1.3)
|
||||
|
||||
'@tybys/wasm-util@0.9.0':
|
||||
dependencies:
|
||||
@@ -3819,19 +3818,11 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
'@types/react-dom@19.1.1(@types/react@19.1.0)':
|
||||
'@types/react-dom@19.1.3(@types/react@19.1.3)':
|
||||
dependencies:
|
||||
'@types/react': 19.1.0
|
||||
'@types/react': 19.1.3
|
||||
|
||||
'@types/react-dom@19.1.2(@types/react@19.1.1)':
|
||||
dependencies:
|
||||
'@types/react': 19.1.1
|
||||
|
||||
'@types/react@19.1.0':
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
|
||||
'@types/react@19.1.1':
|
||||
'@types/react@19.1.3':
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
|
||||
@@ -3972,51 +3963,51 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@swc/helpers'
|
||||
|
||||
'@vitest/eslint-plugin@1.1.42(@typescript-eslint/utils@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3)(vitest@3.1.1(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3)))':
|
||||
'@vitest/eslint-plugin@1.1.42(@typescript-eslint/utils@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3)(vitest@3.1.4(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.7)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3)))':
|
||||
dependencies:
|
||||
'@typescript-eslint/utils': 8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.7.3)
|
||||
eslint: 9.24.0(jiti@2.4.2)
|
||||
vitest: 3.1.1(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3))
|
||||
vitest: 3.1.4(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.7)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3))
|
||||
optionalDependencies:
|
||||
typescript: 5.7.3
|
||||
|
||||
'@vitest/expect@3.1.1':
|
||||
'@vitest/expect@3.1.4':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.1.1
|
||||
'@vitest/utils': 3.1.1
|
||||
'@vitest/spy': 3.1.4
|
||||
'@vitest/utils': 3.1.4
|
||||
chai: 5.2.0
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/mocker@3.1.1(vite@6.1.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3)))':
|
||||
'@vitest/mocker@3.1.4(vite@6.1.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3)))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.1.1
|
||||
'@vitest/spy': 3.1.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.17
|
||||
optionalDependencies:
|
||||
vite: 6.1.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3))
|
||||
|
||||
'@vitest/pretty-format@3.1.1':
|
||||
'@vitest/pretty-format@3.1.4':
|
||||
dependencies:
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/runner@3.1.1':
|
||||
'@vitest/runner@3.1.4':
|
||||
dependencies:
|
||||
'@vitest/utils': 3.1.1
|
||||
'@vitest/utils': 3.1.4
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/snapshot@3.1.1':
|
||||
'@vitest/snapshot@3.1.4':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 3.1.1
|
||||
'@vitest/pretty-format': 3.1.4
|
||||
magic-string: 0.30.17
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/spy@3.1.1':
|
||||
'@vitest/spy@3.1.4':
|
||||
dependencies:
|
||||
tinyspy: 3.0.2
|
||||
|
||||
'@vitest/utils@3.1.1':
|
||||
'@vitest/utils@3.1.4':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 3.1.1
|
||||
'@vitest/pretty-format': 3.1.4
|
||||
loupe: 3.1.3
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
@@ -4359,7 +4350,7 @@ snapshots:
|
||||
iterator.prototype: 1.1.4
|
||||
safe-array-concat: 1.1.3
|
||||
|
||||
es-module-lexer@1.6.0: {}
|
||||
es-module-lexer@1.7.0: {}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
dependencies:
|
||||
@@ -4647,6 +4638,10 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
|
||||
fdir@6.4.4(picomatch@4.0.2):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
dependencies:
|
||||
flat-cache: 4.0.1
|
||||
@@ -4743,7 +4738,7 @@ snapshots:
|
||||
|
||||
graphemer@1.4.0: {}
|
||||
|
||||
happy-dom@17.4.4:
|
||||
happy-dom@17.4.7:
|
||||
dependencies:
|
||||
webidl-conversions: 7.0.0
|
||||
whatwg-mimetype: 3.0.0
|
||||
@@ -5632,8 +5627,6 @@ snapshots:
|
||||
gopd: 1.2.0
|
||||
which-builtin-type: 1.2.1
|
||||
|
||||
regenerator-runtime@0.14.1: {}
|
||||
|
||||
regexp.prototype.flags@1.5.3:
|
||||
dependencies:
|
||||
call-bind: 1.0.8
|
||||
@@ -5809,7 +5802,7 @@ snapshots:
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
std-env@3.8.1: {}
|
||||
std-env@3.9.0: {}
|
||||
|
||||
string.prototype.matchall@4.0.12:
|
||||
dependencies:
|
||||
@@ -5896,6 +5889,11 @@ snapshots:
|
||||
fdir: 6.4.3(picomatch@4.0.2)
|
||||
picomatch: 4.0.2
|
||||
|
||||
tinyglobby@0.2.13:
|
||||
dependencies:
|
||||
fdir: 6.4.4(picomatch@4.0.2)
|
||||
picomatch: 4.0.2
|
||||
|
||||
tinypool@1.0.2: {}
|
||||
|
||||
tinyrainbow@2.0.0: {}
|
||||
@@ -5924,32 +5922,32 @@ snapshots:
|
||||
tslib@2.8.1:
|
||||
optional: true
|
||||
|
||||
turbo-darwin-64@2.5.0:
|
||||
turbo-darwin-64@2.5.3:
|
||||
optional: true
|
||||
|
||||
turbo-darwin-arm64@2.5.0:
|
||||
turbo-darwin-arm64@2.5.3:
|
||||
optional: true
|
||||
|
||||
turbo-linux-64@2.5.0:
|
||||
turbo-linux-64@2.5.3:
|
||||
optional: true
|
||||
|
||||
turbo-linux-arm64@2.5.0:
|
||||
turbo-linux-arm64@2.5.3:
|
||||
optional: true
|
||||
|
||||
turbo-windows-64@2.5.0:
|
||||
turbo-windows-64@2.5.3:
|
||||
optional: true
|
||||
|
||||
turbo-windows-arm64@2.5.0:
|
||||
turbo-windows-arm64@2.5.3:
|
||||
optional: true
|
||||
|
||||
turbo@2.5.0:
|
||||
turbo@2.5.3:
|
||||
optionalDependencies:
|
||||
turbo-darwin-64: 2.5.0
|
||||
turbo-darwin-arm64: 2.5.0
|
||||
turbo-linux-64: 2.5.0
|
||||
turbo-linux-arm64: 2.5.0
|
||||
turbo-windows-64: 2.5.0
|
||||
turbo-windows-arm64: 2.5.0
|
||||
turbo-darwin-64: 2.5.3
|
||||
turbo-darwin-arm64: 2.5.3
|
||||
turbo-linux-64: 2.5.3
|
||||
turbo-linux-arm64: 2.5.3
|
||||
turbo-windows-64: 2.5.3
|
||||
turbo-windows-arm64: 2.5.3
|
||||
|
||||
type-check@0.4.0:
|
||||
dependencies:
|
||||
@@ -6113,11 +6111,11 @@ snapshots:
|
||||
'@types/unist': 3.0.3
|
||||
vfile-message: 4.0.2
|
||||
|
||||
vite-node@3.1.1(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3)):
|
||||
vite-node@3.1.4(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3)):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.0
|
||||
es-module-lexer: 1.6.0
|
||||
es-module-lexer: 1.7.0
|
||||
pathe: 2.0.3
|
||||
vite: 6.1.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3))
|
||||
transitivePeerDependencies:
|
||||
@@ -6150,32 +6148,33 @@ snapshots:
|
||||
lightningcss: 1.29.1
|
||||
sugarss: 4.0.1(postcss@8.5.3)
|
||||
|
||||
vitest@3.1.1(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3)):
|
||||
vitest@3.1.4(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.7)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3)):
|
||||
dependencies:
|
||||
'@vitest/expect': 3.1.1
|
||||
'@vitest/mocker': 3.1.1(vite@6.1.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3)))
|
||||
'@vitest/pretty-format': 3.1.1
|
||||
'@vitest/runner': 3.1.1
|
||||
'@vitest/snapshot': 3.1.1
|
||||
'@vitest/spy': 3.1.1
|
||||
'@vitest/utils': 3.1.1
|
||||
'@vitest/expect': 3.1.4
|
||||
'@vitest/mocker': 3.1.4(vite@6.1.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3)))
|
||||
'@vitest/pretty-format': 3.1.4
|
||||
'@vitest/runner': 3.1.4
|
||||
'@vitest/snapshot': 3.1.4
|
||||
'@vitest/spy': 3.1.4
|
||||
'@vitest/utils': 3.1.4
|
||||
chai: 5.2.0
|
||||
debug: 4.4.0
|
||||
expect-type: 1.2.1
|
||||
magic-string: 0.30.17
|
||||
pathe: 2.0.3
|
||||
std-env: 3.8.1
|
||||
std-env: 3.9.0
|
||||
tinybench: 2.9.0
|
||||
tinyexec: 0.3.2
|
||||
tinyglobby: 0.2.13
|
||||
tinypool: 1.0.2
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 6.1.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3))
|
||||
vite-node: 3.1.1(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3))
|
||||
vite-node: 3.1.4(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.1)(sugarss@4.0.1(postcss@8.5.3))
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/debug': 4.1.12
|
||||
'@types/node': 22.14.1
|
||||
happy-dom: 17.4.4
|
||||
happy-dom: 17.4.7
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- less
|
||||
|
||||
Reference in New Issue
Block a user