vitejs bundling, templating, and SSR for actix-web · Wulf/create-rust-app@c8df21e

2 min read Original article ↗
1+

use actix_files::NamedFile;

2+

use actix_web::{HttpRequest, HttpResponse, Scope, web};

3+

use http::StatusCode;

4+

use tera::Context;

5+

use crate::util::template_utils::TEMPLATES;

6+7+

struct SinglePageApplication {

8+

view_name: String

9+

}

10+11+

/// 'route': the route where the SPA should be served from, for example: "/app"

12+

/// 'view': the view which renders the SPA, for example: "spa/index.html"

13+

pub fn render_single_page_application(route: String, view: String) -> Scope {

14+

use actix_web::web::Data;

15+16+

let route = route.strip_prefix("/").unwrap();

17+

let view = view.strip_prefix("/").unwrap();

18+19+

actix_web::web::scope(&format!("/{}{{tail:(/.*)?}}", route))

20+

.app_data(Data::new(SinglePageApplication {

21+

view_name: view.to_string()

22+

}))

23+

.route("", web::get().to(render_spa_handler))

24+

}

25+26+

async fn render_spa_handler(req: HttpRequest, spa_info: web::Data<SinglePageApplication>) -> HttpResponse {

27+

let content = TEMPLATES.render(spa_info.view_name.as_str(), &Context::new()).unwrap();

28+

template_response(content)

29+

}

30+31+

const DEFAULT_TEMPLATE: &'static str = "index.html";

32+

fn to_template_name(request_path: &str) -> &'_ str {

33+

let request_path = request_path.strip_prefix("/").unwrap();

34+

return if request_path.eq("") { DEFAULT_TEMPLATE } else { request_path }

35+

}

36+37+

pub async fn render_views(req: HttpRequest) -> HttpResponse {

38+

let path = req.path();

39+40+

if path.eq("/__vite_ping") {

41+

println!("The vite dev server seems to be down...");

42+

return HttpResponse::NotFound().finish();

43+

}

44+45+

let mut template_path = to_template_name(req.path());

46+

let mut content_result = TEMPLATES.render(template_path, &Context::new());

47+48+

if content_result.is_err() {

49+

#[cfg(debug_assertions)] {

50+

// dev asset serving

51+

let asset_path = &format!("./frontend{path}");

52+

if std::path::PathBuf::from(asset_path).is_file() {

53+

println!("ASSET_FILE {path} => {asset_path}");

54+

return NamedFile::open(asset_path).unwrap().into_response(&req)

55+

}

56+57+

let public_path = &format!("./frontend/public{path}");

58+

if std::path::PathBuf::from(public_path).is_file() {

59+

println!("PUBLIC_FILE {path} => {public_path}");

60+

return NamedFile::open(public_path).unwrap().into_response(&req)

61+

}

62+

}

63+64+

#[cfg(not(debug_assertions))] {

65+

// production asset serving

66+

let static_path = &format!("./frontend/dist{path}");

67+

if std::path::PathBuf::from(static_path).is_file() {

68+

return NamedFile::open(static_path).unwrap().into_response(&req);

69+

}

70+

}

71+72+

content_result = TEMPLATES.render(DEFAULT_TEMPLATE, &Context::new());

73+

template_path = DEFAULT_TEMPLATE;

74+

if content_result.is_err() {

75+

// default template doesn't exist -- return 404 not found

76+

return HttpResponse::NotFound().finish()

77+

}

78+

}

79+80+

println!("TEMPLATE_FILE {path} => {template_path}");

81+82+

let content = content_result.unwrap();

83+84+

template_response(content)

85+

}

86+87+

fn template_response(content: String) -> HttpResponse {

88+

let mut content = content;

89+

#[cfg(debug_assertions)] {

90+

let inject: &str = r##"

91+

<!-- development mode -->

92+

<script type="module">

93+

import RefreshRuntime from 'http://localhost:3000/@react-refresh'

94+

RefreshRuntime.injectIntoGlobalHook(window)

95+

window.$RefreshReg$ = () => {}

96+

window.$RefreshSig$ = () => (type) => type

97+

window.__vite_plugin_react_preamble_installed__ = true

98+

</script>

99+

<script type="module" src="http://localhost:3000/src/main.tsx"></script>

100+

"##;

101+102+

if content.contains("<body>") {

103+

content = content.replace("<body>", &format!("<body>{inject}"));

104+

} else {

105+

content = format!("{inject}{content}");

106+

}

107+

}

108+109+

HttpResponse::build(StatusCode::OK)

110+

.content_type("text/html")

111+

.body(content)

112+

}