diff --git a/crates/tuono/src/lib.rs b/crates/tuono/src/lib.rs index 7f6756a9..09dc0622 100644 --- a/crates/tuono/src/lib.rs +++ b/crates/tuono/src/lib.rs @@ -1,6 +1,8 @@ use clap::{Parser, Subcommand}; use std::process::Command; -mod axum_source_builder; + +mod source_builder; +use source_builder::{bundle_axum_source, create_client_entry_files}; mod watch; #[derive(Subcommand, Debug)] @@ -24,11 +26,14 @@ pub fn cli() { let args = Args::parse(); match args.action.unwrap() { - Actions::Dev => watch::watch().unwrap(), + Actions::Dev => { + bundle_axum_source(); + create_client_entry_files().unwrap(); + watch::watch().unwrap(); + } Actions::Build => { - axum_source_builder::bundle_axum_source(); - - println!("Build JS source"); + bundle_axum_source(); + create_client_entry_files().unwrap(); let mut vite_build = Command::new("./node_modules/.bin/tuono-build-prod"); let _ = &vite_build.output().unwrap(); } diff --git a/crates/tuono/src/axum_source_builder.rs b/crates/tuono/src/source_builder.rs similarity index 82% rename from crates/tuono/src/axum_source_builder.rs rename to crates/tuono/src/source_builder.rs index e2821ba3..c13d50ca 100644 --- a/crates/tuono/src/axum_source_builder.rs +++ b/crates/tuono/src/source_builder.rs @@ -1,11 +1,30 @@ use glob::glob; use std::collections::HashMap; use std::fs; +use std::io; use std::io::prelude::*; use std::path::Path; use std::path::PathBuf; const ROOT_FOLDER: &'static str = "src/routes"; +const DEV_FOLDER: &'static str = ".tuono"; +const SERVER_ENTRY_DATA: &'static str = "// File automatically generated by tuono + +import { routeTree } from './routeTree.gen' +import { serverSideRendering } from 'tuono/ssr' + +export const renderFn = serverSideRendering(routeTree) +"; + +const CLIENT_ENTRY_DATA: &'static str = "// File automatically generated by tuono + +import { hydrate } from 'tuono/hydration' + +// Import the generated route tree +import { routeTree } from './routeTree.gen' + +hydrate(routeTree) +"; enum Mode { Prod, @@ -159,3 +178,14 @@ pub fn bundle_axum_source() { create_main_file(&base_path, &bundled_file); } + +pub fn create_client_entry_files() -> io::Result<()> { + let dev_folder = Path::new(DEV_FOLDER); + let mut server_entry = fs::File::create(&dev_folder.join("server-main.tsx"))?; + let mut client_entry = fs::File::create(&dev_folder.join("client-main.tsx"))?; + + server_entry.write(SERVER_ENTRY_DATA.as_bytes())?; + client_entry.write(CLIENT_ENTRY_DATA.as_bytes())?; + + Ok(()) +} diff --git a/crates/tuono/src/watch.rs b/crates/tuono/src/watch.rs index 100f32f2..7206ca72 100644 --- a/crates/tuono/src/watch.rs +++ b/crates/tuono/src/watch.rs @@ -6,7 +6,7 @@ use watchexec::Watchexec; use watchexec_signals::Signal; use watchexec_supervisor::job::start_job; -use crate::axum_source_builder::bundle_axum_source; +use crate::source_builder::bundle_axum_source; // What is the development pipeline? // @@ -22,7 +22,6 @@ use crate::axum_source_builder::bundle_axum_source; #[tokio::main] pub async fn watch() -> Result<()> { - bundle_axum_source(); let (watch_client, _) = start_job(Arc::new(Command { program: Program::Exec { prog: "node_modules/.bin/tuono-dev-watch".into(), diff --git a/packages/tuono/package.json b/packages/tuono/package.json index 4a077563..33ceb81a 100644 --- a/packages/tuono/package.json +++ b/packages/tuono/package.json @@ -25,6 +25,26 @@ "default": "./dist/cjs/build/index.js" } }, + "./ssr": { + "import": { + "types": "./dist/esm/ssr/index.d.ts", + "default": "./dist/esm/ssr/index.js" + }, + "require": { + "types": "./dist/cjs/ssr/index.d.ts", + "default": "./dist/cjs/ssr/index.js" + } + }, + "./hydration": { + "import": { + "types": "./dist/esm/hydration/index.d.ts", + "default": "./dist/esm/hydration/index.js" + }, + "require": { + "types": "./dist/cjs/ssr/index.d.ts", + "default": "./dist/cjs/ssr/index.js" + } + }, ".": { "import": { "types": "./dist/esm/index.d.ts", @@ -65,6 +85,7 @@ "@types/babel__core": "^7.20.5", "@types/node": "^20.12.7", "@vitejs/plugin-react-swc": "^3.7.0", + "fast-text-encoding": "^1.0.6", "prettier": "^3.2.4", "vite": "^5.2.11", "zustand": "4.4.7" diff --git a/packages/tuono/src/hydration/index.tsx b/packages/tuono/src/hydration/index.tsx new file mode 100644 index 00000000..d6737fcd --- /dev/null +++ b/packages/tuono/src/hydration/index.tsx @@ -0,0 +1,19 @@ +import 'vite/modulepreload-polyfill' +import React from 'react' +import ReactDOM from 'react-dom/client' +import { RouterProvider, createRouter } from '../router' + +export function hydrate(routeTree: any) { + // Create a new router instance + const router = createRouter({ routeTree }) + + // Render the app + const rootElement = document.getElementById('__tuono')! + + ReactDOM.hydrateRoot( + rootElement, + + + , + ) +} diff --git a/packages/tuono/src/ssr/index.tsx b/packages/tuono/src/ssr/index.tsx new file mode 100644 index 00000000..a0d270c4 --- /dev/null +++ b/packages/tuono/src/ssr/index.tsx @@ -0,0 +1,48 @@ +import 'fast-text-encoding' // Mandatory for React18 +import * as React from 'react' +import { renderToString, renderToStaticMarkup } from 'react-dom/server' +import { RouterProvider } from '../router' +import { createRouter } from '../router' + +export function serverSideRendering(routeTree: any) { + return function render(payload: string | undefined): string { + const props = payload ? JSON.parse(payload) : {} + + const router = createRouter({ routeTree }) // Render the app + + const app = renderToString( + , + ) + + const developmentScript = ` + + ` + + return ` + + + + + Playground + + +
${app}
+ ${renderToStaticMarkup( +