How to host SPA files and embed too with axum and rust-embed - rust

I'm having hard time understanding how to embed SPA (single page application) files with rust-embed and axum.
I have no trouble without rust-embed using a single line of code with axum (from here):
app.fallback(get_service(ServeDir::new("./app/static")).handle_error(error_handler))
It works because all files are correctly downloaded. But:
FIRST PROBLEM
What is missing for a properly SPA handling is the redirect on the index.html if for example the user reloads the page on a SPA nested route.
Example: I'm on the page: /home/customers which is not a file nor a dir but just a fake javascript route and if I reload the page axum gives me 404 (Not found).
SECOND PROBLEM
I need to embed those files in my final executable. In Golang this is "native" using embed: directive.
I saw that in Rust this is well done with rust-embed but I cannot complete my task for SPA.
The need is that every path typed by the user (and that is not an existent file such as .js or .css which obviously must be downloaded by the browser) leads to the "index.html" file in the root of my static dir.
If I use the example axum code I can see the route:
.route("/dist/*file", static_handler.into_service())
which has /dist/*file and I don't need that /dist because the index.html calls many files with custom paths, such as /_works, menu, images.
If I remove the dist part I get this error:
thread 'main' panicked at 'Invalid route: insertion failed due to conflict with previously registered route: /index.html'
Can you help me understand how to properly accomplish this task?
Thanks.

I had a similar issue, building with Vue and Axum/Rust.
Here's how I solved Problem one
Install the tower_http crate
use axum::routing::get_service to serve the build SPA.
//example implementation
...
//static file mounting
let assets_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("views");
let static_files_service = get_service(
ServeDir::new(assets_dir).append_index_html_on_directories(true),
)
.handle_error(|error: std::io::Error| async move {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Unhandled internal error: {}", error),
)
});
...
Mount the static file rendering
//mount the app routes and middleware
let app = Router::new()
.fallback(static_files_service)
.nest("/api/v1/", routes::root::router())
.layer(cors)
.layer(TraceLayer::new_for_http())
.layer(Extension(database));
Check out the full source code here. Another thing is, Axum seems to have breaking changes in subsequent versions as I found out here, so you might need to check the doc/example that corresponds to the version of Axum you are using :)

Related

How to make the Rust Game of Life WebAssembly work as a static website?

I have gone through the tutorial for the Rust Game of Life and have a working game in a web browser, but it only works from the demo web server that comes bundled with it. I can start the server with npm start and it runs the webpack-dev-server on port 8080. When I access the site through that port, it works fine. However, if I try to copy the site to a web server like Apache, it does not load correctly. The error I am currently getting from it is:
Error importing `index.js`: TypeError: Error resolving module specifier “wasm-game-of-life”. Relative module specifiers must start with “./”, “../” or “/”. bootstrap.js:5:23
<anonymous> http://www.north-winds.org/gol/bootstrap.js:5
From the tutorial, the root of the website is a folder called www/ in the repository and the generated wasm module from the Rust program is placed under pkg/. There is a symbolic link from www/node_modules/wasm-game-of-life that points up to ../../pkg/ and I've replaced that symlink with an actual copy of the top-level pkg/ folder so that the website is entirely contained inside the www/ folder and then placed that folder on my website at http://www.north-winds.org/gol/, however, accessing it returns the error above. What do I need to modify to make it work stand-alone?
As I understand it, this WebAssembly Game-of-Life is basically a stand-alone client-side app and should not require anything beyond a web server that can provide static files with the appropriate mime-types attached. I don't see anything special that should be required. I did see mention of WebSockets somewhere, but I don't know why that is required for this app. I compared this to the "Hello, World" WebAssembly example for C from https://webassembly.org/ and it ended up with a .wasm file generated from the C source code, and a single JavaScript and HTML supporting file to execute it. The files worked correctly when simply copied to static web server location. This is what I'd like for the Rust example.
Some relevant code from the Rust Game-of-Life is as follows. The top-level HTML file includes this among other lines:
<script src="./bootstrap.js"></script>
The bootstrap JavaScript file contains only this:
import("./index.js")
.catch(e => console.error("Error importing `index.js`:", e));
And the index.js file that it references has this among other glue logic for the Wasm:
import { Universe, Cell } from "wasm-game-of-life";
// Import the WebAssembly memory at the top of the file.
import { memory } from "wasm-game-of-life/wasm_game_of_life_bg";
What's missing to make this work standalone?
The www and pkg folders contain the source files you need, but you do not have a static site yet. The create-wasm-app template uses Webpack, so you need to build the final output by running npm run build in the www folder. This will create a subfolder named dist which contains the actual static files that can be placed on your web server.

React dynamic image importing in development

I am building a React application which needs to display images dynamically which are stored, by the thousands, on a server-side file system. All of my attempts to successfully implement this have failed, including many which were taken from responses to similar questions.
Some details:
I used create-react-app to initialize my application. I am running in development mode (have not run npm-build). I'm using Express.js (Node.js) as a web-server, which I interact with through a proxy (only '/api' http requests use the proxy). My js code which attempts to 'require' the images is in the 'src' folder. The images are located in an 'images' folder in the default 'public' folder.
I thought I had found the solution when reading this page from create-react-app, as it states to use the public folder when 'You have thousands of images and need to dynamically reference their paths'. The page further instructs to use '%PUBLIC_URL%' or 'process.env.PUBLIC_URL' to access the 'public' folder. When using either of these I receive an 'Error: Cannot find module' message. Upon checking I notice that 'process.env.PUBLIC_URL' contains an empty string, and quickly notice that PUBLIC_URL is ignored in development mode.
I find this to be tremendously confusing, given that the 'Using the Public Folder' page is apparently describing the development phase of production, and yet it advises the use of something which is meaningless during development. Adding to my confusion, it appears as if the contents of that page resolved the issue for nearly all of those who have encountered a similar requirement in the past (example: 1, example: 2; both fail for me). Likewise, all attempts to to construct relative paths to the 'public' folder from the 'src' folder have yielded error messages. Failed code example:
let img = process.env.PUBLIC_URL + '/images/Team.jpg';
<img src={require(`${img}`)} alt="X" />
Error: Cannot find module '/images/Team.jpg'
I never imagined showing images in React would be so difficult. Any help is truly very much appreciated.
I think you are correct, you just don't need the require, return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />; as you can see their docs
If you open in your browser http://localhost:PORT/images/Team.jpg that should open.
That's the reason process.env.PUBLIC_URL is empty in development, because they resolve everything inside this folder directly.

Static path Express + Nginx

I have very strange behavior in my application. I want to add multiply static path to my app.js file.
First for main application:
app.use(express.static(path.join(__dirname, 'public')));
And second for landing pages which located in 'ads' directory.
app.use('/ads', express.static(path.join(__dirname, 'ads')));
Folder structure:
public
- build
- ...
ads
- currency
- public
- build
- 1.css
- 2.js
- index.html
...
app.js
In my main application all JS and CSS files loading successfully, but when i get in to path /ads/currency my index.html loaded but .css, .js and images don't. However if i pass to command line /ads/currency/public/build/1.css it is loading normal.
Does someone know about it?
Screenshots was attached:
Nginx config:
It has nothing to do with your configuration but might be related to Chrome (i.e. an extension), since the error says ERR_BLOCKED_BY_CLIENT.
Check your ad blockers log. The keyword ads in the path might be blocked, since a filter might try to catch a javascript miner.
You should make sure the move the whole "ads" part into a different folder - avoiding the keyword "ads" at all. Disabling the ad blocker might work for you know, bot not for your users

Why can't I require files which are available due to app.use?

If a directory has been made available to a node application in the server.js file which sits in the main directory using:
app.use("/scripts",express.static(__dirname + "/scripts"));
and I attempt to use require from a file inside of that directory (/scripts/custom.js) using:
var Testing123 = require('../app/models/article');
Is there a reason this is not possible? and is there a solution to that problem?
Edit: In one of my views (views/tree.ejs) I use:
<script type="text/javascript" src="../scripts/custom.js"></script>
to access my Custom script which sits inside my scripts folder which is made available using express.static, Custom uses a web scraper to scrape articles and present them in circles (in the form of an image, title and link) on views/tree.ejs, I now want custom.js to save each article it creates to a mongodb database but to do so, it needs access to things like my Article Schema hence the problem above.
You cannot because Node.js scripts and browser scripts do not run in the same context. Your app.use call just exposes a /scripts route that serves assets statically on your HTTP Server.
Your scripts/custom.js script seems to be a browser-side script (Because you load it with a script tag inside an ejs view) but you want to use require inside it and this will not work as this is a Node.js function.
Have a look at LearnYouNode which is an excellent Node beginner tutorial so that you will understand how modules work in Node and know a bit more about the separation between server-side and client-side JS.

How do you change the express static directories?

I am working on a development platform, I have code similar to the following:
app.use('/public', express.static( config.directory.public ));
The issue is that there are many (100s) of projects each with its own directory structure. The project will be selected via the URL:
http://localhost/dev/accounts
Where accounts is a project with its own directory tree and static public directory.
I do not want to run a separate copy of node for each project. Once a project has been selected via the URL then express needs to be reconfigured to serve files for that request.
However, that approach is probably not feasible because we may be working on many projects at the same time. So every request for static files would have to be processed according to the project URL. It seems to negate the benefit of static directories.
I think what I am after is a way to put variables into the directory path
http://localhost/dev/accounts
Would set a variable called prj = "accounts" and then somehow set express so that the root directory is "c:\projects\" + prj + "\public".
If I simply issue a new app.use(..) statement for every request I imagine bad things will happen.
Maybe I am better off just manually reading the file contents for each static request and sending the contents back.
Is there another way to approach this problem?
I'm not sure if I understood your question correctly, but express serves static files in file directories automatically for you. If you have a bunch of projects in some 'path/to/public' folder, you just need to do something like
app.use('/', express.static( __dirname + '/public' ));
That way, you just need to type some url like
http://localhost/project1
or
http://localhost/project2

Resources