mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-27 13:52:47 -07:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 166e61bab4 | |||
| 93cd8bd8da | |||
| f5c1641af2 | |||
| 62ffed0624 | |||
| 8d0a4a7417 | |||
| b1b274ea12 | |||
| fc38865965 | |||
| 911adaaecb | |||
| 7626ed8d1d | |||
| 6135fd7e1b | |||
| 14bea36356 |
+6
-1
@@ -1 +1,6 @@
|
||||
pnpm-lock.yaml
|
||||
pnpm-lock.yaml
|
||||
|
||||
dist
|
||||
.tuono
|
||||
|
||||
packages/lazy-fn-vite-plugin/tests/sources/*
|
||||
|
||||
@@ -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.2"
|
||||
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.2"
|
||||
},
|
||||
"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',
|
||||
|
||||
@@ -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.4"
|
||||
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.4"
|
||||
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.4"}
|
||||
# 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.4"
|
||||
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;
|
||||
|
||||
|
||||
@@ -14,6 +14,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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tuono-fs-router-vite-plugin",
|
||||
"version": "0.16.2",
|
||||
"version": "0.16.4",
|
||||
"description": "Plugin for the tuono's file system router. Tuono is the react/rust fullstack framework",
|
||||
"homepage": "https://tuono.dev",
|
||||
"scripts": {
|
||||
|
||||
@@ -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.4",
|
||||
"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.4",
|
||||
"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"
|
||||
|
||||
@@ -41,7 +41,9 @@ export const dynamic = (importFn: ImportFn): React.JSX.Element => {
|
||||
return <></>
|
||||
}
|
||||
|
||||
export const lazyLoadComponent = (factory: ImportFn): RouteComponent => {
|
||||
export const __tuono__internal__lazyLoadComponent = (
|
||||
factory: ImportFn,
|
||||
): RouteComponent => {
|
||||
let LoadedComponent: ComponentType<any> | undefined
|
||||
const LazyComponent = React.lazy(factory) as unknown as RouteComponent
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
dist
|
||||
pnpm-lock.yaml
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "tuono",
|
||||
"version": "0.16.2",
|
||||
"version": "0.16.4",
|
||||
"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"
|
||||
|
||||
@@ -17,6 +17,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 +47,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 }),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ export {
|
||||
Link,
|
||||
RouterProvider,
|
||||
dynamic,
|
||||
lazyLoadComponent,
|
||||
useRouter,
|
||||
__tuono__internal__lazyLoadComponent,
|
||||
} from 'tuono-router'
|
||||
|
||||
export type { TuonoProps } from './types'
|
||||
|
||||
Generated
+17
-17
@@ -84,8 +84,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.2
|
||||
version: 0.16.2(@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
|
||||
@@ -3111,20 +3111,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.2:
|
||||
resolution: {integrity: sha512-D0SHBX4IT1eQGeNNmXUYpnAtwVPutmnboMGRc0Tsf6tK2PjHY4Q10X2vUwFXm9Ps1mg0DHdAHl/90b4mtvG6Xg==}
|
||||
|
||||
tuono-lazy-fn-vite-plugin@0.16.0:
|
||||
resolution: {integrity: sha512-+ab7pbRrqGqYPePHdPvMGZMWsnEJi2TPTvkHXwV5dcflpRvhhrCppC7QbG5lk24HD+hCFryWpOiZlfzf/j6p+w==}
|
||||
tuono-lazy-fn-vite-plugin@0.16.2:
|
||||
resolution: {integrity: sha512-LFIO73qG9ckKdPmtT79vnwjcnFiRm2ntyoJU2Ce3/BNPVgDR1McITjEkeWwSwwRXiMDTX1cb7uDabprZcV8qSg==}
|
||||
|
||||
tuono-router@0.16.0:
|
||||
resolution: {integrity: sha512-JuTXoSjjgKCbFWJQTRQA2NyN9fHqasXrE1+vC3TV1PJ5P66EM2f4zlAzwMkUNbeGWCl/Kihi/gKT1pVqnieUFA==}
|
||||
tuono-router@0.16.2:
|
||||
resolution: {integrity: sha512-NhRbICCjF9l/1saRbvFOoso342FBJC6+Cx4rzFdHnY+uPqBd+SrDjydDpw1b+uqoHj1p5Y+QcXNIEqHRK1ajww==}
|
||||
peerDependencies:
|
||||
react: '>=16.3.0'
|
||||
react-dom: '>=16.3.0'
|
||||
|
||||
tuono@0.16.0:
|
||||
resolution: {integrity: sha512-VFuRPsIf3V3UMlTtgVo1/EgKruJRjNMFkahF88a9he1b7TS0FFTlw/AEC8D92NmkB6PFjyH7g9ZNw8bpogPTmQ==}
|
||||
tuono@0.16.2:
|
||||
resolution: {integrity: sha512-Cvs5ZMezUIZAU++F9/qMWfU0gN1BSF+o3yBw9YpYuIbWhsLWbIzgTUHs+kKOIGBlpgjARldwXDnWMPMz0m2h7A==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
react: '>=16.3.0'
|
||||
@@ -6846,7 +6846,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.2(@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 +6863,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.2(@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 +6879,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.2(@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 +6898,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.2(@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 +6916,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.2(@types/node@22.10.0)(sugarss@4.0.1(postcss@8.4.49))
|
||||
tuono-lazy-fn-vite-plugin: 0.16.2(@types/node@22.10.0)(sugarss@4.0.1(postcss@8.4.49))
|
||||
tuono-router: 0.16.2(@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'
|
||||
|
||||
Reference in New Issue
Block a user