mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-27 13:52:47 -07:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f1ab7df5ce | |||
| 986de49052 | |||
| d09ca5548a | |||
| 77e22c40b6 | |||
| de74647c43 | |||
| 498601c21a | |||
| e99f06ddf5 | |||
| f06af59168 | |||
| d50e3a7fc0 | |||
| 3e4e7ff1ab | |||
| 21ff3c8655 | |||
| 166e61bab4 | |||
| 93cd8bd8da | |||
| f5c1641af2 | |||
| 62ffed0624 | |||
| 8d0a4a7417 | |||
| b1b274ea12 | |||
| fc38865965 | |||
| 911adaaecb | |||
| 7626ed8d1d | |||
| 6135fd7e1b | |||
| 14bea36356 |
@@ -0,0 +1,7 @@
|
||||
[*]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
max_line_length = 80
|
||||
@@ -32,55 +32,24 @@ body:
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: tuono-version
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: Tuono version
|
||||
placeholder: '[e.g. 0.4.0]'
|
||||
label: System Info
|
||||
description: Output of `npx envinfo --system --npmPackages 'tuono' --binaries --browsers`
|
||||
render: shell
|
||||
placeholder: System, Binaries, Browsers
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: os-version
|
||||
required: true
|
||||
- type: textarea
|
||||
id: system-info-rust
|
||||
attributes:
|
||||
label: OS
|
||||
placeholder: '[e.g. MacOS, Windows]'
|
||||
label: System info (Rust)
|
||||
description: Output of `rustc --version && cargo --version && tuono --version`
|
||||
render: shell
|
||||
placeholder: 'rustc, cargo and tuono binary version'
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: browser-version
|
||||
attributes:
|
||||
label: Browser
|
||||
placeholder: '[e.g. chrome, safari]'
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: node-version
|
||||
attributes:
|
||||
label: Node version
|
||||
placeholder: '[e.g. 20.0.0]'
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: rust-version
|
||||
attributes:
|
||||
label: Rust version
|
||||
placeholder: '[e.g. 1.79.0]'
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: create-version
|
||||
attributes:
|
||||
label: Crate version
|
||||
placeholder: '[e.g. 1.78.0]'
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: node-package-manager-version
|
||||
attributes:
|
||||
label: Node Package Manger version
|
||||
placeholder: '[e.g. pnpm: 9.5.0] '
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Install tuono
|
||||
run: cargo install tuono@0.15.0
|
||||
run: cargo install tuono@0.16.4
|
||||
|
||||
- name: Build project
|
||||
working-directory: ./apps/documentation
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Install tuono
|
||||
run: cargo install tuono@0.16.0
|
||||
run: cargo install tuono@0.16.4
|
||||
|
||||
- name: Build project
|
||||
working-directory: ./apps/documentation
|
||||
|
||||
@@ -8,6 +8,8 @@ dist/
|
||||
|
||||
examples/*/pnpm-lock.yaml
|
||||
|
||||
vite.config.ts.timestamp*
|
||||
|
||||
## Rust related ignores
|
||||
|
||||
# Generated by Cargo
|
||||
|
||||
+6
-1
@@ -1 +1,6 @@
|
||||
pnpm-lock.yaml
|
||||
pnpm-lock.yaml
|
||||
|
||||
dist
|
||||
.tuono
|
||||
|
||||
packages/lazy-fn-vite-plugin/tests/sources/*
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
* @Valerioageno @marcalexiei
|
||||
|
||||
# Rust
|
||||
/crates/ @Valerioageno
|
||||
/Cargo.toml @Valerioageno
|
||||
|
||||
# Misc
|
||||
/.github/ @marcalexiei
|
||||
|
||||
@@ -26,7 +26,7 @@ Some of its features are:
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
The [documentation](https://tuono.dev/documentation) is available on
|
||||
The [documentation](https://tuono.dev/) is available on
|
||||
[tuono.dev](https://tuono.dev/).
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -8,7 +8,7 @@ name = "tuono"
|
||||
path = ".tuono/main.rs"
|
||||
|
||||
[dependencies]
|
||||
tuono_lib = "0.15.0"
|
||||
tuono_lib = "0.16.4"
|
||||
glob = "0.3.1"
|
||||
time = { version = "0.3", features = ["macros"] }
|
||||
serde = { version = "1.0.202", features = ["derive"] }
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"format": "prettier -u --write --ignore-unknown .",
|
||||
"format": "prettier --write --ignore-unknown .",
|
||||
"format:check": "prettier --check --ignore-unknown .",
|
||||
"types": "tsc --noEmit"
|
||||
},
|
||||
@@ -19,7 +19,7 @@
|
||||
"clsx": "2.1.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"tuono": "npm:tuono@0.16.0"
|
||||
"tuono": "npm:tuono@0.16.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mdx-js/rollup": "3.1.0",
|
||||
|
||||
@@ -111,6 +111,11 @@ export const sidebarElements: Array<SidebarElement> = [
|
||||
label: 'Defining routes',
|
||||
href: '/documentation/routing/defining-routes',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Dynamic routes',
|
||||
href: '/documentation/routing/dynamic-routes',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Link and navigation',
|
||||
@@ -126,11 +131,6 @@ export const sidebarElements: Array<SidebarElement> = [
|
||||
label: 'Loading state',
|
||||
href: '/documentation/routing/loading-state',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Dynamic routes',
|
||||
href: '/documentation/routing/dynamic-routes',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Layouts',
|
||||
|
||||
@@ -64,7 +64,9 @@ export function TableOfContents({
|
||||
),
|
||||
)
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
return (): void => window.removeEventListener('scroll', handleScroll)
|
||||
return (): void => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [router.pathname])
|
||||
|
||||
if (filteredHeadings.length === 0) {
|
||||
|
||||
@@ -17,9 +17,9 @@ export default function ThemeBtn(): JSX.Element {
|
||||
|
||||
return (
|
||||
<ActionIcon
|
||||
onClick={() =>
|
||||
onClick={() => {
|
||||
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
|
||||
}
|
||||
}}
|
||||
variant="default"
|
||||
size="lg"
|
||||
aria-label="Toggle color scheme"
|
||||
|
||||
@@ -9,6 +9,53 @@ import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs breadcrumbs={[{ label: 'Dynamic routes' }]} />
|
||||
|
||||
# Loading state
|
||||
# Dynamic routes
|
||||
|
||||
Todo
|
||||
> 🚧 Only available for SSR apps. This feature will be soon available also for static generated apps.
|
||||
|
||||
When the exact segment names are not known in advance and you need to generate routes from dynamic data,
|
||||
you can use dynamic segments, which are populated at request time.
|
||||
|
||||
## Convention
|
||||
|
||||
A dynamic segment can be created by wrapping a
|
||||
file or a folder name in square brackets: `[segmentName]`.
|
||||
|
||||
The dynamic segment can be used in folders as well as file names:
|
||||
|
||||
- `src/routes/blog/[slug].tsx`
|
||||
- `src/routes/blog/[slug]/index.tsx`
|
||||
|
||||
A path can contain multiple dynamic segments, allowing for more flexible routing:
|
||||
|
||||
- `src/routes/blog/[post]/[comment].tsx`
|
||||
|
||||
### Example
|
||||
|
||||
Assuming you're working on a blog with multiple posts,
|
||||
you can use a dynamic segment to define the post page: `src/routes/blog/[slug].tsx`.
|
||||
|
||||
The `[slug]` is the dynamic segment which will be used to identify individual blog posts.
|
||||
|
||||
```tsx
|
||||
import { useRouter } from 'tuono'
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter()
|
||||
return <p>Post: {router.pathname}</p>
|
||||
}
|
||||
```
|
||||
|
||||
## Catch all segments
|
||||
|
||||
Dynamic Segments can be extended to catch-all subsequent segments by adding
|
||||
an ellipsis inside the brackets `[...segmentName]`.
|
||||
|
||||
Given the following `src/routes/shop/[...slug].tsx`,
|
||||
the following paths will be matched:
|
||||
|
||||
- `/shop/clothes`
|
||||
- `/shop/clothes/tops`
|
||||
- `/shop`
|
||||
- `/shop/clothes/tops/t-shirts`
|
||||
- and so on.
|
||||
|
||||
@@ -32,6 +32,5 @@
|
||||
// Completeness
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src", "tuono.config.ts", "postcss.config.js"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
"include": ["src", "tuono.config.ts", "postcss.config.js"]
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -7,9 +7,6 @@ const config: TuonoConfig = {
|
||||
'@': 'src',
|
||||
'@tabler/icons-react': '@tabler/icons-react/dist/esm/icons/index.mjs',
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: ['@mdx-js/react'],
|
||||
},
|
||||
plugins: [
|
||||
{ enforce: 'pre', ...mdx({ providerImportSource: '@mdx-js/react' }) },
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tuono"
|
||||
version = "0.16.2"
|
||||
version = "0.16.6"
|
||||
edition = "2021"
|
||||
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
||||
description = "Superfast React fullstack framework"
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
//! ## Tuono
|
||||
//! Tuono is a full-stack web framework for building React applications using Rust as the backend with a strong focus on usability and performance.
|
||||
//!
|
||||
//! You can find the full documentation at [tuono.dev](https://tuono.dev/)
|
||||
|
||||
mod app;
|
||||
pub mod cli;
|
||||
mod mode;
|
||||
|
||||
@@ -52,7 +52,7 @@ impl AxumInfo {
|
||||
.replace('/', "_")
|
||||
.replace('-', "_hyphen_")
|
||||
.replace('[', "dyn_")
|
||||
.replace("...", "_catch_all_")
|
||||
.replace("...", "catch_all_")
|
||||
.replace(']', ""),
|
||||
axum_route: axum_route
|
||||
.replace("[...", "*")
|
||||
|
||||
@@ -84,11 +84,11 @@ fn create_routes_declaration(routes: &HashMap<String, Route>) -> String {
|
||||
|
||||
if !route.is_api() {
|
||||
route_declarations.push_str(&format!(
|
||||
r#".route("{axum_route}", get({module_import}::route))"#
|
||||
r#".route("{axum_route}", get({module_import}::tuono__internal__route))"#
|
||||
));
|
||||
|
||||
route_declarations.push_str(&format!(
|
||||
r#".route("/__tuono/data{axum_route}", get({module_import}::api))"#
|
||||
r#".route("/__tuono/data{axum_route}", get({module_import}::tuono__internal__api))"#
|
||||
));
|
||||
} else {
|
||||
for method in route.api_data.as_ref().unwrap().methods.clone() {
|
||||
|
||||
@@ -30,7 +30,7 @@ fn it_successfully_create_the_index_route() {
|
||||
assert!(temp_main_rs_content.contains("mod index;"));
|
||||
|
||||
assert!(temp_main_rs_content
|
||||
.contains(r#".route("/", get(index::route)).route("/__tuono/data/", get(index::api))"#));
|
||||
.contains(r#".route("/", get(index::tuono__internal__route)).route("/__tuono/data/", get(index::tuono__internal__api))"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -120,18 +120,23 @@ fn it_successfully_create_catch_all_routes() {
|
||||
fs::read_to_string(&temp_main_rs_path).expect("Failed to read '.tuono/main.rs' content.");
|
||||
|
||||
assert!(temp_main_rs_content.contains(r#"#[path="../src/routes/api/[...all_apis].rs"]"#));
|
||||
assert!(temp_main_rs_content.contains("mod api_dyn__catch_all_all_apis;"));
|
||||
assert!(temp_main_rs_content.contains("mod api_dyn_catch_all_all_apis;"));
|
||||
|
||||
assert!(temp_main_rs_content.contains(r#"#[path="../src/routes/[...all_routes].rs"]"#));
|
||||
assert!(temp_main_rs_content.contains("mod dyn__catch_all_all_routes;"));
|
||||
assert!(temp_main_rs_content.contains("mod dyn_catch_all_all_routes;"));
|
||||
|
||||
assert!(temp_main_rs_content.contains(
|
||||
r#".route("/api/*all_apis", post(api_dyn__catch_all_all_apis::post__tuono_internal_api))"#
|
||||
r#".route("/api/*all_apis", post(api_dyn_catch_all_all_apis::post__tuono_internal_api))"#
|
||||
));
|
||||
|
||||
assert!(temp_main_rs_content.contains(
|
||||
r#".route("/*all_routes", get(dyn_catch_all_all_routes::tuono__internal__route))"#
|
||||
));
|
||||
|
||||
assert!(temp_main_rs_content.contains(
|
||||
r#".route("/*all_routes", get(dyn_catch_all_all_routes::tuono__internal__route))"#
|
||||
));
|
||||
|
||||
assert!(temp_main_rs_content
|
||||
.contains(r#".route("/*all_routes", get(dyn__catch_all_all_routes::route))"#));
|
||||
|
||||
assert!(temp_main_rs_content
|
||||
.contains(r#".route("/__tuono/data/*all_routes", get(dyn__catch_all_all_routes::api))"#));
|
||||
.contains(r#".route("/__tuono/data/*all_routes", get(dyn_catch_all_all_routes::tuono__internal__api))"#));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tuono_lib"
|
||||
version = "0.16.2"
|
||||
version = "0.16.6"
|
||||
edition = "2021"
|
||||
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
||||
description = "Superfast React fullstack framework"
|
||||
@@ -32,7 +32,7 @@ either = "1.13.0"
|
||||
tower-http = {version = "0.6.0", features = ["fs"]}
|
||||
colored = "2.1.0"
|
||||
|
||||
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.16.2"}
|
||||
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.16.6"}
|
||||
# Match the same version used by axum
|
||||
tokio-tungstenite = "0.24.0"
|
||||
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
//! ## Tuono
|
||||
//! Tuono is a full-stack web framework for building React applications using Rust as the backend with a strong focus on usability and performance.
|
||||
//!
|
||||
//! You can find the full documentation at [tuono.dev](https://tuono.dev/)
|
||||
|
||||
mod catch_all;
|
||||
mod logger;
|
||||
mod manifest;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tuono_lib_macros"
|
||||
version = "0.16.2"
|
||||
version = "0.16.6"
|
||||
edition = "2021"
|
||||
description = "Superfast React fullstack framework"
|
||||
homepage = "https://tuono.dev"
|
||||
|
||||
@@ -45,7 +45,7 @@ pub fn handler_core(_args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
||||
#item
|
||||
|
||||
pub async fn route(
|
||||
pub async fn tuono__internal__route(
|
||||
#axum_arguments
|
||||
) -> impl tuono_lib::axum::response::IntoResponse {
|
||||
|
||||
@@ -59,7 +59,7 @@ pub fn handler_core(_args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
#fn_name(req.clone(), #argument_names).await.render_to_string(req)
|
||||
}
|
||||
|
||||
pub async fn api(
|
||||
pub async fn tuono__internal__api(
|
||||
#axum_arguments
|
||||
) -> impl tuono_lib::axum::response::IntoResponse {
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
//! ## Tuono
|
||||
//! Tuono is a full-stack web framework for building React applications using Rust as the backend with a strong focus on usability and performance.
|
||||
//!
|
||||
//! You can find the full documentation at [tuono.dev](https://tuono.dev/)
|
||||
|
||||
extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
|
||||
+38
-12
@@ -2,6 +2,8 @@ import eslint from '@eslint/js'
|
||||
import tseslint from 'typescript-eslint'
|
||||
// @ts-expect-error no types are available for this plugin
|
||||
import eslintPluginImport from 'eslint-plugin-import'
|
||||
// @ts-expect-error no types are available for this plugin
|
||||
import eslintPluginReactHooks from 'eslint-plugin-react-hooks'
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
@@ -14,6 +16,7 @@ export default tseslint.config(
|
||||
|
||||
// #region package-specific
|
||||
'packages/fs-router-vite-plugin/tests/generator/**',
|
||||
'packages/lazy-fn-vite-plugin/tests/sources/**',
|
||||
|
||||
'packages/tuono/bin/**',
|
||||
// #endregion package-specific
|
||||
@@ -24,10 +27,17 @@ export default tseslint.config(
|
||||
reportUnusedDisableDirectives: 'error',
|
||||
},
|
||||
},
|
||||
|
||||
eslint.configs.recommended,
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access */
|
||||
eslintPluginImport.flatConfigs.recommended,
|
||||
eslintPluginImport.flatConfigs.typescript,
|
||||
tseslint.configs.recommendedTypeChecked,
|
||||
/* eslint-enable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
// eslint-disable-next-line import/no-named-as-default-member
|
||||
tseslint.configs.strictTypeChecked,
|
||||
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
@@ -49,16 +59,13 @@ export default tseslint.config(
|
||||
rules: {
|
||||
// #region @typescript-eslint
|
||||
'@typescript-eslint/array-type': ['error', { default: 'generic' }],
|
||||
'@typescript-eslint/no-wrapper-object-types': 'error',
|
||||
'@typescript-eslint/no-empty-object-type': 'error',
|
||||
'@typescript-eslint/no-unsafe-function-type': 'error',
|
||||
'@typescript-eslint/ban-ts-comment': 'error',
|
||||
'@typescript-eslint/consistent-type-definitions': 'error',
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
{ prefer: 'type-imports' },
|
||||
],
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'error',
|
||||
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||
'@typescript-eslint/method-signature-style': ['error', 'property'],
|
||||
'@typescript-eslint/naming-convention': [
|
||||
'error',
|
||||
@@ -73,19 +80,24 @@ export default tseslint.config(
|
||||
},
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-deprecated': 'error',
|
||||
'@typescript-eslint/no-empty-function': 'error',
|
||||
'@typescript-eslint/no-empty-interface': 'error',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||
'@typescript-eslint/no-unnecessary-condition': 'error',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||
|
||||
'@typescript-eslint/no-inferrable-types': [
|
||||
'error',
|
||||
{ ignoreParameters: true },
|
||||
],
|
||||
'@typescript-eslint/restrict-template-expressions': [
|
||||
'error',
|
||||
{
|
||||
allowAny: false,
|
||||
allowBoolean: false,
|
||||
allowNever: false,
|
||||
allowNullish: false,
|
||||
allowNumber: true,
|
||||
allowRegExp: false,
|
||||
},
|
||||
],
|
||||
// #endregion @typescript-eslint
|
||||
|
||||
// #region import
|
||||
@@ -126,8 +138,22 @@ export default tseslint.config(
|
||||
'no-shadow': 'error',
|
||||
'no-undef': 'off',
|
||||
'sort-imports': 'off',
|
||||
// #endregion misc
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
|
||||
plugins: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
'react-hooks': eslintPluginReactHooks,
|
||||
},
|
||||
rules: {
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
files: ['apps/documentation/**'],
|
||||
settings: {
|
||||
|
||||
+1
-2
@@ -31,8 +31,7 @@
|
||||
"eslint": "^9.15.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-hooks": "5.1.0",
|
||||
"prettier": "^3.2.4",
|
||||
"turbo": "^2.2.3",
|
||||
"typescript": "5.6.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tuono-fs-router-vite-plugin",
|
||||
"version": "0.16.2",
|
||||
"version": "0.16.6",
|
||||
"description": "Plugin for the tuono's file system router. Tuono is the react/rust fullstack framework",
|
||||
"homepage": "https://tuono.dev",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { RouteNode } from './types'
|
||||
|
||||
export function buildRouteConfig(nodes: Array<RouteNode>, depth = 1): string {
|
||||
const children = nodes.map((node) => {
|
||||
const route = `${node.variableName}Route`
|
||||
const route = `${node.variableName as string}Route`
|
||||
|
||||
if (node.children?.length) {
|
||||
const childConfigs = buildRouteConfig(node.children, depth + 1)
|
||||
|
||||
@@ -157,14 +157,14 @@ export async function routeGenerator(config = defaultConfig): Promise<void> {
|
||||
(d): number => (d.routePath.includes(`/${ROOT_PATH_ID}`) ? -1 : 1),
|
||||
(d): number => d.routePath.split('/').length,
|
||||
(d): number => (d.routePath.endsWith("index'") ? -1 : 1),
|
||||
(d): any => d,
|
||||
(d): RouteNode => d,
|
||||
])
|
||||
|
||||
const imports = [
|
||||
...sortedRouteNodes.map((node) => {
|
||||
const extension = node.filePath.endsWith('mdx') ? '.mdx' : ''
|
||||
return `const ${
|
||||
node.variableName
|
||||
node.variableName as string
|
||||
}Import = dynamic(() => import('./${replaceBackslash(
|
||||
removeExt(
|
||||
path.relative(
|
||||
@@ -181,18 +181,21 @@ export async function routeGenerator(config = defaultConfig): Promise<void> {
|
||||
...sortedRouteNodes.map((node) => {
|
||||
const isRoot = node.routePath.endsWith(ROOT_PATH_ID)
|
||||
const rootDeclaration = isRoot ? ', isRoot: true' : ''
|
||||
const variableName = node.variableName as string
|
||||
|
||||
return `const ${node.variableName} = createRoute({ component: ${node.variableName}Import${rootDeclaration} })`
|
||||
return `const ${variableName} = createRoute({ component: ${variableName}Import${rootDeclaration} })`
|
||||
}),
|
||||
].join('\n')
|
||||
|
||||
const createRouteUpdates = [
|
||||
sortedRouteNodes
|
||||
.map((node) => {
|
||||
const variableName = node.variableName as string
|
||||
const cleanedPath = node.cleanedPath as string
|
||||
return [
|
||||
`const ${node.variableName}Route = ${node.variableName}.update({
|
||||
`const ${variableName}Route = ${variableName}.update({
|
||||
${[
|
||||
!node.path?.endsWith(ROOT_PATH_ID) && `path: '${node.cleanedPath}'`,
|
||||
!node.path?.endsWith(ROOT_PATH_ID) && `path: '${cleanedPath}'`,
|
||||
`getParentRoute: () => ${node.parent?.variableName ?? 'root'}Route`,
|
||||
rustHandlersNodes.includes(node.path || '')
|
||||
? 'hasHandler: true'
|
||||
@@ -236,7 +239,7 @@ export async function routeGenerator(config = defaultConfig): Promise<void> {
|
||||
|
||||
const routeTreeContent = await fsp
|
||||
.readFile(path.resolve(config.generatedRouteTree), 'utf-8')
|
||||
.catch((e) => {
|
||||
.catch((e: unknown) => {
|
||||
const err = e as Error & { code?: string }
|
||||
if (err.code === 'ENOENT') {
|
||||
return undefined
|
||||
|
||||
@@ -5,7 +5,7 @@ import { describe, it, expect } from 'vitest'
|
||||
import { routeGenerator } from '../src/generator'
|
||||
|
||||
describe('generator works', async () => {
|
||||
const folderNames = await fs.readdir(process.cwd() + '/tests/generator')
|
||||
const folderNames = await fs.readdir(`${process.cwd()}/tests/generator`)
|
||||
|
||||
it.each(folderNames)(
|
||||
'should wire-up the routes for a "%s" tree',
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
dist
|
||||
pnpm-lock.yaml
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "tuono-lazy-fn-vite-plugin",
|
||||
"version": "0.16.2",
|
||||
"version": "0.16.6",
|
||||
"description": "Plugin for the tuono's lazy fn. Tuono is the react/rust fullstack framework",
|
||||
"homepage": "https://tuono.dev",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier -u --write --ignore-unknown '**/*'",
|
||||
"format:check": "prettier --check --ignore-unknown '**/*'",
|
||||
"format": "prettier --write --ignore-unknown --ignore-path ../../.prettierignore .",
|
||||
"format:check": "prettier --check --ignore-unknown --ignore-path ../../.prettierignore .",
|
||||
"types": "tsc --noEmit",
|
||||
"test:watch": "vitest",
|
||||
"test": "vitest run"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export const TUONO_DYNAMIC_FN_ID = 'dynamic'
|
||||
export const TUONO_LAZY_FN_ID = 'lazyLoadComponent'
|
||||
export const TUONO_LAZY_FN_ID = '__tuono__internal__lazyLoadComponent'
|
||||
export const TUONO_MAIN_PACKAGE = 'tuono'
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import * as babel from '@babel/core'
|
||||
import * as t from '@babel/types'
|
||||
import type { Plugin } from 'vite'
|
||||
import type { PluginItem } from '@babel/core'
|
||||
import type {
|
||||
Identifier,
|
||||
CallExpression,
|
||||
ArrowFunctionExpression,
|
||||
StringLiteral,
|
||||
} from '@babel/types'
|
||||
import { transformSync } from '@babel/core'
|
||||
import type { PluginItem as BabelPluginItem } from '@babel/core'
|
||||
import * as BabelTypes from '@babel/types'
|
||||
import { createFilter } from 'vite'
|
||||
import type { Plugin as VitePlugin, Rollup, FilterPattern } from 'vite'
|
||||
|
||||
import {
|
||||
TUONO_MAIN_PACKAGE,
|
||||
@@ -20,11 +15,24 @@ import { isTuonoDynamicFnImported } from './utils'
|
||||
* [SERVER build]
|
||||
* This plugin just removes the `dynamic` imported function from any tuono import
|
||||
*/
|
||||
const RemoveTuonoLazyImport: PluginItem = {
|
||||
const RemoveTuonoLazyImport: BabelPluginItem = {
|
||||
name: 'remove-tuono-lazy-import-plugin',
|
||||
visitor: {
|
||||
ImportSpecifier: (path) => {
|
||||
if (isTuonoDynamicFnImported(path)) {
|
||||
ImportDeclaration: (path) => {
|
||||
const importNode = path.node
|
||||
if (importNode.source.value !== TUONO_MAIN_PACKAGE) return
|
||||
|
||||
path.traverse({
|
||||
ImportSpecifier: (importSpecifierPath) => {
|
||||
if (isTuonoDynamicFnImported(importSpecifierPath)) {
|
||||
importSpecifierPath.remove()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// If there are no specifiers left after traverse
|
||||
// remove the import to avoid unwanted side effects
|
||||
if (importNode.specifiers.length === 0) {
|
||||
path.remove()
|
||||
}
|
||||
},
|
||||
@@ -33,36 +41,56 @@ const RemoveTuonoLazyImport: PluginItem = {
|
||||
|
||||
/**
|
||||
* [CLIENT build]
|
||||
* This plugin replace the `dynamic` function with the `lazyLoadComponent` one
|
||||
* This plugin replace the `dynamic` function with the `__tuono__internal__lazyLoadComponent` one
|
||||
*/
|
||||
const ReplaceTuonoLazyImport: PluginItem = {
|
||||
name: 'remove-tuono-lazy-import-plugin',
|
||||
const ReplaceTuonoLazyImport: BabelPluginItem = {
|
||||
name: 'replace-tuono-lazy-import-plugin',
|
||||
visitor: {
|
||||
ImportSpecifier: (path) => {
|
||||
if (isTuonoDynamicFnImported(path)) {
|
||||
;(path.node.imported as Identifier).name = TUONO_LAZY_FN_ID
|
||||
if (
|
||||
BabelTypes.isIdentifier(path.node.imported) &&
|
||||
isTuonoDynamicFnImported(path)
|
||||
) {
|
||||
path.node.imported.name = TUONO_LAZY_FN_ID
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const turnLazyIntoStatic = {
|
||||
VariableDeclaration: (path: babel.NodePath<t.VariableDeclaration>): void => {
|
||||
path.node.declarations.forEach((el) => {
|
||||
const init = el.init as CallExpression
|
||||
if ((init.callee as Identifier).name === TUONO_DYNAMIC_FN_ID) {
|
||||
const importName = (el.id as Identifier).name
|
||||
const importPath = (
|
||||
(
|
||||
(init.arguments[0] as ArrowFunctionExpression)
|
||||
.body as CallExpression
|
||||
).arguments[0] as StringLiteral
|
||||
).value
|
||||
VariableDeclaration: (
|
||||
path: babel.NodePath<BabelTypes.VariableDeclaration>,
|
||||
): void => {
|
||||
path.node.declarations.forEach((variableDeclarationNode) => {
|
||||
const init = variableDeclarationNode.init
|
||||
|
||||
if (
|
||||
BabelTypes.isCallExpression(init) &&
|
||||
// ensures that the method call is `TUONO_DYNAMIC_FN_ID`
|
||||
BabelTypes.isIdentifier(init.callee, { name: TUONO_DYNAMIC_FN_ID }) &&
|
||||
// import name must be an identifier
|
||||
BabelTypes.isIdentifier(variableDeclarationNode.id) &&
|
||||
// check that the first function parameter is an arrow function
|
||||
BabelTypes.isArrowFunctionExpression(init.arguments[0])
|
||||
) {
|
||||
const cmpImportFn = init.arguments[0]
|
||||
|
||||
// ensures that the first parameter is a call expression (may be a block statement)
|
||||
if (!BabelTypes.isCallExpression(cmpImportFn.body)) return
|
||||
// ensures that the first parameter is a string literal (the import path)
|
||||
if (!BabelTypes.isStringLiteral(cmpImportFn.body.arguments[0])) return
|
||||
|
||||
const importName = variableDeclarationNode.id.name
|
||||
const importPath = cmpImportFn.body.arguments[0].value
|
||||
|
||||
if (importName && importPath) {
|
||||
const importDeclaration = t.importDeclaration(
|
||||
[t.importDefaultSpecifier(t.identifier(importName))],
|
||||
t.stringLiteral(importPath),
|
||||
const importDeclaration = BabelTypes.importDeclaration(
|
||||
[
|
||||
BabelTypes.importDefaultSpecifier(
|
||||
BabelTypes.identifier(importName),
|
||||
),
|
||||
],
|
||||
BabelTypes.stringLiteral(importPath),
|
||||
)
|
||||
|
||||
path.replaceWith(importDeclaration)
|
||||
@@ -76,7 +104,7 @@ const turnLazyIntoStatic = {
|
||||
* [SERVER build]
|
||||
* This plugin statically imports the lazy loaded components
|
||||
*/
|
||||
const TurnLazyIntoStaticImport: PluginItem = {
|
||||
const TurnLazyIntoStaticImport: BabelPluginItem = {
|
||||
name: 'turn-lazy-into-static-import-plugin',
|
||||
visitor: {
|
||||
Program: (path) => {
|
||||
@@ -91,28 +119,50 @@ const TurnLazyIntoStaticImport: PluginItem = {
|
||||
},
|
||||
}
|
||||
|
||||
export function LazyLoadingPlugin(): Plugin {
|
||||
interface LazyLoadingPluginOptions {
|
||||
include: FilterPattern
|
||||
}
|
||||
|
||||
export function LazyLoadingPlugin(
|
||||
options: LazyLoadingPluginOptions,
|
||||
): VitePlugin {
|
||||
const { include } = options
|
||||
|
||||
const filter = createFilter(include)
|
||||
|
||||
return {
|
||||
name: 'vite-plugin-tuono-lazy-loading',
|
||||
enforce: 'pre',
|
||||
transform(code, _id, opts): string | undefined | null {
|
||||
transform(code, id, opts): Rollup.TransformResult {
|
||||
if (!filter(id)) return
|
||||
|
||||
/**
|
||||
* @todo we should exclude non tsx files from this transformation
|
||||
* this might benefit build time avoiding running `includes` on non-tsx files.
|
||||
* This can be executed using `_id` parameter
|
||||
* which is the filepath that is being processed
|
||||
*/
|
||||
|
||||
if (
|
||||
code.includes(TUONO_DYNAMIC_FN_ID) &&
|
||||
code.includes(TUONO_MAIN_PACKAGE)
|
||||
) {
|
||||
const res = babel.transformSync(code, {
|
||||
plugins: [
|
||||
['@babel/plugin-syntax-jsx', {}],
|
||||
['@babel/plugin-syntax-typescript', { isTSX: true }],
|
||||
[!opts?.ssr ? ReplaceTuonoLazyImport : []],
|
||||
[opts?.ssr ? RemoveTuonoLazyImport : []],
|
||||
[opts?.ssr ? TurnLazyIntoStaticImport : []],
|
||||
],
|
||||
sourceMaps: true,
|
||||
})
|
||||
const plugins: Array<BabelPluginItem> = [
|
||||
['@babel/plugin-syntax-jsx', {}],
|
||||
['@babel/plugin-syntax-typescript', { isTSX: true }],
|
||||
]
|
||||
|
||||
if (opts?.ssr) {
|
||||
plugins.push(RemoveTuonoLazyImport, TurnLazyIntoStaticImport)
|
||||
} else {
|
||||
plugins.push(ReplaceTuonoLazyImport)
|
||||
}
|
||||
|
||||
const res = transformSync(code, { plugins })
|
||||
|
||||
return res?.code
|
||||
}
|
||||
|
||||
return code
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import type {
|
||||
Identifier,
|
||||
ImportDeclaration,
|
||||
ImportSpecifier,
|
||||
import {
|
||||
isIdentifier,
|
||||
isImportDeclaration,
|
||||
isImportSpecifier,
|
||||
} from '@babel/types'
|
||||
|
||||
import { TUONO_MAIN_PACKAGE, TUONO_DYNAMIC_FN_ID } from './constants'
|
||||
|
||||
export const isTuonoDynamicFnImported = (
|
||||
path: babel.NodePath<ImportSpecifier>,
|
||||
): boolean => {
|
||||
if ((path.node.imported as Identifier).name !== TUONO_DYNAMIC_FN_ID) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
(path.parentPath.node as ImportDeclaration).source.value !==
|
||||
TUONO_MAIN_PACKAGE
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
/**
|
||||
* By a given AST Node path returns true if the path involves an import specifier
|
||||
* importing {@link TUONO_DYNAMIC_FN_ID}
|
||||
*/
|
||||
export const isTuonoDynamicFnImported = (path: babel.NodePath): boolean => {
|
||||
// If the node isn't an import declaration there is no need to process it
|
||||
if (!isImportDeclaration(path.parentPath?.node)) return false
|
||||
|
||||
// if the import doesn't import from 'tuono' we don't need to process it
|
||||
if (path.parentPath.node.source.value !== TUONO_MAIN_PACKAGE) return false
|
||||
|
||||
// ensure that we are processing an import specifier
|
||||
if (!isImportSpecifier(path.node)) return false
|
||||
|
||||
// finally check if the imported item is `TUONO_DYNAMIC_FN_ID`
|
||||
return isIdentifier(path.node.imported, { name: TUONO_DYNAMIC_FN_ID })
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import React, { Suspense, type JSX } from "react";
|
||||
import { __tuono__internal__lazyLoadComponent as dynamic } from "tuono";
|
||||
const DynamicComponent = dynamic(() => import("../components/DynamicComponent"));
|
||||
const Loading = (): JSX.Element => <>Loading</>;
|
||||
export default function IndexPage(): JSX.Element {
|
||||
return <Suspense fallback={<Loading />}>
|
||||
<DynamicComponent />
|
||||
</Suspense>;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import React, { Suspense, type JSX } from "react";
|
||||
import DynamicComponent from "../components/DynamicComponent";
|
||||
const Loading = (): JSX.Element => <>Loading</>;
|
||||
export default function IndexPage(): JSX.Element {
|
||||
return <Suspense fallback={<Loading />}>
|
||||
<DynamicComponent />
|
||||
</Suspense>;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, { Suspense, type JSX } from "react";
|
||||
import { dynamic } from "tuono";
|
||||
|
||||
const DynamicComponent = dynamic(() => import("../components/DynamicComponent"))
|
||||
|
||||
const Loading = (): JSX.Element => <>Loading</>
|
||||
|
||||
export default function IndexPage(): JSX.Element {
|
||||
return (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<DynamicComponent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createRoute } from 'tuono';
|
||||
import { dynamic } from 'external-lib';
|
||||
const IndexImport = dynamic(() => import('./../src/routes/index'));
|
||||
const PokemonspokemonImport = dynamic(() => import('./../src/routes/pokemons/[pokemon]'));
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createRoute } from 'tuono';
|
||||
import { dynamic } from 'external-lib';
|
||||
const IndexImport = dynamic(() => import('./../src/routes/index'));
|
||||
const PokemonspokemonImport = dynamic(() => import('./../src/routes/pokemons/[pokemon]'));
|
||||
@@ -0,0 +1,7 @@
|
||||
import { createRoute } from 'tuono'
|
||||
import { dynamic } from 'external-lib'
|
||||
|
||||
const IndexImport = dynamic(() => import('./../src/routes/index'))
|
||||
const PokemonspokemonImport = dynamic(
|
||||
() => import('./../src/routes/pokemons/[pokemon]'),
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
import { createRoute, __tuono__internal__lazyLoadComponent as dynamic } from 'tuono';
|
||||
const IndexImport = dynamic(() => import('./../src/routes/index'));
|
||||
const PokemonspokemonImport = dynamic(() => import('./../src/routes/pokemons/[pokemon]'));
|
||||
@@ -0,0 +1,3 @@
|
||||
import { createRoute } from 'tuono';
|
||||
import IndexImport from "./../src/routes/index";
|
||||
import PokemonspokemonImport from "./../src/routes/pokemons/[pokemon]";
|
||||
@@ -0,0 +1,6 @@
|
||||
import { createRoute, dynamic } from 'tuono'
|
||||
|
||||
const IndexImport = dynamic(() => import('./../src/routes/index'))
|
||||
const PokemonspokemonImport = dynamic(
|
||||
() => import('./../src/routes/pokemons/[pokemon]'),
|
||||
)
|
||||
@@ -1,27 +1,11 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import os from 'node:os'
|
||||
|
||||
import { it, expect, describe } from 'vitest'
|
||||
import type { Plugin } from 'vite'
|
||||
|
||||
import { LazyLoadingPlugin } from '../src'
|
||||
|
||||
const SOURCE_CODE = `
|
||||
import { createRoute, dynamic } from 'tuono'
|
||||
|
||||
const IndexImport = dynamic(() => import('./../src/routes/index'))
|
||||
const PokemonspokemonImport = dynamic(
|
||||
() => import('./../src/routes/pokemons/[pokemon]'),
|
||||
)
|
||||
`
|
||||
|
||||
const NON_DYNAMIC_SOURCE = `
|
||||
import { createRoute } from 'tuono'
|
||||
import {dynamic} from 'external-lib'
|
||||
|
||||
const IndexImport = dynamic(() => import('./../src/routes/index'))
|
||||
const PokemonspokemonImport = dynamic(
|
||||
() => import('./../src/routes/pokemons/[pokemon]'),
|
||||
)
|
||||
`
|
||||
|
||||
type ViteTransformHandler = Exclude<
|
||||
Plugin['transform'],
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
@@ -30,48 +14,81 @@ type ViteTransformHandler = Exclude<
|
||||
|
||||
// Create a type-safe transform method
|
||||
function getTransform(): (...args: Parameters<ViteTransformHandler>) => string {
|
||||
return LazyLoadingPlugin().transform as never
|
||||
/** @warning Keep in sync with {@link createBaseViteConfigFromTuonoConfig} */
|
||||
const pluginFilesInclude = /\.(jsx|js|mdx|md|tsx|ts)$/
|
||||
|
||||
return LazyLoadingPlugin({ include: pluginFilesInclude }).transform as never
|
||||
}
|
||||
|
||||
describe('Transpile tuono source', () => {
|
||||
it('Into the client bundle', () => {
|
||||
const pluginTransform = getTransform()
|
||||
const bundle = pluginTransform(SOURCE_CODE, 'id')
|
||||
expect(bundle)
|
||||
.toBe(`import { createRoute, lazyLoadComponent as dynamic } from 'tuono';
|
||||
const IndexImport = dynamic(() => import('./../src/routes/index'));
|
||||
const PokemonspokemonImport = dynamic(() => import('./../src/routes/pokemons/[pokemon]'));`)
|
||||
})
|
||||
describe('"dynamic" sources', async () => {
|
||||
const folderNames = await fs.readdir(`${process.cwd()}/tests/sources`)
|
||||
|
||||
it('Into the server bundle', () => {
|
||||
const pluginTransform = getTransform()
|
||||
const bundle = pluginTransform(SOURCE_CODE, 'id', {
|
||||
ssr: true,
|
||||
describe.each(folderNames)('%s', async (folderName) => {
|
||||
const testDirPath = `${process.cwd()}/tests/sources/${folderName}`
|
||||
|
||||
const sourceFilePath = `${testDirPath}/source.tsx`
|
||||
|
||||
const sourceRaw = await fs.readFile(sourceFilePath, 'utf-8')
|
||||
/**
|
||||
* When adding `packages/lazy-fn-vite-plugin/tests/sources/dynamic-only` only
|
||||
* the test involving that fixture were broken on Windows... but not the one in the other fixtures:
|
||||
* - packages/lazy-fn-vite-plugin/tests/sources/vanilla
|
||||
* - packages/lazy-fn-vite-plugin/tests/sources/external-dynamic
|
||||
*
|
||||
* Awkwardly this doesn't happen on `packages/fs-router-vite-plugin/tests/generator.spec.ts`
|
||||
*
|
||||
* Too much pain and sadness to investigate this right now.
|
||||
* Might worth creating an utility function in the future if this happens again
|
||||
*/
|
||||
const source = sourceRaw.replace(new RegExp(os.EOL, 'g'), '\n')
|
||||
|
||||
it('should generate file for client', async () => {
|
||||
const pluginTransform = getTransform()
|
||||
const clientBundle = pluginTransform(source, sourceFilePath)
|
||||
|
||||
const expectedClientSrc = `${testDirPath}/client.expected.tsx`
|
||||
|
||||
await expect(clientBundle).toMatchFileSnapshot(
|
||||
expectedClientSrc,
|
||||
`${testDirPath} client build should be equal to ${expectedClientSrc}`,
|
||||
)
|
||||
})
|
||||
|
||||
it('should generate file for server', async () => {
|
||||
const pluginTransform = getTransform()
|
||||
const serverBundle = pluginTransform(source, sourceFilePath, {
|
||||
ssr: true,
|
||||
})
|
||||
|
||||
const expectedServerSrc = `${testDirPath}/server.expected.tsx`
|
||||
|
||||
await expect(serverBundle).toMatchFileSnapshot(
|
||||
expectedServerSrc,
|
||||
`${testDirPath} server build should be equal to ${expectedServerSrc}`,
|
||||
)
|
||||
})
|
||||
expect(bundle).toBe(`import { createRoute } from 'tuono';
|
||||
import IndexImport from "./../src/routes/index";
|
||||
import PokemonspokemonImport from "./../src/routes/pokemons/[pokemon]";`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Non tuono dynamic function', () => {
|
||||
it('Into the client bundle', () => {
|
||||
const pluginTransform = getTransform()
|
||||
const bundle = pluginTransform(NON_DYNAMIC_SOURCE, 'id')
|
||||
expect(bundle).toBe(`import { createRoute } from 'tuono';
|
||||
import { dynamic } from 'external-lib';
|
||||
const IndexImport = dynamic(() => import('./../src/routes/index'));
|
||||
const PokemonspokemonImport = dynamic(() => import('./../src/routes/pokemons/[pokemon]'));`)
|
||||
})
|
||||
describe('not valid file should not be processed', () => {
|
||||
const notValidFiles: Array<string> = [
|
||||
'src/styles/module.css',
|
||||
'src/pages/file-without-ext',
|
||||
'src/pages/file-with-invalid-ext',
|
||||
'src/components/fileWithInvalidExt',
|
||||
'src/components/sidebar/sidebar-link.module.css',
|
||||
]
|
||||
|
||||
it('Into the server bundle', () => {
|
||||
it.each(notValidFiles)('"%s"', (fileName) => {
|
||||
const pluginTransform = getTransform()
|
||||
const bundle = pluginTransform(NON_DYNAMIC_SOURCE, 'id', {
|
||||
ssr: true,
|
||||
})
|
||||
expect(bundle).toBe(`import { createRoute } from 'tuono';
|
||||
import { dynamic } from 'external-lib';
|
||||
const IndexImport = dynamic(() => import('./../src/routes/index'));
|
||||
const PokemonspokemonImport = dynamic(() => import('./../src/routes/pokemons/[pokemon]'));`)
|
||||
|
||||
const code = [
|
||||
"import { createRoute, dynamic } from 'tuono'",
|
||||
"const IndexImport = dynamic(() => import('./../src/routes/index'))",
|
||||
].join('\n')
|
||||
|
||||
const result = pluginTransform(code, fileName)
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src", "tests", "vite.config.ts"]
|
||||
"include": ["src", "tests", "vite.config.ts"],
|
||||
"exclude": ["tests/sources"]
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
dist
|
||||
pnpm-lock.yaml
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "tuono-router",
|
||||
"version": "0.16.2",
|
||||
"version": "0.16.6",
|
||||
"description": "React routing component for the framework tuono. Tuono is the react/rust fullstack framework",
|
||||
"homepage": "https://tuono.dev",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier -u --write --ignore-unknown '**/*'",
|
||||
"format:check": "prettier --check --ignore-unknown '**/*'",
|
||||
"format": "prettier --write --ignore-unknown --ignore-path ../../.prettierignore .",
|
||||
"format:check": "prettier --check --ignore-unknown --ignore-path ../../.prettierignore .",
|
||||
"types": "tsc --noEmit",
|
||||
"test:watch": "vitest",
|
||||
"test": "vitest run"
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('Test RouteMatch component', () => {
|
||||
|
||||
test('It should correctly render nested routes', () => {
|
||||
vi.mock('../hooks/useServerSideProps.tsx', () => ({
|
||||
useServerSideProps: (): { data: any; isLoading: boolean } => {
|
||||
useServerSideProps: (): { data: unknown; isLoading: boolean } => {
|
||||
return {
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
|
||||
@@ -20,6 +20,7 @@ export const RouteMatch = ({
|
||||
}: RouteMatchProps): React.JSX.Element => {
|
||||
const { data, isLoading } = useServerSideProps(route, serverSideProps)
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const routes = React.useMemo(() => loadParentComponents(route), [route.id])
|
||||
|
||||
return (
|
||||
@@ -29,12 +30,6 @@ export const RouteMatch = ({
|
||||
)
|
||||
}
|
||||
|
||||
interface ParentProps<TData = unknown> {
|
||||
children: React.ReactNode
|
||||
data: TData
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface TraverseRootComponentsProps<TData = unknown> {
|
||||
routes: Array<Route>
|
||||
data: TData
|
||||
@@ -58,13 +53,7 @@ const TraverseRootComponents = React.memo(
|
||||
children,
|
||||
}: TraverseRootComponentsProps): React.JSX.Element => {
|
||||
if (routes.length > index) {
|
||||
const Parent = React.useMemo(
|
||||
() =>
|
||||
routes[index]?.component as unknown as (
|
||||
props: ParentProps,
|
||||
) => React.JSX.Element,
|
||||
[],
|
||||
)
|
||||
const Parent = (routes[index] as Route).component
|
||||
|
||||
return (
|
||||
<Parent data={data} isLoading={isLoading}>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import type { ReactNode, JSX } from 'react'
|
||||
|
||||
import { useListenBrowserUrlUpdates } from '../hooks/useListenBrowserUrlUpdates'
|
||||
import { initRouterStore } from '../hooks/useRouterStore'
|
||||
import { useInitRouterStore } from '../hooks/useRouterStore'
|
||||
import type { ServerProps } from '../types'
|
||||
import type { Router } from '../router'
|
||||
|
||||
@@ -39,7 +39,7 @@ export function RouterProvider({
|
||||
router,
|
||||
serverProps,
|
||||
}: RouterProviderProps): JSX.Element {
|
||||
initRouterStore(serverProps)
|
||||
useInitRouterStore(serverProps)
|
||||
|
||||
useListenBrowserUrlUpdates()
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from 'react'
|
||||
import type { ReactElement, ComponentType } from 'react'
|
||||
import type { ReactElement } from 'react'
|
||||
|
||||
import type { RouteComponent } from './types'
|
||||
|
||||
type ImportFn = () => Promise<{ default: React.ComponentType<any> }>
|
||||
type ImportFn = () => Promise<{ default: RouteComponent }>
|
||||
|
||||
/**
|
||||
* Helper function to lazy load any component.
|
||||
@@ -41,8 +41,10 @@ export const dynamic = (importFn: ImportFn): React.JSX.Element => {
|
||||
return <></>
|
||||
}
|
||||
|
||||
export const lazyLoadComponent = (factory: ImportFn): RouteComponent => {
|
||||
let LoadedComponent: ComponentType<any> | undefined
|
||||
export const __tuono__internal__lazyLoadComponent = (
|
||||
factory: ImportFn,
|
||||
): RouteComponent => {
|
||||
let LoadedComponent: RouteComponent | undefined
|
||||
const LazyComponent = React.lazy(factory) as unknown as RouteComponent
|
||||
|
||||
const loadComponent = (): Promise<void> =>
|
||||
@@ -50,7 +52,9 @@ export const lazyLoadComponent = (factory: ImportFn): RouteComponent => {
|
||||
LoadedComponent = module.default
|
||||
})
|
||||
|
||||
const Component = (props: any): ReactElement =>
|
||||
const Component = (
|
||||
props: React.ComponentProps<RouteComponent>,
|
||||
): ReactElement =>
|
||||
React.createElement(LoadedComponent || LazyComponent, props)
|
||||
|
||||
Component.preload = loadComponent
|
||||
|
||||
@@ -9,23 +9,26 @@ import { useRouterStore } from './useRouterStore'
|
||||
export const useListenBrowserUrlUpdates = (): void => {
|
||||
const updateLocation = useRouterStore((st) => st.updateLocation)
|
||||
|
||||
const updateLocationOnPopStateChange = ({ target }: PopStateEvent): void => {
|
||||
const { pathname, hash, href, search } = (target as typeof window).location
|
||||
|
||||
updateLocation({
|
||||
pathname,
|
||||
hash,
|
||||
href,
|
||||
searchStr: search,
|
||||
search: Object.fromEntries(new URLSearchParams(search)),
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const updateLocationOnPopStateChange = ({
|
||||
target,
|
||||
}: PopStateEvent): void => {
|
||||
const { location } = target as typeof window
|
||||
const { pathname, hash, href, search } = location
|
||||
|
||||
updateLocation({
|
||||
pathname,
|
||||
hash,
|
||||
href,
|
||||
searchStr: search,
|
||||
search: Object.fromEntries(new URLSearchParams(search)),
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', updateLocationOnPopStateChange)
|
||||
|
||||
return (): void => {
|
||||
window.removeEventListener('popstate', updateLocationOnPopStateChange)
|
||||
}
|
||||
}, [])
|
||||
}, [updateLocation])
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ describe('Test useRoute fn', () => {
|
||||
|
||||
test('match routes by ids', () => {
|
||||
vi.mock('./useInternalRouter.tsx', () => ({
|
||||
useInternalRouter: (): { routesById: Record<string, any> } => {
|
||||
useInternalRouter: (): { routesById: Record<string, { id: string }> } => {
|
||||
return {
|
||||
routesById: {
|
||||
'/': { id: '/' },
|
||||
|
||||
@@ -25,12 +25,12 @@ export function sanitizePathname(pathname: string): string {
|
||||
* Optimizations should occour on both
|
||||
*/
|
||||
export default function useRoute(pathname?: string): Route | undefined {
|
||||
const { routesById } = useInternalRouter()
|
||||
|
||||
if (!pathname) return
|
||||
|
||||
pathname = sanitizePathname(pathname)
|
||||
|
||||
const { routesById } = useInternalRouter()
|
||||
|
||||
if (routesById[pathname]) return routesById[pathname]
|
||||
|
||||
const dynamicRoutes = Object.keys(routesById).filter((route) =>
|
||||
|
||||
@@ -16,7 +16,7 @@ interface UseRouterHook {
|
||||
/**
|
||||
* This object contains all the query params of the current route
|
||||
*/
|
||||
query: Record<string, any>
|
||||
query: Record<string, unknown>
|
||||
|
||||
/**
|
||||
* Returns the current pathname
|
||||
|
||||
@@ -23,7 +23,7 @@ interface RouterState {
|
||||
updateLocation: (loc: ParsedLocation) => void
|
||||
}
|
||||
|
||||
export const initRouterStore = (props?: ServerProps): void => {
|
||||
export const useInitRouterStore = (props?: ServerProps): void => {
|
||||
const updateLocation = useRouterStore((st) => st.updateLocation)
|
||||
|
||||
// Init the store in the server in order to correctly
|
||||
@@ -49,7 +49,7 @@ export const initRouterStore = (props?: ServerProps): void => {
|
||||
searchStr: search,
|
||||
search: Object.fromEntries(new URLSearchParams(search)),
|
||||
})
|
||||
}, [])
|
||||
}, [updateLocation])
|
||||
}
|
||||
|
||||
export const useRouterStore = create<RouterState>()((set) => ({
|
||||
@@ -67,5 +67,7 @@ export const useRouterStore = create<RouterState>()((set) => ({
|
||||
pendingMatches: [],
|
||||
cachedMatches: [],
|
||||
statusCode: 200,
|
||||
updateLocation: (location: ParsedLocation): void => set({ location }),
|
||||
updateLocation: (location: ParsedLocation): void => {
|
||||
set({ location })
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -13,7 +13,7 @@ interface UseServerSidePropsReturn<TData> {
|
||||
}
|
||||
|
||||
interface TuonoApi {
|
||||
data?: any
|
||||
data?: unknown
|
||||
info: {
|
||||
redirect_destination?: string
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export function useServerSideProps<T>(
|
||||
return (): void => {
|
||||
setData(undefined)
|
||||
}
|
||||
}, [location.pathname])
|
||||
}, [location.pathname, route.options.hasHandler, updateLocation])
|
||||
|
||||
return { isLoading, data: data as T }
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@ export { RouterProvider } from './components/RouterProvider'
|
||||
export { default as Link } from './components/Link'
|
||||
export { createRouter } from './router'
|
||||
export { createRoute, createRootRoute } from './route'
|
||||
export { dynamic, lazyLoadComponent } from './dynamic'
|
||||
export { dynamic, __tuono__internal__lazyLoadComponent } from './dynamic'
|
||||
export { useRouter } from './hooks/useRouter'
|
||||
|
||||
@@ -77,7 +77,7 @@ export class Route {
|
||||
this.fullPath = fullPath || ''
|
||||
}
|
||||
|
||||
addChildren(routes: Array<Route>): Route {
|
||||
addChildren(routes: Array<Route>): this {
|
||||
this.children = routes
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ interface RouterOptions {
|
||||
routeTree?: RouteTree
|
||||
}
|
||||
|
||||
export type RouterType = any
|
||||
|
||||
export function createRouter(options: CreateRouterOptions): Router {
|
||||
return new Router(options)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ComponentType as ReactComponentType } from 'react'
|
||||
import type { ComponentType as ReactComponentType, ReactNode } from 'react'
|
||||
|
||||
export interface Segment {
|
||||
type: 'pathname' | 'param' | 'wildcard'
|
||||
@@ -17,6 +17,8 @@ export interface ServerProps<TProps = unknown> {
|
||||
export interface RouteProps<TData = unknown> {
|
||||
data: TData
|
||||
isLoading: boolean
|
||||
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export type RouteComponent = ReactComponentType<RouteProps> & {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
dist
|
||||
pnpm-lock.yaml
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "tuono",
|
||||
"version": "0.16.2",
|
||||
"version": "0.16.6",
|
||||
"description": "Superfast React fullstack framework",
|
||||
"homepage": "https://tuono.dev",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier -u --write --ignore-unknown '**/*'",
|
||||
"format:check": "prettier --check --ignore-unknown '**/*'",
|
||||
"format": "prettier --write --ignore-unknown --ignore-path ../../.prettierignore .",
|
||||
"format:check": "prettier --check --ignore-unknown --ignore-path ../../.prettierignore .",
|
||||
"types": "tsc --noEmit",
|
||||
"test:watch": "vitest",
|
||||
"test": "vitest run"
|
||||
@@ -101,6 +101,7 @@
|
||||
"@babel/template": "^7.24.0",
|
||||
"@babel/traverse": "^7.24.1",
|
||||
"@babel/types": "^7.24.0",
|
||||
"@rollup/plugin-inject": "^5.0.5",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/node": "^22.0.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||
@@ -109,7 +110,8 @@
|
||||
"tuono-fs-router-vite-plugin": "workspace:*",
|
||||
"tuono-lazy-fn-vite-plugin": "workspace:*",
|
||||
"tuono-router": "workspace:*",
|
||||
"vite": "^5.2.11"
|
||||
"vite": "^5.2.11",
|
||||
"web-streams-polyfill": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/babel-traverse": "^6.25.10",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { InlineConfig } from 'vite'
|
||||
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 ViteFsRouter from 'tuono-fs-router-vite-plugin'
|
||||
import { LazyLoadingPlugin } from 'tuono-lazy-fn-vite-plugin'
|
||||
|
||||
@@ -9,6 +10,20 @@ import type { TuonoConfig } from '../config'
|
||||
import { loadConfig, blockingAsync } from './utils'
|
||||
|
||||
const VITE_PORT = 3001
|
||||
const VITE_SSR_PLUGINS: Array<Plugin> = [
|
||||
{
|
||||
enforce: 'post',
|
||||
...inject({
|
||||
ReadableStream: ['web-streams-polyfill', 'ReadableStream'],
|
||||
|
||||
/**
|
||||
* Added to support `react@19`
|
||||
* @see https://github.com/tuono-labs/tuono/issues/218
|
||||
*/
|
||||
MessageChannel: ['tuono/ssr', 'MessageChannelPolyfill'],
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* From a given {@link TuonoConfig} return a `vite` "mergeable" {@link InlineConfig}
|
||||
@@ -17,6 +32,12 @@ const VITE_PORT = 3001
|
||||
function createBaseViteConfigFromTuonoConfig(
|
||||
tuonoConfig: TuonoConfig,
|
||||
): InlineConfig {
|
||||
/**
|
||||
* @warning Keep in sync with {@link LazyLoadingPlugin} tests:
|
||||
* packages/lazy-fn-vite-plugin/tests/transpileSource.test.ts
|
||||
*/
|
||||
const pluginFilesInclude = /\.(jsx|js|mdx|md|tsx|ts)$/
|
||||
|
||||
const viteBaseConfig: InlineConfig = {
|
||||
root: '.tuono',
|
||||
logLevel: 'silent',
|
||||
@@ -41,10 +62,10 @@ function createBaseViteConfigFromTuonoConfig(
|
||||
* seem broken.
|
||||
*/
|
||||
// @ts-expect-error see above comment
|
||||
react({ include: /\.(jsx|js|mdx|md|tsx|ts)$/ }),
|
||||
react({ include: pluginFilesInclude }),
|
||||
|
||||
ViteFsRouter(),
|
||||
LazyLoadingPlugin(),
|
||||
LazyLoadingPlugin({ include: pluginFilesInclude }),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -59,6 +80,7 @@ const developmentSSRBundle = (): void => {
|
||||
mergeConfig<InlineConfig, InlineConfig>(
|
||||
createBaseViteConfigFromTuonoConfig(config),
|
||||
{
|
||||
plugins: VITE_SSR_PLUGINS,
|
||||
build: {
|
||||
ssr: true,
|
||||
minify: false,
|
||||
@@ -137,6 +159,7 @@ const buildProd = (): void => {
|
||||
mergeConfig<InlineConfig, InlineConfig>(
|
||||
createBaseViteConfigFromTuonoConfig(config),
|
||||
{
|
||||
plugins: VITE_SSR_PLUGINS,
|
||||
build: {
|
||||
ssr: true,
|
||||
minify: true,
|
||||
|
||||
@@ -7,8 +7,8 @@ export {
|
||||
Link,
|
||||
RouterProvider,
|
||||
dynamic,
|
||||
lazyLoadComponent,
|
||||
useRouter,
|
||||
__tuono__internal__lazyLoadComponent,
|
||||
} from 'tuono-router'
|
||||
|
||||
export type { TuonoProps } from './types'
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import 'fast-text-encoding' // Mandatory for React18
|
||||
import type { ReadableStream } from 'node:stream/web'
|
||||
|
||||
import * as React from 'react'
|
||||
import { renderToString, renderToStaticMarkup } from 'react-dom/server'
|
||||
import { renderToStaticMarkup, renderToReadableStream } from 'react-dom/server'
|
||||
import type { HelmetServerState } from 'react-helmet-async'
|
||||
import { HelmetProvider } from 'react-helmet-async'
|
||||
import { RouterProvider, createRouter } from 'tuono-router'
|
||||
import type { createRoute } from 'tuono-router'
|
||||
|
||||
import { MessageChannelPolyfill } from './polyfills/messageChannel'
|
||||
import { streamToString } from './utils'
|
||||
|
||||
type RouteTree = ReturnType<typeof createRoute>
|
||||
type Mode = 'Dev' | 'Prod'
|
||||
|
||||
@@ -37,7 +42,7 @@ function generateJsScripts(jsBundles: Array<string>, mode: Mode): string {
|
||||
}
|
||||
|
||||
export function serverSideRendering(routeTree: RouteTree) {
|
||||
return function render(payload: string | undefined): string {
|
||||
return async function render(payload: string | undefined): Promise<string> {
|
||||
const serverProps = (payload ? JSON.parse(payload) : {}) as Record<
|
||||
string,
|
||||
unknown
|
||||
@@ -48,14 +53,21 @@ export function serverSideRendering(routeTree: RouteTree) {
|
||||
const cssBundles = serverProps.cssBundles as Array<string>
|
||||
const router = createRouter({ routeTree }) // Render the app
|
||||
|
||||
const helmetContext = {}
|
||||
const app = renderToString(
|
||||
const helmetContext = {} as { helmet: HelmetServerState }
|
||||
const stream = await renderToReadableStream(
|
||||
<HelmetProvider context={helmetContext}>
|
||||
<RouterProvider router={router} serverProps={serverProps as never} />
|
||||
</HelmetProvider>,
|
||||
)
|
||||
|
||||
const { helmet } = helmetContext as { helmet: HelmetServerState }
|
||||
await stream.allReady
|
||||
|
||||
const { helmet } = helmetContext
|
||||
|
||||
const app = await streamToString(
|
||||
// ReadableStream should be implemented in node)
|
||||
stream as unknown as ReadableStream<Uint8Array>,
|
||||
)
|
||||
|
||||
return `<!doctype html>
|
||||
<html ${helmet.htmlAttributes.toString()}>
|
||||
@@ -72,7 +84,7 @@ export function serverSideRendering(routeTree: RouteTree) {
|
||||
${renderToStaticMarkup(
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `window.__TUONO_SSR_PROPS__=${payload}`,
|
||||
__html: `window.__TUONO_SSR_PROPS__=${payload as string}`,
|
||||
}}
|
||||
/>,
|
||||
)}
|
||||
@@ -83,3 +95,5 @@ export function serverSideRendering(routeTree: RouteTree) {
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
export { MessageChannelPolyfill }
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
|
||||
import { MessageChannelPolyfill, MessagePortPolyfill } from './messageChannel'
|
||||
|
||||
describe('MessagePortPolyfill', () => {
|
||||
it('should invoke onmessage when a message is posted', () => {
|
||||
const port = new MessagePortPolyfill()
|
||||
|
||||
const onmessageMock = vi.fn<EventListener>()
|
||||
|
||||
port.onmessage = onmessageMock
|
||||
|
||||
port.dispatchEvent({ data: 'Hello, world!' } as MessageEvent)
|
||||
|
||||
expect(onmessageMock).toHaveBeenCalledOnce()
|
||||
expect(onmessageMock).toHaveBeenCalledWith({ data: 'Hello, world!' })
|
||||
})
|
||||
|
||||
it('should handle multiple event listeners', () => {
|
||||
const port = new MessagePortPolyfill()
|
||||
|
||||
const listener1 = vi.fn<EventListener>()
|
||||
const listener2 = vi.fn<EventListener>()
|
||||
|
||||
port.addEventListener('message', listener1)
|
||||
port.addEventListener('message', listener2)
|
||||
|
||||
port.dispatchEvent({ data: 'Test message' } as MessageEvent)
|
||||
|
||||
expect(listener1).toHaveBeenCalledOnce()
|
||||
expect(listener1).toHaveBeenCalledWith({ data: 'Test message' })
|
||||
|
||||
expect(listener2).toHaveBeenCalledOnce()
|
||||
expect(listener2).toHaveBeenCalledWith({ data: 'Test message' })
|
||||
})
|
||||
|
||||
it('should not invoke removed event listeners', () => {
|
||||
const port = new MessagePortPolyfill()
|
||||
|
||||
const listener = vi.fn<EventListener>()
|
||||
|
||||
port.addEventListener('message', listener)
|
||||
port.dispatchEvent({ data: 'First message' } as MessageEvent)
|
||||
|
||||
port.removeEventListener('message', listener)
|
||||
port.dispatchEvent({ data: 'Second message' } as MessageEvent)
|
||||
|
||||
expect(listener).toHaveBeenCalledOnce()
|
||||
expect(listener).toHaveBeenCalledWith({ data: 'First message' })
|
||||
})
|
||||
|
||||
it('should not post messages if otherPort is null', () => {
|
||||
const port = new MessagePortPolyfill()
|
||||
|
||||
const listener = vi.fn<EventListener>()
|
||||
|
||||
port.onmessage = listener
|
||||
|
||||
port.postMessage('Hello!')
|
||||
|
||||
expect(listener).not.toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
|
||||
describe('MessageChannelPolyfill', () => {
|
||||
it('should send and receive messages between ports', () => {
|
||||
const channel = new MessageChannelPolyfill()
|
||||
|
||||
const listener = vi.fn<EventListener>()
|
||||
|
||||
channel.port1.onmessage = listener
|
||||
|
||||
channel.port2.postMessage('Hello, port1!')
|
||||
channel.port2.postMessage('How are you?')
|
||||
|
||||
expect(listener).toHaveBeenCalledTimes(2)
|
||||
expect(listener).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ data: 'Hello, port1!' }),
|
||||
)
|
||||
expect(listener).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({ data: 'How are you?' }),
|
||||
)
|
||||
})
|
||||
|
||||
it('should support addEventListener and removeEventListener', () => {
|
||||
const channel = new MessageChannelPolyfill()
|
||||
|
||||
const listener = vi.fn<EventListener>()
|
||||
|
||||
channel.port1.addEventListener('message', listener)
|
||||
channel.port2.postMessage('Hello, port1!')
|
||||
|
||||
expect(listener).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ data: 'Hello, port1!' }),
|
||||
)
|
||||
|
||||
channel.port1.removeEventListener('message', listener)
|
||||
channel.port2.postMessage('Message after removing listener')
|
||||
|
||||
expect(listener).not.toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('should handle bidirectional communication between ports', () => {
|
||||
const channel = new MessageChannelPolyfill()
|
||||
|
||||
const listener1 = vi.fn<EventListener>()
|
||||
const listener2 = vi.fn<EventListener>()
|
||||
|
||||
channel.port1.onmessage = listener1
|
||||
channel.port2.onmessage = listener2
|
||||
|
||||
channel.port1.postMessage('Hello, port2!')
|
||||
channel.port2.postMessage('Hello, port1!')
|
||||
|
||||
expect(listener1).toHaveBeenCalledOnce()
|
||||
expect(listener1).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ data: 'Hello, port1!' }),
|
||||
)
|
||||
|
||||
expect(listener2).toHaveBeenCalledOnce()
|
||||
expect(listener2).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ data: 'Hello, port2!' }),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('MessagePort', () => {
|
||||
it('should not send a message on close', () => {
|
||||
const { port1, port2 } = new MessageChannelPolyfill()
|
||||
|
||||
const listener = vi.fn<EventListener>()
|
||||
|
||||
port1.onmessage = listener
|
||||
|
||||
port2.postMessage('Test message')
|
||||
|
||||
expect(listener).toHaveBeenCalledOnce()
|
||||
expect(listener).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ data: 'Test message' }),
|
||||
)
|
||||
|
||||
port1.close()
|
||||
port2.postMessage('Another message')
|
||||
|
||||
expect(listener).not.toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,102 @@
|
||||
/* Modified from https://github.com/rocwind/message-port-polyfill/blob/master/src/index.ts
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Roc
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
export class MessagePortPolyfill implements MessagePort {
|
||||
onmessage: ((this: MessagePort, ev: MessageEvent) => unknown) | null = null
|
||||
/** @warning this is declared to satisfy {@link MessagePort} interface requirements but is never called */
|
||||
onmessageerror: ((this: MessagePort, ev: MessageEvent) => unknown) | null =
|
||||
null
|
||||
|
||||
otherPort: MessagePortPolyfill | null = null
|
||||
|
||||
private onmessageListeners: Array<(ev: MessageEvent) => void> = []
|
||||
private isClosed = false
|
||||
|
||||
dispatchEvent(event: MessageEvent): boolean {
|
||||
if (this.isClosed) return false
|
||||
if (this.onmessage) {
|
||||
this.onmessage(event)
|
||||
}
|
||||
this.onmessageListeners.forEach((listener) => {
|
||||
listener(event)
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
postMessage(message: unknown): void {
|
||||
if (this.isClosed || !this.otherPort) return
|
||||
|
||||
const event = new MessageEvent('message', { data: message })
|
||||
this.otherPort.dispatchEvent(event)
|
||||
}
|
||||
|
||||
addEventListener(
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
): void {
|
||||
if (this.isClosed || type !== 'message') return
|
||||
|
||||
if (
|
||||
typeof listener === 'function' &&
|
||||
!this.onmessageListeners.includes(listener)
|
||||
) {
|
||||
this.onmessageListeners.push(listener)
|
||||
}
|
||||
}
|
||||
|
||||
removeEventListener(
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
): void {
|
||||
if (this.isClosed || type !== 'message') return
|
||||
|
||||
if (typeof listener === 'function') {
|
||||
const index = this.onmessageListeners.indexOf(listener)
|
||||
if (index !== -1) {
|
||||
this.onmessageListeners.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start(): void {
|
||||
// do nothing at this moment
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.isClosed = true
|
||||
}
|
||||
}
|
||||
|
||||
export class MessageChannelPolyfill implements MessageChannel {
|
||||
readonly port1: MessagePortPolyfill
|
||||
readonly port2: MessagePortPolyfill
|
||||
|
||||
constructor() {
|
||||
this.port1 = new MessagePortPolyfill()
|
||||
this.port2 = new MessagePortPolyfill()
|
||||
|
||||
this.port1.otherPort = this.port2
|
||||
this.port2.otherPort = this.port1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// react ReadableStream type is an empty interface so we are using the one from
|
||||
// node which match the runtime value
|
||||
import type { ReadableStream } from 'node:stream/web'
|
||||
|
||||
function concatArrayBuffers(chunks: Array<Uint8Array>): Uint8Array {
|
||||
const result = new Uint8Array(chunks.reduce((a, c) => a + c.length, 0))
|
||||
let offset = 0
|
||||
for (const chunk of chunks) {
|
||||
result.set(chunk, offset)
|
||||
offset += chunk.length
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function streamToArrayBuffer(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
): Promise<Uint8Array> {
|
||||
const chunks: Array<Uint8Array> = []
|
||||
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
return concatArrayBuffers(chunks)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function awaits for the whole stream before returning the string.
|
||||
*
|
||||
* NOTE: we should improve the bond between the custom V8 runtime and the
|
||||
* renderToReadableStream React function to return a stream directly to the client.
|
||||
*/
|
||||
export async function streamToString(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
): Promise<string> {
|
||||
const buffer = await streamToArrayBuffer(stream)
|
||||
return new TextDecoder().decode(buffer)
|
||||
}
|
||||
Generated
+51
-284
@@ -32,12 +32,9 @@ importers:
|
||||
eslint-plugin-import:
|
||||
specifier: ^2.31.0
|
||||
version: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0)
|
||||
eslint-plugin-react:
|
||||
specifier: ^7.33.2
|
||||
version: 7.37.2(eslint@9.15.0)
|
||||
eslint-plugin-react-hooks:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0(eslint@9.15.0)
|
||||
specifier: 5.1.0
|
||||
version: 5.1.0(eslint@9.15.0)
|
||||
prettier:
|
||||
specifier: ^3.2.4
|
||||
version: 3.4.1
|
||||
@@ -84,8 +81,8 @@ importers:
|
||||
specifier: 18.3.1
|
||||
version: 18.3.1(react@18.3.1)
|
||||
tuono:
|
||||
specifier: npm:tuono@0.16.0
|
||||
version: 0.16.0(@types/react@18.3.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sugarss@4.0.1(postcss@8.4.49))
|
||||
specifier: npm:tuono@0.16.4
|
||||
version: 0.16.4(@types/react@18.3.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sugarss@4.0.1(postcss@8.4.49))
|
||||
devDependencies:
|
||||
'@mdx-js/rollup':
|
||||
specifier: 3.1.0
|
||||
@@ -294,6 +291,9 @@ importers:
|
||||
'@babel/types':
|
||||
specifier: ^7.24.0
|
||||
version: 7.26.0
|
||||
'@rollup/plugin-inject':
|
||||
specifier: ^5.0.5
|
||||
version: 5.0.5(rollup@4.25.0)
|
||||
'@types/babel__core':
|
||||
specifier: ^7.20.5
|
||||
version: 7.20.5
|
||||
@@ -327,6 +327,9 @@ importers:
|
||||
vite:
|
||||
specifier: ^5.2.11
|
||||
version: 5.4.11(@types/node@22.10.0)(sugarss@4.0.1(postcss@8.4.49))
|
||||
web-streams-polyfill:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
devDependencies:
|
||||
'@types/babel-traverse':
|
||||
specifier: ^6.25.10
|
||||
@@ -799,6 +802,15 @@ packages:
|
||||
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
|
||||
engines: {node: '>=12.4.0'}
|
||||
|
||||
'@rollup/plugin-inject@5.0.5':
|
||||
resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
'@rollup/pluginutils@5.1.3':
|
||||
resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -1308,10 +1320,6 @@ packages:
|
||||
resolution: {integrity: sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
array.prototype.findlast@1.2.5:
|
||||
resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
array.prototype.findlastindex@1.2.5:
|
||||
resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1324,10 +1332,6 @@ packages:
|
||||
resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
array.prototype.tosorted@1.1.4:
|
||||
resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
arraybuffer.prototype.slice@1.0.3:
|
||||
resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1612,10 +1616,6 @@ packages:
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-iterator-helpers@1.2.0:
|
||||
resolution: {integrity: sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-module-lexer@1.5.4:
|
||||
resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==}
|
||||
|
||||
@@ -1705,18 +1705,12 @@ packages:
|
||||
'@typescript-eslint/parser':
|
||||
optional: true
|
||||
|
||||
eslint-plugin-react-hooks@5.0.0:
|
||||
resolution: {integrity: sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==}
|
||||
eslint-plugin-react-hooks@5.1.0:
|
||||
resolution: {integrity: sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
|
||||
|
||||
eslint-plugin-react@7.37.2:
|
||||
resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
|
||||
|
||||
eslint-scope@8.2.0:
|
||||
resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -2062,10 +2056,6 @@ packages:
|
||||
resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-async-function@2.0.0:
|
||||
resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-bigint@1.0.4:
|
||||
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
|
||||
|
||||
@@ -2099,13 +2089,6 @@ packages:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-finalizationregistry@1.0.2:
|
||||
resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==}
|
||||
|
||||
is-generator-function@1.0.10:
|
||||
resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-git-repository@1.1.1:
|
||||
resolution: {integrity: sha512-hxLpJytJnIZ5Og5QsxSkzmb8Qx8rGau9bio1JN/QtXcGEFuSsQYau0IiqlsCwftsfVYjF1mOq6uLdmwNSspgpA==}
|
||||
|
||||
@@ -2116,10 +2099,6 @@ packages:
|
||||
is-hexadecimal@2.0.1:
|
||||
resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
|
||||
|
||||
is-map@2.0.3:
|
||||
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-negative-zero@2.0.3:
|
||||
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2155,10 +2134,6 @@ packages:
|
||||
resolution: {integrity: sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-set@2.0.3:
|
||||
resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-shared-array-buffer@1.0.3:
|
||||
resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2187,17 +2162,9 @@ packages:
|
||||
resolution: {integrity: sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-weakmap@2.0.2:
|
||||
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-weakref@1.0.2:
|
||||
resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
|
||||
|
||||
is-weakset@2.0.3:
|
||||
resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-windows@1.0.2:
|
||||
resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -2212,10 +2179,6 @@ packages:
|
||||
resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
iterator.prototype@1.1.3:
|
||||
resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
jju@1.4.0:
|
||||
resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
|
||||
|
||||
@@ -2268,10 +2231,6 @@ packages:
|
||||
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
|
||||
engines: {'0': node >= 0.2.0}
|
||||
|
||||
jsx-ast-utils@3.3.5:
|
||||
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
||||
keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
|
||||
@@ -2509,10 +2468,6 @@ packages:
|
||||
nwsapi@2.2.13:
|
||||
resolution: {integrity: sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==}
|
||||
|
||||
object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
object-inspect@1.13.3:
|
||||
resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2529,10 +2484,6 @@ packages:
|
||||
resolution: {integrity: sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
object.entries@1.1.8:
|
||||
resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
object.fromentries@2.0.8:
|
||||
resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2685,9 +2636,6 @@ packages:
|
||||
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
|
||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||
|
||||
prop-types@15.8.1:
|
||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||
|
||||
property-information@6.5.0:
|
||||
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
|
||||
|
||||
@@ -2723,9 +2671,6 @@ packages:
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
react-is@17.0.2:
|
||||
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
||||
|
||||
@@ -2799,10 +2744,6 @@ packages:
|
||||
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
reflect.getprototypeof@1.0.6:
|
||||
resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
regenerator-runtime@0.14.1:
|
||||
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||
|
||||
@@ -2840,10 +2781,6 @@ packages:
|
||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||
hasBin: true
|
||||
|
||||
resolve@2.0.0-next.5:
|
||||
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
|
||||
hasBin: true
|
||||
|
||||
reusify@1.0.4:
|
||||
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
@@ -2968,13 +2905,6 @@ packages:
|
||||
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
|
||||
engines: {node: '>=0.6.19'}
|
||||
|
||||
string.prototype.matchall@4.0.11:
|
||||
resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
string.prototype.repeat@1.0.0:
|
||||
resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
|
||||
|
||||
string.prototype.trim@1.2.9:
|
||||
resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -3111,20 +3041,20 @@ packages:
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
tuono-fs-router-vite-plugin@0.16.0:
|
||||
resolution: {integrity: sha512-NjMNhCp9vqladhelMI8Whhp6qhwvoyBRUUyeJpqfRVp/W/vwL/+za6iciscWQ1+yVMEDjY5lnMGA0yQuz0cnIg==}
|
||||
tuono-fs-router-vite-plugin@0.16.4:
|
||||
resolution: {integrity: sha512-GZtHp7avh/Ne4Km7XtBNi3Es41v/3EjBvmd4cYwbNvwCcckDXRv8gVZZSQ7lTAdq1WLH0aWrHb/xcocMM1eX6Q==}
|
||||
|
||||
tuono-lazy-fn-vite-plugin@0.16.0:
|
||||
resolution: {integrity: sha512-+ab7pbRrqGqYPePHdPvMGZMWsnEJi2TPTvkHXwV5dcflpRvhhrCppC7QbG5lk24HD+hCFryWpOiZlfzf/j6p+w==}
|
||||
tuono-lazy-fn-vite-plugin@0.16.4:
|
||||
resolution: {integrity: sha512-uUomBxRKjEhTtNA1B3VhuUd1EOor2CsGjD8s7nPN08vPDZ2KiUXN1bpXlGYdGPByVBAgILZtPpggZTCh0A9Mhw==}
|
||||
|
||||
tuono-router@0.16.0:
|
||||
resolution: {integrity: sha512-JuTXoSjjgKCbFWJQTRQA2NyN9fHqasXrE1+vC3TV1PJ5P66EM2f4zlAzwMkUNbeGWCl/Kihi/gKT1pVqnieUFA==}
|
||||
tuono-router@0.16.4:
|
||||
resolution: {integrity: sha512-BC5urCZQDl4QJ8CPJe48d9wWgVWBuyfZCSLHvFBURoYKYAqg3Qo8W8w8xcCM+qQq4IFZU8ClgVPOeqbXQ34f8A==}
|
||||
peerDependencies:
|
||||
react: '>=16.3.0'
|
||||
react-dom: '>=16.3.0'
|
||||
|
||||
tuono@0.16.0:
|
||||
resolution: {integrity: sha512-VFuRPsIf3V3UMlTtgVo1/EgKruJRjNMFkahF88a9he1b7TS0FFTlw/AEC8D92NmkB6PFjyH7g9ZNw8bpogPTmQ==}
|
||||
tuono@0.16.4:
|
||||
resolution: {integrity: sha512-ym2xjwojC8/3vE3jGTBCVgrykvZ3EIBs63o+wuG4DJ4+MaK5j36vCvmAz9U6oURyNGRS0lYpr8PKOXzB9CCT0w==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
react: '>=16.3.0'
|
||||
@@ -3423,6 +3353,10 @@ packages:
|
||||
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
web-streams-polyfill@4.0.0:
|
||||
resolution: {integrity: sha512-0zJXHRAYEjM2tUfZ2DiSOHAa2aw1tisnnhU3ufD57R8iefL+DcdJyRBRyJpG+NUimDgbTI/lH+gAE1PAvV3Cgw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
webidl-conversions@7.0.0:
|
||||
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -3442,14 +3376,6 @@ packages:
|
||||
which-boxed-primitive@1.0.2:
|
||||
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
|
||||
|
||||
which-builtin-type@1.1.4:
|
||||
resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
which-collection@1.0.2:
|
||||
resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
which-typed-array@1.1.15:
|
||||
resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4031,6 +3957,14 @@ snapshots:
|
||||
|
||||
'@nolyfill/is-core-module@1.0.39': {}
|
||||
|
||||
'@rollup/plugin-inject@5.0.5(rollup@4.25.0)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.3(rollup@4.25.0)
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.14
|
||||
optionalDependencies:
|
||||
rollup: 4.25.0
|
||||
|
||||
'@rollup/pluginutils@5.1.3(rollup@4.25.0)':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
@@ -4618,15 +4552,6 @@ snapshots:
|
||||
|
||||
array-slice@1.1.0: {}
|
||||
|
||||
array.prototype.findlast@1.2.5:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.23.3
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.0.0
|
||||
es-shim-unscopables: 1.0.2
|
||||
|
||||
array.prototype.findlastindex@1.2.5:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
@@ -4650,14 +4575,6 @@ snapshots:
|
||||
es-abstract: 1.23.3
|
||||
es-shim-unscopables: 1.0.2
|
||||
|
||||
array.prototype.tosorted@1.1.4:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.23.3
|
||||
es-errors: 1.3.0
|
||||
es-shim-unscopables: 1.0.2
|
||||
|
||||
arraybuffer.prototype.slice@1.0.3:
|
||||
dependencies:
|
||||
array-buffer-byte-length: 1.0.1
|
||||
@@ -4967,24 +4884,6 @@ snapshots:
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
|
||||
es-iterator-helpers@1.2.0:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.23.3
|
||||
es-errors: 1.3.0
|
||||
es-set-tostringtag: 2.0.3
|
||||
function-bind: 1.1.2
|
||||
get-intrinsic: 1.2.4
|
||||
globalthis: 1.0.4
|
||||
gopd: 1.0.1
|
||||
has-property-descriptors: 1.0.2
|
||||
has-proto: 1.0.3
|
||||
has-symbols: 1.0.3
|
||||
internal-slot: 1.0.7
|
||||
iterator.prototype: 1.1.3
|
||||
safe-array-concat: 1.1.2
|
||||
|
||||
es-module-lexer@1.5.4: {}
|
||||
|
||||
es-object-atoms@1.0.0:
|
||||
@@ -5125,32 +5024,10 @@ snapshots:
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-react-hooks@5.0.0(eslint@9.15.0):
|
||||
eslint-plugin-react-hooks@5.1.0(eslint@9.15.0):
|
||||
dependencies:
|
||||
eslint: 9.15.0
|
||||
|
||||
eslint-plugin-react@7.37.2(eslint@9.15.0):
|
||||
dependencies:
|
||||
array-includes: 3.1.8
|
||||
array.prototype.findlast: 1.2.5
|
||||
array.prototype.flatmap: 1.3.2
|
||||
array.prototype.tosorted: 1.1.4
|
||||
doctrine: 2.1.0
|
||||
es-iterator-helpers: 1.2.0
|
||||
eslint: 9.15.0
|
||||
estraverse: 5.3.0
|
||||
hasown: 2.0.2
|
||||
jsx-ast-utils: 3.3.5
|
||||
minimatch: 3.1.2
|
||||
object.entries: 1.1.8
|
||||
object.fromentries: 2.0.8
|
||||
object.values: 1.2.0
|
||||
prop-types: 15.8.1
|
||||
resolve: 2.0.0-next.5
|
||||
semver: 6.3.1
|
||||
string.prototype.matchall: 4.0.11
|
||||
string.prototype.repeat: 1.0.0
|
||||
|
||||
eslint-scope@8.2.0:
|
||||
dependencies:
|
||||
esrecurse: 4.3.0
|
||||
@@ -5571,10 +5448,6 @@ snapshots:
|
||||
call-bind: 1.0.7
|
||||
get-intrinsic: 1.2.4
|
||||
|
||||
is-async-function@2.0.0:
|
||||
dependencies:
|
||||
has-tostringtag: 1.0.2
|
||||
|
||||
is-bigint@1.0.4:
|
||||
dependencies:
|
||||
has-bigints: 1.0.2
|
||||
@@ -5606,14 +5479,6 @@ snapshots:
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-finalizationregistry@1.0.2:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
||||
is-generator-function@1.0.10:
|
||||
dependencies:
|
||||
has-tostringtag: 1.0.2
|
||||
|
||||
is-git-repository@1.1.1:
|
||||
dependencies:
|
||||
execa: 0.6.3
|
||||
@@ -5625,8 +5490,6 @@ snapshots:
|
||||
|
||||
is-hexadecimal@2.0.1: {}
|
||||
|
||||
is-map@2.0.3: {}
|
||||
|
||||
is-negative-zero@2.0.3: {}
|
||||
|
||||
is-number-object@1.0.7:
|
||||
@@ -5652,8 +5515,6 @@ snapshots:
|
||||
dependencies:
|
||||
is-unc-path: 1.0.0
|
||||
|
||||
is-set@2.0.3: {}
|
||||
|
||||
is-shared-array-buffer@1.0.3:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
@@ -5680,17 +5541,10 @@ snapshots:
|
||||
dependencies:
|
||||
unc-path-regex: 0.1.2
|
||||
|
||||
is-weakmap@2.0.2: {}
|
||||
|
||||
is-weakref@1.0.2:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
||||
is-weakset@2.0.3:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
get-intrinsic: 1.2.4
|
||||
|
||||
is-windows@1.0.2: {}
|
||||
|
||||
isarray@2.0.5: {}
|
||||
@@ -5699,14 +5553,6 @@ snapshots:
|
||||
|
||||
isobject@3.0.1: {}
|
||||
|
||||
iterator.prototype@1.1.3:
|
||||
dependencies:
|
||||
define-properties: 1.2.1
|
||||
get-intrinsic: 1.2.4
|
||||
has-symbols: 1.0.3
|
||||
reflect.getprototypeof: 1.0.6
|
||||
set-function-name: 2.0.2
|
||||
|
||||
jju@1.4.0: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
@@ -5769,13 +5615,6 @@ snapshots:
|
||||
|
||||
jsonparse@1.3.1: {}
|
||||
|
||||
jsx-ast-utils@3.3.5:
|
||||
dependencies:
|
||||
array-includes: 3.1.8
|
||||
array.prototype.flat: 1.3.2
|
||||
object.assign: 4.1.5
|
||||
object.values: 1.2.0
|
||||
|
||||
keyv@4.5.4:
|
||||
dependencies:
|
||||
json-buffer: 3.0.1
|
||||
@@ -6194,8 +6033,6 @@ snapshots:
|
||||
|
||||
nwsapi@2.2.13: {}
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
object-inspect@1.13.3: {}
|
||||
|
||||
object-keys@1.1.1: {}
|
||||
@@ -6214,12 +6051,6 @@ snapshots:
|
||||
for-own: 1.0.0
|
||||
isobject: 3.0.1
|
||||
|
||||
object.entries@1.1.8:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.0.0
|
||||
|
||||
object.fromentries@2.0.8:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
@@ -6368,12 +6199,6 @@ snapshots:
|
||||
ansi-styles: 5.2.0
|
||||
react-is: 17.0.2
|
||||
|
||||
prop-types@15.8.1:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
|
||||
property-information@6.5.0: {}
|
||||
|
||||
pseudomap@1.0.2: {}
|
||||
@@ -6403,8 +6228,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-is@17.0.2: {}
|
||||
|
||||
react-number-format@5.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
@@ -6494,16 +6317,6 @@ snapshots:
|
||||
indent-string: 4.0.0
|
||||
strip-indent: 3.0.0
|
||||
|
||||
reflect.getprototypeof@1.0.6:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.23.3
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.2.4
|
||||
globalthis: 1.0.4
|
||||
which-builtin-type: 1.1.4
|
||||
|
||||
regenerator-runtime@0.14.1: {}
|
||||
|
||||
regexp.prototype.flags@1.5.3:
|
||||
@@ -6565,12 +6378,6 @@ snapshots:
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
resolve@2.0.0-next.5:
|
||||
dependencies:
|
||||
is-core-module: 2.15.1
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
reusify@1.0.4: {}
|
||||
|
||||
rollup-plugin-preserve-directives@0.4.0(rollup@4.25.0):
|
||||
@@ -6707,26 +6514,6 @@ snapshots:
|
||||
|
||||
string-argv@0.3.2: {}
|
||||
|
||||
string.prototype.matchall@4.0.11:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.23.3
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.0.0
|
||||
get-intrinsic: 1.2.4
|
||||
gopd: 1.0.1
|
||||
has-symbols: 1.0.3
|
||||
internal-slot: 1.0.7
|
||||
regexp.prototype.flags: 1.5.3
|
||||
set-function-name: 2.0.2
|
||||
side-channel: 1.0.6
|
||||
|
||||
string.prototype.repeat@1.0.0:
|
||||
dependencies:
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.23.3
|
||||
|
||||
string.prototype.trim@1.2.9:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
@@ -6846,7 +6633,7 @@ snapshots:
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tuono-fs-router-vite-plugin@0.16.0(@types/node@22.10.0)(sugarss@4.0.1(postcss@8.4.49)):
|
||||
tuono-fs-router-vite-plugin@0.16.4(@types/node@22.10.0)(sugarss@4.0.1(postcss@8.4.49)):
|
||||
dependencies:
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/types': 7.26.0
|
||||
@@ -6863,7 +6650,7 @@ snapshots:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
tuono-lazy-fn-vite-plugin@0.16.0(@types/node@22.10.0)(sugarss@4.0.1(postcss@8.4.49)):
|
||||
tuono-lazy-fn-vite-plugin@0.16.4(@types/node@22.10.0)(sugarss@4.0.1(postcss@8.4.49)):
|
||||
dependencies:
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/types': 7.26.0
|
||||
@@ -6879,7 +6666,7 @@ snapshots:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
tuono-router@0.16.0(@types/node@22.10.0)(@types/react@18.3.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sugarss@4.0.1(postcss@8.4.49)):
|
||||
tuono-router@0.16.4(@types/node@22.10.0)(@types/react@18.3.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sugarss@4.0.1(postcss@8.4.49)):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
@@ -6898,7 +6685,7 @@ snapshots:
|
||||
- sugarss
|
||||
- terser
|
||||
|
||||
tuono@0.16.0(@types/react@18.3.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sugarss@4.0.1(postcss@8.4.49)):
|
||||
tuono@0.16.4(@types/react@18.3.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sugarss@4.0.1(postcss@8.4.49)):
|
||||
dependencies:
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/generator': 7.26.2
|
||||
@@ -6916,9 +6703,9 @@ snapshots:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-helmet-async: 2.0.5(react@18.3.1)
|
||||
tuono-fs-router-vite-plugin: 0.16.0(@types/node@22.10.0)(sugarss@4.0.1(postcss@8.4.49))
|
||||
tuono-lazy-fn-vite-plugin: 0.16.0(@types/node@22.10.0)(sugarss@4.0.1(postcss@8.4.49))
|
||||
tuono-router: 0.16.0(@types/node@22.10.0)(@types/react@18.3.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sugarss@4.0.1(postcss@8.4.49))
|
||||
tuono-fs-router-vite-plugin: 0.16.4(@types/node@22.10.0)(sugarss@4.0.1(postcss@8.4.49))
|
||||
tuono-lazy-fn-vite-plugin: 0.16.4(@types/node@22.10.0)(sugarss@4.0.1(postcss@8.4.49))
|
||||
tuono-router: 0.16.4(@types/node@22.10.0)(@types/react@18.3.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sugarss@4.0.1(postcss@8.4.49))
|
||||
vite: 5.4.11(@types/node@22.10.0)(sugarss@4.0.1(postcss@8.4.49))
|
||||
transitivePeerDependencies:
|
||||
- '@swc/helpers'
|
||||
@@ -7276,6 +7063,8 @@ snapshots:
|
||||
dependencies:
|
||||
xml-name-validator: 5.0.0
|
||||
|
||||
web-streams-polyfill@4.0.0: {}
|
||||
|
||||
webidl-conversions@7.0.0: {}
|
||||
|
||||
whatwg-encoding@3.1.1:
|
||||
@@ -7297,28 +7086,6 @@ snapshots:
|
||||
is-string: 1.0.7
|
||||
is-symbol: 1.0.4
|
||||
|
||||
which-builtin-type@1.1.4:
|
||||
dependencies:
|
||||
function.prototype.name: 1.1.6
|
||||
has-tostringtag: 1.0.2
|
||||
is-async-function: 2.0.0
|
||||
is-date-object: 1.0.5
|
||||
is-finalizationregistry: 1.0.2
|
||||
is-generator-function: 1.0.10
|
||||
is-regex: 1.1.4
|
||||
is-weakref: 1.0.2
|
||||
isarray: 2.0.5
|
||||
which-boxed-primitive: 1.0.2
|
||||
which-collection: 1.0.2
|
||||
which-typed-array: 1.1.15
|
||||
|
||||
which-collection@1.0.2:
|
||||
dependencies:
|
||||
is-map: 2.0.3
|
||||
is-set: 2.0.3
|
||||
is-weakmap: 2.0.2
|
||||
is-weakset: 2.0.3
|
||||
|
||||
which-typed-array@1.1.15:
|
||||
dependencies:
|
||||
available-typed-arrays: 1.0.7
|
||||
|
||||
Reference in New Issue
Block a user