13 ms·
Making a Statically-Linked, Single-File Web App with React and Rust
- chme 9y agoI really like this guide to get started. Its a bit short though. I would really like to see the setup of a simple uni- or even bi-directional rpc or value binding mechanism between react and rust.
- anderspitman 9y agoThe project that I extracted this from actually uses a bi-directional websocket with proto3. It's just a simple message system and doesn't have binding. The code isn't great or documented but you can check it out here: https://github.com/anderspitman/battle_beetles https://github.com/anderspitman/battle_beetles
- chme 9y agoThx! Nice. Maybe json-api on a websocket on the same port as a transport would also be possible? Protobuf seems a bit overkill for something that is that tightly integrated together IMO. But if it works, it works.
- anderspitman 9y agoThe use of protobuf is mostly to provide a schema and enforce strict[er] typing on JS.
- Justsignedup 9y agoI do similar, except I just deploy to AWS as a static website. Super duper minimal everything.
- INTPenis 9y agoInline JS and css? My first thought was that I'd rather leave the Rust part out and do what you suggest in order to deploy to static file cloud hosts.
- CapacitorSet 9y agoIf you want to go super-minimal at the cost of being more dependent on Amazon, you can deploy the static website on S3 and use Route53 as an HTTP frontend.
- kowdermeister 9y agoLast week I learned that if you configure your bucket name the same as your domain, then a simple CNAME record will work without Route53.
- urschrei 9y agoI don't think you can make SSL work on AWS without Route53, though. Which is (for better or worse) about to become a big problem when Google starts penalising non-SSL sites. You also can't make a AAAA record, so no IPv6.
- seeekr 9y agoPut Cloudflare in front for SSL. Not sure if that makes the solution more or less complex, never worked with Route53.
- estsauver 9y agoAs of two years ago, there was a bug with mobile safari and hash fragment preservation, which actually made using a lot of client side JS code very difficult to use directly from s3.
- cloverich 9y agoThis is what I do on Google cloud, presumably the same. CloudFlare for ssl, google Cloud DNS, then a Google bucket. Real simple, real fast.
- ztoben 9y agoYep, this is what I do for my site. 51 cents a month.
- DaveTheSane 9y agoAnd you can scale this so good, we do something like this with every branch/user story to test it on live environments. Supereasy, once set up.
- ggregoire 9y agoYes. Here is a guide to deploy a React app on S3 and CloudFront (with a backend on EC2): https://stackoverflow.com/questions/41250087/how-to-deploy-a-react-node-express-application-on-aws https://stackoverflow.com/questions/41250087/how-to-deploy-a...
- kowdermeister 9y agoThe tutorial stops where it starts to get interesting, actually implementing any logic with the backend other than serving static files. It would have been interesting to follow through a database transaction or see some data / image manipulation.
- shae 9y agoI have such a thing in Haskell: https://github.com/shapr/sporkle/blob/master/app/Main.hs https://github.com/shapr/sporkle/blob/master/app/Main.hs It's not Rust, but it is a really crappy exercise tracking app I wrote for teaching a class. Thing different about my code: I build the HTML in the source itself, and I don't use React
- anderspitman 9y agoI actually agree. I extracted this tutorial from a project I'm working on developing an RTS game for school. I'm experimenting with having the game loop run in Rust and seeing if I can send render updates fast enough to smoothly run the UI in JS. It's working pretty well so far. I'm using Protobuf 3. I'd love to get capnproto or flatbuffers working eventually, but neither of them seem to have both JS and Rust support mature yet. I think the next obvious evolution from this tutorial would be to connect a sqlite db. Not sure how easy statically linking with it is though.
- NiceGuy_Ty 9y agoAre you talking about statically linking the database itself? Seems like it'd be better to keep the database separate from the binary, unless you're working with a read-only database. For static-linking the sqlite3 library, if you're using rusqlite you can just specify the bundled feature in Cargo.toml. ``` [dependencies.rusqlite] version = "your-version-number" features = ["bundled"] ```
- anderspitman 9y agoYeah I meant statically linking the sqlite library. Thanks for the tip on how to do it
- nothrabannosir 9y agoIf you only have static files, might as well go with Netlify. Can’t beat them for simplicity.
- romanovcode 9y agoIf you want the same thing only with C# instead of Rust you can use https://github.com/ElectronNET/Electron.NET https://github.com/ElectronNET/Electron.NET
- yoz-y 9y agoThat seems to be a wrapper around Electron, the article talks about building a web server with all of the assets bundled inside the binary as strings.
- everdev 9y agoFor me, the Rust syntax is hard to look at and read. I much prefer Go which reads like C, Perl, PHP, JS or many other common languages. I'm wondering if people who prefer Phython prefer Rust? I've been developing for 20+ years and the syntax alone makes Rust feel prohibitive to me.
- carlmr 9y agoI find it has a lot of similarities with the C-family of languages. I'd say more with C++ though (generics, namespaces etc).
- Tade0 9y agoAnecdata: Not a fan of Python's syntax, but Rust looks readable to me.
- the_mitsuhiko 9y agoHow does go look more like C than Rust?
- strken 9y agoTangentially related, but I've used https://github.com/mjibson/esc https://github.com/mjibson/esc before and liked it. I found it was a bit simpler than https://github.com/GeertJohan/go.rice https://github.com/GeertJohan/go.rice.
- axaxs 9y agoYou're not alone. I've tried a few times to dive into rust, but just find something about the syntax jarring. It's not so much bad, it's just unpleasant to look at for me. I'm sure it's a great language, but not for me for that reason.
- NiceGuy_Ty 9y agoTo be fair, this sample of code includes macro usage (every function call that ends in an exclamation point is actually a macro). The syntax inside the macros is different from normal, legal rust syntax. Still, I agree that Rust's syntax approaches C++ level of complexity at times. I find that writing actual statements and expressions is simple enough, but writing structs, lambdas, and function definitions requires knowing how to specify types, type bounds (if using generics), lifetimes (if using references), and Fully Qualified Syntax (for referencing types/items in other modules). And that's before actually having to come to terms with the borrow checker. For what it's worth, once you get over the initial learning curve the cognitive burden goes down. Rust's syntax, to me, is actually visually distinctive enough that it's easy to parse out types, expressions, declarations, etc. from just a quick glance.
- deleted 9y ago[deleted]
- rlopezcc 9y agoI love these things. I made my resume page by piping together two small rust pograms and a bash script to get a single html, from a json in firebase, a handlebars template and several static assets.
- kiddico 9y agoI've never used rust before, but have been meaning to look into it and play around some. OP mentions the include_str! function(maybe its a function, not sure what '!' signifies here) and it blew my mind. I'm betting that almost every language that I've used has something like this, but the succinctness of it and how it's used to keep the source clean, but still compile in the text is really cool.
- empath75 9y agoIt's a macro.
- thezoq2 9y agothe ! indicates that it is a macro, so include_string! is run at compile time.
- anderspitman 9y agoI know what you mean! I first learned about `include_str!` more than a year ago and I've wanted to try this ever since. The '!' means `include_str!` is a macro in Rust, similar to a C macro in function, but Rust has a much better system for macros and ships with many useful ones.
- hie2neiL 9y agoNormally webservers serve files with the sendfile system call for zero copy IO. If the string is statically included in the binary then it first has to copy it into kernel space to send the data. So this is actually less efficient. In theory this could be re-optimized with vmsplice, but I wonder whether that webserver actually does it because vmsplice's lifetime requirements are... complex (although a &'static str would meet those requirements). Or the binary could sendfile itself if it knew the position of the string in its binary image, but that information is not preserved by a &str.
- NiceGuy_Ty 9y agoPutting the string in the binary is really only useful for trying to do ultra-portable deployments like this imo. The overhead of copying the string into memory is pretty meaningless in the use case where you only expect a single, home-desktop user. Now if this was a production server having to service thousands of clients than the sendfile optimization becomes much more important.
- hardwaresofton 9y agoBeing able to include arbitrary data inside a static binary is definitely a nice side-effect of rust and golang being able to produce static binaries. If you're interested in stuff like this for go, I've used https://github.com/GeertJohan/go.rice https://github.com/GeertJohan/go.rice with great success. [EDIT] "feature" -> "side effect" "rust and golang" -> "rust and golang being able to produce static binaries"
- hpkuarg 9y agoHow does go.rice compare with go-bindata[0]? I only have experience with the latter. [0] https://github.com/jteeuwen/go-bindata https://github.com/jteeuwen/go-bindata
- hardwaresofton 9y agoI haven't compared them (as I haven't used go-bindata), but go.rice has a pretty ergonomic API -- I once took a look at go-bindata, but I much prefer go.rice. go.rice also seems more active in general.
- anderspitman 9y agoThis looks really cool. I'd love to have a library for Rust to make this more seamless. In particular it would be nice to have a "development mode" where you just host the HTML/JS files normally while you're working on them, then flip a production switch when you're ready to have them compiled into the binary. Does go.rice have something like this?
- steveklabnik 9y agoI’ve come halfway to building this a few times; it’d be extremely helpful for mdbook, for example.
- hardwaresofton 9y agoI actually thought that's what Rouille (https://github.com/tomaka/rouille https://github.com/tomaka/rouille) was, which was mentioned in the blog post, but it looks like just uses the macro from that package (to include the strings), not the actual library even, since at the end of the day he opts for rocket (https://rocket.rs/ https://rocket.rs/) [EDIT] - I didn't address your question about go.rice -- from what I know, it doesn't have this kind of thing built in, and I don't know that it even should... "flipping a production switch" is a pretty application-specific endeavor. Also this sounds like something you should be doing with your build tools..., can't include something in the binary at runtime. Maybe I misunderstood what you were asking. To clarify, the description for go.rice is: > go.rice is a Go package that makes working with resources such as html,js,css,images,templates, etc very easy. During development go.rice will load required files directly from disk. Upon deployment it is easy to add all resource files to a executable using the rice tool, without changing the source code for your package. go.rice provides several methods to add resources to a binary.
- interfixus 9y agoHave done several sites in this exact fashion, except coded in Nim, not Rust. A reasonably sized, musl-linked, absolutely standalone executable with SQLite baked in for data storage. That's two files all told - the program and the data. Super simple deployment, I usually stuff the thing behind a Caddy server these days. Never let me down so far.
- z3t4 9y agoit's very rare to only run one http based server app (port 80). So you probably want to put a proxy (like nginx) infront of it, but then you could just let it serve the files.
- syncopate 9y agoI wanted to use rust for web development for a while but always struggled with just getting an OAuth2 example running in the past, has this improved recently?
- jimktrains2 9y agoEverything is always improving. Unless you let us know what you had issues with, no one can tell you if it's been made easier. For instance, are these issues with rust you're having, or with a particular framework? Without any information, it's a meaningless question.
- syncopate 9y agoIf this project discussed here is using react and rust together, could I just use an react example for OAuth2 to get started with? Up to some months ago, there were no examples of using any of the Rust web frameworks with OAuth2.
- sampo 9y ago(misunderstood)
- mzzter 9y agoThe JavaScript file is actually added to the Rust binary at compile time as a static string.
- codetrotter 9y agoI turned my private website (not the one listed on my HN profile) into a single binary doing something similar to what OP is doing, except I didn't know about include_str!, only include!, so in my build.rs I first read in my static files and then I write them out as byte array declarations into intermediate Rust files which I then include! in my src/main.rs. Thanks to this short blog post by OP I learned something that will let me simplify some of my code. However the thing that I am doing is not completely useless, because I have been able to embed fonts, favicon and images as well with the method that I am using. The code I have written for this is only the beginning but if anyone wants to look at my code in order to see how I did it here you go: https://gist.github.com/ctsrc/4c4cc05254d12bbc8937a0ea385fcdad https://gist.github.com/ctsrc/4c4cc05254d12bbc8937a0ea385fcd... It's far from great but it's a start. Let me know how you would do it better :) Speaking of Rust and websites, in Python I have fallen in love with the pyTenjin templating engine. http://www.kuwata-lab.com/tenjin/pytenjin-users-guide.html http://www.kuwata-lab.com/tenjin/pytenjin-users-guide.html. Does anyone know of a templating engine like pyTenjin but for Rust?
- masklinn 9y ago> However the thing that I am doing is not completely useless, because I have been able to embed fonts, favicon and images as well with the method that I am using. FWIW there's also an include_bytes! macro to embed binary data right into the executable without going through a separate rustification step.
- deleted 9y ago[deleted]
- codetrotter 9y agoI actually already use the include_bytes! macro within the build.rs. Looking at the source I see that I create arrays of arrays of bytes, not just arrays of bytes as opposed to what I originally said. It was a few months since I wrote that code so I hope I would be forgiven for not remembering such details ;) My eventual plan that I remember now was to make it so that I could reference static files (CSS, JS, icons and images, fonts) in my static HTML files and HTML templates, and build.rs would automatically include the bytes of those files.
- mzzter 9y agoCool small example. It's helpful to have the actual cli commands on hand. There's just a small typo. The script path in the HTML mount file and the router path need to be the same, but do not match. Either one needs to change: // ui/dist/index.html <script src="/main.js"></script> // src/main.rs (GET) ["/bundle.js"] => { Response::text(bundle) },
- anderspitman 9y agoWhat I really need is a tool to generate blog-post code snippets from actual code ;) Thanks, fixed!
- cbcoutinho 9y agoThere's a small typo on the article in /ui/dist/index.html - the script source should be '/bundle.js', not '/main.js'. I think the git repo corrected this error
- anderspitman 9y agoThanks, fixed!
- halayli 9y agoI don't see the point of this. Why not use nginx which comes with startup scripts and point it to the site's directory that includes the bundle/index files? If the single binary that just serves static files you're better off with a web server that supports https, rules, vhosts, etc and is battle tested. Not to mention that this binary you created will need startup scripts of some sort depending on the platform.
- s4vi0r 9y agoAs widely used/loved as nginx seems to be, I couldn't for the life of me find any good resources that hand hold a bit more than just reading the docs and hoping for the best. Know any good nginx books or courses?
- enobrev 9y agoI agree with what you're saying in regards to simply using nginx to handle web requests. I don't agree that there needs to be a point to this to do it.
- NiceGuy_Ty 9y agoUltra portability. And there shouldn't be any startup scripts, however, the binary does need to be compiled for the target platform. I'm pretty sure the use case of this is more akin to an electron replacement, and not a production web server. So the user would just download and run the binary, and interact with the app from their browser.
- thom_nic 9y agoHow about a web interface on an embedded device?
- finchisko 9y agoI don't see point of this either. I aware this would cost me lot of hardly obtained karma, but I feel I have to say this. Strange how some people are obsessed with hype. I think what raised interest of this article was just word "Rust" in the title. Rust seems to be wet dream of many developers. They heard it's safe, so it will magically solve all their problems. But the usage of Rust in this case is meaningless. You could do the same with python, nodejs, java, just plain nginx or million other (and better) ways. Just first google query returned probably better solution if you like single static binary http://miniweb.sourceforge.net/ http://miniweb.sourceforge.net/. It would be two files instead of one miniweb binary and static site compressed as 7z. How does this thing scale? What about SSL? Do I have to put reverse SSL terminating proxy in front of it? Yes, you say? Ok, why not just use that proxy for serving those static files too (nginx does that) and skip this thing completely. What about performance? Have you tried to httperf on it? I would appreciate if this did more then just being a single binary. Like Facebook's HipHop compiler of PHP to static executable or something like that. Sorry but this is real bullshit.
- alpb 9y agoIs there something like "50% statically-linked". I'm trying to understand if static linking can be partial. I often see it done it all in or none.
- zeroxfe 9y agoTechnically, no, but in practice, yes. For example, default static linking does not include the base C libraries that are pretty much available in every OS. You have to explicitly tell the linker to also link those in.
- steveklabnik 9y agoRemember that ultimately, you link multiple things in, and each can be linked dynamically or statically. So, a "100% statically linked" binary would have every library statically linked, a "100% dynamically linked" binary would have every library dynamically linked, with all the ranges in between. This comes up with Rust due to what's in the article: > This part can be skipped if you don’t need 100% static linking. Rust statically links most libraries by default anyway, except for things like libc. Rust statically links Rust code by default, but has a dynamic link to libc. So Rust binaries are "mostly statically linked" for this reason. MUSL lets you link that final dependency, getting up to 100%.
- IshKebab 9y agoSure, it means 50% of its dependencies are linked to statically.
- kodablah 9y ago> This is somewhat similar to what Electron accomplishes, but your backend is in Rust rather than JavaScript, and the user navigates to the app from their browser. An option in the middle is webview[0] which will "appify" it by using OS-native browser lib so you don't have to carry Chromium, V8, or all the multi-process ugliness but you still get the web stack. Also everybody, don't forget to check your Host headers when building apps running HTTP daemons locally or you'll be open to DNS rebinding attack. And if your use case can support it, a random port is nice too. 0 - https://github.com/zserge/webview https://github.com/zserge/webview 1 - https://en.wikipedia.org/wiki/DNS_rebinding https://en.wikipedia.org/wiki/DNS_rebinding
- anderspitman 9y agoThis looks awesome. Does it add much size to the binary?
- kodablah 9y agoJust the size of the tiny bit of C code you'd compile with. It uses shared libs already on the system (though not familiar w/ how many distros have gtk-webkit2 on Linux).
- Waterluvian 9y agoI just wish electron could somehow harness the fact that everyone has a browser installed.
- mrec 9y agoHaving a browser installed isn't the problem. The problem is having a specific version of a specific browser installed because that's the only thing you've tested against. (I'm not condoning this, and haven't worked with Electron myself, but that's my understanding of the motivation.)
- saagarjha 9y agoThat’s literally what a web view does, though.
- ashleyn 9y agoCan it pass C10k?
- esaym 9y agoYou can do this in pretty much any language. Back in the early 2000's, people used to take the Perl source code and replace the main loop (you know, the part that actually opens files and reads in your "perl scripts") with the text of a cut and pasted perl script. So when the executable ran, instead opening a file, it simply ran the perl code that was nothing but a (giant) string within the perl interpreter executable. Nothing stopping you from including the code from a perl http server or other perl framework instead. I think cpanel did something like this for many years as their toolset was all perl but distributed as a single binary file.
- slobotron 9y agoCan of course still do this with FatPacker: https://metacpan.org/pod/App::FatPacker https://metacpan.org/pod/App::FatPacker But, AFAIK, only supports Pure Perl code, so one is SOL for most database drivers etc...
- esaym 9y agoYea I haven't used fatpacker but I've heard it is only for "pure perl" and can't be used with anything that drops down to XS or C.
- kbenson 9y agoPAR[1] is what you're looking for. It supports loading XS modules by overriding DynaLoader bootstrapping methods; it writes shared object file to a temporary file at the time it is needed. 1: https://metacpan.org/pod/PAR https://metacpan.org/pod/PAR
- khanan 9y agoOr, you know... Python.
- robotmay 9y agoWhat if you don't like Python?
- hughes 9y agoHow do you make a statically-linked, single-file web app with python?
- pjmlp 9y agoWith something like py2exe.
- IshKebab 9y agoHa, Python is probably one of the hardest to distribute languages out there. Hence the creation of virtualenv, grumpy, py2exe. None are proper solutions.
- dr-ando 9y agoIf is interesting to you, I also suggest to check out my Rust crate bui-backend[0]. This is a library for building Browser User Interface (BUI) backends. The key idea is a datastore which is synced between the backend and the browser. The demo has several example frontends. The most interesting perhaps is the yew[1] framework, which is somewhat like React but in Rust. This lets you share code directly between the backend (natively compiled) and frontend (compiled to wasm). There is also a pure Javascript frontend, an Elm frontend, and a Rust stdweb frontend in the demo. 0 - http://github.com/astraw/bui-backend http://github.com/astraw/bui-backend 1 - https://github.com/DenisKolodin/yew https://github.com/DenisKolodin/yew
- falcor84 9y agoHow hard would it be to use something like this to deploy directly onto a hypervisor, like with Erlang on Xen?
- steveklabnik 9y agoI had a little Rust site running on rumprun a year or two ago. Not exactly the same, but same idea.
- seabrookmx 9y agoNot sure about on a hypervisor, but if you're using Docker, you could do something like: FROM scratch COPY ./bin/app /app ENTRYPOINT ["app"] which will make a barebones image with no OS cruft. You need to make sure your binary _is_ truly statically linked though. The key being the inclusion of musl libc in in the OP's build. I've tried this with golang and you need to pass some odd flags I can't remember off the top of my head to get it to not dynamically link gnu libc, but once working you have container images that are incredibly small. I'd imagine with a minimal container host like CoreOS you get largely the same effect as running Erlang on Xen.
- zokier 9y agoQuite hard. I don't think any of the rust unikernel projects really have gotten off ground to even serve "hello world" web requests, and there has been several attempts.
- dbrgn 9y agoTo simplify statically cross compiling Rust code for different target platforms, including musl and openssl support, check out https://github.com/messense/rust-musl-cross https://github.com/messense/rust-musl-cross
- anderspitman 9y agoWas really hoping this would include Windows and MacOS support somehow, but it looks useful even without them.
- deleted 9y ago[deleted]
- earksiinni 9y ago"Because I’ve been obsessed with static linking for as long as I can remember and I’m not really sure why." Glad I'm not the only one. When I was younger, I made a hobby Linux distro that ran completely on floppies with a custom filesystem hierarchy. All the binaries were in /Programs and were statically linked against uClibc. Even X was statically linked, there was some abandoned branch of XFree86 that ran with a VESA driver and fit under 1.44 MB. I thought that I was the bee's knees. All this was still possible ~2007. Not sure how masochistic you'd have to be to try today.
- convery 9y agoSame, although people think it's silly I find it less error-prone. The same goes for C++ libraries, only using singe-header or amalgamations because it's less to support and ABIs wont break between versions.
- deleted 9y ago[deleted]
- rhacker 9y agoOne reason this is nice is that certain build systems make it easy to copy 1 file or variable into a repository, so this might be easier than pushing a container to a private repository that can be somewhat configuration error prone. However personally I might still use Docker with this :)
- bruce_one 9y agoI've done this same thing before with Node using [nexe](https://github.com/nexe/nexe https://github.com/nexe/nexe) and musl. With a little bit of effort it's also possible to build and statically link native modules, eg I've had success with nodegit, uws and sqlite. (Needed to rebuild some of the node deps with musl, then rebuilding the native modules with musl, then modifying Node's node.gyp to --whole-archive include the additional .a/.o's (done in nexe), and using rollup to rewrite the .node includes to `process._linkedBinding('the registered module name')`, and then nexe builds the whole lot into a single executable.) Nexe supports bundling resources (although less cleanly than `include_str` imo) so it can also bundle all your ui resources as well.
- zie 9y agoFossil (http://fossil-scm.org/ http://fossil-scm.org/) has been doing very similar for a while in plain C, it ships statically-linked and includes a CLI and a web UI. (not in React, however)