├── .gitattributes ├── .github └── workflows │ ├── audit-on-push.yml │ ├── check-publish.yml │ ├── main.yml │ └── publish.yml ├── .gitignore ├── Cargo.toml ├── LICENSE_APACHE ├── LICENSE_MIT ├── README.md ├── benches └── README.md ├── examples ├── README.md ├── actix.rs ├── actix_with_initial_props.rs ├── axum.rs ├── multi-thread.rs ├── rocket.rs ├── rspack-react │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── pnpm-lock.yaml │ ├── rspack.config.js │ ├── server.rs │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── assets │ │ │ └── react.svg │ │ ├── index.css │ │ ├── main.tsx │ │ ├── react-env.d.ts │ │ └── ssr-entry.tsx │ ├── ssr.config.js │ └── tsconfig.json ├── run.rs ├── tide.rs ├── vite-react │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── pnpm-lock.yaml │ ├── public │ │ └── vite.svg │ ├── server.rs │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── assets │ │ │ └── react.svg │ │ ├── index.css │ │ ├── main.tsx │ │ ├── server-entry.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── vite-svelte │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── backend │ │ └── main.rs │ ├── frontend │ │ ├── App.svelte │ │ ├── app.css │ │ ├── assets │ │ │ └── svelte.svg │ │ ├── lib │ │ │ └── Counter.svelte │ │ ├── main.js │ │ ├── server.js │ │ └── vite-env.d.ts │ ├── index.html │ ├── jsconfig.json │ ├── package.json │ ├── pnpm-lock.yaml │ ├── public │ │ └── vite.svg │ ├── svelte.config.js │ ├── vite.client.config.js │ └── vite.ssr.config.js ├── warp.rs └── webpack-react │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── pnpm-lock.yaml │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ ├── server.rs │ ├── server.tsx │ ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── Async.tsx │ ├── index.css │ ├── index.html │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ ├── setupTests.ts │ ├── ssrEntry.tsx │ └── styleMock.ts │ ├── tsconfig.json │ ├── webpack.client.build.js │ ├── webpack.server.build.js │ └── webpack.ssr.js ├── logo.png ├── src ├── icu.rs ├── icudtl.dat ├── lib.rs └── ssr.rs └── tests ├── assets ├── react-17-iife.js ├── react-18-iife.js ├── svelte-4-iife.js └── svelte-5-iife.js ├── intl.rs ├── react.rs └── svelte.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | *.css linguist-detectable=false 2 | *.js linguist-detectable=false 3 | *.jsx linguist-detectable=false 4 | *.tsx linguist-detectable=false 5 | *.ts linguist-detectable=false 6 | *.rs linguist-detectable=true 7 | *.html linguist-detectable=false 8 | -------------------------------------------------------------------------------- /.github/workflows/audit-on-push.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | push: 4 | paths: 5 | - '**/Cargo.toml' 6 | - '**/Cargo.lock' 7 | jobs: 8 | security_audit: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions-rs/audit-check@v1 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/check-publish.yml: -------------------------------------------------------------------------------- 1 | name: Github Main 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | check-if-publishable: 13 | name: Check if allowed to be published on crates.io 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: stable 20 | override: true 21 | - uses: katyo/publish-crates@v1 22 | with: 23 | registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} 24 | dry-run: true -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Github Main 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | test: 10 | name: Test 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | override: true 20 | 21 | - uses: actions-rs/cargo@v1 22 | with: 23 | command: test 24 | 25 | fmt: 26 | name: Rustfmt 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v2 30 | - uses: actions-rs/toolchain@v1 31 | with: 32 | toolchain: stable 33 | override: true 34 | components: rustfmt 35 | - uses: actions-rs/cargo@v1 36 | with: 37 | command: fmt 38 | args: --all -- --check 39 | 40 | clippy: 41 | name: Clippy 42 | runs-on: ubuntu-latest 43 | 44 | steps: 45 | - uses: actions/checkout@v2 46 | - uses: actions-rust-lang/setup-rust-toolchain@v1 47 | 48 | - run: cargo clippy -- -D warnings 49 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Deploy CI 2 | on: 3 | release: 4 | types: [created] 5 | 6 | jobs: 7 | publish: 8 | name: Publish 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: stable 15 | override: true 16 | - uses: katyo/publish-crates@v1 17 | with: 18 | registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} 19 | check-repo: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea 4 | client2/ 5 | .vscode/ 6 | 7 | dist 8 | process.yml 9 | tarpaulin* 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ssr_rs" 3 | version = "0.8.3" 4 | authors = ["Valerio "] 5 | edition = "2021" 6 | description = "Server side rendering with the v8 engine for parse and evaluate the javascript code" 7 | readme = "./README.md" 8 | homepage = "https://github.com/Valerioageno/ssr-rs" 9 | documentation = "https://docs.rs/ssr_rs" 10 | repository = "https://github.com/Valerioageno/ssr-rs" 11 | keywords = ["web", "ssr", "react", "actix", "server-side-render"] 12 | categories = ["web-programming"] 13 | license-file = "./LICENSE_MIT" 14 | autoexamples = false 15 | include = [ 16 | "src/*.rs", 17 | "src/icudtl.dat", 18 | "Cargo.toml", 19 | ] 20 | 21 | [lib] 22 | name = "ssr_rs" 23 | path = "src/lib.rs" 24 | 25 | [dependencies] 26 | v8="135.1.0" 27 | 28 | [dev-dependencies] 29 | 30 | # Actix depencendies 31 | actix-files = "0.6.4" 32 | actix-web = "4" 33 | 34 | # Warp dependencies 35 | tokio = { version = "1", features = ["full"] } 36 | warp = "0.3" 37 | 38 | # Tide dependencies 39 | tide = "0.16.0" 40 | async-std = { version = "1.6.0", features = ["attributes"] } 41 | serde = { version = "1.0", features = ["derive"] } 42 | 43 | # Axum 44 | axum = "0.7.4" 45 | 46 | # Rocket dependencies 47 | rocket = "0.5.0-rc.2" 48 | 49 | # Salvo dependencies 50 | salvo = { version = "0.68.3", features = ["serve-static"] } 51 | 52 | serde_json = "1.0.118" 53 | tracing = "0.1" 54 | tracing-subscriber = "0.3" 55 | env_logger = "0.9.0" 56 | 57 | [[example]] 58 | name = "actix" 59 | path = "examples/actix.rs" 60 | 61 | [[example]] 62 | name = "tide" 63 | path = "examples/tide.rs" 64 | 65 | [[example]] 66 | name = "actix-with-props" 67 | path = "examples/actix_with_initial_props.rs" 68 | 69 | [[example]] 70 | name = "rocket" 71 | path = "examples/rocket.rs" 72 | 73 | [[example]] 74 | name = "warp" 75 | path = "examples/warp.rs" 76 | 77 | [[example]] 78 | name = "run" 79 | path = "examples/run.rs" 80 | 81 | [[example]] 82 | name = "axum" 83 | path = "examples/axum.rs" 84 | 85 | [[example]] 86 | name = "multi" 87 | path = "examples/multi-thread.rs" 88 | 89 | [[example]] 90 | name = "vite-react" 91 | path = "examples/vite-react/server.rs" 92 | 93 | [[example]] 94 | name = "webpack-react" 95 | path = "examples/webpack-react/server.rs" 96 | 97 | [[example]] 98 | name = "rspack-react" 99 | path = "examples/rspack-react/server.rs" 100 | 101 | [[example]] 102 | name="vite-svelte" 103 | path="examples/vite-svelte/backend/main.rs" 104 | -------------------------------------------------------------------------------- /LICENSE_APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /LICENSE_MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Valerio Ageno 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Rust server side rendering 2 | 3 | [![API](https://docs.rs/ssr_rs/badge.svg)](https://docs.rs/ssr_rs) 4 | [![codecov](https://codecov.io/gh/Valerioageno/ssr-rs/branch/main/graph/badge.svg?token=O0CZIZAR7X)](https://codecov.io/gh/Valerioageno/ssr-rs) 5 | 6 | The crate aims to enable server side rendering on rust servers in the simplest and lightest way possible. 7 | 8 | It uses an embedded version of the [V8](https://v8.dev/) javascript engine (rusty_v8) to parse and evaluate a built bundle file and return a string with the rendered html. 9 | > [!NOTE] 10 | > This project is the backbone of [tuono](https://github.com/Valerioageno/tuono); a fullstack react framework with built in SSR. 11 | 12 | Currently it works with [Vite](https://vitejs.dev/), [Webpack](https://webpack.js.org/), [Rspack](https://www.rspack.dev/), [React 18](https://react.dev/) and [Svelte 4](https://svelte.dev/) - Check the `examples/` folder. 13 | 14 | > Check here the benchmark results. 15 | 16 | ## Getting started 17 | 18 | Add this to your `Cargo.toml`: 19 | 20 | ```bash 21 | cargo add ssr_rs 22 | ``` 23 | 24 | ## Example 25 | 26 | To render to string a bundled react project the application should perform the following 27 | calls. 28 | 29 | ```rust 30 | use ssr_rs::Ssr; 31 | use std::fs::read_to_string; 32 | 33 | fn main() { 34 | Ssr::create_platform(); 35 | 36 | let source = read_to_string("./path/to/build.js").unwrap(); 37 | 38 | let mut js = Ssr::new(&source, "entryPoint").unwrap(); 39 | 40 | let html = js.render_to_string(None).unwrap(); 41 | 42 | assert_eq!(html, "...".to_string()); 43 | } 44 | ``` 45 | 46 | ## What is the "entryPoint"? 47 | 48 | The `entryPoint` could be either: 49 | - the function that returns an object with one or more properties that are functions that when called return the rendered result 50 | - the object itself with one or more properties that are functions that when called return the rendered result 51 | 52 | In case the bundled JS is an IIFE or the plain object the `entryPoint` is an empty string. 53 | 54 | ```javascript 55 | // IIFE example | bundle.js -> See vite-react example 56 | (() => ({ renderToStringFn: (props) => "" }))() // The entryPoint is an empty string 57 | ``` 58 | 59 | ```javascript 60 | // Plain object example | bundle.js 61 | ({renderToStringFn: (props) => ""}); // The entryPoint is an empty string 62 | ``` 63 | 64 | ```javascript 65 | // IIFE variable example | bundle.js -> See webpack-react example 66 | var SSR = (() => ({renderToStringFn: (props) => ""}))() // SSR is the entry point 67 | ``` 68 | 69 | ```javascript 70 | // Variable example | bundle.js -> See webpack-react example 71 | var SSR = {renderToStringFn: (props) => ""}; // SSR is the entry point 72 | ``` 73 | 74 | > The export results are managed by the bundler directly. 75 | 76 | ## Example with initial props 77 | 78 | ```rust 79 | use ssr_rs::Ssr; 80 | use std::fs::read_to_string; 81 | 82 | fn main() { 83 | Ssr::create_platform(); 84 | 85 | let props = r##"{ 86 | "params": [ 87 | "hello", 88 | "ciao", 89 | "こんにちは" 90 | ] 91 | }"##; 92 | 93 | let source = read_to_string("./path/to/build.js").unwrap(); 94 | 95 | let mut js = Ssr::new(&source, "entryPoint").unwrap(); 96 | 97 | let html = js.render_to_string(Some(&props)).unwrap(); 98 | 99 | assert_eq!(html, "...".to_string()); 100 | } 101 | ``` 102 | 103 | ## Example with actix-web 104 | 105 | > Examples with different web frameworks are available in the examples folder. 106 | 107 | Even though the V8 engine allows accessing the same `isolate` from different threads that is forbidden by this crate for two reasons: 108 | 109 | 1. rusty_v8 library have not implemented yet the V8 Locker API. Accessing Ssr struct from a different thread will make the V8 engine to panic. 110 | 2. Rendering HTML does not need shared state across threads. 111 | 112 | For the reasons above parallel computation is a better choice. Following actix-web setup: 113 | 114 | ```rust 115 | use actix_web::{get, http::StatusCode, App, HttpResponse, HttpServer}; 116 | use std::cell::RefCell; 117 | use std::fs::read_to_string; 118 | 119 | use ssr_rs::Ssr; 120 | 121 | thread_local! { 122 | static SSR: RefCell> = RefCell::new( 123 | Ssr::from( 124 | read_to_string("./client/dist/ssr/index.js").unwrap(), 125 | "SSR" 126 | ).unwrap() 127 | ) 128 | } 129 | 130 | #[actix_web::main] 131 | async fn main() -> std::io::Result<()> { 132 | 133 | Ssr::create_platform(); 134 | 135 | HttpServer::new(|| { 136 | App::new() 137 | .service(index) 138 | }) 139 | .bind("127.0.0.1:8080")? 140 | .run() 141 | .await 142 | } 143 | 144 | #[get("/")] 145 | async fn index() -> HttpResponse { 146 | let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None).unwrap()); 147 | 148 | HttpResponse::build(StatusCode::OK) 149 | .content_type("text/html; charset=utf-8") 150 | .body(result) 151 | } 152 | ``` 153 | 154 | ## Contributing 155 | 156 | Any helps or suggestions will be appreciated. 157 | 158 | Known TODOs: 159 | - Add examples with other rust backend frameworks 160 | - Add examples with other frontend frameworks (i.e. vue, quik, solid) 161 | - Add benchmark setup to test against Deno and Bun 162 | - Explore support for V8 snapshots 163 | - Explore js copilation to WASM (i.e. [javy](https://github.com/bytecodealliance/javy)) 164 | 165 | ## License 166 | 167 | This project is licensed under the MIT License - see the LICENSE_MIT || LICENSE_APACHE file for more information. 168 | 169 |
170 | 171 |

172 | 173 |

174 | -------------------------------------------------------------------------------- /benches/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | > Benchmarks have been performed using [wrk](https://github.com/wg/wrk) 4 | 5 | > Benches refers to ssr_rs v0.3.0 6 | 7 | The main crate goal is to be blazingly fast (as the rest of the rust ecosystem). 8 | Following the outcome of the same application built and run by `actix-rs + ssr_rs` and by `node` with a fastify server. 9 | 10 | The source code is in the examples/webpack-react folder. 11 | 12 | ## Actix-rs + ssr_rs 13 | 14 | ```bash 15 | $ cd examples/webpack-react 16 | $ pnpm i && pnpm build:ssr 17 | $ cargo run --example webpack 18 | ``` 19 | 20 | ```bash 21 | ❯ wrk -t12 -c400 -d30s http://localhost:8080 22 | Running 30s test @ http://localhost:8080 23 | 12 threads and 400 connections 24 | Thread Stats Avg Stdev Max +/- Stdev 25 | Latency 2.26ms 5.11ms 277.87ms 94.32% 26 | Req/Sec 22.27k 3.02k 31.52k 85.39% 27 | 8011933 requests in 30.10s, 5.01GB read 28 | Requests/sec: 266177.68 29 | Transfer/sec: 170.33MB 30 | ``` 31 | 32 | ## Node + fastify 33 | 34 | ```bash 35 | $ cd examples/webpack-react 36 | $ pnpm i && pnpm build:server 37 | $ node dist/server/bundle.cjs 38 | ``` 39 | 40 | ```bash 41 | ❯ wrk -t12 -c400 -d30s http://localhost:3000 42 | Running 30s test @ http://localhost:3000 43 | 12 threads and 400 connections 44 | Thread Stats Avg Stdev Max +/- Stdev 45 | Latency 24.04ms 105.19ms 1.99s 98.28% 46 | Req/Sec 2.72k 519.60 11.72k 95.72% 47 | 954264 requests in 30.06s, 662.52MB read 48 | Socket errors: connect 0, read 0, write 0, timeout 48 49 | Requests/sec: 31740.30 50 | Transfer/sec: 22.04MB 51 | ``` 52 | 53 | My computer setup: 54 | 55 | CPU: Intel Core i5 13600KF 56 | 57 | Memory: DDR5 32Gb 3000MHz CL36 Intel XMP 58 | 59 | Benches ran on a WLS machine with Ubuntu installed 60 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # SSR-RS examples 2 | 3 | This folder includes examples to ease the usage of the library. 4 | 5 | The most complete react example is `webpack-react` since it includes the 6 | server side props handling, the react hydration and the assets generation. 7 | 8 | `vite-react` and `rspack-react` just showcase a base bundler configuration. For the above 9 | features the porting is pretty straightforward. 10 | 11 | The other example at the folder root show a simple configuration with the most common rust backend frameworks. 12 | 13 | > Any example improvement or suggestion will be appreciated. 14 | 15 | -------------------------------------------------------------------------------- /examples/actix.rs: -------------------------------------------------------------------------------- 1 | use actix_files as fs; 2 | use actix_web::{get, http::StatusCode, App, HttpResponse, HttpServer}; 3 | use std::cell::RefCell; 4 | use std::env; 5 | use std::fs::read_to_string; 6 | use std::path::Path; 7 | 8 | use ssr_rs::Ssr; 9 | use std::time::Instant; 10 | 11 | thread_local! { 12 | static SSR: RefCell> = RefCell::new( 13 | Ssr::from( 14 | read_to_string(Path::new("./dist/ssr/server-build.js").to_str().unwrap()).unwrap(), 15 | "Index" 16 | ).unwrap() 17 | ) 18 | } 19 | 20 | #[actix_web::main] 21 | async fn main() -> std::io::Result<()> { 22 | println!("{:?}", env::current_dir()?); 23 | 24 | Ssr::create_platform(); 25 | 26 | HttpServer::new(|| { 27 | App::new() 28 | .service(fs::Files::new("/styles", "client/dist/ssr/styles/").show_files_listing()) 29 | .service(fs::Files::new("/images", "client/dist/ssr/images/").show_files_listing()) 30 | .service(fs::Files::new("/scripts", "client/dist/client/").show_files_listing()) 31 | .service(index) 32 | }) 33 | .bind("127.0.0.1:8080")? 34 | .run() 35 | .await 36 | } 37 | 38 | #[get("/")] 39 | async fn index() -> HttpResponse { 40 | let start = Instant::now(); 41 | let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None).unwrap()); 42 | println!("Elapsed: {:?}", start.elapsed()); 43 | 44 | HttpResponse::build(StatusCode::OK) 45 | .content_type("text/html; charset=utf-8") 46 | .body(result) 47 | } 48 | -------------------------------------------------------------------------------- /examples/actix_with_initial_props.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{get, http::StatusCode, App, HttpResponse, HttpServer}; 2 | use std::cell::RefCell; 3 | use std::fs::read_to_string; 4 | 5 | use actix_files as fs; 6 | 7 | use ssr_rs::Ssr; 8 | 9 | thread_local! { 10 | static SSR: RefCell> = RefCell::new( 11 | Ssr::from( 12 | read_to_string("./client/dist/ssr/index.js").unwrap(), 13 | "SSR" 14 | ).unwrap() 15 | ) 16 | } 17 | 18 | #[actix_web::main] 19 | async fn main() -> std::io::Result<()> { 20 | Ssr::create_platform(); 21 | HttpServer::new(|| { 22 | App::new() 23 | .service(fs::Files::new("/styles", "client/dist/ssr/styles/").show_files_listing()) 24 | .service(fs::Files::new("/images", "client/dist/ssr/images/").show_files_listing()) 25 | .service(fs::Files::new("/scripts", "client/dist/client/").show_files_listing()) 26 | .service(index) 27 | }) 28 | .bind("0.0.0.0:8080")? 29 | .run() 30 | .await 31 | } 32 | 33 | #[get("/")] 34 | async fn index() -> HttpResponse { 35 | let mock_props = r##"{ 36 | "params": [ 37 | "hello", 38 | "ciao", 39 | "こんにちは" 40 | ] 41 | }"##; 42 | 43 | HttpResponse::build(StatusCode::OK) 44 | .content_type("text/html; charset=utf-8") 45 | .body(SSR.with(|ssr| ssr.borrow_mut().render_to_string(Some(mock_props)).unwrap())) 46 | } 47 | -------------------------------------------------------------------------------- /examples/axum.rs: -------------------------------------------------------------------------------- 1 | use axum::{response::Html, routing::get, Router}; 2 | use ssr_rs::Ssr; 3 | use std::cell::RefCell; 4 | use std::fs::read_to_string; 5 | use std::time::Instant; 6 | 7 | thread_local! { 8 | static SSR: RefCell> = RefCell::new( 9 | Ssr::from( 10 | read_to_string("./client/dist/ssr/index.js").unwrap(), 11 | "SSR" 12 | ).unwrap() 13 | ) 14 | } 15 | 16 | #[tokio::main] 17 | async fn main() { 18 | Ssr::create_platform(); 19 | 20 | // build our application with a single route 21 | let app = Router::new().route("/", get(root)); 22 | 23 | // run our app with hyper, listening globally on port 3000 24 | let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); 25 | axum::serve(listener, app).await.unwrap(); 26 | } 27 | 28 | async fn root() -> Html { 29 | let start = Instant::now(); 30 | let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None)); 31 | println!("Elapsed: {:?}", start.elapsed()); 32 | Html(result.unwrap()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/multi-thread.rs: -------------------------------------------------------------------------------- 1 | use ssr_rs::Ssr; 2 | use std::cell::RefCell; 3 | use std::fs::read_to_string; 4 | use std::thread; 5 | use std::time::Instant; 6 | 7 | thread_local! { 8 | static SSR: RefCell> = RefCell::new( 9 | Ssr::from( 10 | read_to_string("./client/dist/ssr/index.js").unwrap(), 11 | "SSR" 12 | ).unwrap() 13 | ) 14 | } 15 | 16 | fn main() { 17 | Ssr::create_platform(); 18 | 19 | let threads: Vec<_> = (0..2) 20 | .map(|i| { 21 | thread::spawn(move || { 22 | println!("Thread #{i} started!"); 23 | let start = Instant::now(); 24 | println!( 25 | "result: {}", 26 | SSR.with(|ssr| ssr.borrow_mut().render_to_string(None).unwrap()) 27 | ); 28 | println!( 29 | "Thread #{i} finished! - Elapsed time: {:?}", 30 | start.elapsed() 31 | ); 32 | }) 33 | }) 34 | .collect(); 35 | 36 | for handle in threads { 37 | handle.join().unwrap(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/rocket.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate rocket; 3 | use rocket::fs::FileServer; 4 | use rocket::response::content; 5 | use ssr_rs::Ssr; 6 | use std::cell::RefCell; 7 | use std::fs::read_to_string; 8 | use std::time::Instant; 9 | 10 | thread_local! { 11 | static SSR: RefCell> = RefCell::new( 12 | Ssr::from( 13 | read_to_string("./client/dist/ssr/index.js").unwrap(), 14 | "SSR" 15 | ).unwrap() 16 | ) 17 | } 18 | 19 | #[get("/")] 20 | fn index() -> content::RawHtml { 21 | let start = Instant::now(); 22 | let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None)); 23 | println!("Elapsed: {:?}", start.elapsed()); 24 | content::RawHtml(result.unwrap()) 25 | } 26 | 27 | #[launch] 28 | fn rocket() -> _ { 29 | Ssr::create_platform(); 30 | 31 | rocket::build() 32 | .mount("/styles", FileServer::from("./client/dist/ssr/styles")) 33 | .mount("/scripts", FileServer::from("./client/dist/client/")) 34 | .mount("/images", FileServer::from("./client/dist/ssr/images/")) 35 | .mount("/", routes![index]) 36 | } 37 | -------------------------------------------------------------------------------- /examples/rspack-react/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/rspack-react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Rspack + React + TS 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/rspack-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rspack-react-ts-starter", 3 | "private": true, 4 | "version": "1.0.0", 5 | "scripts": { 6 | "dev": "NODE_ENV=development rspack serve", 7 | "build": "pnpm build:client && pnpm build:ssr", 8 | "build:client": "NODE_ENV=production rspack build", 9 | "build:ssr": "rspack build -c ./ssr.config.js" 10 | }, 11 | "dependencies": { 12 | "fast-text-encoding": "^1.0.6", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0" 15 | }, 16 | "devDependencies": { 17 | "@rspack/cli": "0.5.9", 18 | "@rspack/core": "0.5.9", 19 | "@rspack/plugin-react-refresh": "0.5.9", 20 | "@types/react": "^18.2.48", 21 | "@types/react-dom": "^18.2.18", 22 | "file-loader": "^6.2.0", 23 | "react-refresh": "^0.14.0", 24 | "ts-loader": "^9.5.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/rspack-react/rspack.config.js: -------------------------------------------------------------------------------- 1 | const rspack = require("@rspack/core"); 2 | const refreshPlugin = require("@rspack/plugin-react-refresh"); 3 | const isDev = process.env.NODE_ENV === "development"; 4 | const path = require("path"); 5 | /** 6 | * @type {import('@rspack/cli').Configuration} 7 | */ 8 | module.exports = { 9 | context: __dirname, 10 | entry: { 11 | main: "./src/main.tsx", 12 | }, 13 | output: { 14 | clean: true, 15 | path: path.resolve(__dirname, "./dist/client"), 16 | filename: "index.js", 17 | }, 18 | resolve: { 19 | extensions: ["...", ".ts", ".tsx", ".jsx"], 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.svg$/, 25 | type: "asset", 26 | }, 27 | { 28 | test: /\.(jsx?|tsx?)$/, 29 | use: [ 30 | { 31 | loader: "builtin:swc-loader", 32 | options: { 33 | sourceMap: true, 34 | jsc: { 35 | parser: { 36 | syntax: "typescript", 37 | tsx: true, 38 | }, 39 | transform: { 40 | react: { 41 | runtime: "automatic", 42 | development: isDev, 43 | refresh: isDev, 44 | }, 45 | }, 46 | }, 47 | env: { 48 | targets: [ 49 | "chrome >= 87", 50 | "edge >= 88", 51 | "firefox >= 78", 52 | "safari >= 14", 53 | ], 54 | }, 55 | }, 56 | }, 57 | ], 58 | }, 59 | ], 60 | }, 61 | plugins: [ 62 | new rspack.DefinePlugin({ 63 | "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), 64 | }), 65 | new rspack.ProgressPlugin({}), 66 | new rspack.HtmlRspackPlugin({ 67 | template: "./index.html", 68 | }), 69 | isDev ? new refreshPlugin() : null, 70 | ].filter(Boolean), 71 | }; 72 | -------------------------------------------------------------------------------- /examples/rspack-react/server.rs: -------------------------------------------------------------------------------- 1 | use actix_files as fs; 2 | use actix_web::{get, http::StatusCode, App, HttpResponse, HttpServer}; 3 | use std::cell::RefCell; 4 | use std::fs::read_to_string; 5 | use std::path::Path; 6 | 7 | use ssr_rs::Ssr; 8 | 9 | thread_local! { 10 | static SSR: RefCell> = RefCell::new( 11 | Ssr::from( 12 | read_to_string(Path::new("./dist/ssr/index.js").to_str().unwrap()).unwrap(), 13 | "SSR" 14 | ).unwrap() 15 | ) 16 | } 17 | 18 | #[actix_web::main] 19 | async fn main() -> std::io::Result<()> { 20 | Ssr::create_platform(); 21 | 22 | HttpServer::new(|| { 23 | App::new() 24 | .service(fs::Files::new("/assets", "dist/client/").show_files_listing()) 25 | .service(index) 26 | }) 27 | .bind("127.0.0.1:8080")? 28 | .run() 29 | .await 30 | } 31 | 32 | #[get("/")] 33 | async fn index() -> HttpResponse { 34 | let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None).unwrap()); 35 | 36 | HttpResponse::build(StatusCode::OK) 37 | .content_type("text/html; charset=utf-8") 38 | .body(result) 39 | } 40 | -------------------------------------------------------------------------------- /examples/rspack-react/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | } 13 | .logo:hover { 14 | filter: drop-shadow(0 0 2em #646cffaa); 15 | } 16 | .logo.react:hover { 17 | filter: drop-shadow(0 0 2em #61dafbaa); 18 | } 19 | 20 | @keyframes logo-spin { 21 | from { 22 | transform: rotate(0deg); 23 | } 24 | to { 25 | transform: rotate(360deg); 26 | } 27 | } 28 | 29 | @media (prefers-reduced-motion: no-preference) { 30 | a:nth-of-type(2) .logo { 31 | animation: logo-spin infinite 20s linear; 32 | } 33 | } 34 | 35 | .card { 36 | padding: 2em; 37 | } 38 | 39 | .read-the-docs { 40 | color: #888; 41 | } 42 | -------------------------------------------------------------------------------- /examples/rspack-react/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useState } from "react"; 3 | import reactLogo from "./assets/react.svg"; 4 | import "./App.css"; 5 | 6 | function App() { 7 | const [count, setCount] = useState(0); 8 | 9 | return ( 10 |
11 |
12 | 13 | React logo 14 | 15 |
16 |

Rspack + React + TypeScript

17 |
18 | 21 |

22 | Edit src/App.tsx and save to test HMR 23 |

24 |
25 |

26 | Click on the Rspack and React logos to learn more 27 |

28 |
29 | ); 30 | } 31 | 32 | export default App; 33 | -------------------------------------------------------------------------------- /examples/rspack-react/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/rspack-react/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | button { 41 | border-radius: 8px; 42 | border: 1px solid transparent; 43 | padding: 0.6em 1.2em; 44 | font-size: 1em; 45 | font-weight: 500; 46 | font-family: inherit; 47 | background-color: #1a1a1a; 48 | cursor: pointer; 49 | transition: border-color 0.25s; 50 | } 51 | button:hover { 52 | border-color: #646cff; 53 | } 54 | button:focus, 55 | button:focus-visible { 56 | outline: 4px auto -webkit-focus-ring-color; 57 | } 58 | 59 | @media (prefers-color-scheme: light) { 60 | :root { 61 | color: #213547; 62 | background-color: #ffffff; 63 | } 64 | a:hover { 65 | color: #747bff; 66 | } 67 | button { 68 | background-color: #f9f9f9; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/rspack-react/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { hydrate } from "react-dom"; 3 | import App from "./App.tsx"; 4 | import "./index.css"; 5 | 6 | hydrate(, document.getElementById("root") as HTMLElement); 7 | -------------------------------------------------------------------------------- /examples/rspack-react/src/react-env.d.ts: -------------------------------------------------------------------------------- 1 | // CSS modules 2 | type CSSModuleClasses = { readonly [key: string]: string }; 3 | 4 | declare module "*.module.css" { 5 | const classes: CSSModuleClasses; 6 | export default classes; 7 | } 8 | declare module "*.module.scss" { 9 | const classes: CSSModuleClasses; 10 | export default classes; 11 | } 12 | declare module "*.module.sass" { 13 | const classes: CSSModuleClasses; 14 | export default classes; 15 | } 16 | declare module "*.module.less" { 17 | const classes: CSSModuleClasses; 18 | export default classes; 19 | } 20 | declare module "*.module.styl" { 21 | const classes: CSSModuleClasses; 22 | export default classes; 23 | } 24 | declare module "*.module.stylus" { 25 | const classes: CSSModuleClasses; 26 | export default classes; 27 | } 28 | declare module "*.module.pcss" { 29 | const classes: CSSModuleClasses; 30 | export default classes; 31 | } 32 | declare module "*.module.sss" { 33 | const classes: CSSModuleClasses; 34 | export default classes; 35 | } 36 | 37 | // CSS 38 | declare module "*.css" { 39 | /** 40 | * @deprecated Use `import style from './style.css?inline'` instead. 41 | */ 42 | const css: string; 43 | export default css; 44 | } 45 | declare module "*.scss" { 46 | /** 47 | * @deprecated Use `import style from './style.scss?inline'` instead. 48 | */ 49 | const css: string; 50 | export default css; 51 | } 52 | declare module "*.sass" { 53 | /** 54 | * @deprecated Use `import style from './style.sass?inline'` instead. 55 | */ 56 | const css: string; 57 | export default css; 58 | } 59 | declare module "*.less" { 60 | /** 61 | * @deprecated Use `import style from './style.less?inline'` instead. 62 | */ 63 | const css: string; 64 | export default css; 65 | } 66 | declare module "*.styl" { 67 | /** 68 | * @deprecated Use `import style from './style.styl?inline'` instead. 69 | */ 70 | const css: string; 71 | export default css; 72 | } 73 | declare module "*.stylus" { 74 | /** 75 | * @deprecated Use `import style from './style.stylus?inline'` instead. 76 | */ 77 | const css: string; 78 | export default css; 79 | } 80 | declare module "*.pcss" { 81 | /** 82 | * @deprecated Use `import style from './style.pcss?inline'` instead. 83 | */ 84 | const css: string; 85 | export default css; 86 | } 87 | declare module "*.sss" { 88 | /** 89 | * @deprecated Use `import style from './style.sss?inline'` instead. 90 | */ 91 | const css: string; 92 | export default css; 93 | } 94 | 95 | // images 96 | declare module "*.png" { 97 | const src: string; 98 | export default src; 99 | } 100 | declare module "*.jpg" { 101 | const src: string; 102 | export default src; 103 | } 104 | declare module "*.jpeg" { 105 | const src: string; 106 | export default src; 107 | } 108 | declare module "*.jfif" { 109 | const src: string; 110 | export default src; 111 | } 112 | declare module "*.pjpeg" { 113 | const src: string; 114 | export default src; 115 | } 116 | declare module "*.pjp" { 117 | const src: string; 118 | export default src; 119 | } 120 | declare module "*.gif" { 121 | const src: string; 122 | export default src; 123 | } 124 | declare module "*.svg" { 125 | const ReactComponent: React.FC>; 126 | const content: string; 127 | 128 | export { ReactComponent }; 129 | export default content; 130 | } 131 | declare module "*.ico" { 132 | const src: string; 133 | export default src; 134 | } 135 | declare module "*.webp" { 136 | const src: string; 137 | export default src; 138 | } 139 | declare module "*.avif" { 140 | const src: string; 141 | export default src; 142 | } 143 | 144 | // media 145 | declare module "*.mp4" { 146 | const src: string; 147 | export default src; 148 | } 149 | declare module "*.webm" { 150 | const src: string; 151 | export default src; 152 | } 153 | declare module "*.ogg" { 154 | const src: string; 155 | export default src; 156 | } 157 | declare module "*.mp3" { 158 | const src: string; 159 | export default src; 160 | } 161 | declare module "*.wav" { 162 | const src: string; 163 | export default src; 164 | } 165 | declare module "*.flac" { 166 | const src: string; 167 | export default src; 168 | } 169 | declare module "*.aac" { 170 | const src: string; 171 | export default src; 172 | } 173 | 174 | declare module "*.opus" { 175 | const src: string; 176 | export default src; 177 | } 178 | 179 | // fonts 180 | declare module "*.woff" { 181 | const src: string; 182 | export default src; 183 | } 184 | declare module "*.woff2" { 185 | const src: string; 186 | export default src; 187 | } 188 | declare module "*.eot" { 189 | const src: string; 190 | export default src; 191 | } 192 | declare module "*.ttf" { 193 | const src: string; 194 | export default src; 195 | } 196 | declare module "*.otf" { 197 | const src: string; 198 | export default src; 199 | } 200 | 201 | // other 202 | declare module "*.webmanifest" { 203 | const src: string; 204 | export default src; 205 | } 206 | declare module "*.pdf" { 207 | const src: string; 208 | export default src; 209 | } 210 | declare module "*.txt" { 211 | const src: string; 212 | export default src; 213 | } 214 | -------------------------------------------------------------------------------- /examples/rspack-react/src/ssr-entry.tsx: -------------------------------------------------------------------------------- 1 | import "fast-text-encoding"; // Mandatory for React18 2 | import React from "react"; 3 | import { renderToString } from "react-dom/server"; 4 | import App from "./App"; 5 | 6 | export const Index = (params: string | undefined) => { 7 | const props = params ? JSON.parse(params) : {}; 8 | const app = renderToString(); 9 | 10 | return ` 11 | 12 | 13 | React SSR 14 | 15 | 16 | 17 | 18 |
${app}
19 | 20 | `; 21 | }; 22 | -------------------------------------------------------------------------------- /examples/rspack-react/ssr.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | /** 3 | * @type {import('@rspack/cli').Configuration} 4 | */ 5 | module.exports = { 6 | mode: "production", 7 | entry: path.resolve(__dirname, "./src/ssr-entry.tsx"), 8 | target: "webworker", 9 | output: { 10 | clean: true, 11 | chunkFormat: false, 12 | chunkLoading: false, 13 | path: path.resolve(__dirname, "./dist/ssr"), 14 | publicPath: "", 15 | globalObject: "this", 16 | filename: "index.js", 17 | iife: false, 18 | library: { 19 | type: "var", 20 | // Entry point 21 | name: "SSR", 22 | }, 23 | }, 24 | optimization: { 25 | minimize: false, 26 | splitChunks: false, 27 | }, 28 | resolve: { 29 | fallback: { fs: false, path: false }, 30 | extensions: [".ts", ".tsx", ".jsx"], 31 | }, 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.svg$/, 36 | type: "asset", 37 | }, 38 | { 39 | test: /\.(jsx?|tsx?)$/, 40 | use: [ 41 | { 42 | loader: "builtin:swc-loader", 43 | options: { 44 | jsc: { 45 | parser: { 46 | syntax: "typescript", 47 | tsx: true, 48 | }, 49 | }, 50 | }, 51 | }, 52 | ], 53 | }, 54 | { 55 | test: /\.(png|jp(e*)g|svg|gif)$/, 56 | use: [ 57 | { 58 | loader: "file-loader", 59 | options: { 60 | name: "./images/[hash]-[name].[ext]", 61 | }, 62 | }, 63 | ], 64 | }, 65 | ], 66 | }, 67 | plugins: [], 68 | }; 69 | -------------------------------------------------------------------------------- /examples/rspack-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowImportingTsExtensions": true, 8 | "resolveJsonModule": true, 9 | "isolatedModules": true, 10 | "noEmit": true, 11 | "jsx": "react-jsx", 12 | "strict": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noFallthroughCasesInSwitch": true 16 | }, 17 | "include": ["src"] 18 | } 19 | -------------------------------------------------------------------------------- /examples/run.rs: -------------------------------------------------------------------------------- 1 | //This example exist just for develop purposes 2 | use ssr_rs::Ssr; 3 | use std::fs::read_to_string; 4 | 5 | fn main() { 6 | let source = read_to_string("./client/dist/ssr/index.js").unwrap(); 7 | 8 | Ssr::create_platform(); 9 | 10 | // This takes roughly 40ms 11 | let mut ssr = Ssr::from(source, "SSR").unwrap(); 12 | 13 | // This takes roughly 0.5ms 14 | println!("{}", ssr.render_to_string(None).unwrap()); 15 | println!("{}", ssr.render_to_string(None).unwrap()); 16 | } 17 | -------------------------------------------------------------------------------- /examples/tide.rs: -------------------------------------------------------------------------------- 1 | use ssr_rs::Ssr; 2 | use std::cell::RefCell; 3 | use std::fs::read_to_string; 4 | use std::time::Instant; 5 | use tide::{Request, Response}; 6 | 7 | thread_local! { 8 | static SSR: RefCell> = RefCell::new( 9 | Ssr::from( 10 | read_to_string("./client/dist/ssr/index.js").unwrap(), 11 | "SSR" 12 | ).unwrap() 13 | ) 14 | } 15 | 16 | #[async_std::main] 17 | async fn main() -> tide::Result<()> { 18 | Ssr::create_platform(); 19 | let mut app = tide::new(); 20 | app.at("/styles/*").serve_dir("client/dist/ssr/styles/")?; 21 | app.at("/images/*").serve_dir("client/dist/ssr/images/")?; 22 | app.at("/scripts/*").serve_dir("client/dist/client/")?; 23 | app.at("/").get(return_html); 24 | app.listen("127.0.0.1:8080").await?; 25 | Ok(()) 26 | } 27 | 28 | async fn return_html(_req: Request<()>) -> tide::Result { 29 | let start = Instant::now(); 30 | let html = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None)); 31 | println!("Elapsed: {:?}", start.elapsed()); 32 | 33 | let response = Response::builder(200) 34 | .body(html.unwrap()) 35 | .content_type(tide::http::mime::HTML) 36 | .build(); 37 | 38 | Ok(response) 39 | } 40 | -------------------------------------------------------------------------------- /examples/vite-react/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /examples/vite-react/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vite-react/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /examples/vite-react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vite-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-react", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "build:rust-ssr": "vite build --ssr src/server-entry.tsx", 10 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 11 | "preview": "vite preview" 12 | }, 13 | "dependencies": { 14 | "fast-text-encoding": "^1.0.6", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0" 17 | }, 18 | "devDependencies": { 19 | "@rollup/plugin-node-resolve": "^15.2.3", 20 | "@types/react": "^18.2.56", 21 | "@types/react-dom": "^18.2.19", 22 | "@typescript-eslint/eslint-plugin": "^7.0.2", 23 | "@typescript-eslint/parser": "^7.0.2", 24 | "@vitejs/plugin-react-swc": "^3.5.0", 25 | "eslint": "^8.56.0", 26 | "eslint-plugin-react-hooks": "^4.6.0", 27 | "eslint-plugin-react-refresh": "^0.4.5", 28 | "typescript": "^5.2.2", 29 | "vite": "^5.1.4" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/vite-react/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vite-react/server.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{get, http::StatusCode, App, HttpResponse, HttpServer}; 2 | use std::cell::RefCell; 3 | use std::fs::read_to_string; 4 | use std::path::Path; 5 | 6 | use ssr_rs::Ssr; 7 | 8 | thread_local! { 9 | static SSR: RefCell> = RefCell::new( 10 | Ssr::from( 11 | read_to_string(Path::new("./dist/server-entry.js").to_str().unwrap()).unwrap(), 12 | "" 13 | ).unwrap() 14 | ) 15 | } 16 | 17 | #[actix_web::main] 18 | async fn main() -> std::io::Result<()> { 19 | Ssr::create_platform(); 20 | 21 | HttpServer::new(|| App::new().service(index)) 22 | .bind("127.0.0.1:8080")? 23 | .run() 24 | .await 25 | } 26 | 27 | #[get("/")] 28 | async fn index() -> HttpResponse { 29 | let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None).unwrap()); 30 | 31 | HttpResponse::build(StatusCode::OK) 32 | .content_type("text/html; charset=utf-8") 33 | .body(result) 34 | } 35 | -------------------------------------------------------------------------------- /examples/vite-react/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /examples/vite-react/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import reactLogo from './assets/react.svg' 3 | import viteLogo from '/vite.svg' 4 | import './App.css' 5 | 6 | function App() { 7 | const [count, setCount] = useState(0) 8 | 9 | return ( 10 | <> 11 | 19 |

Vite + React

20 |
21 | 24 |

25 | Edit src/App.tsx and save to test HMR 26 |

27 |
28 |

29 | Click on the Vite and React logos to learn more 30 |

31 | 32 | ) 33 | } 34 | 35 | export default App 36 | -------------------------------------------------------------------------------- /examples/vite-react/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vite-react/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/vite-react/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /examples/vite-react/src/server-entry.tsx: -------------------------------------------------------------------------------- 1 | import "fast-text-encoding"; // Mandatory for React18 2 | import { renderToString } from "react-dom/server"; 3 | import App from "./App"; 4 | 5 | export const Index = () => { 6 | return renderToString(); 7 | }; 8 | -------------------------------------------------------------------------------- /examples/vite-react/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/vite-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /examples/vite-react/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /examples/vite-react/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react-swc"; 3 | import { nodeResolve } from "@rollup/plugin-node-resolve"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | build: { 8 | rollupOptions: { 9 | //input: "./src/server-entry.tsx", 10 | output: { 11 | format: "iife", 12 | dir: "dist/", 13 | }, 14 | }, 15 | }, 16 | ssr: { 17 | target: "webworker", 18 | noExternal: true, 19 | }, 20 | plugins: [react()], 21 | }); 22 | -------------------------------------------------------------------------------- /examples/vite-svelte/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | target/ 11 | node_modules/ 12 | dist 13 | dist-ssr 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /examples/vite-svelte/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "svelte-salvo-ssr" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "svelte-salvo-ssr" 8 | path = "backend/main.rs" 9 | 10 | [dependencies] 11 | salvo = { version = "0.68.3", features = ["serve-static"] } 12 | serde_json = "1.0.118" 13 | ssr_rs = "^0.8.1" 14 | tokio = { version = "1", features = ["macros"] } 15 | tracing = "0.1" 16 | tracing-subscriber = "0.3" -------------------------------------------------------------------------------- /examples/vite-svelte/README.md: -------------------------------------------------------------------------------- 1 | # Svelte-Salvo-SSR-template 2 | It is a template for Svelte SSR with Salvo-rs and Vite. 3 | 4 | 1. Install npm dependencies: 5 | ```sh 6 | pnpm install 7 | ``` 8 | 9 | 2. Use vite to build svelte client JS and CSS: 10 | ```sh 11 | pnpx vite build --config vite.client.config.js 12 | ``` 13 | 14 | 3. Use vite to build svelte SSR JS: 15 | ```sh 16 | pnpx vite build --config vite.ssr.config.js 17 | ``` 18 | 4. Run Rust server: 19 | ```sh 20 | cargo run 21 | ``` 22 | -------------------------------------------------------------------------------- /examples/vite-svelte/backend/main.rs: -------------------------------------------------------------------------------- 1 | use salvo::prelude::*; 2 | use ssr_rs::Ssr; 3 | use std::cell::RefCell; 4 | use std::fs::read_to_string; 5 | use std::path::Path; 6 | 7 | thread_local! { 8 | static SSR: RefCell> = RefCell::new( 9 | Ssr::from( 10 | read_to_string(Path::new("./dist/server/server.js").to_str().unwrap()).unwrap(), 11 | "" 12 | ).unwrap_or_else(|err| { 13 | eprintln!("Failed to initialize SSR: {}", err); 14 | std::process::exit(1); 15 | }) 16 | ) 17 | } 18 | 19 | #[handler] 20 | async fn index(res: &mut Response) { 21 | let result = SSR.with(|ssr| { 22 | let mut ssr = ssr.borrow_mut(); 23 | ssr.render_to_string(None).unwrap_or_else(|err| { 24 | eprintln!("Error rendering to string: {}", err); 25 | String::new() 26 | }) 27 | }); 28 | 29 | if result.is_empty() { 30 | eprintln!("Rendered result is empty"); 31 | res.status_code(StatusCode::INTERNAL_SERVER_ERROR); 32 | res.render(Text::Plain("Internal Server Error")); 33 | return; 34 | } 35 | 36 | //println!("Rendered result: {}", result); // For debugging 37 | 38 | let result: serde_json::Value = match serde_json::from_str(&result) { 39 | Ok(val) => val, 40 | Err(err) => { 41 | eprintln!("Failed to parse JSON: {}", err); 42 | res.status_code(StatusCode::INTERNAL_SERVER_ERROR); 43 | res.render(Text::Plain("Internal Server Error")); 44 | return; 45 | } 46 | }; 47 | 48 | let head = result["head"].as_str().unwrap_or(""); 49 | let body = result["body"].as_str().unwrap_or(""); 50 | 51 | let full_html = format!( 52 | r#" 53 | 54 | 55 | 56 | {} 57 | 58 | 59 |
{}
60 | 61 | 62 | "#, 63 | head, body 64 | ); 65 | res.render(Text::Html(full_html)); 66 | } 67 | 68 | #[tokio::main] 69 | async fn main() { 70 | Ssr::create_platform(); 71 | let router = Router::new() 72 | .push(Router::with_path("/client/<**path>").get(StaticDir::new(["./dist/client"]))) 73 | .push( 74 | Router::with_path("/client/assets/<**path>") 75 | .get(StaticDir::new(["./dist/assets/client"])), 76 | ) 77 | .push(Router::with_path("/").get(index)); 78 | 79 | let acceptor = TcpListener::new("127.0.0.1:8080").bind().await; 80 | 81 | tracing_subscriber::fmt().init(); 82 | tracing::info!("Listening on http://{:?}", acceptor.local_addr()); 83 | 84 | Server::new(acceptor).serve(router).await; 85 | } 86 | -------------------------------------------------------------------------------- /examples/vite-svelte/frontend/App.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | Vite + Svelte + Rust SSR 8 |
9 | 17 |

Vite + Svelte

18 | 19 |
20 | 21 |
22 | 23 |

24 | Check out SvelteKit, the official Svelte app framework powered by Vite! 25 |

26 | 27 |

28 | Click on the Vite and Svelte logos to learn more 29 |

30 |
31 | 32 | 49 | -------------------------------------------------------------------------------- /examples/vite-svelte/frontend/app.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | .card { 39 | padding: 2em; 40 | } 41 | 42 | #app { 43 | max-width: 1280px; 44 | margin: 0 auto; 45 | padding: 2rem; 46 | text-align: center; 47 | } 48 | 49 | button { 50 | border-radius: 8px; 51 | border: 1px solid transparent; 52 | padding: 0.6em 1.2em; 53 | font-size: 1em; 54 | font-weight: 500; 55 | font-family: inherit; 56 | background-color: #1a1a1a; 57 | cursor: pointer; 58 | transition: border-color 0.25s; 59 | } 60 | button:hover { 61 | border-color: #646cff; 62 | } 63 | button:focus, 64 | button:focus-visible { 65 | outline: 4px auto -webkit-focus-ring-color; 66 | } 67 | 68 | @media (prefers-color-scheme: light) { 69 | :root { 70 | color: #213547; 71 | background-color: #ffffff; 72 | } 73 | a:hover { 74 | color: #747bff; 75 | } 76 | button { 77 | background-color: #f9f9f9; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /examples/vite-svelte/frontend/assets/svelte.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vite-svelte/frontend/lib/Counter.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /examples/vite-svelte/frontend/main.js: -------------------------------------------------------------------------------- 1 | import { hydrate } from "svelte"; 2 | 3 | import App from "./App.svelte"; 4 | import "./app.css"; 5 | 6 | hydrate(App, { 7 | target: document.querySelector("#svelte-app"), 8 | }); 9 | -------------------------------------------------------------------------------- /examples/vite-svelte/frontend/server.js: -------------------------------------------------------------------------------- 1 | import { render as renderer } from "svelte/server"; 2 | import App from "./App.svelte"; 3 | 4 | export function render() { 5 | const { head, body } = renderer(App); 6 | return JSON.stringify({ head, body }); 7 | } 8 | -------------------------------------------------------------------------------- /examples/vite-svelte/frontend/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /examples/vite-svelte/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Svelte 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vite-svelte/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "bundler", 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | /** 7 | * svelte-preprocess cannot figure out whether you have 8 | * a value or a type, so tell TypeScript to enforce using 9 | * `import type` instead of `import` for Types. 10 | */ 11 | "verbatimModuleSyntax": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | /** 15 | * To have warnings / errors of the Svelte compiler at the 16 | * correct position, enable source maps by default. 17 | */ 18 | "sourceMap": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | /** 22 | * Typecheck JS in `.svelte` and `.js` files by default. 23 | * Disable this if you'd like to use dynamic types. 24 | */ 25 | "checkJs": true 26 | }, 27 | /** 28 | * Use global.d.ts instead of compilerOptions.types 29 | * to avoid limiting type declarations. 30 | */ 31 | "include": ["frontend/**/*.d.ts", "frontend/**/*.js", "frontend/**/*.svelte"] 32 | } 33 | -------------------------------------------------------------------------------- /examples/vite-svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-svelte", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build --config vite.client.config.js && vite build --config vite.ssr.config.js", 9 | "preview": "cargo run" 10 | }, 11 | "devDependencies": { 12 | "@sveltejs/vite-plugin-svelte": "^5.0.3", 13 | "svelte": "^5.23.0", 14 | "vite": "^6.2.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/vite-svelte/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | '@sveltejs/vite-plugin-svelte': 12 | specifier: ^5.0.3 13 | version: 5.0.3(svelte@5.23.0)(vite@6.2.2) 14 | svelte: 15 | specifier: ^5.23.0 16 | version: 5.23.0 17 | vite: 18 | specifier: ^6.2.2 19 | version: 6.2.2 20 | 21 | packages: 22 | 23 | '@ampproject/remapping@2.3.0': 24 | resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 25 | engines: {node: '>=6.0.0'} 26 | 27 | '@esbuild/aix-ppc64@0.25.1': 28 | resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==} 29 | engines: {node: '>=18'} 30 | cpu: [ppc64] 31 | os: [aix] 32 | 33 | '@esbuild/android-arm64@0.25.1': 34 | resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==} 35 | engines: {node: '>=18'} 36 | cpu: [arm64] 37 | os: [android] 38 | 39 | '@esbuild/android-arm@0.25.1': 40 | resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==} 41 | engines: {node: '>=18'} 42 | cpu: [arm] 43 | os: [android] 44 | 45 | '@esbuild/android-x64@0.25.1': 46 | resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==} 47 | engines: {node: '>=18'} 48 | cpu: [x64] 49 | os: [android] 50 | 51 | '@esbuild/darwin-arm64@0.25.1': 52 | resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==} 53 | engines: {node: '>=18'} 54 | cpu: [arm64] 55 | os: [darwin] 56 | 57 | '@esbuild/darwin-x64@0.25.1': 58 | resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==} 59 | engines: {node: '>=18'} 60 | cpu: [x64] 61 | os: [darwin] 62 | 63 | '@esbuild/freebsd-arm64@0.25.1': 64 | resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==} 65 | engines: {node: '>=18'} 66 | cpu: [arm64] 67 | os: [freebsd] 68 | 69 | '@esbuild/freebsd-x64@0.25.1': 70 | resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==} 71 | engines: {node: '>=18'} 72 | cpu: [x64] 73 | os: [freebsd] 74 | 75 | '@esbuild/linux-arm64@0.25.1': 76 | resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==} 77 | engines: {node: '>=18'} 78 | cpu: [arm64] 79 | os: [linux] 80 | 81 | '@esbuild/linux-arm@0.25.1': 82 | resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==} 83 | engines: {node: '>=18'} 84 | cpu: [arm] 85 | os: [linux] 86 | 87 | '@esbuild/linux-ia32@0.25.1': 88 | resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==} 89 | engines: {node: '>=18'} 90 | cpu: [ia32] 91 | os: [linux] 92 | 93 | '@esbuild/linux-loong64@0.25.1': 94 | resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==} 95 | engines: {node: '>=18'} 96 | cpu: [loong64] 97 | os: [linux] 98 | 99 | '@esbuild/linux-mips64el@0.25.1': 100 | resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==} 101 | engines: {node: '>=18'} 102 | cpu: [mips64el] 103 | os: [linux] 104 | 105 | '@esbuild/linux-ppc64@0.25.1': 106 | resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==} 107 | engines: {node: '>=18'} 108 | cpu: [ppc64] 109 | os: [linux] 110 | 111 | '@esbuild/linux-riscv64@0.25.1': 112 | resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==} 113 | engines: {node: '>=18'} 114 | cpu: [riscv64] 115 | os: [linux] 116 | 117 | '@esbuild/linux-s390x@0.25.1': 118 | resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==} 119 | engines: {node: '>=18'} 120 | cpu: [s390x] 121 | os: [linux] 122 | 123 | '@esbuild/linux-x64@0.25.1': 124 | resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==} 125 | engines: {node: '>=18'} 126 | cpu: [x64] 127 | os: [linux] 128 | 129 | '@esbuild/netbsd-arm64@0.25.1': 130 | resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==} 131 | engines: {node: '>=18'} 132 | cpu: [arm64] 133 | os: [netbsd] 134 | 135 | '@esbuild/netbsd-x64@0.25.1': 136 | resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==} 137 | engines: {node: '>=18'} 138 | cpu: [x64] 139 | os: [netbsd] 140 | 141 | '@esbuild/openbsd-arm64@0.25.1': 142 | resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==} 143 | engines: {node: '>=18'} 144 | cpu: [arm64] 145 | os: [openbsd] 146 | 147 | '@esbuild/openbsd-x64@0.25.1': 148 | resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==} 149 | engines: {node: '>=18'} 150 | cpu: [x64] 151 | os: [openbsd] 152 | 153 | '@esbuild/sunos-x64@0.25.1': 154 | resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==} 155 | engines: {node: '>=18'} 156 | cpu: [x64] 157 | os: [sunos] 158 | 159 | '@esbuild/win32-arm64@0.25.1': 160 | resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==} 161 | engines: {node: '>=18'} 162 | cpu: [arm64] 163 | os: [win32] 164 | 165 | '@esbuild/win32-ia32@0.25.1': 166 | resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==} 167 | engines: {node: '>=18'} 168 | cpu: [ia32] 169 | os: [win32] 170 | 171 | '@esbuild/win32-x64@0.25.1': 172 | resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==} 173 | engines: {node: '>=18'} 174 | cpu: [x64] 175 | os: [win32] 176 | 177 | '@jridgewell/gen-mapping@0.3.8': 178 | resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} 179 | engines: {node: '>=6.0.0'} 180 | 181 | '@jridgewell/resolve-uri@3.1.2': 182 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 183 | engines: {node: '>=6.0.0'} 184 | 185 | '@jridgewell/set-array@1.2.1': 186 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 187 | engines: {node: '>=6.0.0'} 188 | 189 | '@jridgewell/sourcemap-codec@1.5.0': 190 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 191 | 192 | '@jridgewell/trace-mapping@0.3.25': 193 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 194 | 195 | '@rollup/rollup-android-arm-eabi@4.35.0': 196 | resolution: {integrity: sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==} 197 | cpu: [arm] 198 | os: [android] 199 | 200 | '@rollup/rollup-android-arm64@4.35.0': 201 | resolution: {integrity: sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==} 202 | cpu: [arm64] 203 | os: [android] 204 | 205 | '@rollup/rollup-darwin-arm64@4.35.0': 206 | resolution: {integrity: sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==} 207 | cpu: [arm64] 208 | os: [darwin] 209 | 210 | '@rollup/rollup-darwin-x64@4.35.0': 211 | resolution: {integrity: sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==} 212 | cpu: [x64] 213 | os: [darwin] 214 | 215 | '@rollup/rollup-freebsd-arm64@4.35.0': 216 | resolution: {integrity: sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==} 217 | cpu: [arm64] 218 | os: [freebsd] 219 | 220 | '@rollup/rollup-freebsd-x64@4.35.0': 221 | resolution: {integrity: sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==} 222 | cpu: [x64] 223 | os: [freebsd] 224 | 225 | '@rollup/rollup-linux-arm-gnueabihf@4.35.0': 226 | resolution: {integrity: sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==} 227 | cpu: [arm] 228 | os: [linux] 229 | 230 | '@rollup/rollup-linux-arm-musleabihf@4.35.0': 231 | resolution: {integrity: sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==} 232 | cpu: [arm] 233 | os: [linux] 234 | 235 | '@rollup/rollup-linux-arm64-gnu@4.35.0': 236 | resolution: {integrity: sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==} 237 | cpu: [arm64] 238 | os: [linux] 239 | 240 | '@rollup/rollup-linux-arm64-musl@4.35.0': 241 | resolution: {integrity: sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==} 242 | cpu: [arm64] 243 | os: [linux] 244 | 245 | '@rollup/rollup-linux-loongarch64-gnu@4.35.0': 246 | resolution: {integrity: sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==} 247 | cpu: [loong64] 248 | os: [linux] 249 | 250 | '@rollup/rollup-linux-powerpc64le-gnu@4.35.0': 251 | resolution: {integrity: sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==} 252 | cpu: [ppc64] 253 | os: [linux] 254 | 255 | '@rollup/rollup-linux-riscv64-gnu@4.35.0': 256 | resolution: {integrity: sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==} 257 | cpu: [riscv64] 258 | os: [linux] 259 | 260 | '@rollup/rollup-linux-s390x-gnu@4.35.0': 261 | resolution: {integrity: sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==} 262 | cpu: [s390x] 263 | os: [linux] 264 | 265 | '@rollup/rollup-linux-x64-gnu@4.35.0': 266 | resolution: {integrity: sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==} 267 | cpu: [x64] 268 | os: [linux] 269 | 270 | '@rollup/rollup-linux-x64-musl@4.35.0': 271 | resolution: {integrity: sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==} 272 | cpu: [x64] 273 | os: [linux] 274 | 275 | '@rollup/rollup-win32-arm64-msvc@4.35.0': 276 | resolution: {integrity: sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==} 277 | cpu: [arm64] 278 | os: [win32] 279 | 280 | '@rollup/rollup-win32-ia32-msvc@4.35.0': 281 | resolution: {integrity: sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==} 282 | cpu: [ia32] 283 | os: [win32] 284 | 285 | '@rollup/rollup-win32-x64-msvc@4.35.0': 286 | resolution: {integrity: sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==} 287 | cpu: [x64] 288 | os: [win32] 289 | 290 | '@sveltejs/acorn-typescript@1.0.5': 291 | resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} 292 | peerDependencies: 293 | acorn: ^8.9.0 294 | 295 | '@sveltejs/vite-plugin-svelte-inspector@4.0.1': 296 | resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==} 297 | engines: {node: ^18.0.0 || ^20.0.0 || >=22} 298 | peerDependencies: 299 | '@sveltejs/vite-plugin-svelte': ^5.0.0 300 | svelte: ^5.0.0 301 | vite: ^6.0.0 302 | 303 | '@sveltejs/vite-plugin-svelte@5.0.3': 304 | resolution: {integrity: sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==} 305 | engines: {node: ^18.0.0 || ^20.0.0 || >=22} 306 | peerDependencies: 307 | svelte: ^5.0.0 308 | vite: ^6.0.0 309 | 310 | '@types/estree@1.0.6': 311 | resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 312 | 313 | acorn@8.14.1: 314 | resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} 315 | engines: {node: '>=0.4.0'} 316 | hasBin: true 317 | 318 | aria-query@5.3.2: 319 | resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} 320 | engines: {node: '>= 0.4'} 321 | 322 | axobject-query@4.1.0: 323 | resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} 324 | engines: {node: '>= 0.4'} 325 | 326 | clsx@2.1.1: 327 | resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} 328 | engines: {node: '>=6'} 329 | 330 | debug@4.4.0: 331 | resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 332 | engines: {node: '>=6.0'} 333 | peerDependencies: 334 | supports-color: '*' 335 | peerDependenciesMeta: 336 | supports-color: 337 | optional: true 338 | 339 | deepmerge@4.3.1: 340 | resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} 341 | engines: {node: '>=0.10.0'} 342 | 343 | esbuild@0.25.1: 344 | resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} 345 | engines: {node: '>=18'} 346 | hasBin: true 347 | 348 | esm-env@1.2.2: 349 | resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} 350 | 351 | esrap@1.4.5: 352 | resolution: {integrity: sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==} 353 | 354 | fsevents@2.3.3: 355 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 356 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 357 | os: [darwin] 358 | 359 | is-reference@3.0.3: 360 | resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} 361 | 362 | kleur@4.1.5: 363 | resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} 364 | engines: {node: '>=6'} 365 | 366 | locate-character@3.0.0: 367 | resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} 368 | 369 | magic-string@0.30.17: 370 | resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} 371 | 372 | ms@2.1.3: 373 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 374 | 375 | nanoid@3.3.10: 376 | resolution: {integrity: sha512-vSJJTG+t/dIKAUhUDw/dLdZ9s//5OxcHqLaDWWrW4Cdq7o6tdLIczUkMXt2MBNmk6sJRZBZRXVixs7URY1CmIg==} 377 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 378 | hasBin: true 379 | 380 | picocolors@1.1.1: 381 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 382 | 383 | postcss@8.5.3: 384 | resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} 385 | engines: {node: ^10 || ^12 || >=14} 386 | 387 | rollup@4.35.0: 388 | resolution: {integrity: sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==} 389 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 390 | hasBin: true 391 | 392 | source-map-js@1.2.1: 393 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 394 | engines: {node: '>=0.10.0'} 395 | 396 | svelte@5.23.0: 397 | resolution: {integrity: sha512-v0lL3NuKontiCxholEiAXCB+BYbndlKbwlDMK0DS86WgGELMJSpyqCSbJeMEMBDwOglnS7Ar2Rq0wwa/z2L8Vg==} 398 | engines: {node: '>=18'} 399 | 400 | vite@6.2.2: 401 | resolution: {integrity: sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==} 402 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 403 | hasBin: true 404 | peerDependencies: 405 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 406 | jiti: '>=1.21.0' 407 | less: '*' 408 | lightningcss: ^1.21.0 409 | sass: '*' 410 | sass-embedded: '*' 411 | stylus: '*' 412 | sugarss: '*' 413 | terser: ^5.16.0 414 | tsx: ^4.8.1 415 | yaml: ^2.4.2 416 | peerDependenciesMeta: 417 | '@types/node': 418 | optional: true 419 | jiti: 420 | optional: true 421 | less: 422 | optional: true 423 | lightningcss: 424 | optional: true 425 | sass: 426 | optional: true 427 | sass-embedded: 428 | optional: true 429 | stylus: 430 | optional: true 431 | sugarss: 432 | optional: true 433 | terser: 434 | optional: true 435 | tsx: 436 | optional: true 437 | yaml: 438 | optional: true 439 | 440 | vitefu@1.0.6: 441 | resolution: {integrity: sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==} 442 | peerDependencies: 443 | vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 444 | peerDependenciesMeta: 445 | vite: 446 | optional: true 447 | 448 | zimmerframe@1.1.2: 449 | resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} 450 | 451 | snapshots: 452 | 453 | '@ampproject/remapping@2.3.0': 454 | dependencies: 455 | '@jridgewell/gen-mapping': 0.3.8 456 | '@jridgewell/trace-mapping': 0.3.25 457 | 458 | '@esbuild/aix-ppc64@0.25.1': 459 | optional: true 460 | 461 | '@esbuild/android-arm64@0.25.1': 462 | optional: true 463 | 464 | '@esbuild/android-arm@0.25.1': 465 | optional: true 466 | 467 | '@esbuild/android-x64@0.25.1': 468 | optional: true 469 | 470 | '@esbuild/darwin-arm64@0.25.1': 471 | optional: true 472 | 473 | '@esbuild/darwin-x64@0.25.1': 474 | optional: true 475 | 476 | '@esbuild/freebsd-arm64@0.25.1': 477 | optional: true 478 | 479 | '@esbuild/freebsd-x64@0.25.1': 480 | optional: true 481 | 482 | '@esbuild/linux-arm64@0.25.1': 483 | optional: true 484 | 485 | '@esbuild/linux-arm@0.25.1': 486 | optional: true 487 | 488 | '@esbuild/linux-ia32@0.25.1': 489 | optional: true 490 | 491 | '@esbuild/linux-loong64@0.25.1': 492 | optional: true 493 | 494 | '@esbuild/linux-mips64el@0.25.1': 495 | optional: true 496 | 497 | '@esbuild/linux-ppc64@0.25.1': 498 | optional: true 499 | 500 | '@esbuild/linux-riscv64@0.25.1': 501 | optional: true 502 | 503 | '@esbuild/linux-s390x@0.25.1': 504 | optional: true 505 | 506 | '@esbuild/linux-x64@0.25.1': 507 | optional: true 508 | 509 | '@esbuild/netbsd-arm64@0.25.1': 510 | optional: true 511 | 512 | '@esbuild/netbsd-x64@0.25.1': 513 | optional: true 514 | 515 | '@esbuild/openbsd-arm64@0.25.1': 516 | optional: true 517 | 518 | '@esbuild/openbsd-x64@0.25.1': 519 | optional: true 520 | 521 | '@esbuild/sunos-x64@0.25.1': 522 | optional: true 523 | 524 | '@esbuild/win32-arm64@0.25.1': 525 | optional: true 526 | 527 | '@esbuild/win32-ia32@0.25.1': 528 | optional: true 529 | 530 | '@esbuild/win32-x64@0.25.1': 531 | optional: true 532 | 533 | '@jridgewell/gen-mapping@0.3.8': 534 | dependencies: 535 | '@jridgewell/set-array': 1.2.1 536 | '@jridgewell/sourcemap-codec': 1.5.0 537 | '@jridgewell/trace-mapping': 0.3.25 538 | 539 | '@jridgewell/resolve-uri@3.1.2': {} 540 | 541 | '@jridgewell/set-array@1.2.1': {} 542 | 543 | '@jridgewell/sourcemap-codec@1.5.0': {} 544 | 545 | '@jridgewell/trace-mapping@0.3.25': 546 | dependencies: 547 | '@jridgewell/resolve-uri': 3.1.2 548 | '@jridgewell/sourcemap-codec': 1.5.0 549 | 550 | '@rollup/rollup-android-arm-eabi@4.35.0': 551 | optional: true 552 | 553 | '@rollup/rollup-android-arm64@4.35.0': 554 | optional: true 555 | 556 | '@rollup/rollup-darwin-arm64@4.35.0': 557 | optional: true 558 | 559 | '@rollup/rollup-darwin-x64@4.35.0': 560 | optional: true 561 | 562 | '@rollup/rollup-freebsd-arm64@4.35.0': 563 | optional: true 564 | 565 | '@rollup/rollup-freebsd-x64@4.35.0': 566 | optional: true 567 | 568 | '@rollup/rollup-linux-arm-gnueabihf@4.35.0': 569 | optional: true 570 | 571 | '@rollup/rollup-linux-arm-musleabihf@4.35.0': 572 | optional: true 573 | 574 | '@rollup/rollup-linux-arm64-gnu@4.35.0': 575 | optional: true 576 | 577 | '@rollup/rollup-linux-arm64-musl@4.35.0': 578 | optional: true 579 | 580 | '@rollup/rollup-linux-loongarch64-gnu@4.35.0': 581 | optional: true 582 | 583 | '@rollup/rollup-linux-powerpc64le-gnu@4.35.0': 584 | optional: true 585 | 586 | '@rollup/rollup-linux-riscv64-gnu@4.35.0': 587 | optional: true 588 | 589 | '@rollup/rollup-linux-s390x-gnu@4.35.0': 590 | optional: true 591 | 592 | '@rollup/rollup-linux-x64-gnu@4.35.0': 593 | optional: true 594 | 595 | '@rollup/rollup-linux-x64-musl@4.35.0': 596 | optional: true 597 | 598 | '@rollup/rollup-win32-arm64-msvc@4.35.0': 599 | optional: true 600 | 601 | '@rollup/rollup-win32-ia32-msvc@4.35.0': 602 | optional: true 603 | 604 | '@rollup/rollup-win32-x64-msvc@4.35.0': 605 | optional: true 606 | 607 | '@sveltejs/acorn-typescript@1.0.5(acorn@8.14.1)': 608 | dependencies: 609 | acorn: 8.14.1 610 | 611 | '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.23.0)(vite@6.2.2))(svelte@5.23.0)(vite@6.2.2)': 612 | dependencies: 613 | '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.23.0)(vite@6.2.2) 614 | debug: 4.4.0 615 | svelte: 5.23.0 616 | vite: 6.2.2 617 | transitivePeerDependencies: 618 | - supports-color 619 | 620 | '@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.23.0)(vite@6.2.2)': 621 | dependencies: 622 | '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.23.0)(vite@6.2.2))(svelte@5.23.0)(vite@6.2.2) 623 | debug: 4.4.0 624 | deepmerge: 4.3.1 625 | kleur: 4.1.5 626 | magic-string: 0.30.17 627 | svelte: 5.23.0 628 | vite: 6.2.2 629 | vitefu: 1.0.6(vite@6.2.2) 630 | transitivePeerDependencies: 631 | - supports-color 632 | 633 | '@types/estree@1.0.6': {} 634 | 635 | acorn@8.14.1: {} 636 | 637 | aria-query@5.3.2: {} 638 | 639 | axobject-query@4.1.0: {} 640 | 641 | clsx@2.1.1: {} 642 | 643 | debug@4.4.0: 644 | dependencies: 645 | ms: 2.1.3 646 | 647 | deepmerge@4.3.1: {} 648 | 649 | esbuild@0.25.1: 650 | optionalDependencies: 651 | '@esbuild/aix-ppc64': 0.25.1 652 | '@esbuild/android-arm': 0.25.1 653 | '@esbuild/android-arm64': 0.25.1 654 | '@esbuild/android-x64': 0.25.1 655 | '@esbuild/darwin-arm64': 0.25.1 656 | '@esbuild/darwin-x64': 0.25.1 657 | '@esbuild/freebsd-arm64': 0.25.1 658 | '@esbuild/freebsd-x64': 0.25.1 659 | '@esbuild/linux-arm': 0.25.1 660 | '@esbuild/linux-arm64': 0.25.1 661 | '@esbuild/linux-ia32': 0.25.1 662 | '@esbuild/linux-loong64': 0.25.1 663 | '@esbuild/linux-mips64el': 0.25.1 664 | '@esbuild/linux-ppc64': 0.25.1 665 | '@esbuild/linux-riscv64': 0.25.1 666 | '@esbuild/linux-s390x': 0.25.1 667 | '@esbuild/linux-x64': 0.25.1 668 | '@esbuild/netbsd-arm64': 0.25.1 669 | '@esbuild/netbsd-x64': 0.25.1 670 | '@esbuild/openbsd-arm64': 0.25.1 671 | '@esbuild/openbsd-x64': 0.25.1 672 | '@esbuild/sunos-x64': 0.25.1 673 | '@esbuild/win32-arm64': 0.25.1 674 | '@esbuild/win32-ia32': 0.25.1 675 | '@esbuild/win32-x64': 0.25.1 676 | 677 | esm-env@1.2.2: {} 678 | 679 | esrap@1.4.5: 680 | dependencies: 681 | '@jridgewell/sourcemap-codec': 1.5.0 682 | 683 | fsevents@2.3.3: 684 | optional: true 685 | 686 | is-reference@3.0.3: 687 | dependencies: 688 | '@types/estree': 1.0.6 689 | 690 | kleur@4.1.5: {} 691 | 692 | locate-character@3.0.0: {} 693 | 694 | magic-string@0.30.17: 695 | dependencies: 696 | '@jridgewell/sourcemap-codec': 1.5.0 697 | 698 | ms@2.1.3: {} 699 | 700 | nanoid@3.3.10: {} 701 | 702 | picocolors@1.1.1: {} 703 | 704 | postcss@8.5.3: 705 | dependencies: 706 | nanoid: 3.3.10 707 | picocolors: 1.1.1 708 | source-map-js: 1.2.1 709 | 710 | rollup@4.35.0: 711 | dependencies: 712 | '@types/estree': 1.0.6 713 | optionalDependencies: 714 | '@rollup/rollup-android-arm-eabi': 4.35.0 715 | '@rollup/rollup-android-arm64': 4.35.0 716 | '@rollup/rollup-darwin-arm64': 4.35.0 717 | '@rollup/rollup-darwin-x64': 4.35.0 718 | '@rollup/rollup-freebsd-arm64': 4.35.0 719 | '@rollup/rollup-freebsd-x64': 4.35.0 720 | '@rollup/rollup-linux-arm-gnueabihf': 4.35.0 721 | '@rollup/rollup-linux-arm-musleabihf': 4.35.0 722 | '@rollup/rollup-linux-arm64-gnu': 4.35.0 723 | '@rollup/rollup-linux-arm64-musl': 4.35.0 724 | '@rollup/rollup-linux-loongarch64-gnu': 4.35.0 725 | '@rollup/rollup-linux-powerpc64le-gnu': 4.35.0 726 | '@rollup/rollup-linux-riscv64-gnu': 4.35.0 727 | '@rollup/rollup-linux-s390x-gnu': 4.35.0 728 | '@rollup/rollup-linux-x64-gnu': 4.35.0 729 | '@rollup/rollup-linux-x64-musl': 4.35.0 730 | '@rollup/rollup-win32-arm64-msvc': 4.35.0 731 | '@rollup/rollup-win32-ia32-msvc': 4.35.0 732 | '@rollup/rollup-win32-x64-msvc': 4.35.0 733 | fsevents: 2.3.3 734 | 735 | source-map-js@1.2.1: {} 736 | 737 | svelte@5.23.0: 738 | dependencies: 739 | '@ampproject/remapping': 2.3.0 740 | '@jridgewell/sourcemap-codec': 1.5.0 741 | '@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.1) 742 | '@types/estree': 1.0.6 743 | acorn: 8.14.1 744 | aria-query: 5.3.2 745 | axobject-query: 4.1.0 746 | clsx: 2.1.1 747 | esm-env: 1.2.2 748 | esrap: 1.4.5 749 | is-reference: 3.0.3 750 | locate-character: 3.0.0 751 | magic-string: 0.30.17 752 | zimmerframe: 1.1.2 753 | 754 | vite@6.2.2: 755 | dependencies: 756 | esbuild: 0.25.1 757 | postcss: 8.5.3 758 | rollup: 4.35.0 759 | optionalDependencies: 760 | fsevents: 2.3.3 761 | 762 | vitefu@1.0.6(vite@6.2.2): 763 | optionalDependencies: 764 | vite: 6.2.2 765 | 766 | zimmerframe@1.1.2: {} 767 | -------------------------------------------------------------------------------- /examples/vite-svelte/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vite-svelte/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | } 8 | -------------------------------------------------------------------------------- /examples/vite-svelte/vite.client.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | export default defineConfig({ 5 | base: '/client/', 6 | plugins: [svelte({ 7 | compilerOptions: { 8 | hydratable: true 9 | } 10 | })], 11 | build: { 12 | outDir: 'dist/client', 13 | emptyOutDir: true, 14 | rollupOptions: { 15 | input: './frontend/main.js', 16 | output: { 17 | format: 'esm', 18 | entryFileNames: '[name].js', 19 | chunkFileNames: '[name]-[hash].js', 20 | assetFileNames: 'assets/[name][extname]', 21 | }, 22 | } 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /examples/vite-svelte/vite.ssr.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | export default defineConfig({ 5 | base: '/client/', 6 | plugins: [svelte()], 7 | build: { 8 | ssr: true, 9 | outDir: 'dist/server', 10 | emptyOutDir: true, 11 | rollupOptions: { 12 | input: './frontend/server.js', 13 | output: { 14 | format: 'iife', 15 | entryFileNames: '[name].js', 16 | chunkFileNames: '[name]-[hash].js', 17 | } 18 | } 19 | }, 20 | ssr: { 21 | noExternal: true, 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /examples/warp.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | use ssr_rs::Ssr; 3 | use std::cell::RefCell; 4 | use std::fs::read_to_string; 5 | use std::time::Instant; 6 | use warp::{http::Response, Filter}; 7 | 8 | thread_local! { 9 | static SSR: RefCell> = RefCell::new( 10 | Ssr::from( 11 | read_to_string("./client/dist/ssr/index.js").unwrap(), 12 | "SSR" 13 | ).unwrap() 14 | ) 15 | } 16 | 17 | #[tokio::main] 18 | async fn main() { 19 | Ssr::create_platform(); 20 | 21 | let html = warp::path::end().map(move || { 22 | let start = Instant::now(); 23 | let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None)); 24 | println!("Elapsed: {:?}", start.elapsed()); 25 | Response::builder().body(result.unwrap()) 26 | }); 27 | 28 | let css = warp::path("styles").and(warp::fs::dir("./client/dist/ssr/styles/")); 29 | let scripts = warp::path("scripts").and(warp::fs::dir("./client/dist/client/")); 30 | let img = warp::path("images").and(warp::fs::dir("./client/dist/ssr/images/")); 31 | 32 | let routes = warp::get().and(html.or(css).or(scripts).or(img)); 33 | 34 | warp::serve(routes).run(([127, 0, 0, 1], 3030)).await; 35 | } 36 | -------------------------------------------------------------------------------- /examples/webpack-react/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .parcel-cache 26 | -------------------------------------------------------------------------------- /examples/webpack-react/README.md: -------------------------------------------------------------------------------- 1 | # Create React App boilerplate 2 | 3 | This example is made using create-react-app boilerplate with the typescript `--template` flag. 4 | 5 | We then pulled out the "react-scripts" dependencies in order to update to Webpack 5.65.0 and replaced it with Parcel for quick development bundling. 6 | 7 | From the initial scaffolding project there are the following differences: 8 | 9 | ```json 10 | // package.json 11 | 12 | //[...] 13 | 14 | "scripts": { 15 | //[...] 16 | "start": "parcel", 17 | "test": "jest", 18 | "build:all": "webpack --config ./webpack.client.build.js --progress && webpack --config ./webpack.ssr.js --progress", 19 | "build:client": "webpack --config ./webpack.client.build.js --progress", 20 | "build:ssr": "webpack --config ./webpack.ssr.js --progress", 21 | //[...] 22 | } 23 | 24 | //[...] 25 | 26 | "devDependencies": { 27 | "ts-jest": "^27.1.1", 28 | "jest": "^27.4.5", 29 | "clean-webpack-plugin": "^4.0.0-alpha.0", 30 | "css-loader": "^5.2.2", 31 | "file-loader": "^6.2.0", 32 | "mini-css-extract-plugin": "^1.5.0", 33 | "parcel": "^2.0.1", 34 | "webpack": "^5.65.0", 35 | "webpack-cli": "^4.6.0" 36 | } 37 | 38 | //[...] 39 | ``` 40 | 41 | ```typescript 42 | // src/ssrEntry.tsx 43 | 44 | import React from 'react'; 45 | import { renderToString, renderToStaticMarkup } from 'react-dom/server'; 46 | import App from './App'; 47 | import './index.css'; 48 | 49 | export const Index = (params: string | undefined) => { 50 | const props = params ? JSON.parse(params) : {}; 51 | const app = renderToString(); 52 | 53 | return ` 54 | 55 | 56 | React SSR 57 | 58 | 59 | 60 | 61 | ${renderToStaticMarkup( 62 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/webpack-react/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, hydrate } from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const mockProps = { 8 | params: ['first param', 'second param', 'third param'], 9 | }; 10 | 11 | const props = (() => { 12 | const stateHolder = window as { __INITIAL_PROPS__?: any }; 13 | const ssrState = stateHolder.__INITIAL_PROPS__; 14 | 15 | if (ssrState) { 16 | //delete stateHolder.__INITIAL_PROPS__; 17 | return ssrState; 18 | } 19 | return mockProps; 20 | })(); 21 | 22 | if (process.env.NODE_ENV !== 'production') { 23 | render( 24 | 25 | 26 | , 27 | document.getElementById('root') 28 | ); 29 | 30 | // If you want to start measuring performance in your app, pass a function 31 | // to log results (for example: reportWebVitals(console.log)) 32 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 33 | reportWebVitals(); 34 | } else { 35 | // We might have the element already 36 | let el = document.getElementById('root'); 37 | if (el) { 38 | hydrate(, el); 39 | 40 | reportWebVitals(); 41 | } else { 42 | // otherwise set up an observer 43 | const observer = new MutationObserver((mutations, obs) => { 44 | if (el) { 45 | hydrate(, el); 46 | 47 | reportWebVitals(); 48 | obs.disconnect(); 49 | return; 50 | } 51 | }); 52 | 53 | observer.observe(document, { 54 | childList: true, 55 | subtree: true, 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/webpack-react/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/webpack-react/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare module '*.svg' { 3 | const content: any; 4 | export default content; 5 | } 6 | -------------------------------------------------------------------------------- /examples/webpack-react/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /examples/webpack-react/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /examples/webpack-react/src/ssrEntry.tsx: -------------------------------------------------------------------------------- 1 | import { renderToString, renderToStaticMarkup } from 'react-dom/server'; 2 | import App from './App'; 3 | import './index.css'; 4 | 5 | export const Index = (params: string | undefined) => { 6 | const props = params ? JSON.parse(params) : {}; 7 | const app = renderToString(); 8 | 9 | return ` 10 | 11 | 12 | React SSR 13 | 14 | 15 | 16 | 17 | ${renderToStaticMarkup( 18 |