From dc42f3829f97aa9742a9c6a20fead0189d292f36 Mon Sep 17 00:00:00 2001 From: Jerome Date: Thu, 27 Jun 2024 02:19:18 +0800 Subject: [PATCH 1/3] add vite-svelte example --- examples/vite-svelte/Cargo.toml | 16 ++++ examples/vite-svelte/README.md | 21 +++++ examples/vite-svelte/backend/main.rs | 84 +++++++++++++++++++ examples/vite-svelte/frontend/App.svelte | 47 +++++++++++ examples/vite-svelte/frontend/app.css | 79 +++++++++++++++++ .../vite-svelte/frontend/assets/svelte.svg | 1 + examples/vite-svelte/frontend/head.js | 6 ++ .../vite-svelte/frontend/lib/Counter.svelte | 10 +++ examples/vite-svelte/frontend/main.js | 8 ++ examples/vite-svelte/frontend/server.js | 6 ++ examples/vite-svelte/frontend/vite-env.d.ts | 2 + examples/vite-svelte/index.html | 13 +++ examples/vite-svelte/jsconfig.json | 32 +++++++ examples/vite-svelte/package.json | 16 ++++ examples/vite-svelte/public/vite.svg | 1 + examples/vite-svelte/svelte.config.js | 7 ++ examples/vite-svelte/vite.client.config.js | 24 ++++++ examples/vite-svelte/vite.ssr.config.js | 23 +++++ 18 files changed, 396 insertions(+) create mode 100644 examples/vite-svelte/Cargo.toml create mode 100644 examples/vite-svelte/README.md create mode 100644 examples/vite-svelte/backend/main.rs create mode 100644 examples/vite-svelte/frontend/App.svelte create mode 100644 examples/vite-svelte/frontend/app.css create mode 100644 examples/vite-svelte/frontend/assets/svelte.svg create mode 100644 examples/vite-svelte/frontend/head.js create mode 100644 examples/vite-svelte/frontend/lib/Counter.svelte create mode 100644 examples/vite-svelte/frontend/main.js create mode 100644 examples/vite-svelte/frontend/server.js create mode 100644 examples/vite-svelte/frontend/vite-env.d.ts create mode 100644 examples/vite-svelte/index.html create mode 100644 examples/vite-svelte/jsconfig.json create mode 100644 examples/vite-svelte/package.json create mode 100644 examples/vite-svelte/public/vite.svg create mode 100644 examples/vite-svelte/svelte.config.js create mode 100644 examples/vite-svelte/vite.client.config.js create mode 100644 examples/vite-svelte/vite.ssr.config.js diff --git a/examples/vite-svelte/Cargo.toml b/examples/vite-svelte/Cargo.toml new file mode 100644 index 0000000..4f52a49 --- /dev/null +++ b/examples/vite-svelte/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "svelte-salvo-ssr" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "svelte-salvo-ssr" +path = "backend/main.rs" + +[dependencies] +salvo = { version = "0.68.3", features = ["serve-static"] } +serde_json = "1.0.118" +ssr_rs = "0.5.4" +tokio = { version = "1", features = ["macros"] } +tracing = "0.1" +tracing-subscriber = "0.3" \ No newline at end of file diff --git a/examples/vite-svelte/README.md b/examples/vite-svelte/README.md new file mode 100644 index 0000000..7b83373 --- /dev/null +++ b/examples/vite-svelte/README.md @@ -0,0 +1,21 @@ +# Svelte-Salvo-SSR-template +It is a template for Svelte SSR with Salvo-rs and Vite. + +1. Install npm dependencies: +```sh +pnpm install +``` + +2. Use vite to build svelte client JS and CSS: +```sh +pnpx vite build --config vite.client.config.js +``` + +3. Use vite to build svelte SSR JS: +```sh +pnpx vite build --config vite.ssr.config.js +``` +4. Run Rust server: +```sh +cargo run +``` diff --git a/examples/vite-svelte/backend/main.rs b/examples/vite-svelte/backend/main.rs new file mode 100644 index 0000000..5531694 --- /dev/null +++ b/examples/vite-svelte/backend/main.rs @@ -0,0 +1,84 @@ +use salvo::prelude::*; +use std::cell::RefCell; +use std::fs::read_to_string; +use std::path::Path; +use ssr_rs::Ssr; + +thread_local! { + static SSR: RefCell> = RefCell::new( + Ssr::from( + read_to_string(Path::new("./dist/server/server.js").to_str().unwrap()).unwrap(), + "" + ).unwrap_or_else(|err| { + eprintln!("Failed to initialize SSR: {}", err); + std::process::exit(1); + }) + ) +} + +#[handler] +async fn index(res: &mut Response) { + let result = SSR.with(|ssr| { + let mut ssr = ssr.borrow_mut(); + ssr.render_to_string(None).unwrap_or_else(|err| { + eprintln!("Error rendering to string: {}", err); + String::new() + }) + }); + + if result.is_empty() { + eprintln!("Rendered result is empty"); + res.status_code(StatusCode::INTERNAL_SERVER_ERROR); + res.render(Text::Plain("Internal Server Error")); + return; + } + + //println!("Rendered result: {}", result); // For debugging + + let result: serde_json::Value = match serde_json::from_str(&result) { + Ok(val) => val, + Err(err) => { + eprintln!("Failed to parse JSON: {}", err); + res.status_code(StatusCode::INTERNAL_SERVER_ERROR); + res.render(Text::Plain("Internal Server Error")); + return; + } + }; + + let html = result["html"].as_str().unwrap_or(""); + let css = result["css"].as_str().unwrap_or(""); + + let full_html = format!( + r#" + + + + + + +
{}
+ + + "#, + css, html + ); + res.render(Text::Html(full_html)); +} + +#[tokio::main] +async fn main() { + Ssr::create_platform(); + let router = Router::new() + .push(Router::with_path("/client/<**path>") + .get(StaticDir::new(["./dist/client"]))) + .push(Router::with_path("/client/assets/<**path>") + .get(StaticDir::new(["./dist/assets/client"]))) + .push(Router::with_path("/").get(index)); + + let acceptor = TcpListener::new("127.0.0.1:8080").bind().await; + + tracing_subscriber::fmt().init(); + tracing::info!("Listening on http://{:?}", acceptor.local_addr()); + + Server::new(acceptor).serve(router).await; +} diff --git a/examples/vite-svelte/frontend/App.svelte b/examples/vite-svelte/frontend/App.svelte new file mode 100644 index 0000000..1f31354 --- /dev/null +++ b/examples/vite-svelte/frontend/App.svelte @@ -0,0 +1,47 @@ + + +
+
+ + + + + + +
+

Vite + Svelte

+ +
+ +
+ +

+ Check out SvelteKit, the official Svelte app framework powered by Vite! +

+ +

+ Click on the Vite and Svelte logos to learn more +

+
+ + diff --git a/examples/vite-svelte/frontend/app.css b/examples/vite-svelte/frontend/app.css new file mode 100644 index 0000000..617f5e9 --- /dev/null +++ b/examples/vite-svelte/frontend/app.css @@ -0,0 +1,79 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/examples/vite-svelte/frontend/assets/svelte.svg b/examples/vite-svelte/frontend/assets/svelte.svg new file mode 100644 index 0000000..c5e0848 --- /dev/null +++ b/examples/vite-svelte/frontend/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/vite-svelte/frontend/head.js b/examples/vite-svelte/frontend/head.js new file mode 100644 index 0000000..f690331 --- /dev/null +++ b/examples/vite-svelte/frontend/head.js @@ -0,0 +1,6 @@ +export function buildHead(head, css) { + return ` + ${head} + + `; + } \ No newline at end of file diff --git a/examples/vite-svelte/frontend/lib/Counter.svelte b/examples/vite-svelte/frontend/lib/Counter.svelte new file mode 100644 index 0000000..e45f903 --- /dev/null +++ b/examples/vite-svelte/frontend/lib/Counter.svelte @@ -0,0 +1,10 @@ + + + diff --git a/examples/vite-svelte/frontend/main.js b/examples/vite-svelte/frontend/main.js new file mode 100644 index 0000000..b2b3c4c --- /dev/null +++ b/examples/vite-svelte/frontend/main.js @@ -0,0 +1,8 @@ +import App from './App.svelte'; + +const app = new App({ + target: document.querySelector('#svelte-app'), + hydrate: true +}); + +export default app; diff --git a/examples/vite-svelte/frontend/server.js b/examples/vite-svelte/frontend/server.js new file mode 100644 index 0000000..2ba0fc2 --- /dev/null +++ b/examples/vite-svelte/frontend/server.js @@ -0,0 +1,6 @@ +import App from './App.svelte'; + +export function render() { + const { html, css } = App.render(); + return JSON.stringify({ html, css: css.code }); +} diff --git a/examples/vite-svelte/frontend/vite-env.d.ts b/examples/vite-svelte/frontend/vite-env.d.ts new file mode 100644 index 0000000..4078e74 --- /dev/null +++ b/examples/vite-svelte/frontend/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/vite-svelte/index.html b/examples/vite-svelte/index.html new file mode 100644 index 0000000..dbbfa2a --- /dev/null +++ b/examples/vite-svelte/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Svelte + + +
+ + + diff --git a/examples/vite-svelte/jsconfig.json b/examples/vite-svelte/jsconfig.json new file mode 100644 index 0000000..588de60 --- /dev/null +++ b/examples/vite-svelte/jsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "moduleResolution": "bundler", + "target": "ESNext", + "module": "ESNext", + /** + * svelte-preprocess cannot figure out whether you have + * a value or a type, so tell TypeScript to enforce using + * `import type` instead of `import` for Types. + */ + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + /** + * To have warnings / errors of the Svelte compiler at the + * correct position, enable source maps by default. + */ + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable this if you'd like to use dynamic types. + */ + "checkJs": true + }, + /** + * Use global.d.ts instead of compilerOptions.types + * to avoid limiting type declarations. + */ + "include": ["frontend/**/*.d.ts", "frontend/**/*.js", "frontend/**/*.svelte"] +} diff --git a/examples/vite-svelte/package.json b/examples/vite-svelte/package.json new file mode 100644 index 0000000..6dc5886 --- /dev/null +++ b/examples/vite-svelte/package.json @@ -0,0 +1,16 @@ +{ + "name": "my-vue-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build && vite build --config vite.config.ssr.js", + "preview": "vite preview" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.1.1", + "svelte": "^4.2.18", + "vite": "^5.3.1" + } +} diff --git a/examples/vite-svelte/public/vite.svg b/examples/vite-svelte/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/examples/vite-svelte/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/vite-svelte/svelte.config.js b/examples/vite-svelte/svelte.config.js new file mode 100644 index 0000000..b0683fd --- /dev/null +++ b/examples/vite-svelte/svelte.config.js @@ -0,0 +1,7 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +} diff --git a/examples/vite-svelte/vite.client.config.js b/examples/vite-svelte/vite.client.config.js new file mode 100644 index 0000000..960c9b6 --- /dev/null +++ b/examples/vite-svelte/vite.client.config.js @@ -0,0 +1,24 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + base: '/client/', + plugins: [svelte({ + compilerOptions: { + hydratable: true + } + })], + build: { + outDir: 'dist/client', + emptyOutDir: true, + rollupOptions: { + input: './frontend/main.js', + output: { + format: 'esm', + entryFileNames: '[name].js', + chunkFileNames: '[name]-[hash].js', + assetFileNames: 'assets/[name][extname]', + }, + } + } +}) diff --git a/examples/vite-svelte/vite.ssr.config.js b/examples/vite-svelte/vite.ssr.config.js new file mode 100644 index 0000000..3002b99 --- /dev/null +++ b/examples/vite-svelte/vite.ssr.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + base: '/client/', + plugins: [svelte()], + build: { + ssr: true, + outDir: 'dist/server', + emptyOutDir: true, + rollupOptions: { + input: './frontend/server.js', + output: { + format: 'iife', + entryFileNames: '[name].js', + chunkFileNames: '[name]-[hash].js', + } + } + }, + ssr: { + noExternal: true, + } +}) From 89052bc4ba62a9cd5d5713f6d69298aa206a8fdc Mon Sep 17 00:00:00 2001 From: Jerome Date: Thu, 27 Jun 2024 09:44:34 +0800 Subject: [PATCH 2/3] add vite-svelte example --- Cargo.toml | 10 ++++++ tests/assets/svelte-4-iife.js | 63 +++++++++++++++++++++++++++++++++++ tests/svelte.rs | 24 +++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 tests/assets/svelte-4-iife.js create mode 100644 tests/svelte.rs diff --git a/Cargo.toml b/Cargo.toml index a9654ee..a59d6fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,12 @@ axum = "0.7.4" # Rocket dependencies rocket = "0.5.0-rc.2" +# Salvo dependencies +salvo = { version = "0.68.3", features = ["serve-static"] } + +serde_json = "1.0.118" +tracing = "0.1" +tracing-subscriber = "0.3" env_logger = "0.9.0" [[example]] @@ -90,3 +96,7 @@ path = "examples/webpack-react/server.rs" [[example]] name = "rspack-react" path = "examples/rspack-react/server.rs" + +[[example]] +name="vite-svelte" +path="examples/vite-svelte/backend/main.rs" \ No newline at end of file diff --git a/tests/assets/svelte-4-iife.js b/tests/assets/svelte-4-iife.js new file mode 100644 index 0000000..9f85b2f --- /dev/null +++ b/tests/assets/svelte-4-iife.js @@ -0,0 +1,63 @@ +(function(exports) { + "use strict"; + function run(fn) { + return fn(); + } + function blank_object() { + return /* @__PURE__ */ Object.create(null); + } + function run_all(fns) { + fns.forEach(run); + } + let current_component; + function set_current_component(component) { + current_component = component; + } + let on_destroy; + function create_ssr_component(fn) { + function $$render(result, props, bindings, slots, context) { + const parent_component = current_component; + const $$ = { + on_destroy, + context: new Map(context || (parent_component ? parent_component.$$.context : [])), + // these will be immediately discarded + on_mount: [], + before_update: [], + after_update: [], + callbacks: blank_object() + }; + set_current_component({ $$ }); + const html = fn(result, props, bindings, slots); + set_current_component(parent_component); + return html; + } + return { + render: (props = {}, { $$slots = {}, context = /* @__PURE__ */ new Map() } = {}) => { + on_destroy = []; + const result = { title: "", head: "", css: /* @__PURE__ */ new Set() }; + const html = $$render(result, props, {}, $$slots, context); + run_all(on_destroy); + return { + html, + css: { + code: Array.from(result.css).map((css) => css.code).join("\n"), + map: null + // TODO + }, + head: result.title + result.head + }; + }, + $$render + }; + } + const App = create_ssr_component(($$result, $$props, $$bindings, slots) => { + return `
`; + }); + function render() { + const { html } = App.render(); + return html; + } + exports.render = render; + Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); + return exports; +})({}); diff --git a/tests/svelte.rs b/tests/svelte.rs new file mode 100644 index 0000000..114367c --- /dev/null +++ b/tests/svelte.rs @@ -0,0 +1,24 @@ +use ssr_rs::Ssr; +use std::fs::read_to_string; +use std::sync::Once; + +static INIT: Once = Once::new(); + +fn prepare() { + INIT.call_once(|| { + Ssr::create_platform(); + }) +} + +#[test] +fn renders_svelte_exported_as_iife() { + prepare(); + + let source = read_to_string("./tests/assets/svelte-4-iife.js").unwrap(); + + let mut js = Ssr::from(source, "").unwrap(); + + let html = js.render_to_string(None).unwrap(); + + assert_eq!(html, "
"); +} \ No newline at end of file From 78f6092b8ca83fa2f38e92600663d2afdc2fb14f Mon Sep 17 00:00:00 2001 From: Jerome Date: Thu, 27 Jun 2024 14:56:15 +0800 Subject: [PATCH 3/3] fix Rustfmt --- examples/vite-svelte/backend/main.rs | 13 +++++++------ tests/svelte.rs | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/vite-svelte/backend/main.rs b/examples/vite-svelte/backend/main.rs index 5531694..48ce172 100644 --- a/examples/vite-svelte/backend/main.rs +++ b/examples/vite-svelte/backend/main.rs @@ -1,8 +1,8 @@ use salvo::prelude::*; +use ssr_rs::Ssr; use std::cell::RefCell; use std::fs::read_to_string; use std::path::Path; -use ssr_rs::Ssr; thread_local! { static SSR: RefCell> = RefCell::new( @@ -47,7 +47,7 @@ async fn index(res: &mut Response) { let html = result["html"].as_str().unwrap_or(""); let css = result["css"].as_str().unwrap_or(""); - + let full_html = format!( r#" @@ -69,10 +69,11 @@ async fn index(res: &mut Response) { async fn main() { Ssr::create_platform(); let router = Router::new() - .push(Router::with_path("/client/<**path>") - .get(StaticDir::new(["./dist/client"]))) - .push(Router::with_path("/client/assets/<**path>") - .get(StaticDir::new(["./dist/assets/client"]))) + .push(Router::with_path("/client/<**path>").get(StaticDir::new(["./dist/client"]))) + .push( + Router::with_path("/client/assets/<**path>") + .get(StaticDir::new(["./dist/assets/client"])), + ) .push(Router::with_path("/").get(index)); let acceptor = TcpListener::new("127.0.0.1:8080").bind().await; diff --git a/tests/svelte.rs b/tests/svelte.rs index 114367c..c27bd41 100644 --- a/tests/svelte.rs +++ b/tests/svelte.rs @@ -21,4 +21,4 @@ fn renders_svelte_exported_as_iife() { let html = js.render_to_string(None).unwrap(); assert_eq!(html, "
"); -} \ No newline at end of file +}