
Making a Statically-Linked, Single-File Web App with React and Rust - anderspitman
https://anderspitman.net/2018/04/04/static-react-rust-webapp/
======
kodablah
> 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)

~~~
Waterluvian
I just wish electron could somehow harness the fact that everyone has a
browser installed.

~~~
kodablah
Well, Chrome doesn't really allow chrome.dll to be easily used. Electron has
deep requirements on Chromium for V8, IPC, webview, customer protocol
handlers, etc IIRC so it can't just easily switch browser engines. I'd say if
you want this, the Chromium project would have to prioritize a stable Chrome
ABI from their shared lib which I doubt they would do. And it still doesn't
solve the extremely mem-bloated approach they take to serving the web stack.
So for that, the Chromium project would have to prioritize better runtime
feature gating for mem reduction and maybe revive first-class support for
single-process mode, which I doubt they'd do. And even then, you're carrying
node.

To sum up my rant, I'm afraid the fast moving web stack, coupled with the fact
only a few companies can keep up, coupled with the fact that those companies
are laser focused on their own browser use case and not embeddability combine
to make this incredibly common desktop use case still suck. In the meantime,
just hope these OS engines stay minimal and don't fall too far behind the
standards you want.

~~~
plurgid
This is what makes me want to see something like Electron developed over
Firefox internals.

Packaging my own browser with my code so that I am now developing toward ONE
client instead of any random thing that can speak http, and at the same time,
being able to take the core of my app, tweak it a little, and plop it out on
the web without a complete rewrite ... that is completely amazing. It's what
I've wanted since forever.

Even better would be the ability to optionally include features in my build.
Like if I don't NEED WebGL, the MIDI and Audio APIs, etc, etc ... it'd be
pretty nice to be able to optionally exclude those from the bundled browser to
minimize size.

However, I don't want to be welded to google. Mozilla's codebase seems ripe
for such a development.

~~~
kodablah
> This is what makes me want to see something like Electron developed over
> Firefox internals.

Tried, but abandoned presumably based on prioritization:
[https://github.com/mozilla/positron](https://github.com/mozilla/positron). I
was hoping Servo would help here, but from the outside looking in, the pieces
are being moved into FF proper and the Servo browser's priorities have been
reshifted to being a part of the VR team. Sure they're still making a general
use browser (and that's quite a feat), but the embeddability game may suffer
(I believe conforming to the CEF iface that was there original goal has become
stale).

------
earksiinni
"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
Same, 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.

------
hardwaresofton
Being 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"

~~~
pjmlp
Maybe for younger generations.

Apple and Microsoft systems have been supporting this on their native
toolchains since the mid-90's.

~~~
hardwaresofton
Yes, including arbitrary data in binaries is probably as old as making
binaries itself.

I think what _is_ new is that in the mid 90s you didn't have an open source,
memory safe, ergonomic, strongly typed, type inferenced language that targets
multiple platforms with ease, with a decent standard lib. Rust is delivering
that. Golang is delivering that. I think those two are novel in that sense.

From where I'm sitting the last few decades have been ruled by VMs and the
explosion of scripting languages, because no one wanted to use those
Apple/Microsoft toolchains that have been around in the 90s. While those
toolchains rested on their laurels (or didn't, can't tell the difference now),
Java became the enterprise workhorse.

~~~
pjmlp
Well, Turbo Pascal, Delphi and Oberon also supported it, and they check some
of those bullet points.

Many devs do actually enjoy those Apple/Microsoft toolchains.

~~~
jaccarmac
Common Lisp as well. For certain definitions of memory safe.

~~~
kbp
The "decent standard lib" part is debatable, especially if you don't slip on
the "targeting multiple platforms with ease" part.

~~~
jaccarmac
Fair enough. Quicklisp + SBCL/Clozure is pretty solid but I suppose that
wasn't the case 20 years ago.

~~~
kbp
For sure, and its standard library equivalent was very, very good for the
time, just not by 2018 standards.

------
codetrotter
I 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/4c4cc05254d12bbc8937a0ea385fcd...](https://gist.github.com/ctsrc/4c4cc05254d12bbc8937a0ea385fcdad)

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
> 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.

~~~
codetrotter
I 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.

~~~
codetrotter
I have now rewritten my code so that I include bytes directly in my
src/main.rs like so:

    
    
        #[get("/fonts/<fname>")]
        fn fonts<'a> (fname: String) -> Result<Content<Vec<u8>>, NotFound<String>>
        {
            match fname.as_ref()
            {
                "autobahn.woff" => Ok(Content(ContentType::WOFF,
                    include_bytes!("../static/fonts/Autobahn/autobahn.woff").to_vec())),
    
                "autobahn.ttf" => Ok(Content(ContentType::TTF,
                    include_bytes!("../static/fonts/Autobahn/autobahn.ttf").to_vec())),
    
                "grobe-deutschmeister.woff" => Ok(Content(ContentType::WOFF,
                    include_bytes!("../static/fonts/Grobe-Deutschmeister/grobe-deutschmeister.woff").to_vec())),
    
                "grobe-deutschmeister.ttf" => Ok(Content(ContentType::TTF,
                    include_bytes!("../static/fonts/Grobe-Deutschmeister/grobe-deutschmeister.ttf").to_vec())),
    
                _ => Err(NotFound(format!("No such file: /fonts/{}", fname)))
            }
        }
    

In the meantime since I first wrote my original code, the web framework I was
using called Iron [1] has become unmaintained and they now urge people to pick
a different framework. I chose to use Rocket because it seemed to have the
features I want and the documentation seemed good enough to get started (and
it was).

For templating I found Askama [2], which I am using from git master in order
to be able to use it together with Rocket.
[https://github.com/djc/askama/issues/71](https://github.com/djc/askama/issues/71)

Eventually I will generate the routes for the included bytes with build.rs
again. My new code is better both short-term and for when I will create said
build.rs.

Thank you masklinn for your comment about include_bytes! which made me look at
my code again and rewrite it.

[1]: [https://rocket.rs/](https://rocket.rs/)

[2]: [https://docs.rs/crate/askama/](https://docs.rs/crate/askama/)

------
esaym
You 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
Can 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...

~~~
kbenson
PAR[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)

~~~
slobotron
Awesome, thanks for the pointer! CPAN still has lots of tricks up its sleeve,
eh :)

~~~
esaym
There will apparently never be a replacement for Perl :)

------
dr-ando
If 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)

------
halayli
I 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.

~~~
finchisko
I 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.

~~~
notheguyouthink
You're bashing it based on some assumption that it's being heralded as an
amazing piece of tech. It's just a static binary. If you want to argue the
merit of static binaries then, by all means. I personally love them, it's one
of the reasons why I like Go so much. I'm sure there's a ton of people who
like static binaries, and who dislike them.

There's nothing else to see here, you either like dependency-less binaries or
not.

Hell, if anything, I don't see the point of your point, calling static
binaries "real bullshit". Depending on the scenario static binaries are either
better for you, or worse for you - it's just a tool in a bag of tools. How can
that possibly be bullshit? Is a wrench bullshit?

~~~
finchisko
I understand that static bundled site as rust executable is not for me.
However I'm asking for who is? Specially considering my concerns about about
SSL, performance.

EDIT: I'm not against static binaries per se. Only against this usage of them.

~~~
notheguyouthink
I've actually used static bundles (including css/etc) for a lot of internal
applications, both at home and at work.

Basically anywhere where reducing difficulty of deployment and asset
management outweighs the valid concerns you pointed out. It's really, really
handy in my view.

Of course, I've got no plans or desire to run some production documentation
site on this. Yet, that problem domain is vastly different than internal
tooling, OSS apps, etc.

Just think of a note web app you write. Do you want your users to _have_ to
manage css/template/image bundles just to use your note app? Why not just make
the binary work with zero configuration/management? Plus, if you wanted - you
can of course support both, allowing the user to override css files if they so
desire, without having to recompile the binary/etc.

~~~
finchisko
If theoretically had that need, I would first go with docker and if hw is
limited, then maybe this thing I just googled out as part of research on the
topic:

[http://miniweb.sourceforge.net/](http://miniweb.sourceforge.net/). It would
be two files. 20k server binary and 7z with site resources. It would safe me
trouble of recompiling binary.

------
interfixus
Have 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.

------
kiddico
I'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.

~~~
hie2neiL
Normally 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
Putting 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.

~~~
XorNot
Even in production though, you can just have a step on startup which copies
built-in resources to a /tmp location and then sendfile's from there.

------
cbcoutinho
There'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
Thanks, fixed!

------
Justsignedup
I do similar, except I just deploy to AWS as a static website. Super duper
minimal everything.

~~~
kowdermeister
The 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.

~~~
anderspitman
I 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
Are 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
Yeah I meant statically linking the sqlite library. Thanks for the tip on how
to do it

------
bruce_one
I'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
Fossil ([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)

------
rlopezcc
I 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.

------
chme
I 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
The 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
Thx! 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
The use of protobuf is mostly to provide a schema and enforce strict[er]
typing on JS.

------
mzzter
Cool 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
What I really need is a tool to generate blog-post code snippets from actual
code ;) Thanks, fixed!

------
falcor84
How hard would it be to use something like this to deploy directly onto a
hypervisor, like with Erlang on Xen?

~~~
seabrookmx
Not 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.

------
rhacker
One 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 :)

------
dbrgn
To 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
Was really hoping this would include Windows and MacOS support somehow, but it
looks useful even without them.

------
everdev
For 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.

~~~
NiceGuy_Ty
To 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.

------
alpb
Is 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.

~~~
steveklabnik
Remember 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%.

------
syncopate
I 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
Everything 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
If 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.

------
ashleyn
Can it pass C10k?

------
khanan
Or, you know... Python.

~~~
hughes
How do you make a statically-linked, single-file web app with python?

~~~
pjmlp
With something like py2exe.

------
z3t4
it'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.

------
sampo
(misunderstood)

~~~
mzzter
The JavaScript file is actually added to the Rust binary at compile time as a
static string.

------
romanovcode
If 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
That 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.

