-
Notifications
You must be signed in to change notification settings - Fork 27
/
web_page_howto.mld
233 lines (183 loc) · 6.24 KB
/
web_page_howto.mld
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
{0 Web page howto}
A few tips to compile and integrate your programs in web pages.
{1:quick Quick!}
Okay! Make a minimal web page.
{v
cat - > min.html <<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<script type="text/javascript" defer="defer" src="min.js"></script>
<title>Brr minimal example</title>
</head>
<body>
<noscript>Sorry, you need to enable JavaScript to see this page.</noscript>
</body>
</html>
EOF
v}
Make a minimal program.
{v
cat - > min.ml <<EOF
open Brr
let () =
El.set_children (Document.body G.document) El.[txt' "Hello World!"]
EOF
v}
Compile your program to bytecode and then to JavaScript
{v
ocamlfind ocamlc -g -linkpkg -no-check-prims -package brr min.ml
js_of_ocaml a.out -o min.js
v}
Load the web page in your browser:
{v
xdg-open min.html # Linux and XDG compliant systems
open min.html # macOS
start min.html # Windows
v}
{2:ocaml_console And the fancy OCaml console?}
Poke your program by side effect by linking against the [brr.poked]
library. Make sure everything gets in by using [-linkall].
{[
ocamlfind ocamlc -g -linkall -linkpkg -no-check-prims \
-package brr,brr.poked min.ml
]}
Compile with [js_of_ocaml]. This time it needs to see the cmis and you need
to add its toplevel and dynlink JavaScript support:
{v
js_of_ocaml $(ocamlfind query -r -i-format brr.poked) -I . \
--toplevel +toplevel.js +dynlink.js a.out -o min.js
v}
Make sure you have the OCaml console extension
{{!page-ocaml_console.ext_install} installed} in your browser, open
[min.html] and click on the OCaml tab of the developer tools.
{1:dune_quick Quick with Dune!}
Assuming you created the two minimal sources [min.ml], [min.html] from
the {{!quick}previous section}. Create this [dune] file:
{v
cat - > dune <<EOF
(executables
(names min)
(libraries brr)
(link_flags (:standard -no-check-prims))
(modes js))
(rule
(targets min.js)
(deps min.bc.js)
(action (run cp %{deps} %{targets})))
(alias
(name app)
(deps min.js min.html))
EOF
v}
Now build and open the web page in your browser:
{v
dune build @app
xdg-open _build/default/min.html # Linux and XDG compliant systems
open _build/default/min.html # macOS
start _build/default/min.html # Windows
v}
{2:dune_ocaml_console And the fancy OCaml console?}
Poke your program by side effect by linking against the [brr.poked]
library. Make sure to [-linkall] the modules into the bytecode and
tweak a bit the [js_of_ocaml] invocation.
But first since you want to access your main program's modules
disable the [dune] renaming business:
{v
echo '(wrapped_executables false)' >> dune-project
v}
This [dune] file should now do:
{v
cat - > dune <<EOF
(executables
(names min)
(libraries brr brr.poked)
(link_flags (:standard -linkall -no-check-prims))
(modes byte))
(rule
(targets min.js)
(action
(run %{bin:js_of_ocaml}
-I %{lib:brr:.} -I .min.eobjs/byte ; to see the program's cmis
--toplevel +toplevel.js +dynlink.js %{dep:min.bc} -o %{targets})))
(alias
(name app)
(deps min.js min.html))
EOF
v}
Now build and open the web page in your browser:
{v
dune build @app
xdg-open _build/default/min.html # Linux and XDG compliant systems
open _build/default/min.html # macOS
start _build/default/min.html # Windows
v}
Make sure you have the OCaml console extension
{{!page-ocaml_console.ext_install} installed} in your browser, and
click on the OCaml tab of the developer tools.
{1:running How do I run my program?}
Most programs need to have the page HTML parsed to operate
meaningfully. There are various ways of detecting this state but the
simplest is to keep the execution of your program to a classical
toplevel [main] invocation and integrate your script in the web page
with the [defer] attribute:
{v
<script type="text/javascript" defer src="myscript.js"></script>
v}
This ensures it is fetched in parallel but only executed when the document
has been parsed ({{:https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer}visual
explanation}). Your main program can simply be:
{[
let main () = Console.(log [str "DOM content loaded."])
let () = main ()
]}
However at this point ressources like stylesheets, images, fonts, etc.
may not have loaded. This means that the page layout is not accurate
and you cannot compute magnitudes that depend on them. Also if you
draw on a canvas with custom fonts these may not be loaded yet and be
substituted by generic ones.
In these cases wait for the {!Brr.Ev.load} event on the window to make
sure all ressources are loaded. Here is a snippet that does this:
{[
open Fut.Syntax
let main () =
Console.(log [str "DOM content loaded."]);
let* _ev = Ev.next Ev.load (Window.as_target G.window) in
Console.(log [str "Resources loaded."]);
Fut.return ()
let () = ignore (main ())
]}
Note that your [main] is always non-blocking (or the browser kills
you) and returns before the page loads. The callbacks you setup and
the futures you trigger do however outlive that invocation.
Also, if your program, maybe indirectly, uses {!Stdlib.at_exit}
– which is not made for the browser – these functions will execute at
the {e beginning} of the life of your web page as they are
automatically executed after all toplevel OCaml executions are
done.
{1:page_size Web page size}
Watch your program size. Keep the tubes unclogged. Trim convenience
dependencies and try not to integrate libraries providing
functionality that already exists in the browser – like JSON parsing.
[js_of_ocaml] performs excellent dead code elimination. All these
modules of {!Brr} you are not using won't impact your page
size. However it's only as good as it can be; global mutable state may
be impossible to dead code away.
{2:fmt Avoid mentions of [Format]}
Unless you really want to use it, avoid any mention of {!Format}.
[js_of_ocaml] can't help you on that one – likely due to the presence
of global mutable state in {!Format}. With [js_of_ocaml] 3.6.0 and
OCaml 4.09.0 this program:
{[
open Brr
let () = Console.(log [str "Hey!"])
]}
compiles, without special options, to 14ko. Adding this dead code:
{[
open Brr
let pp_float = Format.pp_print_float
let () = Console.(log [str "Hey!"])
]}
makes it 35ko, which is 2.5 larger.