package b0
Software construction and deployment kit
Install
dune-project
Dependency
Authors
Maintainers
Sources
b0-0.0.6.tbz
sha512=e9aa779e66c08fc763019f16d4706f465d16c05d6400b58fbd0313317ef33ddea51952e2b058db28e65f7ddb7012f328c8bf02d8f1da17bb543348541a2587f0
doc/src/b0.kit/b0_jsoo.ml.html
Source file b0_jsoo.ml
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 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
(*--------------------------------------------------------------------------- Copyright (c) 2020 The b0 programmers. All rights reserved. SPDX-License-Identifier: ISC ---------------------------------------------------------------------------*) let () = B0_scope.open_lib ~module':__MODULE__ "jsoo" open B0_std open B0_std.Fut.Syntax let env_vars = ["BUILD_PATH_PREFIX_MAP"] let tool = B0_memo.Tool.by_name ~vars:env_vars "js_of_ocaml" (* Meta keys *) let tag = B0_meta.Key.make_tag "tag" ~doc:"js_of_ocaml related entity" let assets_root = let doc = "Root path from which assets are rerooted." in let pp_value = Fpath.pp_unquoted in B0_meta.Key.make "assets-root" ~pp_value ~doc type compilation_mode = [ `Separate | `Whole ] let pp_compilation_mode ppf = function | `Separate -> Fmt.string ppf "separate" | `Whole -> Fmt.string ppf "whole" let compilation_mode = let doc = "Compilation mode" in let default = `Whole and pp_value = pp_compilation_mode in B0_meta.Key.make "comp-mode" ~default ~pp_value ~doc let compile_opts = let doc = "Options added to the js_of_ocaml compile command" in let pp_value = Cmd.pp in B0_meta.Key.make "comp" ~default:Cmd.empty ~pp_value ~doc let link_opts = let doc = "Options added to the js_of_ocaml link command" in let pp_value = Cmd.pp in B0_meta.Key.make "link" ~default:Cmd.empty ~pp_value ~doc type source_map = [`Inline | `File ] option let pp_source_map ppf = function | None -> Fmt.string ppf "none" | Some `Inline -> Fmt.string ppf "inline" | Some `File -> Fmt.string ppf "file" let source_map = let pp_value = pp_source_map in B0_meta.Key.make "src-map" ~pp_value ~doc:"Source map desires" let toplevel = let doc = "Compile with toplevel support" in let pp_value = Fmt.bool in B0_meta.Key.make "toplevel" ~default:false ~doc ~pp_value (* Build fragments *) let build_runtime m ~opts ~jss ~o = let jsoo = B0_memo.tool m tool in B0_memo.spawn m ~reads:jss ~writes:[o] @@ jsoo Cmd.(arg "build-runtime" % "-o" %% (unstamp @@ path o) %% opts %% unstamp (paths jss)) let handle_source_map ~o = function | None -> [o], Cmd.empty | Some `Inline -> [o], Cmd.(arg "--source-map-inline") | Some `File -> [o; Fpath.(o -+ ".map")], Cmd.(arg "--source-map") let compile m ~opts ~source_map ~jss ~byte ~o = let jsoo = B0_memo.tool m tool in let writes, source_map = handle_source_map ~o source_map in B0_memo.spawn m ~reads:(byte :: jss) ~writes @@ jsoo Cmd.(arg "compile" % "-o" %% (unstamp @@ path o) %% opts %% source_map %% (unstamp @@ paths jss %% path byte)) let link m ~opts ~source_map ~jss ~o = let jsoo = B0_memo.tool m tool in let writes, source_map = handle_source_map ~o source_map in B0_memo.spawn m ~reads:jss ~writes @@ jsoo Cmd.(arg "link" % "-o" %% (unstamp @@ path o) %% opts %% source_map %% (unstamp @@ paths jss)) let write_page ?(lang = "") ?(generator = "") ?(styles = []) ?(scripts = []) ?(title = "") m ~o = let title = if title = "" then Fpath.basename ~strip_exts:true o else title in let stamp = List.rev_append styles scripts in let stamp = String.concat "" (lang :: generator :: title :: stamp) in B0_memo.write m ~stamp o @@ fun () -> let open B0_html in let body = let sorry = "Sorry, you need to enable JavaScript to see this page." in El.body El.[noscript [txt sorry]] in let page = El.page ~generator ~lang ~scripts ~styles ~title body in Ok (El.to_string ~doctype:true page) (* Build fragments *) let get_modsrcs b ~srcs = let build_dir = B0_build.current_dir b in let src_root = B0_build.scope_dir b in B0_ocaml.Modsrc.map_of_files (B0_build.memo b) ~build_dir ~src_root ~srcs let get_link_objs m ~code ~resolver ~requires ~modsrcs = let modsrcs = B0_ocaml.Modsrc.sort (* for link *) ~deps:B0_ocaml.Modsrc.ml_deps modsrcs in let mod_objs = List.filter_map (B0_ocaml.Modsrc.impl_file ~code) modsrcs in let* link_requires = B0_ocaml.Libresolver.get_list_and_deps m resolver requires in let lib_objs = List.filter_map B0_ocaml.Lib.cma link_requires in let lib_jss = List.concat_map B0_ocaml.Lib.js_stubs link_requires in Fut.return (lib_objs, mod_objs, lib_jss) let compile_byte m ~opts ~resolver ~requires ~modsrcs = let code = B0_ocaml.Code.Byte in let comp = B0_ocaml.Tool.ocamlc in let* requires = B0_ocaml.Libresolver.get_list_and_exports m resolver requires in ignore @@ B0_ocaml.Compile.intfs ~and_cmti:true m ~comp ~opts ~requires ~modsrcs; ignore @@ B0_ocaml.Compile.impls ~and_cmt:true m ~code ~opts ~requires ~modsrcs; Fut.return () let link_byte m ~conf ~opts ~resolver ~requires ~modsrcs ~o = let code = B0_ocaml.Code.Byte in let* lib_objs, mod_objs, lib_jss = get_link_objs m ~code ~resolver ~requires ~modsrcs in let cobjs = lib_objs @ mod_objs in B0_ocaml.Link.code m ~conf ~code ~opts ~c_objs:[] ~cobjs ~o; Fut.return lib_jss let byte_exe ~modsrcs ~o b = let meta = B0_build.current_meta b in let requires = B0_meta.get B0_ocaml.requires meta in let* conf = B0_build.get b B0_ocaml.Conf.key in let o = Fpath.(o + B0_ocaml.Conf.exe_ext conf) in let* resolver = B0_build.get b B0_ocaml.Libresolver.key in let toplevel = Option.value ~default:false (B0_meta.find toplevel meta) in let global_opts = Cmd.(arg "-g") (* TODO *) in let opts = global_opts in let m = B0_build.memo b in let* () = compile_byte m ~opts ~resolver ~requires ~modsrcs in let opts = Cmd.(global_opts %% if' toplevel (arg "-linkall")) in let* lib_jss = link_byte m ~conf ~opts ~resolver ~requires ~modsrcs ~o in Fut.return (o, lib_jss) let js_of_byte_exe ~jss ~modsrcs ~o b = let* byte, lib_jss = byte_exe ~modsrcs ~o:Fpath.(o -+ ".byte") b in let meta = B0_build.current_meta b in let source_map = Option.join (B0_meta.find source_map meta) in let opts = Option.value ~default:Cmd.empty (B0_meta.find compile_opts meta) in let toplevel = Option.value ~default:false (B0_meta.find toplevel meta) in let opts = if toplevel then Cmd.(opts % "--toplevel") else opts in let jss = List.append lib_jss jss in compile (B0_build.memo b) ~opts ~source_map ~jss ~byte ~o; Fut.return () let js_of_byte_objs ~jss ~modsrcs ~o b = let meta = B0_build.current_meta b in let requires = B0_meta.get B0_ocaml.requires meta in let* conf = B0_build.get b B0_ocaml.Conf.key in let* resolver = B0_build.get b B0_ocaml.Libresolver.key in let m = B0_build.memo b in let global_opts = Cmd.(arg "-g") (* TODO *) in let opts = global_opts in let* () = compile_byte m ~opts ~resolver ~requires ~modsrcs in let code = B0_ocaml.Code.Byte in let* lib_objs, mod_objs, lib_jss = get_link_objs m ~code ~resolver ~requires ~modsrcs in let jss = List.append lib_jss jss in let source_map = Option.join (B0_meta.find source_map meta) in let toplevel = Option.value ~default:false (B0_meta.find toplevel meta) in let ocamlrt_js = let opts = Cmd.empty in let build_dir = B0_build.current_dir b in let o = Fpath.(build_dir / "ocamlrt.js") in build_runtime m ~opts ~jss ~o; o in let opts = B0_meta.find_or_default compile_opts meta in let opts = if toplevel then Cmd.(opts % "--toplevel") else opts in let lib_jss, std_exit_js = (* We need a cache similar to B0_ocaml.Libresolver for jsing libs. The ops will be cached across units but not the file writes which we should do in the shared dir *) let build_dir = B0_build.current_dir b in let compile obj = (* FIXME this won't work with lib convention we need to remember the lib_name and mangle. *) let o = Fpath.(build_dir / (Fpath.basename obj ^ ".js")) in let opts = Cmd.(opts %% arg "-I" %% path (Fpath.parent obj)) in compile m ~opts ~source_map ~jss:[] ~byte:obj ~o; o in let compile_lib acc obj = compile obj :: acc in let jss = List.rev (List.fold_left compile_lib [] lib_objs) in (* FIXME at least stdlib should be looked up via resolver *) let stdlib_cma = Fpath.(B0_ocaml.Conf.where conf / "stdlib.cma") in let stdlib_js = B0_memo.ready_file m stdlib_cma; compile stdlib_cma in let std_exit_cmo = Fpath.(B0_ocaml.Conf.where conf / "std_exit.cmo") in let std_exit_js = B0_memo.ready_file m std_exit_cmo; compile std_exit_cmo in stdlib_js :: jss, std_exit_js in let mod_obj_jss = let compile_obj acc obj = let o = Fpath.(obj + ".js") in compile m ~opts ~source_map ~jss:[] ~byte:obj ~o; o :: acc in List.rev (List.fold_left compile_obj [] mod_objs) in let jss = ocamlrt_js :: (lib_jss @ mod_obj_jss @ [std_exit_js]) in let opts = B0_meta.find_or_default link_opts meta in link m ~opts ~source_map ~jss ~o; Fut.return () let js_exe ~jss ~modsrcs ~o b = let comp_mode = B0_meta.find compilation_mode (B0_build.current_meta b) in match Option.value ~default:`Whole comp_mode with | `Whole -> js_of_byte_exe ~jss ~modsrcs ~o b | `Separate -> js_of_byte_objs ~jss ~modsrcs ~o b (* XXX the assets root churn may indicate it would be better to have that in B0_srcs. (or the deploy stuff) *) let build_setup ~srcs b = (* return a record maybe ? *) let* srcs = B0_srcs.(Fut.map by_ext @@ select b srcs) in let* modsrcs = get_modsrcs b ~srcs in (* FIXME the lookup should be ordered here: *) let jss = B0_file_exts.find_files B0_file_exts.js srcs in let build_dir = B0_build.current_dir b in let tool_name = B0_meta.get B0_unit.tool_name (B0_build.current_meta b) in let js = Fpath.(build_dir / tool_name) in let html = Fpath.(js -+ ".html") in let exe_html = Fpath.basename html in let htmls = B0_file_exts.find_files B0_file_exts.html srcs in let html = match htmls with | [] -> html (* This will be generated *) | htmls -> let base b f = Fpath.basename f = b in let file = match List.find_opt (base exe_html) htmls with | Some f -> f | None -> match List.find_opt (base "index.html") htmls with | Some f -> f | None -> List.hd htmls in match B0_meta.find assets_root (B0_build.current_meta b) with | Some r when Fpath.is_prefix r file -> Fpath.reroot ~src_root:r ~dst_root:build_dir file | _ -> Fpath.(build_dir / Fpath.basename file) in Fut.return (srcs, modsrcs, jss, js, html) (* Build js executables *) let exe_proc set_exe_path set_modsrcs srcs build = let* () = B0_ocaml.Code.check_any ~supported:B0_ocaml.Code.byte ~by:build in let* _srcs, modsrcs, jss, js, _html = build_setup ~srcs build in set_modsrcs modsrcs; set_exe_path js; js_exe ~modsrcs ~jss ~o:js build (* let node_action build u ~args = let err e = Log.err (fun m -> m "%s" e); Fut.return B0_cli.Exit.some_error in match B0_unit.get_meta B0_meta.exe_file u with | Error e -> err e | Ok exe_file -> let* exe_file = exe_file in let node = Fpath.v "node" in match Os.Cmd.get_tool (* FIXME first search in build *) node with | Error e -> err e | Ok node_exe -> let cmd = Cmd.(path node %% path exe_file %% list args) in B0_unit.Action.exec_file build u node_exe cmd *) let restrict = "To bytecode", fun built -> if B0_ocaml.Code.Set.mem Byte built then B0_ocaml.Code.byte else B0_ocaml.Code.none let meta_base ~name ~modsrcs ~tool_name ~public ~requires ~assets_root:ar ~exe_path = B0_meta.empty |> B0_meta.tag tag |> B0_meta.add_some assets_root ar |> B0_meta.add B0_meta.public public |> B0_meta.tag B0_ocaml.tag |> B0_meta.add_some_or_default B0_ocaml.requires requires |> B0_meta.add B0_ocaml.modsrcs modsrcs |> B0_meta.add B0_ocaml.Code.restrict restrict |> B0_meta.tag B0_meta.exe |> B0_meta.add B0_unit.tool_name tool_name |> B0_meta.add B0_unit.exe_file exe_path let exe ?(wrap = fun proc b -> proc b) ?doc ?(meta = B0_meta.empty) ?assets_root ?requires ?(public = false) ?name tool_name ~srcs = let name = match name with | None -> String.map (function '.' -> '-' | c -> c) tool_name | Some name -> name in let modsrcs, set_modsrcs = Fut.make () in let exe_path, set_exe_path = Fut.make () in let base = meta_base ~name ~modsrcs ~tool_name ~public ~requires ~assets_root ~exe_path in let meta = B0_meta.override base ~by:meta in let proc = wrap (exe_proc set_exe_path set_modsrcs srcs) in B0_unit.make ?doc ~meta name proc (* Build html webs *) let copy_assets m srcs ~exts ~assets_root ~dst = let assets = B0_file_exts.find_files exts srcs in let copy acc src = let dst = match assets_root with | Some r when Fpath.is_prefix r src -> Fpath.reroot ~src_root:r ~dst_root:dst src | _ -> Fpath.(dst / Fpath.basename src) in B0_memo.copy m src ~dst; Fpath.Set.add dst acc in B0_memo.ready_files m assets; List.fold_left copy Fpath.Set.empty assets let copy_html_page_assets ~srcs b = let assets_root = match B0_meta.find assets_root (B0_build.current_meta b) with | None -> None | Some r -> Some (Fpath.(B0_build.scope_dir b // r)) in let build_dir = B0_build.current_dir b in let exts = String.Set.remove ".js" B0_file_exts.www in copy_assets (B0_build.memo b) srcs ~exts ~assets_root ~dst:build_dir let html_page_proc ~html_file ~js_file set_modsrcs srcs b = let* () = B0_ocaml.Code.check_any ~supported:B0_ocaml.Code.byte ~by:b in let html_file = B0_build.in_current_dir b html_file in let js_file = B0_build.in_current_dir b js_file in let* srcs = B0_srcs.(Fut.map by_ext @@ select b srcs) in let* modsrcs = get_modsrcs b ~srcs in set_modsrcs modsrcs; (* FIXME the lookup should be ordered here: *) let jss = B0_file_exts.find_files B0_file_exts.js srcs in let* () = js_exe ~modsrcs ~jss ~o:js_file b in let assets = copy_html_page_assets ~srcs b in if Fpath.Set.mem html_file assets then Fut.return () else let css = Fpath.Set.filter (Fpath.has_ext ".css") assets in let styles = let build_dir = B0_build.current_dir b in let base f = Fpath.to_string (Option.get (Fpath.strip_prefix build_dir f)) in List.map base (Fpath.Set.elements css) in let scripts = [Fpath.basename js_file] in write_page (B0_build.memo b) ~styles ~scripts ~o:html_file; Fut.return () let html_page ?(wrap = fun proc b -> proc b) ?doc ?(meta = B0_meta.empty) ?assets_root:aroot ?requires ?name ?js_file page ~srcs = let name = Option.value ~default:page name in let html_file = Fpath.fmt "%s.html" page in let js_file = match js_file with | Some f -> Fpath.v f | None -> Fpath.fmt "%s.js" page in let modsrcs, set_modsrcs = Fut.make () in let base = B0_meta.empty |> B0_meta.tag tag |> B0_meta.tag B0_ocaml.tag |> B0_meta.tag B0_meta.exe |> B0_meta.add_some assets_root aroot |> B0_meta.add_some_or_default B0_ocaml.requires requires |> B0_meta.add B0_ocaml.modsrcs modsrcs |> B0_meta.add B0_ocaml.Code.restrict restrict |> B0_meta.add B0_show_url.url (`In (`Unit_dir, html_file)) |> B0_meta.add B0_unit.Action.key B0_show_url.action in let meta = B0_meta.override base ~by:meta in let proc = wrap (html_page_proc ~html_file ~js_file set_modsrcs srcs) in B0_unit.make ?doc ~meta name proc let () = B0_scope.close ()
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>