用 JavaScript:JS Callback 示例的同步導(dǎo)入與 Suspense 懶加載實(shí)戰(zhàn))
Yew 中通過 wasm-bindgen 調(diào)用 JavaScriptJS Callback 示例的同步導(dǎo)入與 Suspense 懶加載實(shí)戰(zhàn)【免費(fèi)下載鏈接】yewRust / Wasm framework for creating reliable and efficient web applications項(xiàng)目地址: https://gitcode.com/gh_mirrors/ye/yew本文以倉庫 examples/js_callback 示例為主體完整講解在 YewRust/Wasm 前端框架應(yīng)用中通過wasm-bindgen導(dǎo)入 JavaScript 功能的兩種落地方式基于JS Snippets的自動(dòng)拷貝方案與基于trunk 復(fù)制 動(dòng)態(tài)import()的懶加載方案并深入剖析如何借助 Yew 的 Suspense 機(jī)制延遲渲染依賴 JS 模塊的組件。讀完本文你將掌握在真實(shí) Yew 工程中組織、構(gòu)建、運(yùn)行 JS 互操作代碼的完整鏈路包括綁定聲明、構(gòu)建期鉤子注入運(yùn)行期路徑、動(dòng)態(tài)導(dǎo)入與異步渲染的配合寫法。示例概覽與核心概念js_callback示例是一個(gè)純粹的技術(shù)演示頁面包含兩段內(nèi)容一段“重要”Important信息在應(yīng)用首屏加載時(shí)立即顯示另一段“不重要”Unimportant信息則只有在用戶點(diǎn)擊按鈕后才通過動(dòng)態(tài)加載的 JavaScript 模塊取得。它集中展示了三件事使用wasm-bindgen從 Rust 導(dǎo)入 JavaScript 功能相關(guān)背景可參考 wasm-bindgen 官方指南中“import-js”章節(jié)。JS 文件的兩種服務(wù)serving方式一是由 wasm-bindgen 的 JS Snippets 機(jī)制自動(dòng)拷貝二是借助 trunk 的構(gòu)建鉤子手動(dòng)復(fù)制文件。使用 Yew 的 Suspense 延遲加載讓依賴異步導(dǎo)入 JS 模塊的組件在數(shù)據(jù)就緒前先展示 fallback。從工程結(jié)構(gòu)看examples/js_callback示例由三塊組成文件職責(zé)js/imp.js首屏同步加載的 JS 模塊導(dǎo)出hello()js/unimp.js被懶加載的 JS 模塊導(dǎo)出bye()src/bindings.rs全部wasm-bindgen綁定聲明extern 塊、模塊導(dǎo)入、內(nèi)聯(lián) JSsrc/main.rs組件與 Suspense 邏輯trunk_post_build.rstrunk 的post_build鉤子負(fù)責(zé)注入 snippets 路徑兩種 JS 文件服務(wù)方案原文檔明確指出只要 JS 文件位于構(gòu)建產(chǎn)物dist目錄中就可以被正確服務(wù)。將文件送入dist有兩條路徑使用 JS Snippets由wasm-bindgen負(fù)責(zé)拷貝文件。開發(fā)者只需在 Rust 側(cè)聲明extern塊構(gòu)建時(shí) wasm-bindgen 會(huì)自動(dòng)把文件復(fù)制到dist/snippets/bin-name-hash/目錄下。使用 trunk 復(fù)制文件并手動(dòng)導(dǎo)入僅在 JS 模塊需要懶加載時(shí)才必需。它跳過了向應(yīng)用提供 snippets 目錄路徑的步驟將文件復(fù)制到一個(gè)已知位置方便在import()語句中直接引用。本示例同時(shí)采用了兩種方案正好可以對比學(xué)習(xí)。方案一JS Snippets同步加載JS Snippets 是 wasm-bindgen 的內(nèi)置機(jī)制只要在extern塊中標(biāo)注#[wasm_bindgen(module ...)]wasm-bindgen 就會(huì)在構(gòu)建期把對應(yīng) JS 文件打包進(jìn)產(chǎn)物開發(fā)者無需手工管理拷貝。本示例對js/imp.js就采用這種方式見 src/bindings.rs#[wasm_bindgen(module /js/imp.js)] unsafe extern C { #[wasm_bindgen] pub fn hello() - String; }對應(yīng)的 js/imp.js 是一個(gè) ES Moduleexport function hello() { return very important message }由于該模塊需要隨應(yīng)用初始加載因此直接使用普通的模塊導(dǎo)入即可。在組件里hello()被包進(jìn)use_memo中只在首次渲染時(shí)執(zhí)行一次#[function_component] fn Important() - Html { let msg use_memo((), |_| bindings::hello()); html! { h2{Important}/h2 p{msg}/p } }這段代碼展示了 JS Snippets 最省事的用法寫extern聲明 正常調(diào)用其余交給 wasm-bindgen。方案二懶加載 trunk 復(fù)制動(dòng)態(tài)導(dǎo)入如果 JS 模塊體積較大、希望延遲到用戶真正需要時(shí)才下載就需要?jiǎng)討B(tài)import()。此時(shí)的問題在于動(dòng)態(tài)導(dǎo)入需要一個(gè)運(yùn)行期才能確定的路徑因?yàn)?snippets 目錄名含構(gòu)建期 hash因此需要額外兩步配合讓 wasm-bindgen 把文件拷貝進(jìn) snippets 目錄本示例用了一個(gè)“占位”綁定來觸發(fā)拷貝見下文通過 trunk 的post_build鉤子把 snippets 的完整目錄路徑注入到頁面供運(yùn)行期拼接。觸發(fā)懶加載模塊的拷貝占位綁定技巧對于要懶加載的js/unimp.js示例在 src/bindings.rs 中聲明了一個(gè)永遠(yuǎn)不會(huì)被調(diào)用的“占位函數(shù)”目的是讓 wasm-bindgen 把該文件復(fù)制到 snippets 目錄#[wasm_bindgen(module /js/unimp.js)] unsafe extern C { /// This exists so that wasm bindgen copies js/unimp.js to /// dist/snippets/bin-name-hash/js/uninp.js #[wasm_bindgen] fn _dummy_fn_so_wasm_bindgen_copies_over_the_file(); }正如注釋所說這個(gè)函數(shù)本身不會(huì)被調(diào)用它存在的唯一意義就是讓 wasm-bindgen 把js/unimp.js拷貝到dist/snippets/bin-name-hash/js/unimp.js。這與同步加載的區(qū)別在于這里沒有把函數(shù)綁定為可直接調(diào)用的模塊導(dǎo)出而是在需要時(shí)用動(dòng)態(tài)import()在運(yùn)行期加載整個(gè)模塊。動(dòng)態(tài)導(dǎo)入的綁定inline_js 與 Promise運(yùn)行期的動(dòng)態(tài)導(dǎo)入通過一段內(nèi)聯(lián) JS實(shí)現(xiàn)。由于js-sys尚未提供import()的綁定代碼注釋引用了 wasm-bindgen 的 issue #2865、#3208示例在 src/bindings.rs 中用inline_js直接嵌入了一行 JavaScript// https://github.com/rustwasm/wasm-bindgen/issues/3208 #[wasm_bindgen(inline_js export function import2(path) { return import(path); })] unsafe extern C { #[wasm_bindgen(js_name import2)] pub fn import(name: str) - js_sys::Promise; ... }inline_js是 wasm-bindgen 提供的能力允許把 JS 源碼直接寫在 Rust 源碼里作為 ES Module經(jīng)#[wasm_bindgen]暴露給 Rust 調(diào)用。這里它暴露的import2其實(shí)就是原生動(dòng)態(tài)導(dǎo)入import(path)返回js_sys::Promise。模塊加載成功后通過JsFuture把 Promise 轉(zhuǎn)成 Rust 異步來自wasm-bindgen-futures再用unchecked_into把模塊對象轉(zhuǎn)換為預(yù)先聲明的UnimpModule類型并調(diào)用其方法#[wasm_bindgen] unsafe extern C { pub type UnimpModule; #[wasm_bindgen(method)] pub fn bye(this: UnimpModule) - String; }這樣動(dòng)態(tài)導(dǎo)入的模塊在 Rust 側(cè)擁有了類型安全的調(diào)用入口。注入 snippets 路徑trunk post_build 鉤子動(dòng)態(tài)導(dǎo)入需要知道模塊文件在服務(wù)器上的真實(shí) URL即snippets/bin-name-hash/這個(gè)含 hash 的目錄。這個(gè)路徑在構(gòu)建完成后才確定因此需要在構(gòu)建產(chǎn)物上“打個(gè)補(bǔ)丁”。trunk 的鉤子機(jī)制正好勝任本示例用pre_build鉤子把鉤子程序編譯成可執(zhí)行文件再用post_build鉤子運(yùn)行它配置見 Trunk.toml[[hooks]] stage pre_build command rustc command_arguments [trunk_post_build.rs] [[hooks]] stage post_build command ./trunk_post_build鉤子程序 trunk_post_build.rs 的邏輯分四步讀取 trunk 提供的環(huán)境變量TRUNK_STAGING_DIRstaging 目錄與TRUNK_PUBLIC_URL在 staging 目錄下找到唯一的 snippets 子目錄取出含 hash 的目錄名拼接出wasm_bindgen_snippets_path {public_url}/snippets/{dir_name}讀取 staging 目錄中的index.html在/head前插入一行腳本把該路徑寫入全局變量window.wasmBindgenSnippetsPath path;let stage_dir read_env(TRUNK_STAGING_DIR); ... let wasm_bindgen_snippets_path format!({public_url}/snippets/{dir_name}); split.insert(1, format!(scriptwindow.wasmBindgenSnippetsPath {wasm_bindgen_snippets_path};/script/head));對應(yīng)的讀取端在 src/bindings.rs通過一個(gè)Window類型的 getter 綁定讀取該全局變量pub type Window; #[wasm_bindgen(method, getter, js_name wasmBindgenSnippetsPath)] pub fn wasm_bindgen_snippets_path(this: Window) - String;主程序 src/main.rs 在啟動(dòng)渲染前把讀到的路徑存入全局OnceLockString供后續(xù)懶加載邏輯使用static WASM_BINDGEN_SNIPPETS_PATH: OnceLockString OnceLock::new(); fn main() { let wasm_bindgen_snippets_path js_sys::global() .unchecked_into::bindings::Window() .wasm_bindgen_snippets_path(); WASM_BINDGEN_SNIPPETS_PATH .set(wasm_bindgen_snippets_path) .expect(unreachable); yew::Renderer::App::new().render(); }值得注意的是trunk_post_build這種“構(gòu)建后改寫 index.html”的做法本質(zhì)上是把構(gòu)建期才可知的信息回填進(jìn)頁面這正是原文檔所說“跳過向應(yīng)用提供 snippets 目錄路徑的步驟”的反例——本示例走的是需要提供路徑的完整路線而第二種方案trunk 直接復(fù)制文件到已知位置則完全不需要這套注入邏輯。懶加載組件use_future 與 Suspense 的配合拿到 snippets 路徑后懶加載邏輯封裝在一個(gè)自定義 hook 中。它使用 Yew 的use_future鉤子執(zhí)行異步任務(wù)并返回SuspensionResultString從而讓組件可以被 Suspense 掛起#[hook] fn use_do_bye() - SuspensionResultString { let path WASM_BINDGEN_SNIPPETS_PATH .get() .map(|path| format!({path}/js/unimp.js)) .unwrap(); let s use_future(|| async move { let promise bindings::import(path); let module JsFuture::from(promise).await.unwrap_throw(); let module module.unchecked_into::bindings::UnimpModule(); module.bye() })?; Ok((*s).clone()) }關(guān)鍵點(diǎn)路徑拼接{snippets路徑}/js/unimp.js與 wasm-bindgen 拷貝后的目錄結(jié)構(gòu)dist/snippets/bin-name-hash/js/unimp.js一一對應(yīng)異步橋接bindings::import(path)返回js_sys::Promise經(jīng)JsFuture::from(...).await轉(zhuǎn)成 Rust 的Future類型轉(zhuǎn)換模塊對象用unchecked_into::bindings::UnimpModule()轉(zhuǎn)換為有類型綁定后調(diào)用bye()?傳播use_future(...)?的?會(huì)把“未就緒”狀態(tài)向上傳播使組件掛起直到 Future 完成。組件側(cè)通過返回HtmlResult配合 Suspense#[function_component] fn UnImportant() - HtmlResult { let msg use_do_bye()?; Ok(html! { h2{Unimportant}/h2 p{msg}/p }) }父組件App用use_state控制是否顯示“不重要”內(nèi)容并用Suspense包裹fallback 為一段占位文本let showing_unimportant use_state(|| false); let fallback html! {fallback}; html! { main Important / button onclick{show_unimportant}{load unimportant data}/button Suspense {fallback} if *showing_unimportant { UnImportant / } /Suspense /main }運(yùn)行流程因此變成頁面首屏只渲染Important /其 JS 已隨初始加載就緒用戶點(diǎn)擊按鈕后showing_unimportant置為trueUnImportant /開始掛起頁面先展示 fallback待動(dòng)態(tài)import()加載并執(zhí)行完unimp.js后再渲染出“不重要”消息。這正是原文檔所說“用 Suspense 延遲加載代碼片段”的完整落地。工程依賴與構(gòu)建配置示例的依賴配置見 Cargo.toml其中wasm-bindgen、wasm-bindgen-futures、js-sys均復(fù)用 workspace 版本Yew 以本地路徑引用packages/yew并開啟csrfeature客戶端渲染[dependencies] wasm-bindgen.workspace true yew { path ../../packages/yew, features [csr] } wasm-bindgen-futures.workspace true js-sys.workspace true入口頁面 index.html 是典型的 trunk 工程模板通過data-trunk relrust聲明 Rust 構(gòu)建目標(biāo)link>trunk serve --opentrunk serve會(huì)按 Trunk.toml 依次執(zhí)行pre_build編譯鉤子程序、正常構(gòu)建、post_build注入 snippets 路徑然后自動(dòng)打開瀏覽器。需要生產(chǎn)構(gòu)建時(shí)改用trunk build即可產(chǎn)物中的index.html已包含注入的window.wasmBindgenSnippetsPathJS 文件則位于dist/snippets/下。改進(jìn)方向原文檔也坦誠指出本示例是純技術(shù)演示缺乏實(shí)際業(yè)務(wù)意義。若要把它演進(jìn)為更有價(jià)值的示例可以考慮在 JavaScript 代碼中實(shí)現(xiàn)更復(fù)雜的邏輯充分展示wasm-bindgen的參數(shù)傳遞、對象互操作等更多能力用 CSS 改善頁面呈現(xiàn)讓“重要/不重要”信息的對比更直觀將“動(dòng)態(tài)導(dǎo)入 Suspense 懶加載”這一模式遷移進(jìn)一個(gè)小型真實(shí)應(yīng)用例如按需加載的大圖表組件、第三方 SDK 封裝等驗(yàn)證其工程可行性。小結(jié)js_callback示例雖然代碼量不大卻完整覆蓋了 Yew 工程中 JS 互操作的一條主線同步場景用 JS Snippets 普通模塊導(dǎo)入imp.jsbindings::hello懶加載場景用占位綁定觸發(fā)拷貝、inline_js暴露動(dòng)態(tài)導(dǎo)入、post_build鉤子注入運(yùn)行期路徑、use_futureSuspense完成異步渲染。理解這條鏈路后你便能在自己的 Yew 應(yīng)用中按需選擇同步或懶加載方式接入任意 JavaScript 模塊并且清楚每一步背后的構(gòu)建與運(yùn)行機(jī)制。【免費(fèi)下載鏈接】yewRust / Wasm framework for creating reliable and efficient web applications項(xiàng)目地址: https://gitcode.com/gh_mirrors/ye/yew創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考