Dancing Web is now distributed with the Tarantella Package Manager — a tool I've made to simplify setup of projects like these!
To install the Tarantella Package Manager, you need the Rust toolchain — for instructions on how to install it, see this. Once you're done with that, you can install Tarantella by running: cargo install tarantella
.
To start-off, setup a new main module WASM project with: tapm new my-first-project
. This will start a new C WASM project with:
- a build folder,
- a dependencies folder,
- an index.html,
- a
Makefile
, - a releases folder,
- a src folder with some starting code in
main.c
, and a Tarantella.toml
file.
Next, add the latest version of Dancing Web as a dependency with: tapm add "danbugs/dancing_web"
. This will automatically download DCW to your dependencies folder, add it to your Tarantella.toml
list of dependencies, and append dependencies/dcw/dcw.o
to the DEPENDENCIES
variable of your Makefile
to facilitate compilation.
Now, let's create our first HTML-equivalent "C Markdown Language" file (.cml
) to be rendered from C! First, create a folder called frontend/
and, inside it, create a hello_world.cml
file with the following code:
HTML(
<div>
<h1 class="red">${hello_world()}$</h1>
</div>
<style>
.red {
color: red;
}
</style>
);
You can call C functions within the ${
and }$
markers (called renderable markers) or within the $E
and }$
markers (called executable markers).
- Renderable markers are for C functions that return content that is meant to be rendered, and
- Executable markers are for C functions that are meant to be called but don't necessarily render HTML (e.g., functions within a button's
onclick
).
Next, in our main.c
, replace its' contents with:
#include <stdio.h>
#include <stdlib.h>
#include <emscripten.h>
#include "../dependencies/dcw/dcw.h"
extern void display_html(html_t raw_html);
// ^^^ rendering function from DCW that is
// external to our main module
// this is compulsory for functions
EMSCRIPTEN_KEEPALIVE // <- you intend to call from HTML
html_t hello_world()
{
// ^^^ functions that return content to be rendered
// must return the html_t or char* types
html_t tmp;
// ^^^ you don't need to free this malloc,
// the framework will do it for you.
asprintf(&tmp, "%s", "Hello, World!");
// ^^^ this works because html_t expands
// to char*
return tmp;
}
// ^^^ this is the hello_world function
// we are calling from our .cml file
int main()
{
html_t html =
#include "../frontend/hello_world.cml"
;
// ^^^ this brings the .cml component into scope!
// things to note:
// - the #include statement must be in a line of its'
// own, and
// - the ';' is optional but, if present, must also be
// on a line of its' own
// (usually, I like to include the ';' because it
// helps with formatting)
display_html(html);
// ^^^ this will render our html.
}
To finish off, in your Makefile
, append --js-library dependencies/dcw/dcw.js
to your EMCC_FLAGS
variable. It should look like this: EMCC_CFLAGS=-s MAIN_MODULE=1 --js-library dependencies/dcw/dcw.js
.
Now, let's build the project with: tapm build
. This will use the Makefile
to compile the project to the build/
folder.
To test the project, run: tapm run
. Navigating to http://127.0.0.1:4000
in your browser, you should see:
For more complex examples, view the project in the examples/
folder of the repo!
If you are using an IDE, to avoid C auto-formatting the HTML to something that doesn't work, you can create separate files for it. These files can have any extension but I've been using .cml
(i.e., C Markup Language).
In addition, to get code auto-formatting working for .cml
files on VSCode, open settings.json
and add the following at the end of it:
"files.associations": {
"*.cml": "html"
},
You can write HTML directly within C, like this:
html_t html = HTML(
<div>
<h1 class="red">${hello_world()}$</h1>
</div>
<style>
.red {
color: red;
}
</style>
);
...or:
html_t html = HTMLIFY(" \
<div> \
<h1 class='red'>${hello_world()}$</h1> \
</div> \
<style> \
.red { \
color: red; \
} \
</style> \
");
EMSCRIPTEN_KEEPALIVE
html_t add_two_numbers(int a, int b)
{
char *tmp;
int result = a + b;
asprintf(&tmp, "%d", result);
return tmp;
}
For the record, a function like:
EMSCRIPTEN_KEEPALIVE
html_t hello_world()
{
return "Hello, World!";
}
... would also work. Thing is, it is not the best because the framework will try to free a pointer that wasn't malloc-ed. While this doesn't cause an error, I wouldn't call it a best practice.
To check for any problems (i.e., mem leaks and whatnot) in the code at runtime, compile like so: emcc hello_world.c ../../src/dcw.c --js-library ../../src/dcw.js -fsanitize=address -s ALLOW_MEMORY_GROWTH -s INITIAL_MEMORY=285212672 -gsource-map --source-map-base http://127.0.0.1:4000
. After that, run with: tapm run
.