Table of Contents
HipHop Environment
The source files used in this chapter are avaiable in the HipHop github repository.
Running HipHop programs on the Web
In this chapter, we show how to run HipHop programs on a server-side of an application, e.g., on Nodejs, and on the client-side of a web application, .e.g., on Firefox. To run the examples, first create a fresh directory and install HipHop inside it.
mkdir example
cd example
npm install https://www-sop.inria.fr/members/Manuel.Serrano/software/npmx/hiphop.tgz
In the rest of this section we assume the file abro.hh.js
defined as:
import { ReactiveMachine } from "@hop/hiphop";
const prg = hiphop module() {
in A;
in B;
in R;
out O = 0;
do {
fork {
await (A.now);
} par {
await (B.now);
}
emit O(O.preval + 1);
} every (R.now)
}
export const mach = new ReactiveMachine(prg);
Server-side execution
To execute a HipHop program on an unmodified JavaScript execution engines,
let it be Nodejs, Hop, or any other compliant engine, it has to be
compiled first. This is accomplished by the hhc.mjs
compiler that
is part of the standard HipHop distribution. To compile our abro.hh.js
example, one has to use the following command:
./node_module/@hop/hiphop/bin/hhc.mjs abro.hh.mjs -o abro.mjs
This will generate two files:
abro.mjs
, which a standard JavaScript ES6 module.abro.map.json
, which is a source map file that will let the native JavaScript engine refer to source location insideabro.hh.js
instead ofabro.mjs
.
Once, compiled, the program can be imported by any regular ES6 module and executed using the HipHop API. Example:
import { mach } from "./abro.mjs";
mach.addEventListener("O", e => console.log(e.nowval));
mach.react();
mach.react({ A: undefined });
mach.react({ B: undefined });
mach.react({ B: undefined });
mach.react({ R: undefined });
mach.react({ A: undefined, B: undefined });
mach.react();
This program can be executed with:
nodejs --enable-source-maps hello.mjs
Client-side execution with a Nodejs server
In this section we show how to use HipHop on client-side of web
applications. We show how to proceed to implement a web app executing
the abro.hh.mjs
reactive program on a web browser. Let's implement a
minimal web server only using the bare
Nodejs' http api.
Let us first describe the web page the server delivers.
<html>
<script type="importmap"> {
"imports": {
"@hop/hiphop": "/hiphop-client.mjs"
}
}
</script>
<script type="module">
import { mach } from "./abro.mjs";
globalThis.mach = mach;
mach.addEventListener("O", (evt) => {
document.getElementById("console").innerHTML = evt.nowval;
});
mach.react();
</script>
<div>
<button onclick="mach.react({A: 1})">A</button>
<button onclick="mach.react({B: 1})">B</button>
<button onclick="mach.react({R: 1})">R</button>
</div>
<div id="console">-</div>
</html>
The script type="importmap"
specify a mapping from JavaScript module
names to URL. As such, when the JavaScript code running on the browser
will import the module @hop/hiphop
, the browser will request the URL
/hiphop.mjs
to the server (we'll see in a moment how we configure
the server so that it can respond to this request). By specifying a
module-to-URL mapping, we can re-use the same program abro.mjs
as the one we use when running on the server, althought the module
@hop/hiphop
has different implementations for the server and for the
client.
The configuration of the web server is as follows:
import { createServer } from "node:http";
import { readFileSync, readdirSync } from "node:fs";
const host = "localhost";
const port = 8888;
const contents = {
"/abro.mjs": readFileSync("./abro.mjs"),
"/": readFileSync("./index.html"),
"/hiphop-client.mjs": readFileSync("./node_modules/@hop/hiphop/hiphop-client.mjs")
}
for (let file of readdirSync("./node_modules/@hop/hiphop/lib")) {
if (file.match(/\.m?js$/)) {
contents["/lib/" + file] = readFileSync("./node_modules/@hop/hiphop/lib/" + file);
}
}
const handler = function(req, res) {
const content = contents[req.url];
if (content) {
if (req.url.match(/\.m?js$/)) {
res.setHeader("Content-Type", "text/javascript");
} else {
res.setHeader("Content-Type", "text/html");
}
res.writeHead(200);
res.end(content);
} else {
res.writeHead(404);
res.end("no such file");
}
}
const server = createServer(handler);
server.listen(port, host, () => {
console.log(`Server is running on http://${host}:${port}`);
});
It is a standard implementation of a web server using Nodejs and contains no code specific to HipHop.
Client-side execution with a Hop Server
We can simplify the implementation of our simple HipHop web application by using a hop server instead of a plain Nodejs server.
As for the previous example, the Hiphop abro.hh.mjs
file remain unchanged.
In Hop, the standard way to generate HTML documents is to build them
dynamically using the builtin XML syntax. The HTML page will be then
generated by the index
function defined as:
export function index(R) {
return (o) => <html>
<script type="importmap"> {
"imports": {
"@hop/hiphop": "${R.register('./node_modules/@hop/hiphop/hiphop-client.mjs')}"
}
}
</script>
<script type="module">
import { mach } from ${R.register("./abro.mjs")};
globalThis.mach = mach;
mach.addEventListener("O", (evt) => {
document.getElementById("console").innerHTML = evt.nowval;
});
mach.react();
</script>
<div>
<button onclick=~{mach.react({A: 1})}>A</button>
<button onclick=~{mach.react({B: 1})}>B</button>
<button onclick=~{mach.react({R: 1})}>R</button>
</div>
<div id="console">-</div>
</html>
}
Before being executed, this module has to be compiled to plain JavaScript
./node_modules/@hop/hop/bin/hopc index.hop.mjs -o index.mjs
The main difference with the static document we have used for Nodejs is
the use of the R
variable. The Nodejs version was only able to deliver
a fixed set of files and the mapping between URL and files was fixed once
for all at the initialization of the program. Hop uses a more elaborated
mechanism for implementing the mapping. The only useful information here
is that R
is a bidirectional data structure mapping actual file names
to URL and vice-versa.
The server-side module is defined as follows:
import { readFileSync, readdirSync } from "node:fs";
import { dirname } from "node:path";
import * as hop from "@hop/hop";
import { index } from "./index.mjs";
const hopConfig = {
ports: { http: 8888 },
users: [ {name: "anonymous", services: "*", directories: "*"} ]
};
hop.init(hopConfig);
const cwd = dirname(import.meta.url.toString().replace(/^file:\/\//, ""));
const R = new hop.Resolver(cwd);
const rootSvc = hop.Service(index(R), "/");
hop.listen().then(v => console.log(`Server is running on http://${rootSvc()}`));
Typed HipHop (a.k.a. TypeScript compilation)
In this section we show to use add type annotations to HipHop programs and how to use the TypeScript compiler to detect type errors statically.
The build process is more complex because TypeScript requires all the
sources to be available when it compiles a module (otherwise, it
cannot type-check the module being compiled) and because of the import
rules TypeScript uses. In a TypeScript import
form, the module file
name designates the name of the generated JavaScript file, not the
name of the TypeScript source file. The last difficulty is to deal with
source-map files. The TypeScript compiler generates source-map but does not
read any. Meaning that if a TypeScript file is generated, the TypeScript
compiler cannot associates the program it compiles with another source
file. In this section, we show how to use the hhc
, the HipHop compiler,
and hopc
, the Hop compiler to solve all these issues.
First let's add type annotations to the abro
HipHop module. This
typed version is stored in a file named abro.hh.ts
.
import { ReactiveMachine } from "@hop/hiphop";
const prg = hiphop module() {
in A;
in B;
in R;
out O = 0;
do {
fork {
await (A.now);
} par {
await (B.now);
}
emit O(O.preval + 1);
} every (R.now)
}
interface abroType {
A: undefined;
B: undefined;
R: boolean;
O: number;
};
export const mach = new ReactiveMachine(prg) as ReactiveMachine<abroType>;
This file has to be compiled in two steps. First, it has to be compiled to plain TypeScript.
./node_module/@hop/hiphop/bin/hhc.mjs abro.hh.mjs -o abro.ts
and the, using the hopc
compiler into a plain JavaScript file. The hopc
compiler is a mere wrapper around the tsc
compiler. To let tsc
generates
a JavaScript ES module instead of the CommonJS module, there must be
a package.json
. For instance:
{
"name": "TypedHipHop",
"type": "module",
"dependencies": {
"@hop/hiphop": "https://www-sop.inria.fr/members/Manuel.Serrano/software/npmx/hiphop.tgz",
"@hop/hopc": "https://www-sop.inria.fr/members/Manuel.Serrano/software/npmx/hopc.tgz"
},
"scripts": {
"test": "node --enable-source-maps hello.mjs",
"build": "node_modules/@hop/hiphop/bin/hhc.mjs abro.hh.ts -o abro.ts && node_modules/@hop/hopc/bin/hopc.mjs abro.ts -o abro.mjs && node_modules/@hop/hopc/bin/hopc.mjs hello.ts -o hello.mjs"
}
}
The declaration "type": "module"
tells tsc
to generate an ES module. The
compilation is executed with:
./node_module/@hop/hiphop/bin/hopc.mjs abro.ts -o abro.mjs
We compiled
hopc
instead oftsc
for compiling the generated TypeScript file, becausehopc
is able to deal with multiple source-map file, so that the fileabro.map.json
that will get generated will correctly map any possible runtime erroor into the orignalabro.hh.ts
file. If we had usedtsc
instead, the generatedabro.map.json
file would have pointed toabro.ts
instead.
Let us assume a main program that imports the HipHop machine:
★ hello.ts
import { mach } from "./abro.mjs";
mach.addEventListener("O", e => console.log(e.nowval));
mach.react();
mach.react({ A: undefined });
mach.react({ B: undefined });
mach.react({ B: undefined });
mach.react({ R: true });
mach.react({ A: undefined, B: undefined });
mach.react();
This program can be compiled and executed with:
./node_module/@hop/hiphop/bin/hopc.mjs hello.ts -o hello.mjs
node --enable-source-maps hello.mjs
Visualizing the Net List
The HipHop compiler generates a net list from a HipHop source. This compiled
program can be executed by simulating the generated circuit. The tools
tools/nets2dot.mjs
can be used in conjunction with the
dot graph visualizer to generate PDF files.
Here is how to proceed for generating these files, considering a HipHop
source file named foo.hh.js
:
- Add the option
{ dumpNets: true }
to the reactive machine for which you want to dump the net list. - Run your program. This will generate two files:
foo.hh.js.nets-.json
andfoo.hh.js.nets+.json
. The former is the net list before optimization the latter with optimization. - Generate the
.dot
files:- bin/nets2dot.js foo.hh.js.nets-.json > nets-.dot
- bin/nets2dot.js foo.hh.js.nets+.json > nets+.dot
- Generate the PDF files:
- dot -T pdf nets-.dot > nets-.pdf
- dot -T pdf nets+.dot > nets+.pdf