[[!meta date="2015-11-14 22:15"]]
[[!tag emscripten nixcloud nix tourofnix]]
[[!img media/Emscripten_logo_full.png width="250px" class="noFancy" style="float: right"]]
[[!summary a tour of nix is an educational purpose project on teaching the Nix programming language. It is also an introduction on how to use LLVM and emscripten to convert C++ programs to run in the browser.]]
[[!series emscripten]]
# motivation
lately i've been working with emscripten 1.29.10 and i compiled **nix-instantiate** from **c++ to javascript**. why?
nix is a tricky programming language to get into. the motivation for 'a tour of nix' was to make it easier for new developers to get into nix. compiling nix-instantiate and later nix-repl to javascript might be enablers for new technologies like:
* **webbased editor with nix-syntax autocompletion**, directly from the language itself by using nix-repl
* **extend the nix documentation with interactive API usage examples** (much like 'a tour of go' and godocs)
* **use nix to deploy javascript**
while the former points are all interesting targets in itself, this posting will focus on 'a tour of nix' and 'emscripten'. [A tour of Nix](https://nixcloud.io/tour) is a html5/javascript based tutorial about the nix programming language.
[[!img media/tour_of_nix.jpg title="" alt="the 'A touf of nix'-webpage"]]
# implementation
nix-instantiate is a very good candidate among the various nix-tools. since emscripten emulates a complete POSIX-filesystem we
1. clone nix/nixpkgs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.bash}
git clone https://github.com/nixos/nix
git clone https://github.com/nixos/nixpkgs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'nix' contains nix-instantiate which we want to compile to javascript while 'nixpkgs' will be bundled with nix-instantiate.js so we can use the lib functions.
2. development environment
to play with the code, run:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.bash}
nix-shell --pure -p vim emscripten nodejs autoconf automake pkgconfig perlPackages.DBDSQLite perl perlPackages.WWWCurl bzip2 zlib libtool bison flex curl sqlite git file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**note:** we will be using llvm instead of gcc since emscripten is llvm only!
2. learn about nix-instantiate dependencies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.bash}
./dependencies.sh =nix-instantiate dependencies.jpg
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[dependencies.sh](http://domseichter.blogspot.de/2008/02/visualize-dependencies-of-binaries-and.html) lists the dependencies of the vanialla nix-instantiate
[[!img media/nix-instantiate-dependencies.jpg title="" alt="the dependencies of nix-instantiate"]]
what had to be changed:
- the **build-system** was dramatically changed to use:
- only builds nix-instantiate (all other targets removed)
- **static linking** for all libs (.a instead of .so)
- O3, -g0
- source code changes: **libraries** were removed from nix-instantiate
- bzip2, curl, ssl, sqlite3, dl, z, m, crypto, gc
- removed sigaction(..) code
- libstore requirement from nix-instantiate was removed
- various other changes to make nix compile and work in a browser
- **bundle (a bash script)** was added to src/nix-instantiate
to test emscripten a simple 'hello world' example was created.
4. the build using './bundle'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.bash}
emcc -g0 -O3 nix-instantiate.bc ../libexpr/libnixexpr.bc ../libstore/libnixstore.bc ../boost/format/libnixformat.bc ../libutil/libnixutil.bc ../libmain/libnixmain.bc \
-o nix-instantiate.js \
--preload-file nix/derivation.nix \
--preload-file nixpkgs \
--preload-file test.nix \
-s NO_EXIT_RUNTIME=1 \ #important
-s INVOKE_RUN=0 \ #important
-s ALLOW_MEMORY_GROWTH=1 \ # importatnt!
-s TOTAL_MEMORY=350777216 \ # could be less i suppose
--pre-js pre-js.js \ # important for nix-instantiate (to remove the spinner)
-s DISABLE_EXCEPTION_CATCHING=0 \ # important for nix-instantiate (when the expression creates an error)
--llvm-lto 3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5. after you executed ./bundle:
you can test if everything went fine, if it built a new version of:
* nix-instantiate.data
* nix-instantiate.js
* nix-instantiate.js.mem
6. nix-instantiate.js usage
**index.html includes nix-instantiate.js** and calls it every time we want to evaluate a nix expression
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.java}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
this is actually similar to calling nix-instantiate from the shell:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.bash}
nix-instantiate -I nixpkgs=nixpkgs/ --eval, --strict, --show-trace, /test.nix
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**note:** i want to point out that **callMain** was used over **cwrap**
7. code optimization
**check your webserver has compression enabled!**
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.haskell}
services.httpd = {
enable = true;
logPerVirtualHost = true;
adminAddr="js@lastlog.de";
hostName = "lastlog.de";
extraModules = [
{ name = "php5"; path = "${pkgs.php}/modules/libphp5.so"; }
{ name = "deflate"; path = "${pkgs.apacheHttpd}/modules/mod_deflate.so"; }
];
virtualHosts = [
# nixcloud.io
{
hostName = "nixcloud.io";
serverAliases = [ "nixcloud.io" "www.nixcloud.io" ];
documentRoot = "/www/nixcloud.io/";
extraConfig = ''
RedirectMatch ^/$ /main/page
Options -Indexes
AllowOverride None
Require all granted
SetOutputFilter DEFLATE
'';
};
]
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
this is how we at nixcloud set up mod_deflate for apache, which curiously is not a default option. **this reduces the 30mb download to 6mb only!**
8. html5 specialities
we got a working browser history, so you can use the 'back' and 'forward' buttons of your browser to navigate the page
// implement browser forward and back button actions
window.addEventListener("popstate", function(e) {
var id = getURLParameter("id");
var diff = id - current_question - 1;
change_question_withoutHistoryChange(diff);
});
// update current question and modify the window.history
var change_question = function(myinput) {
change_question_withoutHistoryChange(myinput);
window.history.pushState('', '', window.location.pathname + '?id=' + (current_question + 1));
}
# documentation
* [http://aidanhs.github.io/empython/](http://aidanhs.github.io/empython/)
* [https://github.com/aidanhs/empython](https://github.com/aidanhs/empython)
* [http://kripken.github.io/emscripten-site/_sources/docs/optimizing/Optimizing-Code.txt](http://kripken.github.io/emscripten-site/_sources/docs/optimizing/Optimizing-Code.txt)
# conclusion
i especially wanted to **thank aidanhs**, the author of empython, for all his time with very valuable hints on how to address my problems.
**happy nix-learning from paul and joachim!** also thanks a lot for the irc support on emscripten from: **irc.mozilla.org#emscripten**!