├── .gitignore ├── rust-toolchain.toml ├── crates ├── vite-rs │ ├── examples │ │ ├── vite-project-folder │ │ │ ├── public │ │ │ │ └── test.txt │ │ │ ├── .gitignore │ │ │ ├── app │ │ │ │ ├── index.ts │ │ │ │ ├── pack1.ts │ │ │ │ ├── index.html │ │ │ │ ├── vite.svg │ │ │ │ └── index.css │ │ │ ├── tsconfig.node.json │ │ │ ├── vite.config.ts │ │ │ ├── .eslintrc.cjs │ │ │ ├── tsconfig.json │ │ │ └── package.json │ │ ├── custom_ctrl_c_handler.rs │ │ └── basic_usage.rs │ ├── test_projects │ │ ├── normal_usage_test │ │ │ ├── public │ │ │ │ └── test.txt │ │ │ ├── .gitignore │ │ │ ├── app │ │ │ │ ├── index.ts │ │ │ │ ├── pack1.ts │ │ │ │ ├── index.html │ │ │ │ ├── vite.svg │ │ │ │ └── index.css │ │ │ └── vite.config.ts │ │ ├── recompilation_test │ │ │ ├── app │ │ │ │ ├── test.txt │ │ │ │ └── vite.config.ts │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── main.rs │ │ ├── ctrl_c_handling_test │ │ │ ├── file.txt │ │ │ ├── vite.config.ts │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── main.rs │ │ ├── custom_output_dir_test │ │ │ ├── file.txt │ │ │ ├── Cargo.toml │ │ │ ├── vite.config.ts │ │ │ ├── src │ │ │ │ └── main.rs │ │ │ └── Cargo.lock │ │ ├── custom_dev_server_port_test │ │ │ ├── vite.config.ts │ │ │ └── index.html │ │ ├── tsconfig.node.json │ │ ├── package.json │ │ └── tsconfig.json │ ├── experiments │ │ ├── renaming_bundles_feature │ │ │ ├── renaming_bundles │ │ │ │ ├── script.js │ │ │ │ ├── public │ │ │ │ │ └── script.js │ │ │ │ └── vite.config.ts │ │ │ ├── renaming_bundles.rs │ │ │ └── README.md │ │ ├── README.md │ │ └── detecting_locally_running_vite_dev_server │ │ │ └── README.md │ ├── script │ │ └── list_vite_processes.sh │ ├── src │ │ └── lib.rs │ ├── Cargo.toml │ └── tests │ │ ├── ctrl_c_handling_test.rs │ │ ├── dev_server_port_test.rs │ │ ├── recompilation_test.rs │ │ └── normal_usage_test.rs ├── vite-rs-axum-0-8 │ ├── tests │ │ ├── util │ │ │ ├── mod.rs │ │ │ └── run_project.rs │ │ ├── ctrl_c_handling_test.rs │ │ └── basic_usage_test.rs │ ├── examples │ │ └── basic_usage │ │ │ ├── app │ │ │ ├── public │ │ │ │ ├── file.txt │ │ │ │ └── test.css │ │ │ ├── App.tsx │ │ │ ├── index.html │ │ │ ├── tsconfig.node.json │ │ │ ├── script.tsx │ │ │ ├── vite.config.ts │ │ │ ├── package.json │ │ │ └── tsconfig.json │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ └── main.rs │ ├── test_projects │ │ ├── basic_usage_test │ │ │ ├── app │ │ │ │ ├── public │ │ │ │ │ ├── file.txt │ │ │ │ │ └── test.css │ │ │ │ ├── App.tsx │ │ │ │ ├── index.html │ │ │ │ ├── tsconfig.node.json │ │ │ │ ├── script.tsx │ │ │ │ ├── vite.config.ts │ │ │ │ ├── package.json │ │ │ │ └── tsconfig.json │ │ │ └── README.md │ │ └── ctrl_c_handling_test │ │ │ ├── app │ │ │ ├── public │ │ │ │ ├── file.txt │ │ │ │ └── test.css │ │ │ ├── App.tsx │ │ │ ├── index.html │ │ │ ├── tsconfig.node.json │ │ │ ├── script.tsx │ │ │ ├── vite.config.ts │ │ │ ├── package.json │ │ │ └── tsconfig.json │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ └── main.rs │ ├── src │ │ ├── lib.rs │ │ ├── vite_tower_service.rs │ │ └── vite_serve.rs │ ├── Cargo.toml │ └── README.md ├── vite-rs-embed-macro │ ├── src │ │ ├── hash_utils.rs │ │ ├── syn_utils.rs │ │ ├── vite │ │ │ ├── build │ │ │ │ ├── vite_manifest.rs │ │ │ │ └── file_entry.rs │ │ │ └── mod.rs │ │ └── lib.rs │ ├── Cargo.toml │ └── Cargo.lock ├── vite-rs-interface │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── vite-rs-dev-server │ ├── Cargo.toml │ └── src │ ├── util.rs │ └── lib.rs ├── .vscode └── settings.json ├── COPYRIGHT ├── docs ├── OLD_OUTPUT_DIR_WARNING.md └── OLD_ASYNC_WARNING.md ├── Cargo.toml ├── LICENSE-MIT ├── .github └── workflows │ └── test.yml └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | node_modules 3 | dist 4 | .vite -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | -------------------------------------------------------------------------------- /crates/vite-rs/examples/vite-project-folder/public/test.txt: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/tests/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod run_project; 2 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/normal_usage_test/public/test.txt: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/recompilation_test/app/test.txt: -------------------------------------------------------------------------------- 1 | asdf -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/ctrl_c_handling_test/file.txt: -------------------------------------------------------------------------------- 1 | some asset 123 -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": [""] 3 | } 4 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/examples/basic_usage/app/public/file.txt: -------------------------------------------------------------------------------- 1 | some asset 123 -------------------------------------------------------------------------------- /crates/vite-rs/examples/vite-project-folder/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | !dist/.gitkeep -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/custom_output_dir_test/file.txt: -------------------------------------------------------------------------------- 1 | some asset 123 -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/normal_usage_test/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | !dist/.gitkeep -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/basic_usage_test/app/public/file.txt: -------------------------------------------------------------------------------- 1 | some asset 123 -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/ctrl_c_handling_test/app/public/file.txt: -------------------------------------------------------------------------------- 1 | some asset 123 -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/recompilation_test/.gitignore: -------------------------------------------------------------------------------- 1 | app/dist/ 2 | !app/dist/.gitkeep -------------------------------------------------------------------------------- /crates/vite-rs/experiments/renaming_bundles_feature/renaming_bundles/script.js: -------------------------------------------------------------------------------- 1 | console.log("private"); -------------------------------------------------------------------------------- /crates/vite-rs/experiments/renaming_bundles_feature/renaming_bundles/public/script.js: -------------------------------------------------------------------------------- 1 | console.log("public"); -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod vite_serve; 2 | mod vite_tower_service; 3 | 4 | pub use vite_serve::{CacheStrategy, ViteServe}; 5 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/basic_usage_test/README.md: -------------------------------------------------------------------------------- 1 | This directory holds the ViteJS project for the `basic_usage_test.rs` test. 2 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/examples/basic_usage/app/public/test.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: black; 3 | color: white; 4 | font-family: Arial, sans-serif; 5 | padding: 42px; 6 | } 7 | -------------------------------------------------------------------------------- /crates/vite-rs/examples/vite-project-folder/app/index.ts: -------------------------------------------------------------------------------- 1 | const div = document.createElement('div') 2 | 3 | div.innerHTML = 'Hello from vite-rs!' 4 | 5 | document.body.appendChild(div) 6 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/normal_usage_test/app/index.ts: -------------------------------------------------------------------------------- 1 | const div = document.createElement('div') 2 | 3 | div.innerHTML = 'Hello from vite-rs!' 4 | 5 | document.body.appendChild(div) 6 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/examples/basic_usage/app/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const App = () => { 4 | return Test; 5 | }; 6 | 7 | export default App; 8 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/basic_usage_test/app/public/test.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: black; 3 | color: white; 4 | font-family: Arial, sans-serif; 5 | padding: 42px; 6 | } 7 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/basic_usage_test/app/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const App = () => { 4 | return Test; 5 | }; 6 | 7 | export default App; 8 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/ctrl_c_handling_test/app/public/test.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: black; 3 | color: white; 4 | font-family: Arial, sans-serif; 5 | padding: 42px; 6 | } 7 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/ctrl_c_handling_test/app/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const App = () => { 4 | return Test; 5 | }; 6 | 7 | export default App; 8 | -------------------------------------------------------------------------------- /crates/vite-rs/examples/vite-project-folder/app/pack1.ts: -------------------------------------------------------------------------------- 1 | const test = (() => { 2 | console.log('This is a test') 3 | 4 | const a: number = 3 5 | 6 | return a 7 | })() 8 | 9 | const num = test 10 | 11 | console.log('NUM: ', num) 12 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/recompilation_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "recompilation_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | vite-rs = { path = "../../" } 9 | 10 | [workspace] -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/recompilation_test/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(vite_rs::Embed)] 2 | #[root = "./app"] 3 | struct Assets; 4 | 5 | fn main() { 6 | for asset in Assets::iter() { 7 | println!("{}", asset); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/normal_usage_test/app/pack1.ts: -------------------------------------------------------------------------------- 1 | const test = (() => { 2 | console.log('This is a test') 3 | 4 | const a: number = 3 5 | 6 | return a 7 | })() 8 | 9 | const num = test 10 | 11 | console.log('NUM: ', num) 12 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/custom_dev_server_port_test/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | 3 | export default defineConfig({ 4 | build: { 5 | rollupOptions: { 6 | input: ["index.html"], 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/custom_output_dir_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ctrl_c_handling_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | vite-rs = { path = "../../" } 9 | 10 | [workspace] 11 | -------------------------------------------------------------------------------- /crates/vite-rs/experiments/README.md: -------------------------------------------------------------------------------- 1 | # Unsupported Features 2 | 3 | This directory holds tests for features that are not supported and attempts to document in writing or code why it is difficult or unreasonable to support them given the trajectory of the crate/community. 4 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/ctrl_c_handling_test/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | 3 | export default defineConfig({ 4 | build: { 5 | rollupOptions: { 6 | input: "file.txt", 7 | }, 8 | manifest: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /crates/vite-rs-embed-macro/src/hash_utils.rs: -------------------------------------------------------------------------------- 1 | use sha2::{Digest, Sha256}; 2 | 3 | pub fn get_content_hash(content: &[u8]) -> String { 4 | let mut hasher = Sha256::new(); 5 | hasher.update(content); 6 | let hash = hasher.finalize(); 7 | format!("{:X}", hash) 8 | } 9 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/custom_output_dir_test/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | 3 | export default defineConfig({ 4 | build: { 5 | rollupOptions: { 6 | input: "file.txt", 7 | }, 8 | outDir: "./custom-output-dir/dist", 9 | manifest: true, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/custom_output_dir_test/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(vite_rs::Embed)] 2 | #[root = "."] 3 | #[output = "./custom-output-dir/dist"] 4 | struct Assets; 5 | 6 | fn main() { 7 | let _guard = Assets::start_dev_server(true); 8 | 9 | std::thread::sleep(std::time::Duration::from_secs(12)); 10 | } 11 | -------------------------------------------------------------------------------- /crates/vite-rs/experiments/renaming_bundles_feature/renaming_bundles/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | 3 | export default defineConfig({ 4 | build: { 5 | rollupOptions: { 6 | input: { bundle: "./script.js" }, 7 | }, 8 | manifest: true, 9 | }, 10 | publicDir: "public", 11 | }); 12 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/custom_dev_server_port_test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Custom Dev Server Port Test 6 | 7 | 8 |

Custom Dev Server Port Test

9 |

It works!

10 | 11 | 12 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/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 | } -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/examples/basic_usage/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World 5 | 6 | 7 | 8 |

Loading...

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-rs-tests", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "vite": "vite" 7 | }, 8 | "devDependencies": { 9 | "@types/node": "^22.4.0", 10 | "glob": "^11.0.0", 11 | "typescript": "^5.2.2", 12 | "vite": "^5.2.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/examples/basic_usage/app/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 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/basic_usage_test/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World 5 | 6 | 7 | 8 |

Loading...

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /crates/vite-rs/examples/vite-project-folder/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 | } -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/basic_usage_test/app/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 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/ctrl_c_handling_test/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World 5 | 6 | 7 | 8 |

Loading...

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/ctrl_c_handling_test/app/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 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/examples/basic_usage/app/script.tsx: -------------------------------------------------------------------------------- 1 | import React, { lazy } from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | 4 | (async () => { 5 | const App = await lazy(() => import("./App")); 6 | 7 | ReactDOM.createRoot(document.body).render( 8 | 9 | 10 | 11 | ); 12 | })(); 13 | -------------------------------------------------------------------------------- /crates/vite-rs/script/list_vite_processes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Tests if any vite dev servers are running, printing the PID and args of the process. 4 | # 5 | # This script was built for unix-like operation systems in mind. 6 | # It likely won't work on Windows. 7 | 8 | ps aux | grep -v grep | grep -v 'list_vite_processes.sh' | grep vite | tr -s " " | cut -d' ' -f2,11- 9 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright 2024 Haris Khan 2 | 3 | Licensed under the Apache License, Version 2.0 or the MIT license 5 | , at your 6 | option. All files in the project carrying such notice may not be 7 | copied, modified, or distributed except according to those terms. 8 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/recompilation_test/app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import path from "path"; 3 | import { globSync } from "glob"; 4 | 5 | export default defineConfig(() => ({ 6 | build: { 7 | rollupOptions: { 8 | input: globSync(path.resolve(__dirname, "*.txt")), 9 | }, 10 | manifest: true, 11 | }, 12 | })); 13 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/basic_usage_test/app/script.tsx: -------------------------------------------------------------------------------- 1 | import React, { lazy } from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | 4 | (async () => { 5 | const App = await lazy(() => import("./App")); 6 | 7 | ReactDOM.createRoot(document.body).render( 8 | 9 | 10 | 11 | ); 12 | })(); 13 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/ctrl_c_handling_test/app/script.tsx: -------------------------------------------------------------------------------- 1 | import React, { lazy } from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | 4 | (async () => { 5 | const App = await lazy(() => import("./App")); 6 | 7 | ReactDOM.createRoot(document.body).render( 8 | 9 | 10 | 11 | ); 12 | })(); 13 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/examples/basic_usage/app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | build: { 7 | rollupOptions: { 8 | input: ["index.html"], 9 | }, 10 | }, 11 | server: { 12 | hmr: { 13 | port: 21012, 14 | }, 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/basic_usage_test/app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | build: { 7 | rollupOptions: { 8 | input: ["index.html"], 9 | }, 10 | }, 11 | server: { 12 | hmr: { 13 | port: 21012, 14 | }, 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/ctrl_c_handling_test/app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | build: { 7 | rollupOptions: { 8 | input: ["index.html"], 9 | }, 10 | }, 11 | server: { 12 | hmr: { 13 | port: 21012, 14 | }, 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/examples/basic_usage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "basic_usage" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | axum = { version = "0.8", features = ["macros"] } 8 | tokio = { version = "1", default-features = false, features = ["macros", "rt-multi-thread"] } 9 | vite-rs = { path = "../../../vite-rs" } 10 | vite-rs-axum-0-8 = { path = "../../" } 11 | 12 | [workspace] 13 | -------------------------------------------------------------------------------- /crates/vite-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 2 | #[cfg(feature = "ctrlc")] 3 | #[cfg(not(doctest))] // for some reason, the cfgs above don't apply to doc tests 4 | pub use vite_rs_dev_server::ctrlc; 5 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 6 | pub use vite_rs_dev_server::{self, ViteProcess}; 7 | pub use vite_rs_embed_macro::Embed; 8 | 9 | pub use vite_rs_interface::*; 10 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/normal_usage_test/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | 3 | export default defineConfig({ 4 | build: { 5 | rollupOptions: { 6 | input: ["app/index.html", "app/pack1.ts"], 7 | }, 8 | manifest: true, // **IMPORTANT**: this is required. 9 | outDir: "./dist", // this is the default value 10 | }, 11 | publicDir: "./public", // this is the default value 12 | }); 13 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/examples/basic_usage/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "dependencies": { 10 | "vite": "^6.3.5" 11 | }, 12 | "devDependencies": { 13 | "@vitejs/plugin-react": "^4.5.2", 14 | "react": "^19.1.0", 15 | "react-dom": "^19.1.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/basic_usage_test/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "dependencies": { 10 | "vite": "^6.3.5" 11 | }, 12 | "devDependencies": { 13 | "@vitejs/plugin-react": "^4.5.2", 14 | "react": "^19.1.0", 15 | "react-dom": "^19.1.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /crates/vite-rs-interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vite-rs-interface" 3 | version.workspace = true 4 | description.workspace = true 5 | repository.workspace = true 6 | readme.workspace = true 7 | authors.workspace = true 8 | keywords.workspace = true 9 | categories.workspace = true 10 | license.workspace = true 11 | edition.workspace = true 12 | homepage.workspace = true 13 | 14 | [dependencies] 15 | 16 | [features] 17 | debug-prod = [] 18 | content-hash = [] -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/ctrl_c_handling_test/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "dependencies": { 10 | "vite": "^6.3.5" 11 | }, 12 | "devDependencies": { 13 | "@vitejs/plugin-react": "^4.5.2", 14 | "react": "^19.1.0", 15 | "react-dom": "^19.1.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/ctrl_c_handling_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ctrl_c_handling_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | vite-rs = { path = "../../", default-features = false } 9 | ctrlc = { optional = true, version = "3.4.4", features = ["termination"] } 10 | 11 | [features] 12 | default = [] 13 | custom-ctrl-c-handler = ["ctrlc"] 14 | builtin-ctrl-c-handler = ["vite-rs/ctrlc"] 15 | 16 | [workspace] 17 | -------------------------------------------------------------------------------- /crates/vite-rs/examples/vite-project-folder/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | build: { 7 | rollupOptions: { 8 | input: ['app/index.html', 'app/pack1.ts'], 9 | }, 10 | manifest: true, // **IMPORTANT**: this is required. 11 | outDir: './dist', // this is the default value 12 | }, 13 | publicDir: './public', // this is the default value 14 | }) 15 | -------------------------------------------------------------------------------- /docs/OLD_OUTPUT_DIR_WARNING.md: -------------------------------------------------------------------------------- 1 | # Note: this is no longer the case since ViteJS's default emptyOutDir option is set to true, we assume it's safe to create the directory for you. 2 | 3 | > `Output directory 'path/to/dist' does not exist. Please create it. rust-analyzer macro-error` 4 | 5 | For release builds, this library requires you to manually create a dist folder (where the compiled assets are outputted by Vite). You should make this directory in your CI steps and also locally if you do --release builds. 6 | -------------------------------------------------------------------------------- /crates/vite-rs/examples/vite-project-folder/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vite-rs 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/normal_usage_test/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vite-rs 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /crates/vite-rs/examples/vite-project-folder/.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 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/ctrl_c_handling_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ctrl_c_handling_test" 3 | version = "1.0.0" 4 | edition = "2024" 5 | publish = false 6 | 7 | [[bin]] 8 | name = "axum_test" 9 | path = "src/main.rs" 10 | 11 | [dependencies] 12 | axum = { version = "0.8", features = ["macros"] } 13 | ctrlc = { version = "3", features = ["termination"] } 14 | tokio = { version = "1", features = ["macros", "rt-multi-thread"] } 15 | vite-rs = { path = "../../../vite-rs", version = "0.2.1", default-features = false, features = [ 16 | "content-hash", 17 | ] } 18 | vite-rs-axum-0-8 = { path = "../../" } 19 | 20 | [features] 21 | 22 | [workspace] 23 | -------------------------------------------------------------------------------- /crates/vite-rs-dev-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vite-rs-dev-server" 3 | version.workspace = true 4 | description.workspace = true 5 | repository.workspace = true 6 | readme.workspace = true 7 | authors.workspace = true 8 | keywords.workspace = true 9 | categories.workspace = true 10 | license.workspace = true 11 | edition.workspace = true 12 | homepage.workspace = true 13 | 14 | [dependencies] 15 | reqwest = { version = "0.12", default-features = false, features = [ 16 | "blocking", 17 | ] } 18 | command-group = "5.0.1" 19 | lazy_static = "1.4.0" 20 | ctrlc = { optional = true, version = "3.4.4", features = ["termination"] } 21 | 22 | [features] 23 | ctrlc = ["dep:ctrlc"] 24 | debug-prod = [] 25 | -------------------------------------------------------------------------------- /crates/vite-rs-dev-server/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, TcpListener, ToSocketAddrs}, 3 | ops::Range, 4 | }; 5 | 6 | fn test_bind(addr: A) -> bool { 7 | TcpListener::bind(addr) 8 | .map(|t| t.local_addr().is_ok()) 9 | .unwrap_or(false) 10 | } 11 | 12 | pub fn is_port_free(port: u16) -> bool { 13 | let ipv4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port); 14 | let ipv6 = SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, port, 0, 0); 15 | 16 | test_bind(ipv6) && test_bind(ipv4) 17 | } 18 | 19 | pub fn find_free_port(mut range: Range) -> Option { 20 | range.find(|port| is_port_free(*port)) 21 | } 22 | -------------------------------------------------------------------------------- /crates/vite-rs/examples/vite-project-folder/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 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/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 | "types": ["node"], 18 | 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noFallthroughCasesInSwitch": true 24 | }, 25 | "include": ["src"], 26 | "references": [{ "path": "./tsconfig.node.json" }] 27 | } 28 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/examples/basic_usage/app/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 | "types": ["node"], 18 | 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noFallthroughCasesInSwitch": true 24 | }, 25 | "include": ["src"], 26 | "references": [{ "path": "./tsconfig.node.json" }] 27 | } 28 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/basic_usage_test/app/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 | "types": ["node"], 18 | 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noFallthroughCasesInSwitch": true 24 | }, 25 | "include": ["src"], 26 | "references": [{ "path": "./tsconfig.node.json" }] 27 | } 28 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/ctrl_c_handling_test/app/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 | "types": ["node"], 18 | 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noFallthroughCasesInSwitch": true 24 | }, 25 | "include": ["src"], 26 | "references": [{ "path": "./tsconfig.node.json" }] 27 | } 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "crates/vite-rs", 5 | "crates/vite-rs-axum-0-8", 6 | "crates/vite-rs-dev-server", 7 | "crates/vite-rs-embed-macro", 8 | "crates/vite-rs-interface", 9 | ] 10 | 11 | [workspace.package] 12 | version = "0.2.1" 13 | description = "Uses ViteJS to compile frontend assets and embeds the results into your Rust binary. (Serves from the ViteJS dev server in development.)" 14 | repository = "https://github.com/Wulf/vite-rs" 15 | readme = "README.md" 16 | authors = ["Haris <4259838+Wulf@users.noreply.github.com>"] 17 | keywords = ["vite", "vitejs", "bundler", "swc", "esbuild"] 18 | categories = ["web-programming", "development-tools", "filesystem"] 19 | license = "MIT OR Apache-2.0" 20 | edition = "2021" 21 | homepage = "https://github.com/Wulf/vite-rs" 22 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/examples/basic_usage/src/main.rs: -------------------------------------------------------------------------------- 1 | use axum::Router; 2 | use vite_rs_axum_0_8::ViteServe; 3 | 4 | #[derive(vite_rs::Embed)] 5 | #[root = "./app"] 6 | struct Assets; 7 | 8 | #[tokio::main] 9 | async fn main() { 10 | #[cfg(debug_assertions)] 11 | let _guard = Assets::start_dev_server(true); 12 | 13 | println!("Starting server on http://localhost:3000"); 14 | 15 | let _ = axum::serve( 16 | tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(), 17 | Router::new() 18 | .route_service("/", ViteServe::new(Assets::boxed())) 19 | .route_service("/{*path}", ViteServe::new(Assets::boxed())) 20 | .into_make_service(), 21 | ) 22 | .await; 23 | 24 | // see `crates/vite-rs-axum-0-8/test_projects/ctrl_c_handling_test` for an example on how to handle graceful shutdown! 25 | } 26 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/src/vite_tower_service.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | use std::future::Future; 3 | use std::pin::Pin; 4 | use std::task::{Context, Poll}; 5 | 6 | use axum::body::{Bytes, HttpBody}; 7 | use axum::http::request::Request; 8 | use axum::response::Response; 9 | use tower::Service; 10 | 11 | use crate::vite_serve::ViteServe; 12 | 13 | impl Service> for ViteServe 14 | where 15 | B: HttpBody + Send + 'static, 16 | { 17 | type Response = Response; 18 | type Error = Infallible; 19 | type Future = Pin> + Send>>; 20 | 21 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 22 | Poll::Ready(Ok(())) 23 | } 24 | 25 | fn call(&mut self, req: Request) -> Self::Future { 26 | let svc = self.clone(); 27 | Box::pin(async move { Ok(svc.serve(req).await) }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vite-rs-axum-0-8" 3 | version.workspace = true 4 | description.workspace = true 5 | repository.workspace = true 6 | readme.workspace = true 7 | authors.workspace = true 8 | keywords.workspace = true 9 | categories.workspace = true 10 | license.workspace = true 11 | edition.workspace = true 12 | homepage.workspace = true 13 | 14 | [dependencies] 15 | vite-rs-interface = { path = "../vite-rs-interface", features = [ 16 | "content-hash", 17 | ], version = "0.2.1" } 18 | axum = { version = "0.8", default-features = false } 19 | tower = "0.5" 20 | 21 | [dev-dependencies] 22 | nix = { version = "0.29.0", features = ["signal"] } # for tests 23 | reqwest = { version = "0.12.7", features = ["blocking"] } # for tests 24 | vite-rs = { path = "../vite-rs", version = "0.2.1", default-features = false, features = [ 25 | "ctrlc", 26 | "content-hash", 27 | ] } # for tests 28 | # http = "1.3.1" # for tests 29 | tokio = { version = "1", features = ["macros", "rt-multi-thread"] } # for tests 30 | 31 | [features] 32 | debug-prod = ["vite-rs-interface/debug-prod"] 33 | -------------------------------------------------------------------------------- /crates/vite-rs/experiments/renaming_bundles_feature/renaming_bundles.rs: -------------------------------------------------------------------------------- 1 | #[derive(vite_rs::Embed)] 2 | #[root = "./tests/renaming_bundles"] 3 | struct Assets; 4 | 5 | #[test] 6 | pub fn test() { 7 | #[cfg(debug_assertions)] 8 | let _guard = Assets::start_dev_server(true); 9 | 10 | #[cfg(debug_assertions)] 11 | std::thread::sleep(std::time::Duration::from_secs(2)); 12 | 13 | #[cfg(not(debug_assertions))] 14 | Assets::iter().for_each(|asset| { 15 | println!("(renaming_bundles) asset: '{:?}'", asset); 16 | }); 17 | 18 | ensure_public_script(); 19 | ensure_script_is_renamed_to_bundle(); 20 | } 21 | 22 | fn ensure_public_script() { 23 | let file = Assets::get("script.js").unwrap(); 24 | 25 | let content = std::str::from_utf8(&file.bytes).unwrap(); 26 | 27 | assert_eq!(content, "console.log(\"public\");"); 28 | } 29 | 30 | fn ensure_script_is_renamed_to_bundle() { 31 | let file = Assets::get("bundle.js").unwrap(); 32 | 33 | let content = std::str::from_utf8(&file.bytes).unwrap(); 34 | 35 | assert_eq!(content, "console.log(\"private\");"); 36 | } 37 | -------------------------------------------------------------------------------- /crates/vite-rs-embed-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vite-rs-embed-macro" 3 | version.workspace = true 4 | description.workspace = true 5 | repository.workspace = true 6 | readme.workspace = true 7 | authors.workspace = true 8 | keywords.workspace = true 9 | categories.workspace = true 10 | license.workspace = true 11 | edition.workspace = true 12 | homepage.workspace = true 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | vite-rs-dev-server = { path = "../vite-rs-dev-server", version = "0.2.1" } 19 | 20 | syn = { version = "2", default-features = false, features = [ 21 | "derive", 22 | "parsing", 23 | "proc-macro", 24 | "printing", 25 | ] } 26 | quote = "1" 27 | proc-macro2 = "1" 28 | walkdir = "2.5.0" 29 | mime_guess = "2.0.4" 30 | serde_json = "1.0.116" 31 | serde = { version = "1.0.199", features = ["derive"] } 32 | chrono = { version = "0.4", default-features = false, features = ["alloc"] } 33 | sha2 = { optional = true, version = "0.10", default-features = false } 34 | 35 | [features] 36 | default = [] 37 | debug-prod = [] 38 | ctrlc = [] 39 | content-hash = ["sha2"] 40 | -------------------------------------------------------------------------------- /crates/vite-rs/examples/vite-project-folder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-react-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview", 11 | "vite_rs:start": "npm run dev", 12 | "vite_rs:build": "npm run build" 13 | }, 14 | "dependencies": { 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^20.12.7", 20 | "@types/react": "^18.2.66", 21 | "@types/react-dom": "^18.2.22", 22 | "@typescript-eslint/eslint-plugin": "^7.2.0", 23 | "@typescript-eslint/parser": "^7.2.0", 24 | "@vitejs/plugin-react": "^4.2.1", 25 | "eslint": "^8.57.0", 26 | "eslint-plugin-react-hooks": "^4.6.0", 27 | "eslint-plugin-react-refresh": "^0.4.6", 28 | "typescript": "^5.2.2", 29 | "vite": "^5.2.0" 30 | }, 31 | "prettier": { 32 | "semi": false, 33 | "singleQuote": true, 34 | "trailingComma": "all" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Haris Khan 4 | 5 | Permission is hereby granted, free of charge, to any 6 | person obtaining a copy of this software and associated 7 | documentation files (the "Software"), to deal in the 8 | Software without restriction, including without 9 | limitation the rights to use, copy, modify, merge, 10 | publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software 12 | is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice 16 | shall be included in all copies or substantial portions 17 | of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 20 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 21 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 22 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 23 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 26 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /crates/vite-rs/experiments/renaming_bundles_feature/README.md: -------------------------------------------------------------------------------- 1 | # Note: this was moved to 'unsupported'. At the moment, we can't rename bundles in development mode since that would require parsing the vite config file. Currently, this crate attempts to be low-touch and tries to avoid relying on the vite configuration. 2 | 3 | # Renaming Bundles 4 | 5 | When the vite config has a hash for rollupOptions.input, the `Asset::get()` method should use the key used as the entrypoint name. 6 | 7 | **Why?** This is useful when dealing with naming conflicts between entrypoints and public files served from the `viteConfig.publicDir` option. 8 | 9 | # Example 10 | 11 | ### Directory structure 12 | 13 | ``` 14 | . 15 | ├── public 16 | │ └── script.js 17 | ├── script.js // name clash with public/script.js 18 | └── vite.config.ts 19 | ``` 20 | 21 | ### `vite.config.ts` 22 | 23 | ```typescript 24 | import { defineConfig } from "vite"; 25 | 26 | export default defineConfig({ 27 | build: { 28 | rollupOptions: { 29 | input: { bundle: "./script.js" }, 30 | }, 31 | manifest: true, 32 | }, 33 | publicDir: "./public", 34 | }); 35 | ``` 36 | 37 | ### Rust usage 38 | 39 | ```rust 40 | #[vite_rs::Embed] 41 | #[root = "./tests/renaming_bundles"] 42 | struct Assets; 43 | 44 | fn main() { 45 | let bundle = Assets::get("bundle.js"); 46 | let script = Assets::get("script.js"); 47 | 48 | // ... 49 | } 50 | 51 | ``` 52 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/ctrl_c_handling_test/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(vite_rs::Embed)] 2 | #[root = "."] 3 | struct Assets; 4 | 5 | fn main() { 6 | #[cfg(not(debug_assertions))] 7 | { 8 | panic!("This binary should not be run in release mode."); 9 | } 10 | 11 | #[cfg(debug_assertions)] 12 | { 13 | #[cfg(all( 14 | not(feature = "builtin-ctrl-c-handler"), 15 | not(feature = "custom-ctrl-c-handler") 16 | ))] 17 | { 18 | panic!("You need to enable one of the 'builtin-ctrl-c-handler' or 'custom-ctrl-c-handler' features to run this test."); 19 | } 20 | 21 | /// 22 | /// Feature: Built-in ctrl-c handling 23 | /// 24 | 25 | #[cfg(feature = "builtin-ctrl-c-handler")] 26 | let _guard = Assets::start_dev_server(true); 27 | 28 | /// 29 | /// Feature: Custom ctrl-c handling 30 | /// 31 | 32 | #[cfg(feature = "custom-ctrl-c-handler")] 33 | let _guard = Assets::start_dev_server(); 34 | 35 | #[cfg(feature = "custom-ctrl-c-handler")] 36 | ctrlc::set_handler(move || { 37 | println!("Custom handler called!"); 38 | 39 | Assets::stop_dev_server(); 40 | 41 | std::process::exit(0); 42 | }) 43 | .expect("Could not set ctrl-c handler."); 44 | 45 | std::thread::sleep(std::time::Duration::from_secs(12)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/vite-rs/examples/vite-project-folder/app/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/normal_usage_test/app/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /crates/vite-rs/examples/vite-project-folder/app/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 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/normal_usage_test/app/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 | -------------------------------------------------------------------------------- /crates/vite-rs/experiments/detecting_locally_running_vite_dev_server/README.md: -------------------------------------------------------------------------------- 1 | # Detecting a locally-running Vite dev server 2 | 3 | _This is a thought experiment_. 4 | 5 | Imagine a user wants to run a ViteJS dev server locally instead of having the `cargo run` manage its lifecycle. Perhaps for debugging reasons. 6 | 7 | This one-off scenario could be supported by checking if a ViteJS dev server is running on the configured port; however, this behaviour is problematic given that a vite dev server running for another app on that port would serve unexpected content. 8 | 9 | So, perhaps, it's not such a good idea. 10 | 11 | The question becomes: can we confirm that the vite dev server is running for the project specified by `#[root = "path/to/project"]`? 12 | 13 | ```rust 14 | #[vite_rs::Embed] 15 | #[root = "path/to/project"] 16 | struct Assets; 17 | 18 | fn main() { 19 | #[cfg(debug_assertions)] 20 | { 21 | const port = Asset::get_dev_server_port(); 22 | 23 | let expected_signature = Asset::get_dev_server_signature(); 24 | 25 | if vite_rs_dev_server::is_vite_dev_server_running(port, expected_signature) { 26 | println!("Dev server is running!"); 27 | } else { 28 | println!("Dev server is not running!"); 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | Note: `get_dev_server_signature` and `is_vite_dev_server_running(port, signature)` are the implementation details we're interested in here. 35 | The signature has to be something that the vite dev server can provide, and it would be unique to the project. 36 | -------------------------------------------------------------------------------- /crates/vite-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vite-rs" 3 | version.workspace = true 4 | description.workspace = true 5 | repository.workspace = true 6 | readme.workspace = true 7 | authors.workspace = true 8 | keywords.workspace = true 9 | categories.workspace = true 10 | license.workspace = true 11 | edition.workspace = true 12 | homepage.workspace = true 13 | 14 | [[example]] 15 | name = "basic_usage" 16 | path = "examples/basic_usage.rs" 17 | 18 | [[example]] 19 | name = "custom_ctrl_c_handler" 20 | path = "examples/custom_ctrl_c_handler.rs" 21 | 22 | [dev-dependencies] 23 | nix = { version = "0.29.0", features = ["signal"] } # for tests 24 | reqwest = { version = "0.12.7" } # for tests 25 | command-group = { version = "5.0.1" } # for tests 26 | ctrlc = { version = "3.4.4" } # for examples and tests 27 | sha2 = "0.10.9" # for tests 28 | 29 | [dependencies] 30 | vite-rs-embed-macro = { path = "../vite-rs-embed-macro", version = "0.2.1" } 31 | vite-rs-dev-server = { path = "../vite-rs-dev-server", version = "0.2.1" } 32 | vite-rs-interface = { path = "../vite-rs-interface", version = "0.2.1" } 33 | 34 | [features] 35 | default = ["ctrlc", "content-hash"] 36 | ctrlc = ["vite-rs-dev-server/ctrlc", "vite-rs-embed-macro/ctrlc"] 37 | debug-prod = [ 38 | "vite-rs-interface/debug-prod", 39 | "vite-rs-dev-server/debug-prod", 40 | "vite-rs-embed-macro/debug-prod", 41 | ] 42 | content-hash = [ 43 | "vite-rs-interface/content-hash", 44 | "vite-rs-embed-macro/content-hash", 45 | ] 46 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/tests/util/run_project.rs: -------------------------------------------------------------------------------- 1 | fn test_project_path(test_project_name: &str) -> std::path::PathBuf { 2 | let workspace_dir = 3 | std::env::var("CARGO_MANIFEST_DIR").expect("Could not determine workspace directory."); 4 | 5 | // for some reason, the current directory is not the root workspace directory, but instead 6 | // the `crates/vite-rs-axum-0-8` directory when running the tests. 7 | // 8 | // let's make sure this comment is correct by doing this assertion: 9 | assert!(workspace_dir.ends_with("crates/vite-rs-axum-0-8")); 10 | 11 | let test_project_path = 12 | std::path::PathBuf::from_iter(&[&workspace_dir, "test_projects", test_project_name]); 13 | 14 | test_project_path 15 | } 16 | 17 | pub fn run( 18 | test_project_name: &str, 19 | release_build: bool, 20 | feature_flags: Vec<&str>, 21 | ) -> std::process::Child { 22 | assert!(std::process::Command::new("cargo") 23 | .arg("build") 24 | .args(if release_build { 25 | vec!["--release"] 26 | } else { 27 | vec![] 28 | }) 29 | .args(feature_flags) 30 | .current_dir(test_project_path(test_project_name)) 31 | .status() 32 | .expect("Failed to compile test project.") 33 | .success()); 34 | 35 | std::process::Command::new("cargo") 36 | .arg("run") 37 | .current_dir(test_project_path(test_project_name)) 38 | .spawn() 39 | .expect("Failed to run test project.") 40 | } 41 | 42 | #[cfg(unix)] 43 | pub fn stop(mut child: std::process::Child) { 44 | assert!(child.kill().is_ok()); 45 | assert!(child.wait().is_ok()); 46 | } 47 | -------------------------------------------------------------------------------- /crates/vite-rs-interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Production File 2 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 3 | #[derive(Debug, Clone)] 4 | /// File retrieved from a ViteJS-compiled project 5 | pub struct ViteFile { 6 | pub bytes: ::std::borrow::Cow<'static, [u8]>, 7 | pub last_modified: Option<&'static str>, 8 | pub content_type: &'static str, 9 | pub content_length: u64, 10 | #[cfg(feature = "content-hash")] 11 | /// SHA-256 hash of the file contents. 12 | pub content_hash: &'static str, 13 | } 14 | 15 | // Production Struct Trait 16 | /// Note: this is used to allow dynamic usage of embedded asset structs. 17 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 18 | pub trait GetFromVite: Send + Sync + 'static { 19 | fn get(&self, file_path: &str) -> Option; 20 | fn clone_box(&self) -> Box; 21 | } 22 | 23 | // Development File 24 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 25 | #[derive(Debug, Clone)] 26 | /// File retreived from the ViteJS dev server 27 | pub struct ViteFile { 28 | pub bytes: Vec, 29 | pub last_modified: Option, 30 | pub content_type: String, 31 | pub content_length: u64, 32 | #[cfg(feature = "content-hash")] 33 | /// Note: in development mode, this is a weak hash returned by the ViteJS dev server. 34 | pub content_hash: String, 35 | } 36 | 37 | // Development Struct Trait 38 | /// Note: this is used to allow dynamic usage of embedded asset structs. 39 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 40 | pub trait GetFromVite: Send + Sync + 'static { 41 | fn get(&self, file_path: &str) -> Option; 42 | fn clone_box(&self) -> Box; 43 | } 44 | -------------------------------------------------------------------------------- /docs/OLD_ASYNC_WARNING.md: -------------------------------------------------------------------------------- 1 | # Note: this is no longer the case and the warning has been removed from the main readme. We keep this checked-in for historic reasons. 2 | 3 | # Async usage warning 4 | 5 | **This applies to #[cfg(debug_assertions)] only:** 6 | 7 | In development mode, we use a blocking `reqwest` client to retrieve files from the ViteJS dev server. This has the consequence that usage of `YourStruct::get("./some/asset.html")` will need to be wrapped in a `tokio::task::spawn_blocking` in async environments. See https://docs.rs/reqwest/latest/reqwest/blocking/index.html for more information. Here's the particular excerpt: 8 | 9 | > [**Module reqwest::blocking**](https://docs.rs/reqwest/latest/reqwest/blocking/index.html) 10 | > 11 | > [...] If the immediate context is only synchronous, but a transitive caller is async, consider changing that caller to use [`tokio::task::spawn_blocking`](https://docs.rs/tokio/1.37.0/tokio/task/fn.spawn_blocking.html) around the calls that need to block. 12 | 13 | ### Async example (tokio) 14 | 15 | ```rust 16 | #[vite_rs::Embed] 17 | #[input = "./assets"] 18 | struct Assets; 19 | 20 | #[tokio::main] 21 | async fn main() { 22 | // DEV example: 23 | #[cfg(debug_assertions)] 24 | { 25 | let asset = tokio::task::spawn_blocking(|| { 26 | Assets::get("index.html") 27 | }).await.unwrap();; 28 | 29 | println!("{}", asset); 30 | } 31 | 32 | 33 | // PROD example: 34 | #[cfg(not(debug_assertions))] 35 | { 36 | let asset = Assets::get("index.html"); 37 | println!("{}", asset); 38 | } 39 | } 40 | ``` 41 | 42 | ### Why doesn't `vite_rs` use an async `reqwest` client and make the `::get()` fn async? 43 | 44 | The `rust_embed` crate doesn't do this, and we wanted to keep the API consistent with that crate. 45 | -------------------------------------------------------------------------------- /crates/vite-rs-embed-macro/src/syn_utils.rs: -------------------------------------------------------------------------------- 1 | use syn::{Data, Expr, ExprLit, Fields, Lit, Meta, MetaNameValue}; 2 | 3 | /// Find all pairs of the `name = "value"` attribute from the derive input 4 | pub fn find_attribute_values(ast: &syn::DeriveInput, attr_name: &str) -> Vec { 5 | ast.attrs 6 | .iter() 7 | .filter(|value| value.path().is_ident(attr_name)) 8 | .filter_map(|attr| match &attr.meta { 9 | // `name = "value"` 10 | Meta::NameValue(MetaNameValue { 11 | value: 12 | Expr::Lit(ExprLit { 13 | lit: Lit::Str(val), .. 14 | }), 15 | .. 16 | }) => Some(val.value()), 17 | // `name = 123` 18 | Meta::NameValue(MetaNameValue { 19 | value: 20 | Expr::Lit(ExprLit { 21 | lit: Lit::Int(val), .. 22 | }), 23 | .. 24 | }) => Some(val.base10_digits().to_string()), 25 | // other 26 | _ => None, 27 | }) 28 | .collect() 29 | } 30 | 31 | /// Returns an Err if the DeriveInput is not a unit struct 32 | /// 33 | /// # Example 34 | /// 35 | /// ```ignore 36 | /// #[derive(vite_rs::Embed)] 37 | /// struct MyStruct; 38 | /// ``` 39 | /// 40 | /// Instead of: 41 | /// 42 | /// ```ignore 43 | /// #[derive(vite_rs::Embed)] 44 | /// struct MyStruct { 45 | /// field: String, 46 | /// } 47 | /// ``` 48 | pub fn ensure_unit_struct(ast: &syn::DeriveInput) -> syn::Result<()> { 49 | match ast.data { 50 | Data::Struct(ref data) => match data.fields { 51 | Fields::Unit => {} 52 | _ => { 53 | return Err(syn::Error::new_spanned( 54 | ast, 55 | "Embed can only be derived for unit structs", 56 | )) 57 | } 58 | }, 59 | _ => { 60 | return Err(syn::Error::new_spanned( 61 | ast, 62 | "Embed can only be derived for unit structs", 63 | )) 64 | } 65 | }; 66 | 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /crates/vite-rs-embed-macro/src/vite/build/vite_manifest.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[allow(dead_code, non_snake_case)] 4 | #[derive(serde::Deserialize)] 5 | pub struct ViteManifestEntry { 6 | /// Script content to load for this entry 7 | pub file: String, 8 | 9 | /// Script content to lazy-load for this entry 10 | pub dynamicImports: Option>, // using `import(..)` 11 | 12 | /// Style content to load for this entry 13 | pub css: Option>, // using import '*.css' 14 | 15 | /// If true, eager-load this content 16 | pub isEntry: Option, 17 | 18 | /// If true, lazy-load this content 19 | pub isDynamicEntry: Option, // src: String, /* => not necessary :) */ 20 | // assets: Option>, /* => these will be served by the server! */ 21 | } 22 | 23 | pub type ViteManifest = HashMap; 24 | 25 | pub fn load_vite_manifest(path: &str) -> ViteManifest { 26 | let manifest_json_str = std::fs::read_to_string(path).unwrap(); 27 | 28 | parse_vite_manifest_json_str(&manifest_json_str) 29 | } 30 | 31 | pub fn parse_vite_manifest_json_str(manifest_json: &str) -> ViteManifest { 32 | let manifest_json = serde_json::from_str(manifest_json).expect("failed to parse vite manifest"); 33 | 34 | let mut manifest: ViteManifest = HashMap::new(); 35 | 36 | match manifest_json { 37 | serde_json::Value::Object(obj) => { 38 | obj.keys().for_each(|manifest_key| { 39 | let details = obj.get(manifest_key).unwrap(); 40 | 41 | let manifest_entry = serde_json::from_value::(details.clone()) 42 | .expect( 43 | "invalid vite manifest (or perhaps the vite-rs parser isn't up-to-date!)", 44 | ); 45 | 46 | manifest.insert(manifest_key.to_string(), manifest_entry); 47 | }); 48 | // done parsing manifest 49 | } 50 | _ => { 51 | panic!("invalid vite manifest (or perhaps the vite-rs parser isn't up-to-date!)"); 52 | } 53 | } 54 | 55 | manifest 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | format: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | toolchain: stable 14 | - uses: actions/checkout@master 15 | - run: rustup component add rustfmt 16 | - run: cargo fmt --all -- --check 17 | test: 18 | runs-on: ${{ matrix.os }} 19 | continue-on-error: ${{ matrix.experimental }} 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | os: [ubuntu-latest, macOS-latest] # todo: add 'windows-latest' 24 | rust: [stable] 25 | experimental: [false] 26 | # Note: We're no longer reliant on nightly, so we can remove this. 27 | # Keeping it here for future reference. 28 | # 29 | # # include: 30 | # # # Test on stable -- we expect this to fail, but we want to know when it starts working! 31 | # # - rust: stable 32 | # # os: ubuntu-latest 33 | # # experimental: true 34 | steps: 35 | - uses: actions-rs/toolchain@v1 36 | with: 37 | toolchain: ${{ matrix.rust }} 38 | - uses: actions/checkout@master 39 | - name: Install npm packages for examples & tests 40 | run: | 41 | (cd ./crates/vite-rs/test_projects && npm ci) 42 | (cd ./crates/vite-rs-axum-0-8/test_projects/basic_usage_test/app && npm ci) 43 | (cd ./crates/vite-rs-axum-0-8/test_projects/ctrl_c_handling_test/app && npm ci) 44 | (cd ./crates/vite-rs-axum-0-8/examples/basic_usage/app && npm ci) 45 | (cd ./crates/vite-rs/examples/vite-project-folder && npm ci) 46 | - name: Run tests # note: this step also compiles examples, but does not run them. 47 | run: | 48 | # VITE-RS 49 | cargo test -p vite-rs 50 | cargo test -p vite-rs --release 51 | 52 | # AXUM 53 | cargo test -p vite-rs-axum-0-8 54 | cargo test -p vite-rs-axum-0-8 --release 55 | - name: Run/compile examples 56 | run: | 57 | # VITE-RS 58 | cargo run -p vite-rs --example basic_usage 59 | cargo run -p vite-rs --example basic_usage --release 60 | cargo run -p vite-rs --example custom_ctrl_c_handler 61 | cargo run -p vite-rs --example custom_ctrl_c_handler --release 62 | 63 | # AXUM 64 | (cd ./crates/vite-rs-axum-0-8/examples/basic_usage && cargo build) 65 | (cd ./crates/vite-rs-axum-0-8/examples/basic_usage && cargo build --release) 66 | -------------------------------------------------------------------------------- /crates/vite-rs/examples/custom_ctrl_c_handler.rs: -------------------------------------------------------------------------------- 1 | use vite_rs::Embed; 2 | 3 | #[derive(Embed)] 4 | #[root = "./examples/vite-project-folder"] 5 | struct Assets; 6 | 7 | fn main() { 8 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 9 | // We use an RAII guard to gracefully exit the dev server 10 | let _guard = Assets::start_dev_server(false); 11 | 12 | ctrlc::try_set_handler(|| { 13 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 14 | Assets::stop_dev_server(); 15 | std::process::exit(0); 16 | }) 17 | .unwrap(); 18 | 19 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 20 | { 21 | println!("Waiting for the dev server to start (2 second)"); 22 | std::thread::sleep(std::time::Duration::from_secs(2)); 23 | } 24 | 25 | let file = Assets::get("app/index.html").unwrap(); 26 | let file_content = std::str::from_utf8(&file.bytes).unwrap(); 27 | 28 | println!("Reading index.html:"); 29 | println!("{}", file_content); 30 | 31 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 32 | assert_eq!( 33 | strip_space(file_content), 34 | strip_space( 35 | r#" 36 | 37 | 38 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | vite-rs 53 | 54 | 55 |
56 | 57 | 58 | "# 59 | ) 60 | ); 61 | 62 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 63 | assert_eq!( 64 | strip_space(file_content), 65 | strip_space( 66 | "\n\n\n\n\n\nvite-rs\n\n\n\n\n\n\n" 67 | ) 68 | ); 69 | } 70 | 71 | fn strip_space(s: &str) -> String { 72 | s.trim().replace(" ", "") 73 | } 74 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/tests/ctrl_c_handling_test.rs: -------------------------------------------------------------------------------- 1 | mod util; 2 | 3 | /// Note: we only have a single #[test] because we can't run multiple tests in parallel 4 | /// since the vite dev server can't be started multiple times. 5 | #[test] 6 | fn test() { 7 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 8 | { 9 | #[cfg(unix)] 10 | { 11 | const UNIX_SIGNAL_MATRIX: [nix::sys::signal::Signal; 3] = [ 12 | nix::sys::signal::Signal::SIGINT, 13 | nix::sys::signal::Signal::SIGTERM, 14 | nix::sys::signal::Signal::SIGHUP, 15 | ]; 16 | 17 | for signal in UNIX_SIGNAL_MATRIX.into_iter() { 18 | println!("Running test for signal: {:?}", signal); 19 | dev_tests::unix_ensure_dev_server_exits_on_signal(signal); 20 | } 21 | } 22 | } 23 | } 24 | 25 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 26 | mod dev_tests { 27 | #[cfg(unix)] 28 | pub fn unix_ensure_dev_server_exits_on_signal(signal: nix::sys::signal::Signal) { 29 | assert_dev_server_is_not_running(); 30 | 31 | let child = run(); 32 | 33 | assert_dev_server_is_running(); 34 | 35 | send_term_signal(child, signal); 36 | 37 | assert_dev_server_is_not_running(); 38 | } 39 | 40 | #[cfg(unix)] 41 | fn send_term_signal(child: std::process::Child, signal: nix::sys::signal::Signal) { 42 | use nix::sys::signal; 43 | use nix::unistd::Pid; 44 | 45 | let pid = Pid::from_raw(child.id() as i32); 46 | 47 | signal::kill(pid, signal).expect("Failed to send SIGTERM signal."); 48 | } 49 | 50 | fn assert_dev_server_is_not_running() { 51 | use reqwest::blocking::Client; 52 | 53 | let client = Client::new(); 54 | // Since it's possible that port 21012 is taken already and the dev server 55 | // starts on a new port, this test could fail. 56 | // 57 | // But for the sake of this test, we assume the first port is available. 58 | let response = client.get("http://localhost:21012").send(); 59 | 60 | assert!(response.is_err()); 61 | assert!(response.unwrap_err().is_request()); 62 | } 63 | 64 | fn assert_dev_server_is_running() { 65 | let asset = "file.txt"; 66 | let asset_content = "some asset 123"; 67 | 68 | // we wait 2 seconds to make sure the dev server has time to start 69 | std::thread::sleep(std::time::Duration::from_secs(2)); 70 | 71 | use reqwest::blocking::Client; 72 | 73 | let client = Client::new(); 74 | // Since it's possible that port 21012 is taken already and the dev server 75 | // starts on a new port, this test could fail. 76 | // 77 | // But for the sake of this test, we assume the first port is available. 78 | let response = client 79 | .get(format!("http://localhost:21012/{asset}")) 80 | .send() 81 | .expect("Failed to send request."); 82 | 83 | assert!(response.status().is_success()); 84 | assert_eq!( 85 | response.text().expect("Failed to get response text."), 86 | asset_content 87 | ); 88 | } 89 | 90 | fn run() -> std::process::Child { 91 | super::util::run_project::run("ctrl_c_handling_test", false, vec![]) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/README.md: -------------------------------------------------------------------------------- 1 | # Axum integration for `vite-rs` 2 | 3 | This crate provides a Tower service that can be used in Axum projects to serve your embedded ViteJS assets. 4 | 5 | The exposed `ViteServe` service can be used as a fallback service or mounted at a specific path in your Axum router. 6 | 7 | ## Quick Start 8 | 9 | 1. Add dependencies: 10 | 11 | ```sh 12 | cargo add vite-rs 13 | cargo add vite-rs-axum-0-8 14 | cargo add axum@0.8 15 | cargo add tokio --features macros,rt-multi-thread 16 | ``` 17 | 18 | 2. Create a Vite project in `./app` (it should contain a `vite.config.js` file). For help, refer to the Quick Start section in the `vite-rs` README. 19 | 20 | 3. Update your binary: 21 | 22 | ```rs 23 | // src/main.rs 24 | use vite_rs_axum_0_8::ViteServe; 25 | 26 | #[derive(vite_rs::Embed)] 27 | #[root = "./app"] 28 | struct Assets; 29 | 30 | #[tokio::main] 31 | async fn main() { 32 | #[cfg(debug_assertions)] 33 | let _guard = Assets::start_dev_server(true); 34 | 35 | println!("Starting server on http://localhost:3000"); 36 | 37 | axum::serve( 38 | tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap(), 39 | axum::Router::new() 40 | // we mount our Vite app to / 41 | .route_service("/", ViteServe::new(Assets::boxed())) 42 | .route_service("/{*path}", ViteServe::new(Assets::boxed())) 43 | // we can also use a fallback service instead: 44 | // .fallback_service(ViteServe::new(Assets::boxed())) 45 | .into_make_service(), 46 | ) 47 | .await 48 | .unwrap() 49 | } 50 | ``` 51 | 52 | ## HTTP Caching Behaviour 53 | 54 | See [CacheStrategy rust docs](https://docs.rs/vite-rs-axum-0-8?search=CacheStrategy) for details on the caching strategies available. By default, release builds use the `Eager` caching strategy, while debug builds use `None`. You can override this by explicitly setting the cache strategy. Use them as follows: 55 | 56 | ```diff 57 | use vite_rs_axum_0_8::{CacheStrategy, ViteServe}; 58 | 59 | #[derive(vite_rs::Embed)] 60 | #[root = "./app"] 61 | struct Assets; 62 | 63 | fn main() { 64 | let service = ViteServe::new(Assets::boxed()) 65 | + .with_cache_strategy(CacheStrategy::Eager); 66 | } 67 | ``` 68 | 69 | ## Graceful shutdown 70 | 71 | It's recommended to use `test_projects/ctrl_c_handling_test` as a reference in setting up your server binary. This will help you gracefully handle Ctrl-C and other signals in unix when managing the ViteJS dev server in Rust. Alternatively, manage the dev server lifecycle yourself (refer to `vite-rs` crate docs), and use Axum's graceful shutdown example instead. 72 | 73 | **If any of this is overwhelming**, use the quick start above, and kill your dev server with `killall node` if you find your dev server has not shutdown properly. Although not necessary, you can add a bit more more robustness by adding a panic hook handler after your dev server starts (this will ensure your dev server is stopped on unlikely panics outside axum handlers): 74 | 75 | ```rs 76 | #[cfg(debug_assertions)] 77 | let _guard = Assets::start_dev_server(true); 78 | 79 | #[cfg(debug_assertions)] 80 | { 81 | let hook = std::panic::take_hook(); 82 | std::panic::set_hook(Box::new(move |info| { 83 | Assets::stop_dev_server(); 84 | 85 | // run super's panic hook 86 | hook(info); 87 | })); 88 | } 89 | ``` 90 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/test_projects/ctrl_c_handling_test/src/main.rs: -------------------------------------------------------------------------------- 1 | use axum::Router; 2 | use std::panic; 3 | use std::sync::{ 4 | atomic::{AtomicBool, Ordering}, 5 | Arc, 6 | }; 7 | use tokio::sync::broadcast; 8 | 9 | use vite_rs_axum_0_8::ViteServe; 10 | 11 | #[derive(vite_rs::Embed)] 12 | #[root = "./app"] 13 | struct Assets; 14 | 15 | #[tokio::main] 16 | async fn main() { 17 | // Set up panic hook to ensure dev server is stopped even in case of panic 18 | let default_hook = panic::take_hook(); 19 | panic::set_hook(Box::new(move |panic_info| { 20 | println!("Server panic occurred: {}", panic_info); 21 | 22 | #[cfg(debug_assertions)] 23 | Assets::stop_dev_server(); 24 | 25 | // Call the default panic hook 26 | default_hook(panic_info); 27 | })); 28 | 29 | // Shutdown signal 30 | let (tx_for_ctrl_c, rx) = broadcast::channel::<()>(1); 31 | let tx_for_error = tx_for_ctrl_c.clone(); 32 | 33 | // Use an atomic to track if we've already initiated shutdown 34 | let shutdown_flag = Arc::new(AtomicBool::new(false)); 35 | let shutdown_flag_clone = shutdown_flag.clone(); 36 | 37 | #[cfg(debug_assertions)] 38 | let _guard = Assets::start_dev_server(); 39 | 40 | println!("GET / http://localhost:3000/"); 41 | 42 | // Set up Ctrl+C handler before starting the server 43 | ctrlc::set_handler(move || { 44 | println!("Received Ctrl+C, shutting down..."); 45 | 46 | // Only initiate shutdown once 47 | if !shutdown_flag.load(Ordering::SeqCst) { 48 | shutdown_flag.store(true, Ordering::SeqCst); 49 | 50 | #[cfg(debug_assertions)] 51 | Assets::stop_dev_server(); 52 | 53 | let _ = tx_for_ctrl_c.send(()); 54 | } 55 | }) 56 | .expect("Error setting Ctrl-C handler"); 57 | 58 | // Start the server with graceful shutdown 59 | println!("Server started. Press Ctrl+C to stop."); 60 | 61 | // Use tokio::spawn with a Result handling to catch potential panics 62 | let server_handle = tokio::spawn(async move { 63 | // Create a receiver for the shutdown signal 64 | let mut rx = rx; 65 | 66 | let server = axum::serve( 67 | tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(), 68 | Router::new() 69 | .route_service("/", ViteServe::new(Assets::boxed())) 70 | .route_service("/{*path}", ViteServe::new(Assets::boxed())) 71 | .into_make_service(), 72 | ) 73 | .with_graceful_shutdown(async move { 74 | // Wait for the shutdown signal 75 | let _ = rx.recv().await; 76 | println!("Server shutdown complete"); 77 | }); 78 | 79 | server.await 80 | }); 81 | 82 | // Wait for the server to complete - this will block until the server is shutdown 83 | // via the Ctrl+C handler or if a panic occurs 84 | match server_handle.await { 85 | Ok(server_result) => { 86 | if let Err(e) = server_result { 87 | eprintln!("Server error: {}", e); 88 | } 89 | } 90 | Err(e) => { 91 | eprintln!("Server task failed: {}", e); 92 | 93 | // In case the server task panicked, make sure dev server is stopped 94 | if !shutdown_flag_clone.load(Ordering::SeqCst) { 95 | shutdown_flag_clone.store(true, Ordering::SeqCst); 96 | #[cfg(debug_assertions)] 97 | Assets::stop_dev_server(); 98 | 99 | // Signal shutdown in case something is still waiting 100 | let _ = tx_for_error.send(()); 101 | } 102 | } 103 | } 104 | 105 | println!("Exiting application"); 106 | } 107 | -------------------------------------------------------------------------------- /crates/vite-rs-embed-macro/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "mime" 7 | version = "0.3.17" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 10 | 11 | [[package]] 12 | name = "mime_guess" 13 | version = "2.0.4" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" 16 | dependencies = [ 17 | "mime", 18 | "unicase", 19 | ] 20 | 21 | [[package]] 22 | name = "proc-macro2" 23 | version = "1.0.79" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 26 | dependencies = [ 27 | "unicode-ident", 28 | ] 29 | 30 | [[package]] 31 | name = "quote" 32 | version = "1.0.35" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 35 | dependencies = [ 36 | "proc-macro2", 37 | ] 38 | 39 | [[package]] 40 | name = "same-file" 41 | version = "1.0.6" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 44 | dependencies = [ 45 | "winapi-util", 46 | ] 47 | 48 | [[package]] 49 | name = "syn" 50 | version = "2.0.58" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" 53 | dependencies = [ 54 | "proc-macro2", 55 | "quote", 56 | "unicode-ident", 57 | ] 58 | 59 | [[package]] 60 | name = "unicase" 61 | version = "2.7.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 64 | dependencies = [ 65 | "version_check", 66 | ] 67 | 68 | [[package]] 69 | name = "unicode-ident" 70 | version = "1.0.12" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 73 | 74 | [[package]] 75 | name = "version_check" 76 | version = "0.9.4" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 79 | 80 | [[package]] 81 | name = "vite-rs-macro" 82 | version = "0.1.0" 83 | dependencies = [ 84 | "mime_guess", 85 | "proc-macro2", 86 | "quote", 87 | "syn", 88 | "walkdir", 89 | ] 90 | 91 | [[package]] 92 | name = "walkdir" 93 | version = "2.5.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 96 | dependencies = [ 97 | "same-file", 98 | "winapi-util", 99 | ] 100 | 101 | [[package]] 102 | name = "winapi" 103 | version = "0.3.9" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 106 | dependencies = [ 107 | "winapi-i686-pc-windows-gnu", 108 | "winapi-x86_64-pc-windows-gnu", 109 | ] 110 | 111 | [[package]] 112 | name = "winapi-i686-pc-windows-gnu" 113 | version = "0.4.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 116 | 117 | [[package]] 118 | name = "winapi-util" 119 | version = "0.1.6" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 122 | dependencies = [ 123 | "winapi", 124 | ] 125 | 126 | [[package]] 127 | name = "winapi-x86_64-pc-windows-gnu" 128 | version = "0.4.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 131 | -------------------------------------------------------------------------------- /crates/vite-rs-embed-macro/src/vite/build/file_entry.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | 3 | pub struct FileEntry { 4 | /// The string used to lookup this file. 5 | /// It's either the original file name or the compiled file name. 6 | key: String, 7 | 8 | /// Path to the file on the filesystem. 9 | absolute_file_path: String, 10 | 11 | /// The last modified timestamp of the file. Useful for caching. 12 | last_modified: Option, 13 | 14 | /// The MIME type of the file. Useful for serving the file. 15 | content_type: String, 16 | 17 | /// The length of the file in bytes. Useful for serving the file. 18 | content_length: u64, 19 | } 20 | 21 | impl FileEntry { 22 | pub fn new(key: String, absolute_file_path: String) -> std::io::Result { 23 | let metadata = std::fs::metadata(&absolute_file_path)?; 24 | let last_modified = metadata.modified().ok().map(|last_modified| { 25 | let last_modified_secs = last_modified 26 | .duration_since(SystemTime::UNIX_EPOCH) 27 | .expect("Time before the UNIX epoch is unsupported") 28 | .as_secs(); 29 | 30 | // Unix timestamps conversion from u64 to i64 won't overflow for several billion years 31 | let last_modified_secs = last_modified_secs as i64; 32 | chrono::DateTime::::from_timestamp(last_modified_secs, 0) 33 | .map(|dt| dt.format("%a, %d %b %Y %H:%M:%S GMT").to_string()) 34 | .expect("Failed to format last-modified date according to HTTP standards") 35 | }); 36 | 37 | Ok(Self { 38 | key, 39 | last_modified: last_modified, 40 | content_type: mime_guess::from_path(&absolute_file_path) 41 | .first_or_octet_stream() 42 | .to_string(), 43 | content_length: metadata.len(), 44 | absolute_file_path, 45 | }) 46 | } 47 | 48 | pub fn match_key(&self) -> &String { 49 | &self.key 50 | } 51 | 52 | pub fn match_value(&self, crate_path: &syn::Path) -> proc_macro2::TokenStream { 53 | self.code(crate_path) 54 | } 55 | 56 | fn code(&self, crate_path: &syn::Path) -> proc_macro2::TokenStream { 57 | use quote::quote; 58 | 59 | let absolute_file_path = &self.absolute_file_path; 60 | 61 | let last_modified = if let Some(last_modified) = &self.last_modified { 62 | quote! { ::std::option::Option::Some(#last_modified) } 63 | } else { 64 | quote! { ::std::option::Option::None } 65 | }; 66 | 67 | let content_type = &self.content_type; 68 | let content_length = self.content_length; 69 | 70 | let content_hash = if cfg!(feature = "content-hash") { 71 | // We have to read the file here because it's currently not possible to use sha2 in const fns until https://github.com/RustCrypto/hashes/issues/288 is resolved. 72 | // And without a const fn, we cant generate a const HASH: &'static str = "..." for each FileEntry (which would be nice and in-line with the const BYTES array). 73 | // Once the above is resolved, we won't have to read the file here and in the include_bytes!. 74 | let bytes = 75 | std::fs::read(absolute_file_path).expect("Failed to read file to compute hash"); 76 | let content_hash = crate::hash_utils::get_content_hash(&bytes); 77 | quote! { content_hash: #content_hash, } 78 | } else { 79 | quote! {} 80 | }; 81 | 82 | quote! { 83 | { 84 | const BYTES: &'static [u8] = include_bytes!(#absolute_file_path); 85 | 86 | #crate_path::ViteFile { 87 | bytes: ::std::borrow::Cow::Borrowed(&BYTES), 88 | last_modified: #last_modified, 89 | content_type: #content_type, 90 | content_length: #content_length, 91 | #content_hash 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /crates/vite-rs/examples/basic_usage.rs: -------------------------------------------------------------------------------- 1 | use vite_rs::Embed; 2 | 3 | #[derive(Embed)] 4 | #[root = "./examples/vite-project-folder"] 5 | struct Assets; 6 | 7 | fn main() { 8 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 9 | // We use an RAII guard to gracefully exit the dev server 10 | let _guard = Assets::start_dev_server(true); 11 | 12 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 13 | { 14 | // only needed for this example 15 | println!("Waiting for the dev server to start (2 second)"); 16 | std::thread::sleep(std::time::Duration::from_secs(2)); 17 | } 18 | 19 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 20 | { 21 | Assets::iter().for_each(|file_name| { 22 | println!("ENTRY: {}", file_name); 23 | }); 24 | } 25 | 26 | println!("Reading index.html:"); 27 | let file = Assets::get("app/index.html").unwrap(); 28 | let file_content = std::str::from_utf8(&file.bytes).unwrap(); 29 | 30 | println!("{}", file_content); 31 | 32 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 33 | assert_eq!( 34 | strip_space(file_content), 35 | strip_space( 36 | r#" 37 | 38 | 39 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | vite-rs 54 | 55 | 56 |
57 | 58 | 59 | "# 60 | ) 61 | ); 62 | 63 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 64 | assert_eq!( 65 | strip_space(file_content), 66 | strip_space( 67 | "\n\n\n\n\n\nvite-rs\n\n\n\n\n\n\n" 68 | ) 69 | ); 70 | 71 | println!("Reading pack1.js:"); 72 | let file = Assets::get("app/pack1.ts").unwrap(); 73 | let file_content = std::str::from_utf8(&file.bytes).unwrap(); 74 | 75 | println!("{}", file_content); 76 | 77 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 78 | assert_eq!( 79 | strip_space(file_content), 80 | strip_space( 81 | r#" 82 | const test = (() => { 83 | console.log("This is a test"); 84 | const a = 3; 85 | return a; 86 | })(); 87 | const num = test; 88 | console.log("NUM: ", num); 89 | 90 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInBhY2sxLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImNvbnN0IHRlc3QgPSAoKCkgPT4ge1xuICBjb25zb2xlLmxvZygnVGhpcyBpcyBhIHRlc3QnKVxuXG4gIGNvbnN0IGE6IG51bWJlciA9IDNcblxuICByZXR1cm4gYVxufSkoKVxuXG5jb25zdCBudW0gPSB0ZXN0XG5cbmNvbnNvbGUubG9nKCdOVU06ICcsIG51bSlcbiJdLCJtYXBwaW5ncyI6IkFBQUEsTUFBTSxRQUFRLE1BQU07QUFDbEIsVUFBUSxJQUFJLGdCQUFnQjtBQUU1QixRQUFNLElBQVk7QUFFbEIsU0FBTztBQUNULEdBQUc7QUFFSCxNQUFNLE1BQU07QUFFWixRQUFRLElBQUksU0FBUyxHQUFHOyIsIm5hbWVzIjpbXX0= 91 | "# 92 | ) 93 | ); 94 | 95 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 96 | assert_eq!( 97 | strip_space(file_content), 98 | strip_space(r#"const o=(console.log("This is a test"),3),s=o;console.log("NUM: ",s);"#) 99 | ); 100 | } 101 | 102 | fn strip_space(s: &str) -> String { 103 | s.trim().replace(" ", "") 104 | } 105 | -------------------------------------------------------------------------------- /crates/vite-rs/tests/ctrl_c_handling_test.rs: -------------------------------------------------------------------------------- 1 | /// Note: we only have a single #[test] because we can't run multiple tests in parallel 2 | /// since the vite dev server can't be started multiple times. 3 | #[test] 4 | fn test() { 5 | const CTRL_HANDLER_FEATURE_MATRIX: [&str; 2] = 6 | ["builtin-ctrl-c-handler", "custom-ctrl-c-handler"]; 7 | 8 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 9 | { 10 | #[cfg(unix)] 11 | { 12 | const UNIX_SIGNAL_MATRIX: [nix::sys::signal::Signal; 3] = [ 13 | nix::sys::signal::Signal::SIGINT, 14 | nix::sys::signal::Signal::SIGTERM, 15 | nix::sys::signal::Signal::SIGHUP, 16 | ]; 17 | 18 | for feature in CTRL_HANDLER_FEATURE_MATRIX.into_iter() { 19 | for signal in UNIX_SIGNAL_MATRIX.into_iter() { 20 | println!( 21 | "Running test for feature: {} and signal: {:?}", 22 | feature, signal 23 | ); 24 | dev_tests::unix_ensure_dev_server_exits_on_signal(feature, signal); 25 | } 26 | } 27 | } 28 | } 29 | } 30 | 31 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 32 | mod dev_tests { 33 | #[cfg(unix)] 34 | pub fn unix_ensure_dev_server_exits_on_signal(feature: &str, signal: nix::sys::signal::Signal) { 35 | assert_dev_server_is_not_running(); 36 | 37 | let child = run(feature); 38 | 39 | assert_dev_server_is_running(); 40 | 41 | send_term_signal(child, signal); 42 | 43 | assert_dev_server_is_not_running(); 44 | } 45 | 46 | #[cfg(unix)] 47 | fn send_term_signal(child: std::process::Child, signal: nix::sys::signal::Signal) { 48 | use nix::sys::signal; 49 | use nix::unistd::Pid; 50 | 51 | let pid = Pid::from_raw(child.id() as i32); 52 | 53 | signal::kill(pid, signal).expect("Failed to send SIGTERM signal."); 54 | } 55 | 56 | fn assert_dev_server_is_not_running() { 57 | use reqwest::blocking::Client; 58 | 59 | let client = Client::new(); 60 | // Since it's possible that port 21012 is taken already and the dev server 61 | // starts on a new port, this test could fail. 62 | // 63 | // But for the sake of this test, we assume the first port is available. 64 | let response = client.get("http://localhost:21012").send(); 65 | 66 | assert!(response.is_err()); 67 | assert!(response.unwrap_err().is_request()); 68 | } 69 | 70 | fn assert_dev_server_is_running() { 71 | let asset = "file.txt"; 72 | let asset_content = "some asset 123"; 73 | 74 | // we wait 2 seconds to make sure the dev server has time to start 75 | std::thread::sleep(std::time::Duration::from_secs(2)); 76 | 77 | use reqwest::blocking::Client; 78 | 79 | let client = Client::new(); 80 | // Since it's possible that port 21012 is taken already and the dev server 81 | // starts on a new port, this test could fail. 82 | // 83 | // But for the sake of this test, we assume the first port is available. 84 | let response = client 85 | .get(format!("http://localhost:21012/{asset}")) 86 | .send() 87 | .expect("Failed to send request."); 88 | 89 | assert!(response.status().is_success()); 90 | assert_eq!( 91 | response.text().expect("Failed to get response text."), 92 | asset_content 93 | ); 94 | } 95 | 96 | fn test_project_path() -> std::path::PathBuf { 97 | let workspace_dir = 98 | std::env::var("CARGO_MANIFEST_DIR").expect("Could not determine workspace directory."); 99 | 100 | // for some reason, the current directory is not the root workspace directory, but instead 101 | // the `crates/vite-rs` directory when running the tests. 102 | // 103 | // let's make sure this comment is correct by doing this assertion: 104 | assert!(workspace_dir.ends_with("crates/vite-rs")); 105 | 106 | let test_project_path = 107 | std::path::PathBuf::from_iter(&[&workspace_dir, "test_projects/ctrl_c_handling_test"]); 108 | 109 | test_project_path 110 | } 111 | 112 | fn run(features: &str) -> std::process::Child { 113 | assert!(std::process::Command::new("cargo") 114 | .arg("build") 115 | .arg("--features") 116 | .arg(features) 117 | .current_dir(test_project_path()) 118 | .status() 119 | .expect("Failed to compile test project.") 120 | .success()); 121 | 122 | std::process::Command::new("cargo") 123 | .arg("run") 124 | .arg("--features") 125 | .arg(features) 126 | .current_dir(test_project_path()) 127 | .spawn() 128 | .expect("Failed to run test project.") 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /crates/vite-rs/tests/dev_server_port_test.rs: -------------------------------------------------------------------------------- 1 | #[derive(vite_rs::Embed)] 2 | #[root = "./test_projects/custom_dev_server_port_test"] 3 | #[dev_server_port = "21232"] 4 | struct Assets; 5 | 6 | #[derive(vite_rs::Embed)] 7 | #[root = "./test_projects/custom_dev_server_port_test"] 8 | #[dev_server_port = 21222] // without quotes 9 | struct AssetsWithoutQuotes; 10 | 11 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 12 | #[test] 13 | fn test_dev_server_port() { 14 | // we're going to set a custom Ctrl-C handler because this test starts 15 | // several dev servers and vite-rs doesn't handle this case out-of-the-box 16 | ctrlc::set_handler(move || { 17 | #[cfg(debug_assertions)] 18 | { 19 | AssetsWithoutQuotes::stop_dev_server(); 20 | Assets::stop_dev_server(); 21 | } 22 | 23 | std::process::exit(0); 24 | }) 25 | .expect("Error setting Ctrl-C handler"); 26 | 27 | #[cfg(unix)] 28 | { 29 | // Rust-managed: Test that the dev server runs on the specified port 30 | { 31 | let _guard = AssetsWithoutQuotes::start_dev_server(false); 32 | 33 | // we wait 2 seconds to make sure the dev server has time to start 34 | std::thread::sleep(std::time::Duration::from_secs(2)); 35 | 36 | match assert_dev_server_running_on_port(21222) { 37 | Ok(resp) => { 38 | assert_eq!(resp, "\n\n \n \n\n \n Custom Dev Server Port Test\n \n \n

Custom Dev Server Port Test

\n

It works!

\n \n\n"); 39 | } 40 | Err(e) => { 41 | panic!("{}", e); 42 | } 43 | } 44 | } 45 | 46 | // Rust-managed: Test that the dev server runs on the specified port 47 | { 48 | assert!(assert_dev_server_running_on_port(21222).is_err()); // ensure the previous dev server has stopped 49 | 50 | let _guard = Assets::start_dev_server(false); 51 | 52 | // we wait 2 seconds to make sure the dev server has time to start 53 | std::thread::sleep(std::time::Duration::from_secs(2)); 54 | 55 | match assert_dev_server_running_on_port(21232) { 56 | Ok(resp) => { 57 | assert_eq!(resp, "\n\n \n \n\n \n Custom Dev Server Port Test\n \n \n

Custom Dev Server Port Test

\n

It works!

\n \n\n"); 58 | } 59 | Err(e) => { 60 | panic!("{}", e); 61 | } 62 | } 63 | } 64 | 65 | // Self-managed: Test that the dev server connects to the specified port 66 | { 67 | // should fail because the dev server would have shut down as it went out of scope 68 | assert!(assert_dev_server_running_on_port(21232).is_err()); 69 | 70 | use command_group::CommandGroup; // npx spawns a group process, so we use this in order to kill it later. 71 | let mut cmd = std::process::Command::new("npx") 72 | .arg("vite") 73 | .arg("--port") 74 | .arg("21232") 75 | .arg("--strictPort") 76 | .arg("--clearScreen") 77 | .arg("false") 78 | .stdin(std::process::Stdio::null()) 79 | .current_dir("./test_projects/custom_dev_server_port_test") 80 | .group_spawn() // npx spawns a group process, so we use a group_spawn in order to kill it later. 81 | .expect("Failed to start dev server"); 82 | 83 | let mut close_dev_server = || { 84 | cmd.kill() 85 | .expect("Failed to stop the ViteJS dev server for this test"); 86 | }; 87 | 88 | // we wait 2 seconds to make sure the dev server has time to start 89 | std::thread::sleep(std::time::Duration::from_secs(2)); 90 | 91 | match assert_dev_server_running_on_port(21232) { 92 | Ok(resp) => { 93 | close_dev_server(); 94 | assert_eq!(resp, "\n\n \n \n\n \n Custom Dev Server Port Test\n \n \n

Custom Dev Server Port Test

\n

It works!

\n \n\n"); 95 | } 96 | Err(e) => { 97 | close_dev_server(); 98 | panic!("{}", e); 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | fn assert_dev_server_running_on_port(port: u16) -> Result { 106 | let client = reqwest::blocking::Client::new(); 107 | let url = format!("http://localhost:{}", port); 108 | 109 | match client.get(url).send() { 110 | Ok(res) => { 111 | if res.status() != 200 { 112 | return Err("Expected 200 status code".to_string()); 113 | } else { 114 | return Ok(std::str::from_utf8(&res.bytes().unwrap()) 115 | .unwrap() 116 | .to_string()); 117 | } 118 | } 119 | Err(e) => { 120 | return Err( 121 | format!("Failed to connect to dev server on port {}: {}", port, e).to_string(), 122 | ); 123 | } 124 | }; 125 | } 126 | -------------------------------------------------------------------------------- /crates/vite-rs-dev-server/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 2 | use command_group::GroupChild; 3 | 4 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 5 | #[cfg(feature = "ctrlc")] 6 | pub use ctrlc; 7 | 8 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 9 | pub use reqwest; // exported for use in derived code 10 | 11 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 12 | use std::sync::{Arc, Mutex}; 13 | 14 | pub mod util; 15 | 16 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 17 | pub struct ViteProcess(pub Arc>); 18 | 19 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 20 | pub struct ViteProcess; 21 | 22 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 23 | lazy_static::lazy_static! { 24 | static ref VITE_PROCESS: Arc>> = Arc::new(Mutex::new(None)); 25 | } 26 | 27 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 28 | fn set_dev_server(process: ViteProcess) { 29 | let original = VITE_PROCESS.lock().unwrap().replace(process); 30 | if original.is_some() { 31 | original 32 | .unwrap() 33 | .0 34 | .lock() 35 | .expect("(!) Could not shutdown ViteJS dev server: Mutex poisoned") 36 | .kill() 37 | .expect("(!) Could not shutdown ViteJS dev server."); 38 | } 39 | } 40 | 41 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 42 | fn unset_dev_server() { 43 | let process = VITE_PROCESS.lock().unwrap().take(); 44 | if process.is_some() { 45 | process 46 | .unwrap() 47 | .0 48 | .lock() 49 | .expect("(!) Could not shutdown ViteJS dev server: Mutex poisoned") 50 | .kill() 51 | .expect("(!) Could not shutdown ViteJS dev server."); 52 | } 53 | } 54 | 55 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 56 | impl Drop for ViteProcess { 57 | fn drop(&mut self) { 58 | unset_dev_server(); 59 | } 60 | } 61 | 62 | /// Starts the ViteJS dev server. 63 | /// 64 | /// Example 1 (with the included `ctrlc` feature enabled): 65 | /// 66 | /// ```ignore 67 | /// fn main() { 68 | /// #[cfg(debug_assertions)] 69 | /// let _guard = Assets::start_dev_server(true); 70 | /// 71 | /// // ... 72 | /// } 73 | /// ``` 74 | /// 75 | /// Example 2 (using the `ctrlc` crate to handle Ctrl-C): 76 | /// 77 | /// ```ignore 78 | /// fn main() { 79 | /// #[cfg(debug_assertions)] 80 | /// let _guard = Assets::start_dev_server(); 81 | /// 82 | /// ctrlc::try_set_handler(|| { 83 | /// #[cfg(debug_assertions)] 84 | /// Assets::stop_dev_server(); 85 | /// std::process::exit(0); 86 | /// }).unwrap(); 87 | /// } 88 | /// 89 | /// ``` 90 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 91 | pub fn start_dev_server( 92 | absolute_root_dir: &str, 93 | host: &str, 94 | port: u16, 95 | #[cfg(feature = "ctrlc")] register_ctrl_c_handler: bool, 96 | ) -> Option { 97 | use command_group::CommandGroup; 98 | 99 | if !util::is_port_free(port as u16) { 100 | panic!( 101 | "Selected vite-rs dev server port '{}' is not available.\na) If self-selecting a port via #[dev_server_port = XXX], ensure it is free.\nb) Otherwise, remove the #[dev_server_port] attribute and let vite-rs select a free port for you at compile time.", 102 | port 103 | ) 104 | } 105 | 106 | // println!("Starting dev server!"); 107 | // start ViteJS dev server 108 | let child = Arc::new(Mutex::new( 109 | std::process::Command::new("npx") 110 | .arg("vite") 111 | .arg("--host") 112 | .arg(host) 113 | .arg("--port") 114 | .arg(port.to_string()) 115 | .arg("--strictPort") 116 | .arg("--clearScreen") 117 | .arg("false") 118 | // we don't want to send stdin to the dev server; this also 119 | // hides the "press h + enter to show help" message that the dev server prints 120 | .stdin(std::process::Stdio::null()) 121 | .current_dir( 122 | absolute_root_dir, /*format!( 123 | "{}/examples/basic_usage", 124 | std::env::var("CARGO_MANIFEST_DIR").unwrap() 125 | )*/ 126 | ) 127 | .group_spawn() 128 | .expect("failed to start ViteJS dev server"), 129 | )); 130 | set_dev_server(ViteProcess(child.clone())); 131 | 132 | #[cfg(feature = "ctrlc")] 133 | { 134 | if register_ctrl_c_handler { 135 | // We handle Ctrl-C because the node process does not exit properly otherwise 136 | ctrlc::try_set_handler({ 137 | move || { 138 | unset_dev_server(); 139 | std::process::exit(0); 140 | } 141 | }) 142 | .expect("vite-rs: Error setting Ctrl-C handler; if you are using a custom one, disable the ctrlc feature for the vite-rs crate, and follow the documentation here to integrate it: https://github.com/Wulf/vite-rs#ctrl-c-handler"); 143 | } 144 | } 145 | 146 | // We build an RAII guard around the child process so that the dev server is killed when it's dropped 147 | Some(ViteProcess(child.clone())) 148 | } 149 | 150 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 151 | pub fn start_dev_server( 152 | #[cfg(feature = "ctrlc")] _register_ctrl_c_handler: bool, 153 | ) -> Option { 154 | None 155 | } 156 | 157 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 158 | pub fn stop_dev_server() { 159 | unset_dev_server(); 160 | } 161 | 162 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 163 | pub fn stop_dev_server() { 164 | // do nothing 165 | } 166 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/src/vite_serve.rs: -------------------------------------------------------------------------------- 1 | use axum::body::Body; 2 | use axum::response::Response; 3 | use vite_rs_interface::GetFromVite; 4 | 5 | pub struct ViteServe { 6 | pub cache_strategy: CacheStrategy, 7 | pub assets: Box, 8 | } 9 | 10 | impl Clone for ViteServe { 11 | fn clone(&self) -> Self { 12 | Self { 13 | cache_strategy: self.cache_strategy.clone(), 14 | assets: self.assets.clone_box(), 15 | } 16 | } 17 | } 18 | 19 | /// Caching strategies specify how the server sets the Control-Cache header. 20 | /// In development, we always send 'no-cache' to ensure the latest files are served. 21 | #[derive(Clone)] 22 | pub enum CacheStrategy { 23 | /// Always up-to-date. Checks for new updates before serving files. 24 | /// Clients will always receive the latest version of served assets. 25 | /// (default in release builds) 26 | Eager, 27 | /// Faster initial render. Checks for new updates after cached files are served. 28 | /// Clients may be on older versions of served assets until the next request. 29 | Lazy, 30 | /// No caching. Always serves the latest files without any cache headers. 31 | /// Not recommended, use `Eager` instead. 32 | /// (default in debug builds) 33 | None, 34 | /// Custom caching strategy. Allows you to set your own Control-Cache header. 35 | Custom(&'static str), 36 | } 37 | 38 | impl ViteServe { 39 | pub fn new(assets: Box) -> Self { 40 | Self { 41 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 42 | cache_strategy: CacheStrategy::None, 43 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 44 | cache_strategy: CacheStrategy::Eager, 45 | assets, 46 | } 47 | } 48 | 49 | pub fn with_cache_strategy(mut self, cache_strategy: CacheStrategy) -> Self { 50 | self.cache_strategy = cache_strategy; 51 | self 52 | } 53 | 54 | pub async fn serve(&self, req: axum::http::request::Request) -> Response 55 | where 56 | B: axum::body::HttpBody + Send + 'static, 57 | { 58 | // Extract the path from the request, removing the leading slash 59 | let path = req.uri().path().trim_start_matches('/'); 60 | let query = req 61 | .uri() 62 | .query() 63 | .map(|q| format!("?{}", q)) 64 | .unwrap_or_default(); 65 | 66 | // If the path is empty, default to index.html 67 | let request_file_path = if path.is_empty() { "index.html" } else { path }; 68 | 69 | match self.assets.get(&format!("{}{}", request_file_path, query)) { 70 | Some(file) => { 71 | let mut response = Response::builder(); 72 | 73 | response = response.header("Content-Type", file.content_type); 74 | response = response.header("Content-Length", file.content_length); 75 | 76 | let etag = { 77 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 78 | { 79 | file.content_hash 80 | } 81 | 82 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 83 | { 84 | &file.content_hash 85 | } 86 | }; 87 | 88 | response = response.status(200).header("ETag", etag); 89 | 90 | match self.cache_strategy { 91 | CacheStrategy::Eager => { 92 | response = response.header("Cache-Control", "max-age=0, must-revalidate"); 93 | } 94 | CacheStrategy::Lazy => { 95 | response = response 96 | .header("Cache-Control", "max-age=0, stale-while-revalidate=604800"); 97 | } 98 | CacheStrategy::None => { 99 | response = response.header("Cache-Control", "no-cache"); 100 | } 101 | CacheStrategy::Custom(header) => { 102 | response = response.header("Cache-Control", header); 103 | } 104 | }; 105 | 106 | if let Some(last_modified) = file.last_modified { 107 | response = response.header("Last-Modified", last_modified); 108 | } 109 | 110 | match req.headers().get(axum::http::header::IF_NONE_MATCH) { 111 | Some(header) => { 112 | let header_etag = header.to_str().expect( 113 | "Could not read IF_NONE_MATCH header, it contained invalid characters.", 114 | ); 115 | 116 | if etag.eq(header_etag) { 117 | // If the ETag matches, return 304 Not Modified 118 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 119 | return response.status(304).body(Body::from(vec![])).unwrap(); 120 | 121 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 122 | return response.status(304).body(Body::from(&[][..])).unwrap(); 123 | } else { 124 | // If it doesn't match, return the full response 125 | return response.body(Body::from(file.bytes)).unwrap(); 126 | } 127 | } 128 | None => { 129 | // If no IF_NONE_MATCH header, return the full response 130 | return response.body(Body::from(file.bytes)).unwrap(); 131 | } 132 | } 133 | } 134 | None => { 135 | // Return 404 Not Found with an empty body 136 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 137 | return Response::builder() 138 | .status(404) 139 | .body(Body::from(vec![])) 140 | .unwrap(); 141 | 142 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 143 | return Response::builder() 144 | .status(404) 145 | .body(Body::from(&[][..])) 146 | .unwrap(); 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /crates/vite-rs/tests/recompilation_test.rs: -------------------------------------------------------------------------------- 1 | /// NOTE: Breaking change: we changed the `last_modified` field to be of type String instead of 2 | /// u64 to match the HTTP standard for Last-Modified headers. This test needs to be updated before it is re-enabled. 3 | 4 | /// Note: we only have a single #[test] because we can't run multiple tests in parallel 5 | /// since the vite dev server can't be started multiple times. 6 | #[test] 7 | fn test() { 8 | // These recompilation tests are meant for release builds only 9 | // They're here to ensure the assets that are embedded are properly watched for changes 10 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 11 | { 12 | release_tests::compile_test_project(); 13 | release_tests::ensure_binary_recompiles_on_asset_change(); 14 | release_tests::ensure_binary_does_not_recompile_on_other_changes(); 15 | } 16 | } 17 | 18 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 19 | mod release_tests { 20 | use std::path::PathBuf; 21 | 22 | pub fn ensure_binary_recompiles_on_asset_change() { 23 | delete_asset_if_exists("app/test2.txt"); 24 | compile_test_project(); 25 | ensure_assets_exist(vec![ 26 | /* "app/test.txt" -> */ "assets/test-BPR99Ku7.txt", 27 | ]); 28 | let binary_last_modified = get_compiled_binary_modified_time(); 29 | 30 | add_asset("app/test2.txt", "123"); 31 | compile_test_project(); 32 | ensure_assets_exist(vec![ 33 | /* "app/test2.txt" -> */ "assets/test2-CajEw_O3.txt", 34 | /* "app/test.txt" -> */ "assets/test-BPR99Ku7.txt", 35 | ]); 36 | let binary_last_modified_2 = get_compiled_binary_modified_time(); 37 | 38 | delete_asset_if_exists("app/test2.txt"); // cleanup 39 | 40 | assert!( 41 | binary_last_modified_2 - binary_last_modified > 0, 42 | "Binary was not recompiled on asset change" 43 | ); 44 | } 45 | 46 | /// Note: this test is disabled because, currently, ViteJS recomplies assets 47 | /// even if there is no change. Effectively, this results in a full rebuild 48 | /// of the rust binary. 49 | /// 50 | /// Full example: 51 | /// 1. We run `cargo build --release` 52 | /// 2. Internally, `vite build` is run, generating compiled assets 53 | /// 3. Assets are included using `include_bytes!` which causes the rust compiler 54 | /// to track these files for changes. 55 | /// 4. If we run `cargo build --release` again, the rust compiler notices that 56 | /// the assets have changed, triggering steps 2 and 3 again. 57 | /// 58 | /// Options moving forward: 59 | /// 1. Wait for ViteJS to implement a feature that prevents recompilation when 60 | /// there are no changes. 61 | /// 2. Use `std::fs::read` instead of `include_bytes!` to load assets at runtime. 62 | /// This will prevent the rust compiler from tracking the assets for changes. 63 | pub fn ensure_binary_does_not_recompile_on_other_changes() { 64 | // delete_asset_if_exists("app/test2.txt"); 65 | 66 | // compile_test_project(); 67 | // ensure_assets_exist(vec![ 68 | // /* "app/test.txt" -> */ "assets/test-BPR99Ku7.txt", 69 | // ]); 70 | // let binary_last_modified = get_compiled_binary_modified_time(); 71 | 72 | // add_asset("test.txt", "// some unrelated change"); 73 | // compile_test_project(); 74 | // ensure_assets_exist(vec![ 75 | // /* "app/test.txt" -> */ "assets/test-BPR99Ku7.txt", 76 | // ]); 77 | // let binary_last_modified_2 = get_compiled_binary_modified_time(); 78 | 79 | // delete_asset_if_exists("test.txt"); // cleanup 80 | 81 | // assert_eq!( 82 | // binary_last_modified_2, binary_last_modified, 83 | // "Binary was recompiled on asset change" 84 | // ); 85 | } 86 | 87 | fn ensure_assets_exist(assets: Vec<&str>) { 88 | // sort the compiled assets 89 | let mut compiled_assets: Vec = get_binary_asset_list(); 90 | compiled_assets.sort(); 91 | 92 | // sort the expected assets 93 | let mut assets = assets.clone(); 94 | assets.sort(); 95 | 96 | assert_eq!(assets, compiled_assets); 97 | } 98 | 99 | #[allow(dead_code)] 100 | fn add_asset(asset: &str, content: &str) { 101 | let asset_path = test_project_path().join(asset); 102 | 103 | std::fs::write(&asset_path, content) 104 | .expect(&format!("Failed to write to the asset file: {asset}")); 105 | } 106 | 107 | fn delete_asset_if_exists(asset: &str) { 108 | let asset_path = test_project_path().join(asset); 109 | 110 | if asset_path.exists() { 111 | std::fs::remove_file(asset_path) 112 | .expect(&format!("Failed to delete the asset file: {asset}")); 113 | } 114 | } 115 | 116 | fn test_project_path() -> PathBuf { 117 | let workspace_dir = 118 | std::env::var("CARGO_MANIFEST_DIR").expect("Could not determine workspace directory."); 119 | 120 | // for some reason, the current directory is not the root workspace directory, but instead 121 | // the `crates/vite-rs` directory when running the tests. 122 | // 123 | // let's make sure this comment is correct by doing this assertion: 124 | assert!(workspace_dir.ends_with("crates/vite-rs")); 125 | 126 | let test_project_path = 127 | PathBuf::from_iter(&[&workspace_dir, "test_projects/recompilation_test"]); 128 | 129 | test_project_path 130 | } 131 | 132 | pub fn compile_test_project() { 133 | // Compile the project 134 | let has_compiled = std::process::Command::new("cargo") 135 | .arg("build") 136 | .arg("--release") 137 | .current_dir(test_project_path()) 138 | .status() 139 | .expect("Failed to compile the project") 140 | .success(); 141 | 142 | assert!(has_compiled); 143 | } 144 | 145 | fn get_binary_asset_list() -> Vec { 146 | let output = std::process::Command::new("./recompilation_test") 147 | .current_dir(test_project_path().join("target/release")) 148 | .output() 149 | .expect("Failed to get the list of assets") 150 | .stdout; 151 | 152 | String::from_utf8(output) 153 | .expect("Failed to convert the output to a string") 154 | .lines() 155 | .map(|line| line.to_string()) 156 | .collect() 157 | } 158 | 159 | fn get_compiled_binary_modified_time() -> u128 { 160 | let binary_path = test_project_path().join("target/release/recompilation_test"); 161 | 162 | let modified_time = binary_path 163 | .metadata() 164 | .expect("Failed to get metadata for the compiled binary") 165 | .modified() 166 | .expect("Failed to get modified time of the compiled binary"); 167 | 168 | modified_time 169 | .duration_since(std::time::UNIX_EPOCH) 170 | .expect("Failed to convert the modified time to a SystemTime") 171 | .as_nanos() 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /crates/vite-rs/tests/normal_usage_test.rs: -------------------------------------------------------------------------------- 1 | #[derive(vite_rs::Embed)] 2 | #[root = "./test_projects/normal_usage_test"] 3 | struct Assets; 4 | 5 | /// Note: we only have a single #[test] because we can't run multiple tests in parallel 6 | /// since the vite dev server can't be started multiple times. 7 | #[test] 8 | pub fn test() { 9 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 10 | let _guard = Assets::start_dev_server(true); 11 | 12 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 13 | assert!(_guard.is_some()); 14 | 15 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 16 | std::thread::sleep(std::time::Duration::from_secs(2)); 17 | 18 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 19 | { 20 | ensure_asset_list(); 21 | ensure_aliases(); 22 | ensure_no_dot_vite_dir(); 23 | } 24 | ensure_html_entrypoint(); 25 | ensure_ts_entrypoint(); 26 | ensure_public_dir_files(); 27 | ensure_no_vite_manifest(); 28 | ensure_content_hash_is_correct(); 29 | } 30 | 31 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 32 | fn ensure_asset_list() { 33 | use std::iter::zip; 34 | 35 | let mut list = Assets::iter().collect::>(); 36 | list.sort(); 37 | 38 | let mut expected = vec![ 39 | "assets/vite-DcBtz0py.svg", 40 | "assets/index-BZiJcslM.js", 41 | "assets/pack1-B2m_tRuS.js", 42 | "assets/index-BPvgi06w.css", 43 | // ".vite/manifest.json", 44 | "test.txt", 45 | "app/index.html", 46 | ]; 47 | expected.sort(); 48 | 49 | assert_eq!(list.len(), expected.len()); 50 | 51 | for (file1, file2) in zip(list, expected) { 52 | assert_eq!(file1, file2); 53 | assert!(Assets::get(&file1).is_some()); 54 | } 55 | } 56 | 57 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 58 | fn ensure_aliases() { 59 | let aliases = vec![("app/pack1.ts", "assets/pack1-B2m_tRuS.js")]; 60 | 61 | for alias in aliases { 62 | assert_eq!( 63 | Assets::get(alias.0).unwrap().bytes, 64 | Assets::get(alias.1).unwrap().bytes 65 | ) 66 | } 67 | } 68 | 69 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 70 | fn ensure_no_dot_vite_dir() { 71 | for file in Assets::iter() { 72 | assert!(!file.starts_with(".vite/")); 73 | } 74 | } 75 | 76 | fn ensure_html_entrypoint() { 77 | let file = Assets::get("app/index.html").unwrap(); 78 | 79 | assert_eq!(file.content_type, "text/html"); 80 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 81 | assert_eq!(file.content_length, 475); 82 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 83 | assert_eq!(file.content_length, 470); 84 | 85 | let content = std::str::from_utf8(&file.bytes).unwrap(); 86 | 87 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 88 | assert_eq!( 89 | content.replace(" ", ""), 90 | "\n\n\n\n\n\n\n\n\nvite-rs\n\n\n\n\n\n\n" 91 | .replace(" ", "") 92 | ); 93 | 94 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 95 | assert_eq!( 96 | content.replace(" ", ""), 97 | "\n\n\n\n\n\nvite-rs\n\n\n\n\n\n\n\n" 98 | .replace(" ", "") 99 | ); 100 | } 101 | 102 | fn ensure_ts_entrypoint() { 103 | let file = Assets::get("app/pack1.ts").unwrap(); 104 | let content = std::str::from_utf8(&file.bytes).unwrap(); 105 | 106 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 107 | { 108 | assert_eq!(file.content_type, "text/javascript"); 109 | assert_eq!(file.content_length, 656); 110 | assert_eq!( 111 | content.replace(" ", ""), 112 | "consttest=(()=>{\nconsole.log(\"Thisisatest\");\nconsta=3;\nreturna;\n})();\nconstnum=test;\nconsole.log(\"NUM:\",num);\n\n//#sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInBhY2sxLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImNvbnN0IHRlc3QgPSAoKCkgPT4ge1xuICBjb25zb2xlLmxvZygnVGhpcyBpcyBhIHRlc3QnKVxuXG4gIGNvbnN0IGE6IG51bWJlciA9IDNcblxuICByZXR1cm4gYVxufSkoKVxuXG5jb25zdCBudW0gPSB0ZXN0XG5cbmNvbnNvbGUubG9nKCdOVU06ICcsIG51bSlcbiJdLCJtYXBwaW5ncyI6IkFBQUEsTUFBTSxRQUFRLE1BQU07QUFDbEIsVUFBUSxJQUFJLGdCQUFnQjtBQUU1QixRQUFNLElBQVk7QUFFbEIsU0FBTztBQUNULEdBQUc7QUFFSCxNQUFNLE1BQU07QUFFWixRQUFRLElBQUksU0FBUyxHQUFHOyIsIm5hbWVzIjpbXX0=" 113 | ); 114 | } 115 | 116 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 117 | { 118 | assert_eq!(file.content_type, "application/javascript"); 119 | assert_eq!(file.content_length, 70); 120 | assert_eq!( 121 | content.replace(" ", ""), 122 | "consto=(console.log(\"Thisisatest\"),3),s=o;console.log(\"NUM:\",s);\n" 123 | .replace(" ", "") 124 | ); 125 | } 126 | } 127 | 128 | fn ensure_public_dir_files() { 129 | let file = Assets::get("test.txt").unwrap(); 130 | 131 | assert_eq!(file.content_type, "text/plain"); 132 | assert_eq!(file.content_length, 4); 133 | assert_eq!(file.bytes, "test".as_bytes()); 134 | } 135 | 136 | fn ensure_no_vite_manifest() { 137 | assert!(Assets::get(".vite/manifest.json").is_none()); 138 | } 139 | 140 | fn ensure_content_hash_is_correct() { 141 | let public_file = Assets::get("test.txt").unwrap(); 142 | let input_file = Assets::get("app/index.html").unwrap(); 143 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 144 | let included_file = Assets::get("app/index.ts").unwrap(); 145 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 146 | let included_file = Assets::get("assets/index-BZiJcslM.js").unwrap(); 147 | 148 | check_hash(public_file); 149 | check_hash(input_file); 150 | check_hash(included_file); 151 | 152 | fn check_hash(file: vite_rs::ViteFile) { 153 | use sha2::{Digest, Sha256}; 154 | let hash = Sha256::digest(&file.bytes); 155 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 156 | { 157 | assert!(file.content_hash.starts_with("W/\"")); 158 | assert!(file.content_hash.ends_with("\"")); 159 | assert!(file.content_hash.len() > 4) 160 | } 161 | 162 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 163 | { 164 | let content_hash = format!("{:x}", hash); 165 | 166 | assert_eq!( 167 | file.content_hash.to_lowercase(), 168 | content_hash.to_lowercase() 169 | ); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /crates/vite-rs-axum-0-8/tests/basic_usage_test.rs: -------------------------------------------------------------------------------- 1 | mod util; 2 | 3 | use reqwest::StatusCode; 4 | use tower::ServiceExt; 5 | use vite_rs_axum_0_8::ViteServe; 6 | 7 | use axum::{ 8 | body::{self, Body}, 9 | http, 10 | }; 11 | 12 | #[derive(vite_rs::Embed)] 13 | #[root = "test_projects/basic_usage_test/app"] 14 | struct Assets; 15 | 16 | /// Note: we only have a single #[test] because we can't run multiple tests in parallel 17 | /// since the vite dev server can't be started multiple times. 18 | #[tokio::test] 19 | async fn test() { 20 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 21 | let _guard = Assets::start_dev_server(true); 22 | 23 | let hook = std::panic::take_hook(); 24 | std::panic::set_hook(Box::new(move |info| { 25 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 26 | { 27 | Assets::stop_dev_server(); 28 | } 29 | 30 | // run super's panic hook 31 | hook(info); 32 | })); 33 | 34 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 35 | std::thread::sleep(std::time::Duration::from_secs(2)); // wait for dev server to start 36 | 37 | for app in [app_with_fallback_service, app_with_mounted_service] { 38 | ensure_serves_index(app()).await; 39 | ensure_serves_public_files(app()).await; 40 | ensure_serves_imports(app()).await; 41 | } 42 | 43 | test_default_cache_strategy().await; // includes `Eager` and `None` depending on build type (release/debug) 44 | test_lazy_cache_strategy().await; 45 | test_custom_cache_strategy().await; 46 | 47 | test_cache_response().await; 48 | } 49 | 50 | fn app_with_fallback_service() -> axum::Router { 51 | axum::Router::new().fallback_service(ViteServe::new(Assets::boxed())) 52 | } 53 | 54 | fn app_with_mounted_service() -> axum::Router { 55 | axum::Router::new() 56 | .route_service("/", ViteServe::new(Assets::boxed())) 57 | .route_service("/{*path}", ViteServe::new(Assets::boxed())) 58 | } 59 | 60 | async fn test_cache_response() { 61 | use std::borrow::BorrowMut; 62 | 63 | // Ensure that the cache strategy is applied correctly 64 | let mut app = axum::Router::new().route_service("/", ViteServe::new(Assets::boxed())); 65 | 66 | let req = http::Request::builder() 67 | .uri("/") 68 | .body(Body::empty()) 69 | .unwrap(); 70 | 71 | let response = app.borrow_mut().oneshot(req).await.unwrap(); 72 | assert_eq!(response.status(), StatusCode::OK); 73 | 74 | let etag = response 75 | .headers() 76 | .get("Etag") 77 | .map(|h| h.to_str().unwrap()) 78 | .unwrap(); 79 | 80 | let req2 = http::Request::builder() 81 | .uri("/") 82 | .header("If-None-Match", etag) 83 | .body(Body::empty()) 84 | .unwrap(); 85 | 86 | let response2 = app.oneshot(req2).await.unwrap(); 87 | assert_eq!(response2.status(), StatusCode::NOT_MODIFIED); 88 | } 89 | 90 | async fn test_custom_cache_strategy() { 91 | // custom cache strategy 92 | let app = axum::Router::new().route_service( 93 | "/", 94 | ViteServe::new(Assets::boxed()) 95 | .with_cache_strategy(vite_rs_axum_0_8::CacheStrategy::Custom("asdf")), 96 | ); 97 | 98 | let req = http::Request::builder() 99 | .uri("/") 100 | .body(Body::empty()) 101 | .unwrap(); 102 | 103 | let response = app.oneshot(req).await.unwrap(); 104 | assert_eq!(response.status(), StatusCode::OK); 105 | assert_eq!( 106 | response 107 | .headers() 108 | .get("Cache-Control") 109 | .map(|h| h.to_str().unwrap()), 110 | Some("asdf") 111 | ); 112 | } 113 | 114 | async fn test_lazy_cache_strategy() { 115 | // lazy cache strategy 116 | let app = axum::Router::new().route_service( 117 | "/", 118 | ViteServe::new(Assets::boxed()).with_cache_strategy(vite_rs_axum_0_8::CacheStrategy::Lazy), 119 | ); 120 | 121 | let req = http::Request::builder() 122 | .uri("/") 123 | .body(Body::empty()) 124 | .unwrap(); 125 | 126 | let response = app.oneshot(req).await.unwrap(); 127 | assert_eq!(response.status(), StatusCode::OK); 128 | assert_eq!( 129 | response 130 | .headers() 131 | .get("Cache-Control") 132 | .map(|h| h.to_str().unwrap()), 133 | Some("max-age=0, stale-while-revalidate=604800") 134 | ); 135 | } 136 | 137 | async fn test_default_cache_strategy() { 138 | // default cache strategy is 139 | // - `CacheStrategy::None` in debug builds 140 | // - `CacheStrategy::Eager` in release builds 141 | let app = axum::Router::new().route_service("/", ViteServe::new(Assets::boxed())); 142 | 143 | // 144 | // 1. See if the server requests the right cache strategy 145 | // 146 | 147 | let request = http::Request::builder() 148 | .uri("/") 149 | .body(Body::empty()) 150 | .unwrap(); 151 | let response = app.oneshot(request).await.unwrap(); 152 | 153 | assert_eq!(response.status(), StatusCode::OK); 154 | // Ensure that the default cache strategy is `CacheStrategy::None` 155 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 156 | assert_eq!( 157 | response 158 | .headers() 159 | .get("Cache-Control") 160 | .map(|h| h.to_str().unwrap()), 161 | Some("no-cache") 162 | ); 163 | 164 | #[cfg(not(all(debug_assertions, not(feature = "debug-prod"))))] 165 | // Ensure that the default cache strategy is `CacheStrategy::Eager` 166 | assert_eq!( 167 | response 168 | .headers() 169 | .get("Cache-Control") 170 | .map(|h| h.to_str().unwrap()), 171 | Some("max-age=0, must-revalidate") 172 | ); 173 | } 174 | 175 | async fn ensure_serves_index(app: axum::Router) { 176 | let request = http::Request::builder() 177 | .uri("/") 178 | .body(Body::empty()) 179 | .unwrap(); 180 | 181 | let response = app.oneshot(request).await.unwrap(); 182 | 183 | assert_eq!(response.status(), StatusCode::OK); 184 | 185 | let body_bytes = body::to_bytes(response.into_body(), 2048).await.unwrap(); 186 | 187 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 188 | assert_eq!(body_bytes, "\n\n \n \n\n \n\n Hello World\n \n \n \n

Loading...

\n \n \n\n"); 189 | 190 | #[cfg(not(all(debug_assertions, not(feature = "debug-prod"))))] 191 | assert_eq!(body_bytes, "\n\n \n Hello World\n \n \n \n \n

Loading...

\n \n\n"); 192 | } 193 | 194 | async fn ensure_serves_public_files(app: axum::Router) { 195 | let request = http::Request::builder() 196 | .uri("/test.css") 197 | .body(Body::empty()) 198 | .unwrap(); 199 | 200 | let response = app.oneshot(request).await.unwrap(); 201 | 202 | assert_eq!(response.status(), StatusCode::OK); 203 | 204 | let body_bytes = body::to_bytes(response.into_body(), 2048).await.unwrap(); 205 | 206 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 207 | assert_eq!(body_bytes, "body {\n background-color: black;\n color: white;\n font-family: Arial, sans-serif;\n padding: 42px;\n}\n"); 208 | 209 | #[cfg(not(all(debug_assertions, not(feature = "debug-prod"))))] 210 | assert_eq!(body_bytes, "body {\n background-color: black;\n color: white;\n font-family: Arial, sans-serif;\n padding: 42px;\n}\n"); 211 | } 212 | 213 | async fn ensure_serves_imports(app: axum::Router) { 214 | let uri = if cfg!(all(debug_assertions, not(feature = "debug-prod"))) { 215 | "/script.tsx" 216 | } else { 217 | "/assets/index-CgRBhnJL.js" 218 | }; 219 | 220 | let request = http::Request::builder() 221 | .uri(uri) 222 | .body(Body::empty()) 223 | .unwrap(); 224 | 225 | let response = app.oneshot(request).await.unwrap(); 226 | 227 | assert_eq!(response.status(), StatusCode::OK); 228 | 229 | let body_bytes = body::to_bytes(response.into_body(), 262144).await.unwrap(); 230 | 231 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 232 | assert!(body_bytes.starts_with(b"import __vite__cjsImport0_react_jsxDevRuntime from \"/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=")); 233 | 234 | #[cfg(not(all(debug_assertions, not(feature = "debug-prod"))))] 235 | assert!(body_bytes.starts_with(b"(function(){const vl=document.createElement(\"link\").relList;if(vl&&vl.supports&&vl.supports(\"modulepreload\"))return;for(const Q of document.querySelectorAll('link[rel=\"modulepreload\"]'))r(Q);new MutationObserver(Q=>{for(const L of Q)if(L.type===\"childList\")for(const tl of L.addedNodes)tl.tagName===\"LINK\"&&tl.rel===\"modulepreload\"&&r(tl)}).observe(document,{childList:!0,subtree:!0});function J(Q){const L={};return Q.integrity&&(L.integrity=Q.integrity),Q.referrerPolicy&&(L.referrerPolicy=Q.referrerPolicy),Q.crossOrigin===\"use-credentials\"?L.credentials=\"include\":Q.crossOrigin===\"anonymous\"?L.credentials=\"omit\":L.credentials=\"same-origin\",L}function r(Q){if(Q.ep)return;Q.ep=!0;const L=J(Q);fetch(Q.href,L)}})();const R1=\"modulepreload\",H1=function(_){return\"/\"+_},wv={},N1=function(vl,J,r){let Q=Promise.resolve();if(J&&J.length>0){let tl=function(T){return Promise.all(T.map(U=>Promise.resolve(U).then(k=>({status:\"fulfilled\",value:k}),k=>({status:\"rejected\",reason:k}))))};document.getElementsByTagName(\"link\"")); 236 | } 237 | -------------------------------------------------------------------------------- /crates/vite-rs-embed-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | // #![feature(track_path)] // => please see comments @ crates/vite-rs/tests/recompilation_test.rs:43 2 | #![forbid(unsafe_code)] 3 | 4 | #[cfg(all( 5 | feature = "content-hash", 6 | any(feature = "debug-prod", not(debug_assertions)) 7 | ))] 8 | mod hash_utils; 9 | 10 | mod syn_utils; 11 | mod vite; 12 | 13 | use std::{ 14 | env, 15 | path::{Path, PathBuf}, 16 | }; 17 | 18 | use proc_macro::TokenStream; 19 | use proc_macro2::TokenStream as TokenStream2; 20 | use syn::{parse_macro_input, DeriveInput}; 21 | 22 | /// The root directory is the project directory where the `Cargo.toml` file is located. 23 | /// It can be overridden by specifying a `#[root = "./"]` attribute under the derive macro. 24 | /// 25 | /// This is used to resolve relative paths for the input and output directories. 26 | fn derive_absolute_root_dir(ast: &syn::DeriveInput) -> syn::Result { 27 | let mut root_attrs = syn_utils::find_attribute_values(ast, "root"); 28 | if root_attrs.len() > 1 { 29 | return Err(syn::Error::new_spanned(ast, "When specifying a custom root directory, #[derive(vite_rs::Embed)] must only contain a single #[root = \"./\"] attribute.")); 30 | } 31 | 32 | let root_dir = if root_attrs.len() == 0 { 33 | env::var("CARGO_MANIFEST_DIR").unwrap() 34 | } else { 35 | root_attrs.remove(0) 36 | }; 37 | 38 | let root_dir = PathBuf::from(root_dir); 39 | let root_dir = if root_dir.is_relative() { 40 | let base = std::env::var("CARGO_MANIFEST_DIR").unwrap(); 41 | 42 | Path::new(&base).join(&root_dir) 43 | } else { 44 | root_dir 45 | }; 46 | let root_dir = root_dir.canonicalize().expect(&format!( 47 | "Could not canonicalize root directory path. Does it exist? (path: {:?})", 48 | root_dir 49 | )); 50 | let root_dir_str = root_dir.to_str().unwrap(); 51 | 52 | Ok(root_dir_str.to_string()) 53 | } 54 | 55 | /// The output directory is where the compiled JS/assets are placed, relative to the `root_dir`. 56 | /// By default, it is set to `./dist` but can be overridden by specifying a `#[output = "./dist"]` attribute under the derive macro. 57 | /// 58 | /// Moreover, any output directory specified must be within `root_dir`. 59 | /// 60 | /// Since this deals with compiled assets, it shouldn't be necessary for non-release builds. 61 | #[cfg(any(feature = "debug-prod", not(debug_assertions)))] 62 | fn derive_relative_output_dir( 63 | ast: &syn::DeriveInput, 64 | absolute_root_dir: &str, 65 | ) -> syn::Result { 66 | let mut output_attrs = syn_utils::find_attribute_values(ast, "output"); 67 | if output_attrs.len() > 1 { 68 | return Err(syn::Error::new_spanned( 69 | ast, 70 | "When specifying a custom output directory, #[derive(vite_rs::Embed)] must only contain a single #[output = \"./dist\"] attribute.", 71 | )); 72 | } 73 | 74 | let mut output_dir = PathBuf::from(if output_attrs.len() == 0 { 75 | "dist".to_string() 76 | } else { 77 | output_attrs.remove(0) 78 | }); 79 | 80 | if output_dir.is_relative() { 81 | let root_dir = Path::new(absolute_root_dir); 82 | output_dir = root_dir.join(&output_dir); 83 | } 84 | 85 | // Instead of raising an error when the output directory doesn't exist, 86 | // we create it in release builds. This is a nicer experience. 87 | // 88 | // // if !output_dir.exists() { 89 | // // return Err(syn::Error::new_spanned( 90 | // // ast, 91 | // // format!( 92 | // // "Output directory '{}' does not exist. Please create it.", 93 | // // output_dir.display() 94 | // // ), 95 | // // )); 96 | // // } 97 | create_output_dir_if_not_exists(&ast, &output_dir)?; 98 | 99 | if !output_dir.is_dir() { 100 | return Err(syn::Error::new_spanned( 101 | ast, 102 | format!( 103 | "Output directory '{}' must be a directory", 104 | output_dir.display() 105 | ), 106 | )); 107 | } 108 | 109 | let output_dir = output_dir.canonicalize().unwrap(); 110 | 111 | let relative_output_dir = output_dir 112 | .strip_prefix(&absolute_root_dir) 113 | .expect("output dir specified with #[output = \"...\"] must be within the project root directory.") 114 | .to_str() 115 | .unwrap(); 116 | 117 | Ok(relative_output_dir.to_string()) 118 | } 119 | 120 | #[cfg(any(feature = "debug-prod", not(debug_assertions)))] 121 | fn create_output_dir_if_not_exists( 122 | ast: &syn::DeriveInput, 123 | output_dir: &PathBuf, 124 | ) -> syn::Result<()> { 125 | let create_output_dir = std::fs::create_dir_all(&output_dir); 126 | 127 | if create_output_dir.is_err_and(|e| e.kind() != std::io::ErrorKind::AlreadyExists) { 128 | return Err(syn::Error::new_spanned( 129 | ast, 130 | format!("Could not create output directory (path: {:?})", output_dir), 131 | )); 132 | } 133 | 134 | Ok(()) 135 | } 136 | 137 | /// The dev server port is the port where the vite-rs dev server will run and serve from. 138 | /// By default, it is set to a free port in the range 21012..22022 but can be overridden by specifying a `#[dev_server_port = "123"]` attribute under the derive macro. 139 | fn derive_dev_server_port(ast: &syn::DeriveInput) -> u16 { 140 | let dev_server_port_attrs = syn_utils::find_attribute_values(ast, "dev_server_port"); 141 | if dev_server_port_attrs.len() > 1 { 142 | panic!( 143 | "When specifying a custom dev server port, #[derive(vite_rs::Embed)] must only contain a single #[dev_server_port = \"\"] attribute." 144 | ); 145 | } 146 | 147 | let dev_server_port = dev_server_port_attrs.get(0).map(|port| { 148 | let port = port 149 | .parse::() 150 | .expect("dev_server_port must be a valid unsigned integer (usize)."); 151 | 152 | // // We don't compile-time check if the port is free because the user has the option 153 | // // of running the dev server themselves. A runtime check does exist; see the `vite-rs-dev-server` crate. 154 | // if !vite_rs_dev_server::util::is_port_free(port as u16) { 155 | // panic!( 156 | // "Selected vite-rs dev server port '{}' is not available.", 157 | // port 158 | // ) 159 | // } 160 | 161 | port 162 | }); 163 | 164 | dev_server_port.unwrap_or_else(|| { 165 | // If the user doesn't specify a dev_server_port, this function 166 | // returns a free port in the range 21012..22022. 167 | // 168 | // This can be a source of hair-pulling because this happens on 169 | // macro codegen. In other words, the selected free port may be 170 | // available when the macro code is initially compiled, but may 171 | // no longer be available in subsequent runs. 172 | // 173 | // At the same time, this is necessary because we need to 174 | // coordinate the dev server's port with the generated code 175 | // (to forward requests to the dev server). 176 | // 177 | // To save everyone's time, we'll strongly encourage users to 178 | // specify a #[dev_server_port = 123]. 179 | vite_rs_dev_server::util::find_free_port(21012..22022) 180 | .expect("Could not find a free port for the ViteJS dev server") 181 | }) 182 | } 183 | 184 | /// If crate_path is defined, use that as a syn::Path, otherwise use the crate's name. 185 | /// This is useful when someone is using this crate from a crate path that is different from 186 | /// the default: `crate::vite_rs`. In that case, they can specify something like: 187 | /// `#[crate_path = "some::path::to::vite_rs"]`. 188 | fn derive_crate_path(ast: &syn::DeriveInput) -> syn::Result { 189 | let crate_path_attrs = syn_utils::find_attribute_values(ast, "crate_path"); 190 | if crate_path_attrs.len() > 1 { 191 | return Err(syn::Error::new_spanned( 192 | ast, 193 | "When specifying a custom crate path, #[derive(vite_rs::Embed)] must only contain a single #[crate_path = \"crate_name\"] attribute.", 194 | )); 195 | } 196 | 197 | let crate_path = { 198 | if crate_path_attrs.len() == 0 { 199 | // we don't use env!("CARGO_PKG_NAME") because this code is in the vite-rs-embed-macro, but the end user will be using vite-rs 200 | "vite_rs" 201 | } else { 202 | &crate_path_attrs.get(0).unwrap() 203 | } 204 | }; 205 | 206 | Ok(syn::parse_str::(crate_path)?) 207 | } 208 | 209 | fn impl_vitejs_embed(ast: &syn::DeriveInput) -> syn::Result { 210 | syn_utils::ensure_unit_struct(ast)?; 211 | 212 | let absolute_root_dir = derive_absolute_root_dir(ast)?; 213 | #[cfg(any(feature = "debug-prod", not(debug_assertions)))] 214 | let relative_output_dir = derive_relative_output_dir(ast, &absolute_root_dir)?; 215 | let crate_path = derive_crate_path(ast)?; 216 | 217 | let dev_server_host = "localhost"; 218 | let dev_server_port = derive_dev_server_port(ast); 219 | 220 | vite::build::generate_rust_code( 221 | /* dev-only */ 222 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 223 | dev_server_host, 224 | /* dev-only */ 225 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 226 | dev_server_port, 227 | &crate_path, 228 | &ast.ident, 229 | &absolute_root_dir, 230 | /* prod-only */ 231 | #[cfg(any(feature = "debug-prod", not(debug_assertions)))] 232 | &relative_output_dir, 233 | ) 234 | } 235 | 236 | /// For explanations of the attributes, please see: 237 | /// - #[root]: derive_absolute_root_dir (define above) 238 | /// - #[output]: derive_relative_output_dir (define above) 239 | /// - #[dev_server_port]: derive_dev_server_port (define above) 240 | /// - #[crate_path]: derive_crate_path (define above) 241 | #[proc_macro_derive(Embed, attributes(root, output, dev_server_port, crate_path))] 242 | pub fn derive_input_object(input: TokenStream) -> TokenStream { 243 | let ast = parse_macro_input!(input as DeriveInput); 244 | match impl_vitejs_embed(&ast) { 245 | Ok(ok) => ok.into(), 246 | Err(e) => e.to_compile_error().into(), 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /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 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Haris Khan 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /crates/vite-rs-embed-macro/src/vite/mod.rs: -------------------------------------------------------------------------------- 1 | /// PROD build 2 | #[cfg(any(not(debug_assertions), feature = "debug-prod"))] 3 | pub mod build { 4 | use proc_macro2::TokenStream as TokenStream2; 5 | use quote::quote; 6 | use std::{collections::BTreeMap, path::PathBuf}; 7 | 8 | mod file_entry; 9 | use file_entry::FileEntry; 10 | mod vite_manifest; 11 | 12 | fn list_compiled_files(absolute_output_path: &str) -> Vec { 13 | let compiled_files = walkdir::WalkDir::new(absolute_output_path) 14 | .into_iter() 15 | .filter_map(|entry| entry.ok()) 16 | .filter(|entry| entry.file_type().is_file()) 17 | .map(|entry| { 18 | let path = entry.path(); 19 | let path = path.strip_prefix(absolute_output_path).unwrap(); 20 | let path = path.to_str().unwrap().to_string(); 21 | 22 | path 23 | }) 24 | .filter(|path| !path.starts_with(".vite")) // ignore vite manifest or other vite-internal files 25 | .collect::>(); 26 | 27 | compiled_files 28 | } 29 | 30 | pub fn generate_rust_code( 31 | crate_path: &syn::Path, 32 | struct_ident: &syn::Ident, 33 | absolute_root_dir: &str, 34 | relative_output_dir: &str, 35 | ) -> syn::Result { 36 | // proc_macro::tracked_path::path(absolute_root_dir); // => please see comments @ crates/vite-rs/tests/recompilation_test.rs:43 37 | 38 | let absolute_output_path = { 39 | let p = PathBuf::from_iter(&[absolute_root_dir, relative_output_dir]); 40 | let p = p.canonicalize().expect(&format!( 41 | "Could not canonicalize output directory path. Does it exist? (path: {:?})", 42 | p 43 | )); 44 | 45 | p.to_str().unwrap().to_string() 46 | }; 47 | 48 | let vite_build = std::process::Command::new("npx") 49 | .arg("vite") 50 | .arg("build") 51 | .arg("--manifest") // force manifest generation to `.vite/manifest.json` 52 | .arg("--outDir") 53 | .arg(&absolute_output_path) 54 | .current_dir(absolute_root_dir) 55 | .spawn() 56 | .expect("failed to build") 57 | .wait() 58 | .expect("failed to wait for build to complete") 59 | .success(); 60 | 61 | if !vite_build { 62 | return Err(syn::Error::new( 63 | proc_macro2::Span::call_site(), 64 | "ViteJS build failed", 65 | )); 66 | } 67 | 68 | // the vite manifest is only available AFTER the build, so don't move this line up :) 69 | let absolute_vite_manifest_path = { 70 | let p = PathBuf::from_iter(&[&absolute_output_path, ".vite", "manifest.json"]) 71 | .canonicalize() 72 | .expect(&format!( 73 | "Could not canonicalize ViteJS manifest path. Does it exist? (path: {:?})", 74 | absolute_output_path 75 | )); 76 | 77 | p.to_str().unwrap().to_string() 78 | }; 79 | 80 | let vite_manifest = vite_manifest::load_vite_manifest(&absolute_vite_manifest_path); 81 | 82 | let mut match_values = BTreeMap::new(); 83 | let mut list_values = Vec::::new(); 84 | 85 | list_compiled_files(&absolute_output_path) 86 | .iter() 87 | .flat_map(|relative_file_path| { 88 | let absolute_file_path = { 89 | let p = PathBuf::from_iter(&[&absolute_output_path, relative_file_path]) 90 | .canonicalize() 91 | .expect("Failed to canonicalize"); 92 | 93 | p.to_str().expect("Failed to convert to string").to_string() 94 | }; 95 | 96 | list_values.push(relative_file_path.clone()); 97 | println!( 98 | "Adding entry: {} for {}", 99 | &relative_file_path, absolute_file_path 100 | ); 101 | 102 | FileEntry::new(relative_file_path.clone(), absolute_file_path).map_err(|e| { 103 | return syn::Error::new( 104 | proc_macro2::Span::call_site(), 105 | format!("Failed to read Vite manifest: {}", e), 106 | ); 107 | }) 108 | }) 109 | .for_each(|entry| { 110 | match_values.insert(entry.match_key().clone(), entry.match_value(&crate_path)); 111 | }); 112 | 113 | // Aliases help us refer to entrypoints from their uncompiled name. 114 | // 115 | // For example: 116 | // - A compiled file 'dist/pack1-1234.js' would originally be 'src/pack1.ts'. 117 | // Therefore, Struct::get("src/pack1.ts") should return the contents of 'dist/pack1-1234.js'. 118 | let aliases = { 119 | let mut aliases = BTreeMap::new(); 120 | 121 | vite_manifest 122 | .iter() 123 | .filter(|e| e.1.isEntry.unwrap_or(false)) 124 | .for_each(|(key, value)| { 125 | if !match_values.contains_key(key) { 126 | aliases.insert(key.clone(), value.file.clone()); 127 | } 128 | }); 129 | 130 | aliases.into_iter().map(|(alias, path)| { 131 | quote! { 132 | (#alias, #path), 133 | } 134 | }) 135 | }; 136 | 137 | let match_values = match_values.into_iter().map(|(path, bytes)| { 138 | quote! { 139 | (#path, #bytes), 140 | } 141 | }); 142 | 143 | let array_len = list_values.len(); 144 | 145 | Ok(quote! { 146 | impl #struct_ident { 147 | /// Path resolution; handles aliasing for file paths 148 | fn resolve(path: &str) -> &str { 149 | const ALIASES: &'static [(&'static str, &'static str)] = &[ 150 | #(#aliases)* 151 | ]; 152 | 153 | let path = ALIASES.binary_search_by_key(&path, |entry| entry.0).ok().map(|index| ALIASES[index].1).unwrap_or(path); 154 | 155 | path 156 | } 157 | 158 | pub fn get(path: &str) -> Option<#crate_path::ViteFile> { 159 | let path = Self::resolve(path); 160 | 161 | const ENTRIES: &'static [(&'static str, #crate_path::ViteFile)] = &[ 162 | #(#match_values)* 163 | ]; 164 | let position = ENTRIES.binary_search_by_key(&path, |entry| entry.0); 165 | position.ok().map(|index| ENTRIES[index].1.clone()) 166 | } 167 | 168 | fn names() -> ::std::slice::Iter<'static, &'static str> { 169 | const ITEMS: [&str; #array_len] = [#(#list_values),*]; 170 | ITEMS.iter() 171 | } 172 | 173 | /// Iterates over the file paths in the compiled ViteJS output directory 174 | pub fn iter() -> impl ::std::iter::Iterator> { 175 | Self::names().map(|x| ::std::borrow::Cow::from(*x)) 176 | } 177 | 178 | pub fn boxed() -> ::std::boxed::Box { 179 | ::std::boxed::Box::new(#struct_ident {}) 180 | } 181 | } 182 | 183 | impl #crate_path::GetFromVite for #struct_ident { 184 | fn get(&self, file_path: &str) -> ::std::option::Option<#crate_path::ViteFile> { 185 | #struct_ident::get(file_path) 186 | } 187 | 188 | fn clone_box(&self) -> ::std::boxed::Box { 189 | ::std::boxed::Box::new(#struct_ident {}) 190 | } 191 | } 192 | }) 193 | } 194 | } 195 | 196 | /// DEV build 197 | #[cfg(all(debug_assertions, not(feature = "debug-prod")))] 198 | pub mod build { 199 | use proc_macro2::TokenStream as TokenStream2; 200 | use quote::quote; 201 | 202 | pub fn generate_rust_code( 203 | dev_server_host: &str, 204 | dev_server_port: u16, 205 | crate_path: &syn::Path, 206 | struct_ident: &syn::Ident, 207 | absolute_root_dir: &str, 208 | ) -> syn::Result { 209 | #[cfg(feature = "ctrlc")] 210 | let start_dev_server = quote! { 211 | pub fn start_dev_server( 212 | register_ctrl_c_handler: bool, 213 | ) -> Option<#crate_path::vite_rs_dev_server::ViteProcess> { 214 | #crate_path::vite_rs_dev_server::start_dev_server(#absolute_root_dir, #dev_server_host, #dev_server_port, register_ctrl_c_handler) 215 | } 216 | }; 217 | 218 | #[cfg(not(feature = "ctrlc"))] 219 | let start_dev_server = quote! { 220 | pub fn start_dev_server() -> Option<#crate_path::vite_rs_dev_server::ViteProcess> { 221 | #crate_path::vite_rs_dev_server::start_dev_server(#absolute_root_dir, #dev_server_host, #dev_server_port) 222 | } 223 | }; 224 | 225 | let etag = if cfg!(feature = "content-hash") { 226 | quote! { 227 | let etag = res 228 | .headers() 229 | .get(#crate_path::vite_rs_dev_server::reqwest::header::ETAG) 230 | .expect("FATAL: ViteJS dev server did not return an `ETag` header.") 231 | .to_str() 232 | .unwrap() 233 | .to_string(); 234 | } 235 | } else { 236 | quote! {} 237 | }; 238 | 239 | let content_hash = if cfg!(feature = "content-hash") { 240 | quote! { content_hash: etag, } 241 | } else { 242 | quote! {} 243 | }; 244 | 245 | Ok(quote! { 246 | impl #struct_ident { 247 | #start_dev_server 248 | 249 | pub fn stop_dev_server() { 250 | #crate_path::vite_rs_dev_server::stop_dev_server() 251 | } 252 | 253 | pub fn iter() -> impl ::std::iter::Iterator> { 254 | // https://github.com/rust-lang/rust/issues/36375 255 | if true { 256 | unimplemented!("iter() is out of scope for dev builds and is left unimplemented. It is available in release builds (or when the `debug-prod` feature is enabled)") 257 | } else { 258 | vec![].into_iter() 259 | } 260 | } 261 | 262 | pub fn get(path: &str) -> Option<#crate_path::ViteFile> { 263 | let path = path.to_string(); 264 | 265 | std::thread::spawn(move || { 266 | let client = #crate_path::vite_rs_dev_server::reqwest::blocking::Client::new(); 267 | let url = format!( 268 | "http://{}:{}/{}", 269 | #dev_server_host, 270 | #dev_server_port, 271 | path 272 | ); 273 | 274 | match client.get(&url).send() { 275 | Ok(res) => { 276 | if res.status() == 404 { 277 | return None; 278 | } 279 | 280 | let content_type = res 281 | .headers() 282 | .get(#crate_path::vite_rs_dev_server::reqwest::header::CONTENT_TYPE) 283 | .expect("FATAL: ViteJS dev server did not return a content type!") 284 | .to_str() 285 | .unwrap() 286 | .to_string(); 287 | 288 | let content_length = res 289 | .content_length() 290 | .expect("FATAL: ViteJS dev server did not return a `Content-Length` header."); 291 | 292 | #etag 293 | 294 | let mut bytes = res.bytes().unwrap().to_vec(); 295 | 296 | Some(#crate_path::ViteFile { 297 | last_modified: None, /* we don't send this in dev! */ 298 | content_type: content_type, 299 | content_length: content_length, 300 | bytes: bytes, 301 | #content_hash 302 | }) 303 | } 304 | Err(e) => { 305 | println!("ERR! {:#?}", e); 306 | None 307 | }, 308 | } 309 | }) 310 | .join() 311 | .expect("Failed to spawn thread to fetch ViteJS dev server resource.") 312 | } 313 | 314 | pub fn boxed() -> ::std::boxed::Box { 315 | ::std::boxed::Box::new(#struct_ident {}) 316 | } 317 | } 318 | 319 | impl #crate_path::GetFromVite for #struct_ident { 320 | fn get(&self, file_path: &str) -> Option<#crate_path::ViteFile> { 321 | #struct_ident::get(file_path) 322 | } 323 | 324 | fn clone_box(&self) -> ::std::boxed::Box { 325 | ::std::boxed::Box::new(#struct_ident {}) 326 | } 327 | } 328 | }) 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /crates/vite-rs/test_projects/custom_output_dir_test/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.22.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "autocfg" 22 | version = "1.3.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 25 | 26 | [[package]] 27 | name = "backtrace" 28 | version = "0.3.73" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" 31 | dependencies = [ 32 | "addr2line", 33 | "cc", 34 | "cfg-if", 35 | "libc", 36 | "miniz_oxide", 37 | "object", 38 | "rustc-demangle", 39 | ] 40 | 41 | [[package]] 42 | name = "base64" 43 | version = "0.22.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 46 | 47 | [[package]] 48 | name = "bitflags" 49 | version = "2.6.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 52 | 53 | [[package]] 54 | name = "bumpalo" 55 | version = "3.16.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 58 | 59 | [[package]] 60 | name = "bytes" 61 | version = "1.7.1" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" 64 | 65 | [[package]] 66 | name = "cc" 67 | version = "1.1.15" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" 70 | dependencies = [ 71 | "shlex", 72 | ] 73 | 74 | [[package]] 75 | name = "cfg-if" 76 | version = "1.0.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 79 | 80 | [[package]] 81 | name = "cfg_aliases" 82 | version = "0.2.1" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 85 | 86 | [[package]] 87 | name = "command-group" 88 | version = "5.0.1" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "a68fa787550392a9d58f44c21a3022cfb3ea3e2458b7f85d3b399d0ceeccf409" 91 | dependencies = [ 92 | "nix 0.27.1", 93 | "winapi", 94 | ] 95 | 96 | [[package]] 97 | name = "ctrl_c_handling_test" 98 | version = "0.1.0" 99 | dependencies = [ 100 | "ctrlc", 101 | "vite-rs", 102 | ] 103 | 104 | [[package]] 105 | name = "ctrlc" 106 | version = "3.4.5" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" 109 | dependencies = [ 110 | "nix 0.29.0", 111 | "windows-sys 0.59.0", 112 | ] 113 | 114 | [[package]] 115 | name = "fnv" 116 | version = "1.0.7" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 119 | 120 | [[package]] 121 | name = "form_urlencoded" 122 | version = "1.2.1" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 125 | dependencies = [ 126 | "percent-encoding", 127 | ] 128 | 129 | [[package]] 130 | name = "futures-channel" 131 | version = "0.3.30" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 134 | dependencies = [ 135 | "futures-core", 136 | "futures-sink", 137 | ] 138 | 139 | [[package]] 140 | name = "futures-core" 141 | version = "0.3.30" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 144 | 145 | [[package]] 146 | name = "futures-io" 147 | version = "0.3.30" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 150 | 151 | [[package]] 152 | name = "futures-sink" 153 | version = "0.3.30" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 156 | 157 | [[package]] 158 | name = "futures-task" 159 | version = "0.3.30" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 162 | 163 | [[package]] 164 | name = "futures-util" 165 | version = "0.3.30" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 168 | dependencies = [ 169 | "futures-core", 170 | "futures-io", 171 | "futures-sink", 172 | "futures-task", 173 | "memchr", 174 | "pin-project-lite", 175 | "pin-utils", 176 | "slab", 177 | ] 178 | 179 | [[package]] 180 | name = "gimli" 181 | version = "0.29.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" 184 | 185 | [[package]] 186 | name = "hermit-abi" 187 | version = "0.3.9" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 190 | 191 | [[package]] 192 | name = "http" 193 | version = "1.1.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 196 | dependencies = [ 197 | "bytes", 198 | "fnv", 199 | "itoa", 200 | ] 201 | 202 | [[package]] 203 | name = "http-body" 204 | version = "1.0.1" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 207 | dependencies = [ 208 | "bytes", 209 | "http", 210 | ] 211 | 212 | [[package]] 213 | name = "http-body-util" 214 | version = "0.1.2" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 217 | dependencies = [ 218 | "bytes", 219 | "futures-util", 220 | "http", 221 | "http-body", 222 | "pin-project-lite", 223 | ] 224 | 225 | [[package]] 226 | name = "httparse" 227 | version = "1.9.4" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" 230 | 231 | [[package]] 232 | name = "hyper" 233 | version = "1.4.1" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" 236 | dependencies = [ 237 | "bytes", 238 | "futures-channel", 239 | "futures-util", 240 | "http", 241 | "http-body", 242 | "httparse", 243 | "itoa", 244 | "pin-project-lite", 245 | "smallvec", 246 | "tokio", 247 | "want", 248 | ] 249 | 250 | [[package]] 251 | name = "hyper-util" 252 | version = "0.1.7" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" 255 | dependencies = [ 256 | "bytes", 257 | "futures-channel", 258 | "futures-util", 259 | "http", 260 | "http-body", 261 | "hyper", 262 | "pin-project-lite", 263 | "socket2", 264 | "tokio", 265 | "tower", 266 | "tower-service", 267 | "tracing", 268 | ] 269 | 270 | [[package]] 271 | name = "idna" 272 | version = "0.5.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 275 | dependencies = [ 276 | "unicode-bidi", 277 | "unicode-normalization", 278 | ] 279 | 280 | [[package]] 281 | name = "ipnet" 282 | version = "2.9.0" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 285 | 286 | [[package]] 287 | name = "itoa" 288 | version = "1.0.11" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 291 | 292 | [[package]] 293 | name = "js-sys" 294 | version = "0.3.70" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" 297 | dependencies = [ 298 | "wasm-bindgen", 299 | ] 300 | 301 | [[package]] 302 | name = "lazy_static" 303 | version = "1.5.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 306 | 307 | [[package]] 308 | name = "libc" 309 | version = "0.2.158" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" 312 | 313 | [[package]] 314 | name = "log" 315 | version = "0.4.22" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 318 | 319 | [[package]] 320 | name = "memchr" 321 | version = "2.7.4" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 324 | 325 | [[package]] 326 | name = "mime" 327 | version = "0.3.17" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 330 | 331 | [[package]] 332 | name = "mime_guess" 333 | version = "2.0.5" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 336 | dependencies = [ 337 | "mime", 338 | "unicase", 339 | ] 340 | 341 | [[package]] 342 | name = "miniz_oxide" 343 | version = "0.7.4" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 346 | dependencies = [ 347 | "adler", 348 | ] 349 | 350 | [[package]] 351 | name = "mio" 352 | version = "1.0.2" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 355 | dependencies = [ 356 | "hermit-abi", 357 | "libc", 358 | "wasi", 359 | "windows-sys 0.52.0", 360 | ] 361 | 362 | [[package]] 363 | name = "nix" 364 | version = "0.27.1" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" 367 | dependencies = [ 368 | "bitflags", 369 | "cfg-if", 370 | "libc", 371 | ] 372 | 373 | [[package]] 374 | name = "nix" 375 | version = "0.29.0" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 378 | dependencies = [ 379 | "bitflags", 380 | "cfg-if", 381 | "cfg_aliases", 382 | "libc", 383 | ] 384 | 385 | [[package]] 386 | name = "object" 387 | version = "0.36.4" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" 390 | dependencies = [ 391 | "memchr", 392 | ] 393 | 394 | [[package]] 395 | name = "once_cell" 396 | version = "1.19.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 399 | 400 | [[package]] 401 | name = "percent-encoding" 402 | version = "2.3.1" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 405 | 406 | [[package]] 407 | name = "pin-project" 408 | version = "1.1.5" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 411 | dependencies = [ 412 | "pin-project-internal", 413 | ] 414 | 415 | [[package]] 416 | name = "pin-project-internal" 417 | version = "1.1.5" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 420 | dependencies = [ 421 | "proc-macro2", 422 | "quote", 423 | "syn", 424 | ] 425 | 426 | [[package]] 427 | name = "pin-project-lite" 428 | version = "0.2.14" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 431 | 432 | [[package]] 433 | name = "pin-utils" 434 | version = "0.1.0" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 437 | 438 | [[package]] 439 | name = "proc-macro2" 440 | version = "1.0.86" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 443 | dependencies = [ 444 | "unicode-ident", 445 | ] 446 | 447 | [[package]] 448 | name = "quote" 449 | version = "1.0.37" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 452 | dependencies = [ 453 | "proc-macro2", 454 | ] 455 | 456 | [[package]] 457 | name = "reqwest" 458 | version = "0.12.7" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" 461 | dependencies = [ 462 | "base64", 463 | "bytes", 464 | "futures-channel", 465 | "futures-core", 466 | "futures-util", 467 | "http", 468 | "http-body", 469 | "http-body-util", 470 | "hyper", 471 | "hyper-util", 472 | "ipnet", 473 | "js-sys", 474 | "log", 475 | "mime", 476 | "once_cell", 477 | "percent-encoding", 478 | "pin-project-lite", 479 | "serde", 480 | "serde_json", 481 | "serde_urlencoded", 482 | "sync_wrapper", 483 | "tokio", 484 | "tower-service", 485 | "url", 486 | "wasm-bindgen", 487 | "wasm-bindgen-futures", 488 | "web-sys", 489 | "windows-registry", 490 | ] 491 | 492 | [[package]] 493 | name = "rustc-demangle" 494 | version = "0.1.24" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 497 | 498 | [[package]] 499 | name = "ryu" 500 | version = "1.0.18" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 503 | 504 | [[package]] 505 | name = "same-file" 506 | version = "1.0.6" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 509 | dependencies = [ 510 | "winapi-util", 511 | ] 512 | 513 | [[package]] 514 | name = "serde" 515 | version = "1.0.209" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" 518 | dependencies = [ 519 | "serde_derive", 520 | ] 521 | 522 | [[package]] 523 | name = "serde_derive" 524 | version = "1.0.209" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" 527 | dependencies = [ 528 | "proc-macro2", 529 | "quote", 530 | "syn", 531 | ] 532 | 533 | [[package]] 534 | name = "serde_json" 535 | version = "1.0.127" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" 538 | dependencies = [ 539 | "itoa", 540 | "memchr", 541 | "ryu", 542 | "serde", 543 | ] 544 | 545 | [[package]] 546 | name = "serde_urlencoded" 547 | version = "0.7.1" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 550 | dependencies = [ 551 | "form_urlencoded", 552 | "itoa", 553 | "ryu", 554 | "serde", 555 | ] 556 | 557 | [[package]] 558 | name = "shlex" 559 | version = "1.3.0" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 562 | 563 | [[package]] 564 | name = "slab" 565 | version = "0.4.9" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 568 | dependencies = [ 569 | "autocfg", 570 | ] 571 | 572 | [[package]] 573 | name = "smallvec" 574 | version = "1.13.2" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 577 | 578 | [[package]] 579 | name = "socket2" 580 | version = "0.5.7" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 583 | dependencies = [ 584 | "libc", 585 | "windows-sys 0.52.0", 586 | ] 587 | 588 | [[package]] 589 | name = "syn" 590 | version = "2.0.76" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" 593 | dependencies = [ 594 | "proc-macro2", 595 | "quote", 596 | "unicode-ident", 597 | ] 598 | 599 | [[package]] 600 | name = "sync_wrapper" 601 | version = "1.0.1" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" 604 | dependencies = [ 605 | "futures-core", 606 | ] 607 | 608 | [[package]] 609 | name = "tinyvec" 610 | version = "1.8.0" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 613 | dependencies = [ 614 | "tinyvec_macros", 615 | ] 616 | 617 | [[package]] 618 | name = "tinyvec_macros" 619 | version = "0.1.1" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 622 | 623 | [[package]] 624 | name = "tokio" 625 | version = "1.39.3" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" 628 | dependencies = [ 629 | "backtrace", 630 | "libc", 631 | "mio", 632 | "pin-project-lite", 633 | "socket2", 634 | "windows-sys 0.52.0", 635 | ] 636 | 637 | [[package]] 638 | name = "tower" 639 | version = "0.4.13" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 642 | dependencies = [ 643 | "futures-core", 644 | "futures-util", 645 | "pin-project", 646 | "pin-project-lite", 647 | "tokio", 648 | "tower-layer", 649 | "tower-service", 650 | ] 651 | 652 | [[package]] 653 | name = "tower-layer" 654 | version = "0.3.3" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 657 | 658 | [[package]] 659 | name = "tower-service" 660 | version = "0.3.3" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 663 | 664 | [[package]] 665 | name = "tracing" 666 | version = "0.1.40" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 669 | dependencies = [ 670 | "pin-project-lite", 671 | "tracing-core", 672 | ] 673 | 674 | [[package]] 675 | name = "tracing-core" 676 | version = "0.1.32" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 679 | dependencies = [ 680 | "once_cell", 681 | ] 682 | 683 | [[package]] 684 | name = "try-lock" 685 | version = "0.2.5" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 688 | 689 | [[package]] 690 | name = "unicase" 691 | version = "2.7.0" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 694 | dependencies = [ 695 | "version_check", 696 | ] 697 | 698 | [[package]] 699 | name = "unicode-bidi" 700 | version = "0.3.15" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 703 | 704 | [[package]] 705 | name = "unicode-ident" 706 | version = "1.0.12" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 709 | 710 | [[package]] 711 | name = "unicode-normalization" 712 | version = "0.1.23" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 715 | dependencies = [ 716 | "tinyvec", 717 | ] 718 | 719 | [[package]] 720 | name = "url" 721 | version = "2.5.2" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 724 | dependencies = [ 725 | "form_urlencoded", 726 | "idna", 727 | "percent-encoding", 728 | ] 729 | 730 | [[package]] 731 | name = "version_check" 732 | version = "0.9.5" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 735 | 736 | [[package]] 737 | name = "vite-rs" 738 | version = "0.0.0" 739 | dependencies = [ 740 | "vite-rs-dev-server", 741 | "vite-rs-embed-macro", 742 | ] 743 | 744 | [[package]] 745 | name = "vite-rs-dev-server" 746 | version = "0.0.0" 747 | dependencies = [ 748 | "command-group", 749 | "ctrlc", 750 | "lazy_static", 751 | "reqwest", 752 | ] 753 | 754 | [[package]] 755 | name = "vite-rs-embed-macro" 756 | version = "0.0.0" 757 | dependencies = [ 758 | "mime_guess", 759 | "proc-macro2", 760 | "quote", 761 | "serde", 762 | "serde_json", 763 | "syn", 764 | "vite-rs-dev-server", 765 | "walkdir", 766 | ] 767 | 768 | [[package]] 769 | name = "walkdir" 770 | version = "2.5.0" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 773 | dependencies = [ 774 | "same-file", 775 | "winapi-util", 776 | ] 777 | 778 | [[package]] 779 | name = "want" 780 | version = "0.3.1" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 783 | dependencies = [ 784 | "try-lock", 785 | ] 786 | 787 | [[package]] 788 | name = "wasi" 789 | version = "0.11.0+wasi-snapshot-preview1" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 792 | 793 | [[package]] 794 | name = "wasm-bindgen" 795 | version = "0.2.93" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" 798 | dependencies = [ 799 | "cfg-if", 800 | "once_cell", 801 | "wasm-bindgen-macro", 802 | ] 803 | 804 | [[package]] 805 | name = "wasm-bindgen-backend" 806 | version = "0.2.93" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" 809 | dependencies = [ 810 | "bumpalo", 811 | "log", 812 | "once_cell", 813 | "proc-macro2", 814 | "quote", 815 | "syn", 816 | "wasm-bindgen-shared", 817 | ] 818 | 819 | [[package]] 820 | name = "wasm-bindgen-futures" 821 | version = "0.4.43" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" 824 | dependencies = [ 825 | "cfg-if", 826 | "js-sys", 827 | "wasm-bindgen", 828 | "web-sys", 829 | ] 830 | 831 | [[package]] 832 | name = "wasm-bindgen-macro" 833 | version = "0.2.93" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" 836 | dependencies = [ 837 | "quote", 838 | "wasm-bindgen-macro-support", 839 | ] 840 | 841 | [[package]] 842 | name = "wasm-bindgen-macro-support" 843 | version = "0.2.93" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" 846 | dependencies = [ 847 | "proc-macro2", 848 | "quote", 849 | "syn", 850 | "wasm-bindgen-backend", 851 | "wasm-bindgen-shared", 852 | ] 853 | 854 | [[package]] 855 | name = "wasm-bindgen-shared" 856 | version = "0.2.93" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" 859 | 860 | [[package]] 861 | name = "web-sys" 862 | version = "0.3.70" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" 865 | dependencies = [ 866 | "js-sys", 867 | "wasm-bindgen", 868 | ] 869 | 870 | [[package]] 871 | name = "winapi" 872 | version = "0.3.9" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 875 | dependencies = [ 876 | "winapi-i686-pc-windows-gnu", 877 | "winapi-x86_64-pc-windows-gnu", 878 | ] 879 | 880 | [[package]] 881 | name = "winapi-i686-pc-windows-gnu" 882 | version = "0.4.0" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 885 | 886 | [[package]] 887 | name = "winapi-util" 888 | version = "0.1.9" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 891 | dependencies = [ 892 | "windows-sys 0.59.0", 893 | ] 894 | 895 | [[package]] 896 | name = "winapi-x86_64-pc-windows-gnu" 897 | version = "0.4.0" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 900 | 901 | [[package]] 902 | name = "windows-registry" 903 | version = "0.2.0" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 906 | dependencies = [ 907 | "windows-result", 908 | "windows-strings", 909 | "windows-targets", 910 | ] 911 | 912 | [[package]] 913 | name = "windows-result" 914 | version = "0.2.0" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 917 | dependencies = [ 918 | "windows-targets", 919 | ] 920 | 921 | [[package]] 922 | name = "windows-strings" 923 | version = "0.1.0" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 926 | dependencies = [ 927 | "windows-result", 928 | "windows-targets", 929 | ] 930 | 931 | [[package]] 932 | name = "windows-sys" 933 | version = "0.52.0" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 936 | dependencies = [ 937 | "windows-targets", 938 | ] 939 | 940 | [[package]] 941 | name = "windows-sys" 942 | version = "0.59.0" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 945 | dependencies = [ 946 | "windows-targets", 947 | ] 948 | 949 | [[package]] 950 | name = "windows-targets" 951 | version = "0.52.6" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 954 | dependencies = [ 955 | "windows_aarch64_gnullvm", 956 | "windows_aarch64_msvc", 957 | "windows_i686_gnu", 958 | "windows_i686_gnullvm", 959 | "windows_i686_msvc", 960 | "windows_x86_64_gnu", 961 | "windows_x86_64_gnullvm", 962 | "windows_x86_64_msvc", 963 | ] 964 | 965 | [[package]] 966 | name = "windows_aarch64_gnullvm" 967 | version = "0.52.6" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 970 | 971 | [[package]] 972 | name = "windows_aarch64_msvc" 973 | version = "0.52.6" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 976 | 977 | [[package]] 978 | name = "windows_i686_gnu" 979 | version = "0.52.6" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 982 | 983 | [[package]] 984 | name = "windows_i686_gnullvm" 985 | version = "0.52.6" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 988 | 989 | [[package]] 990 | name = "windows_i686_msvc" 991 | version = "0.52.6" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 994 | 995 | [[package]] 996 | name = "windows_x86_64_gnu" 997 | version = "0.52.6" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1000 | 1001 | [[package]] 1002 | name = "windows_x86_64_gnullvm" 1003 | version = "0.52.6" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1006 | 1007 | [[package]] 1008 | name = "windows_x86_64_msvc" 1009 | version = "0.52.6" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1012 | --------------------------------------------------------------------------------