diff --git a/.vscode/settings.json b/.vscode/settings.json index 17e18e64da..275152650a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,9 @@ "[javascript]": { "editor.formatOnSave": false }, + "[html]": { + "editor.formatOnSave": false + }, "dioxus.formatOnSave": "disabled", // "rust-analyzer.check.workspace": true, // "rust-analyzer.check.workspace": false, @@ -21,4 +24,4 @@ "rust-analyzer.cargo.extraArgs": [ "--tests" ], -} +} \ No newline at end of file diff --git a/packages/cli/assets/web/prod.index.html b/packages/cli/assets/web/prod.index.html index a7188671bd..505b180289 100644 --- a/packages/cli/assets/web/prod.index.html +++ b/packages/cli/assets/web/prod.index.html @@ -1,3 +1,4 @@ + {app_title} diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 17bdd2f60f..576e38333a 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -3364,8 +3364,14 @@ impl BuildRequest { wasm_opt::optimize(&post_bindgen_wasm, &post_bindgen_wasm, &wasm_opt_options).await?; } - // In release mode, we make the wasm and bindgen files into assets so they get bundled with max - // optimizations. + if self.should_bundle_to_asset() { + // Make sure to register the main wasm file with the asset system + assets.register_asset(&post_bindgen_wasm, AssetOptions::Unknown)?; + } + + // Now that the wasm is registered as an asset, we can write the js glue shim + self.write_js_glue_shim(assets)?; + if self.should_bundle_to_asset() { // Register the main.js with the asset system so it bundles in the snippets and optimizes assets.register_asset( @@ -3374,53 +3380,89 @@ impl BuildRequest { )?; } - if self.should_bundle_to_asset() { - // Make sure to register the main wasm file with the asset system - assets.register_asset(&post_bindgen_wasm, AssetOptions::Unknown)?; - } - // Write the index.html file with the pre-configured contents we got from pre-rendering self.write_index_html(assets)?; Ok(()) } + fn write_js_glue_shim(&self, assets: &AssetManifest) -> Result<()> { + let wasm_path = self.bundled_wasm_path(assets); + + // Load and initialize wasm without requiring a separate javascript file. + // This also allows using a strict Content-Security-Policy. + let mut js = std::fs::OpenOptions::new() + .append(true) + .open(self.wasm_bindgen_js_output_file())?; + let mut buf_writer = std::io::BufWriter::new(&mut js); + writeln!( + buf_writer, + r#" +window.__wasm_split_main_initSync = initSync; + +// Actually perform the load +__wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ + // assign this module to be accessible globally + window.__dx_mainWasm = wasm; + window.__dx_mainInit = __wbg_init; + window.__dx_mainInitSync = initSync; + window.__dx___wbg_get_imports = __wbg_get_imports; + + if (wasm.__wbindgen_start == undefined) {{ + wasm.main(); + }} +}}); +"#, + self.base_path_or_default(), + )?; + + Ok(()) + } + /// Write the index.html file to the output directory. This must be called after the wasm and js /// assets are registered with the asset system if this is a release build. pub(crate) fn write_index_html(&self, assets: &AssetManifest) -> Result<()> { - // Get the path to the wasm-bindgen output files. Either the direct file or the opitmized one depending on the build mode - let wasm_bindgen_wasm_out = self.wasm_bindgen_wasm_output_file(); - let wasm_path = if self.should_bundle_to_asset() { + let wasm_path = self.bundled_wasm_path(assets); + let js_path = self.bundled_js_path(assets); + + // Write the index.html file with the pre-configured contents we got from pre-rendering + std::fs::write( + self.root_dir().join("index.html"), + self.prepare_html(assets, &wasm_path, &js_path).unwrap(), + )?; + + Ok(()) + } + + fn bundled_js_path(&self, assets: &AssetManifest) -> String { + let wasm_bindgen_js_out = self.wasm_bindgen_js_output_file(); + if self.should_bundle_to_asset() { let name = assets - .get_first_asset_for_source(&wasm_bindgen_wasm_out) - .expect("The wasm source must exist before creating index.html"); + .get_first_asset_for_source(&wasm_bindgen_js_out) + .expect("The js source must exist before creating index.html"); format!("assets/{}", name.bundled_path()) } else { format!( "wasm/{}", - wasm_bindgen_wasm_out.file_name().unwrap().to_str().unwrap() + wasm_bindgen_js_out.file_name().unwrap().to_str().unwrap() ) - }; + } + } - let wasm_bindgen_js_out = self.wasm_bindgen_js_output_file(); - let js_path = if self.should_bundle_to_asset() { + /// Get the path to the wasm-bindgen output files. Either the direct file or the opitmized one depending on the build mode + fn bundled_wasm_path(&self, assets: &AssetManifest) -> String { + let wasm_bindgen_wasm_out = self.wasm_bindgen_wasm_output_file(); + if self.should_bundle_to_asset() { let name = assets - .get_first_asset_for_source(&wasm_bindgen_js_out) - .expect("The js source must exist before creating index.html"); + .get_first_asset_for_source(&wasm_bindgen_wasm_out) + .expect("The wasm source must exist before creating index.html"); format!("assets/{}", name.bundled_path()) } else { format!( "wasm/{}", - wasm_bindgen_js_out.file_name().unwrap().to_str().unwrap() + wasm_bindgen_wasm_out.file_name().unwrap().to_str().unwrap() ) - }; - - // Write the index.html file with the pre-configured contents we got from pre-rendering - std::fs::write( - self.root_dir().join("index.html"), - self.prepare_html(assets, &wasm_path, &js_path).unwrap(), - )?; - Ok(()) + } } fn info_plist_contents(&self, platform: Platform) -> Result { @@ -3908,7 +3950,7 @@ impl BuildRequest { self.inject_resources(assets, wasm_path, &mut html)?; // Inject loading scripts if they are not already present - self.inject_loading_scripts(&mut html); + self.inject_loading_scripts(assets, &mut html); // Replace any special placeholders in the HTML with resolved values self.replace_template_placeholders(&mut html, wasm_path, js_path); @@ -4000,7 +4042,7 @@ impl BuildRequest { // Manually inject the wasm file for preloading. WASM currently doesn't support preloading in the manganis asset system head_resources.push_str(&format!( - "" + "" )); Self::replace_or_insert_before("{style_include}", " - // We can't use a module script here because we need to start the script immediately when streaming - import("/{base_path}/{js_path}").then( - ({ default: init, initSync, __wbg_get_imports }) => { - // export initSync in case a split module needs to initialize - window.__wasm_split_main_initSync = initSync; - - // Actually perform the load - init({module_or_path: "/{base_path}/{wasm_path}"}).then((wasm) => { - // assign this module to be accessible globally - window.__dx_mainWasm = wasm; - window.__dx_mainInit = init; - window.__dx_mainInitSync = initSync; - window.__dx___wbg_get_imports = __wbg_get_imports; - - if (wasm.__wbindgen_start == undefined) { - wasm.main(); - } - }); - } - ); - + &format!( + r#" , + text_content: Option<&str>, +) { + let window = web_sys::window().expect("no global `window` exists"); + let document = window.document().expect("should have a document on window"); + let head = document.head().expect("document should have a head"); + + let element = document.create_element(local_name).unwrap(); + for (name, value) in attributes { + element.set_attribute(name, value).unwrap(); + } + if text_content.is_some() { + element.set_text_content(text_content); + } + head.append_child(&element).unwrap(); +} + /// Required to avoid blocking the Rust WASM thread. const PROMISE_WRAPPER: &str = r#" return (async function(){