this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Reinitialize x-ocaml backend when universe changes on SPA navigation

Refactor x_ocaml.ml so that the Page/Backend are created lazily
and keyed on the current universe configuration (read from <meta>
tags). When a new <x-ocaml> element connects and the universe has
changed (e.g. after SPA navigation to a page with a different
@x-ocaml.universe), a fresh backend and page are created.

Previously, the backend was created once at script load time and
captured in the custom element's closure. SPA navigation would
update the <meta> tags (via the interactive extension's Js_inline
scripts) but the old backend/worker stayed alive, causing cells
on the new page to use the wrong universe's libraries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+62 -42
+62 -42
src/x_ocaml.ml
··· 13 13 if Jv.is_none content then None 14 14 else Some (Jv.to_string content) 15 15 16 - let () = 16 + let resolve_url relative = 17 + let url_ctor = Jv.get Jv.global "URL" in 18 + let loc = Jv.get (Jv.get Jv.global "window") "location" in 19 + let href = Jv.to_string (Jv.get loc "href") in 20 + let url_obj = Jv.new' url_ctor [| Jv.of_string relative; Jv.of_string href |] in 21 + Jv.to_string (Jv.get url_obj "href") 22 + 23 + let sync_fetch_text url = 24 + let xhr = Jv.new' (Jv.get Jv.global "XMLHttpRequest") [||] in 25 + Jv.call xhr "open" [| Jv.of_string "GET"; Jv.of_string url; Jv.of_bool false |] |> ignore; 26 + (try Jv.call xhr "send" [||] |> ignore with _ -> ()); 27 + let status = Jv.to_int (Jv.get xhr "status") in 28 + if status = 200 then Some (Jv.to_string (Jv.get xhr "responseText")) 29 + else None 30 + 31 + (** Read the current universe configuration from <meta> tags and 32 + create a backend + page. Returns None if no universe is configured. *) 33 + let create_page_for_current_config () = 17 34 let extra_load = 18 35 match current_attribute "src-load" with 19 36 | None -> None ··· 27 44 match read_meta "x-ocaml-backend" with 28 45 | Some name -> name 29 46 | None -> 30 - (* Infer backend from universe: if a universe is configured, use jtw *) 31 47 match read_meta "x-ocaml-universe" with 32 48 | Some _ -> "jtw" 33 49 | None -> "builtin" ··· 42 58 if pkgs = [] then None else Some pkgs 43 59 in 44 60 45 - let resolve_url relative = 46 - let url_ctor = Jv.get Jv.global "URL" in 47 - let loc = Jv.get (Jv.get Jv.global "window") "location" in 48 - let href = Jv.to_string (Jv.get loc "href") in 49 - let url_obj = Jv.new' url_ctor [| Jv.of_string relative; Jv.of_string href |] in 50 - Jv.to_string (Jv.get url_obj "href") 51 - in 52 - 53 61 let findlib_index_url = 54 62 match read_meta "x-ocaml-universe" with 55 63 | None -> None ··· 57 65 let base = if String.length base > 0 && base.[String.length base - 1] = '/' 58 66 then base else base ^ "/" in 59 67 Some (resolve_url (base ^ "findlib_index.json")) 60 - in 61 - 62 - let sync_fetch_text url = 63 - let xhr = Jv.new' (Jv.get Jv.global "XMLHttpRequest") [||] in 64 - Jv.call xhr "open" [| Jv.of_string "GET"; Jv.of_string url; Jv.of_bool false |] |> ignore; 65 - (try Jv.call xhr "send" [||] |> ignore with _ -> ()); 66 - let status = Jv.to_int (Jv.get xhr "status") in 67 - if status = 200 then Some (Jv.to_string (Jv.get xhr "responseText")) 68 - else None 69 68 in 70 69 71 70 let findlib_index_json = ··· 122 121 in 123 122 124 123 match worker_url with 125 - | None -> 126 - (* No worker URL available — x-ocaml elements on this page are inert 127 - (e.g. code blocks on non-interactive pages due to resource leak). *) 128 - () 124 + | None -> None 129 125 | Some worker_url -> 130 - 131 - let backend = Backend.make ~backend:backend_name ?extra_load ?findlib_requires 132 - ?findlib_index:findlib_index_url worker_url 133 - in 126 + let backend = Backend.make ~backend:backend_name ?extra_load ?findlib_requires 127 + ?findlib_index:findlib_index_url worker_url 128 + in 129 + let format_config = 130 + match current_attribute "x-ocamlformat" with 131 + | None -> None 132 + | Some conf -> Some (Jstr.to_string conf) 133 + in 134 + let extra_style = current_attribute "src-style" in 135 + let inline_style = current_attribute "inline-style" in 136 + let default_run_on = 137 + current_attribute "run-on" |> Option.map Jstr.to_string 138 + in 139 + let page = 140 + Page.create ~backend ?extra_style ?inline_style ?default_run_on 141 + ?format_config () 142 + in 143 + Some page 134 144 135 - let format_config = 136 - match current_attribute "x-ocamlformat" with 137 - | None -> None 138 - | Some conf -> Some (Jstr.to_string conf) 139 - in 145 + (** Key that identifies the current universe configuration. 146 + Used to detect when SPA navigation changes the universe. *) 147 + let universe_key () = 148 + let universe = read_meta "x-ocaml-universe" |> Option.value ~default:"" in 149 + let worker = read_meta "x-ocaml-worker" |> Option.value ~default:"" in 150 + universe ^ "|" ^ worker 140 151 141 - let extra_style = current_attribute "src-style" in 142 - let inline_style = current_attribute "inline-style" in 152 + let () = 153 + (* Mutable state: current page and the universe key it was created for. *) 154 + let current_page : Page.t option ref = ref None in 155 + let current_key = ref "" in 143 156 144 - let default_run_on = 145 - current_attribute "run-on" |> Option.map Jstr.to_string 157 + let ensure_page () = 158 + let key = universe_key () in 159 + if key <> !current_key || !current_page = None then begin 160 + current_page := create_page_for_current_config (); 161 + current_key := key 162 + end; 163 + !current_page 146 164 in 147 165 148 - let page = 149 - Page.create ~backend ?extra_style ?inline_style ?default_run_on 150 - ?format_config () 151 - in 166 + (* Create initial page for the current config *) 167 + current_page := create_page_for_current_config (); 168 + current_key := universe_key (); 152 169 153 170 let elt_name = 154 171 match current_attribute "elt-name" with ··· 157 174 in 158 175 159 176 ignore @@ Webcomponent.define elt_name @@ fun this -> 160 - let _cell = Page.register page this in 161 - () 177 + match ensure_page () with 178 + | None -> () (* No universe configured — element is inert *) 179 + | Some page -> 180 + let _cell = Page.register page this in 181 + ()