├── .gitignore
├── Makefile
├── index.html
├── README.md
├── package.json
├── client.jsx
└── server.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /dist
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | start:
2 | npm run start
3 | build:
4 | npm run build
5 | test:
6 | npm run test
7 | clean:
8 | npm run clean
9 |
10 | .PHONY: build test clean
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React use Rust demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React `use rust`
2 |
3 | Use Rust in your React!
4 |
5 |
6 |
7 |
8 | ## TODO
9 |
10 | - [ ] Package Rust for npm
11 | - [ ] Add more folders and files with code that does nothing to look more professional
12 | - [ ] Create `create-react-use-rust` package cuz this ecosystem is broken
13 | - [ ] Add typescript support
14 | - [ ] Make somebody else figure out publishing this to npm (I am still not over it)
15 | - [ ] Remove typescript support
16 | - [ ] Tell windows ppl to just use WSL
17 | - [ ] esbuild is not cursed enough, gotta switch to something else
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-use-rust",
3 | "version": "1.0.0",
4 | "description": "Use Rust in your server side react components",
5 | "main": "this field is so weird",
6 | "scripts": {
7 | "start": "npm run build && node server.js",
8 | "build": "node build.js",
9 | "clean": "rm -r dist",
10 | "test": "true"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/elnardu/react-use-rust.git"
15 | },
16 | "keywords": [
17 | "C",
18 | "Rust",
19 | "React",
20 | "Typescript",
21 | "Bun",
22 | "Zig"
23 | ],
24 | "author": "me",
25 | "license": "WTFPL",
26 | "bugs": {
27 | "url": "https://github.com/elnardu/react-use-rust/issues"
28 | },
29 | "homepage": "https://github.com/elnardu/react-use-rust#readme",
30 | "devDependencies": {
31 | "esbuild": "0.19.5",
32 | "react": "^18.2.0",
33 | "react-dom": "^18.2.0"
34 | },
35 | "dependencies": {
36 | "express": "^4.18.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/client.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | // I haven't used any of this bs in a year, hopefully it still the same
5 |
6 | /// Look mum! I am so cool, I put strings into a different file
7 | function css(strings) {
8 | let style = document.getElementById("css-in-js-style-thing");
9 | if (!style) {
10 | style = document.createElement("style");
11 | style.id = "css-in-js-style-thing";
12 | document.head.appendChild(style);
13 | }
14 |
15 | const randomClassId = Math.random().toString(36).slice(2);
16 | style.textContent += `
17 | .${randomClassId} {
18 | ${strings[0]}
19 | }`;
20 | return randomClassId;
21 | }
22 |
23 | async function runRust(code) {
24 | const res = await fetch("/rpc/rce", {
25 | method: "POST",
26 | body: JSON.stringify({ code: code }),
27 | headers: {
28 | "Content-Type": "application/json",
29 | },
30 | });
31 | return await res.json();
32 | }
33 |
34 | const appStyles = css`
35 | margin: 3rem auto;
36 | max-width: 600px;
37 | padding: 0 1rem;
38 | `;
39 |
40 | const App = () => (
41 |
42 |
React use Rust demo
43 |
44 |
45 | );
46 |
47 | const Demo = () => {
48 | const [output, setOutput] = useState(null);
49 | const rustHelloWorld = async () => {
50 | "use rust";
51 | fn main() {
52 | println!("Hello, world from Rust!");
53 | println!("Also hi HN!");
54 | }
55 | };
56 | const onClick = async () => {
57 | const out = await rustHelloWorld();
58 | setOutput(out.stdout);
59 | };
60 |
61 | return (
62 |
63 |
64 |
Output from Rust:
65 |
{ output }
66 |
67 | );
68 | };
69 |
70 | ReactDOM.render(, document.getElementById("app"));
71 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const childProcess = require("child_process");
3 | const fs = require("fs");
4 | const fsPromises = require("fs/promises");
5 | const path = require("path");
6 | const os = require("os");
7 |
8 | const app = express();
9 | const port = process.env.PORT ?? 3000;
10 |
11 | app.use(express.static("dist"));
12 | app.use(express.json());
13 |
14 | function spawn(command, args) {
15 | return new Promise((resolve) => {
16 | // damn this api is shit
17 | const p = childProcess.spawn(command, args);
18 | const stdouts = [];
19 | const stderrs = [];
20 |
21 | p.stdout.on("data", (data) => {
22 | stdouts.push(data);
23 | });
24 |
25 | p.stderr.on("data", (data) => {
26 | stderrs.push(data);
27 | });
28 |
29 | p.on("close", (code) => {
30 | resolve({
31 | code: code,
32 | stdout: Buffer.concat(stdouts).toString(),
33 | stderr: Buffer.concat(stderrs).toString(),
34 | });
35 | });
36 | });
37 | }
38 |
39 | function maketmp() {
40 | return new Promise((resolve, reject) => {
41 | fs.mkdtemp(path.join(os.tmpdir(), "use-rust"), (err, dir) => {
42 | if (err !== null) reject(err);
43 | else resolve(dir);
44 | });
45 | });
46 | }
47 |
48 | async function runRust(code) {
49 | const dir = await maketmp();
50 | const rustFile = path.join(dir, "main.rs");
51 | const outFile = path.join(dir, "main");
52 | await fsPromises.writeFile(rustFile, decodeURIComponent(code));
53 | await spawn("rustc", [rustFile, "-o", outFile]);
54 | const out = await spawn(outFile, []);
55 | return out;
56 | }
57 |
58 | app.post("/rpc/rce", async (req, res) => {
59 | const { code } = req.body;
60 | const out = await runRust(code);
61 | res.json(out);
62 | });
63 |
64 | async function checkRust() {
65 | try {
66 | const out = await spawn("rustc", ["--version"]);
67 | if (out.code !== 0) {
68 | throw new Error("Rust is not installed");
69 | }
70 | } catch (e) {
71 | console.error("Please install rust");
72 | console.error(" curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh");
73 | console.error("Bye!");
74 | process.exit(1);
75 | }
76 | }
77 |
78 | // Can somebody tell me if top level async is finally ok to use?
79 | (async () => {
80 | await checkRust();
81 |
82 | app.listen(port, () => {
83 | console.log(`Listening on port ${port}`);
84 | });
85 | })();
86 |
--------------------------------------------------------------------------------