---
date: "2023-08-27 14:20:20"
---
# 在 react-native-webview 中使用打包後的 JS 程式碼:使用 vite 整合進 Expo 的開發流程
## 睽違七年的 React Native
上個月回來寫 [React-native](https://reactnative.dev/),想做點自己的小玩具玩。距離上次寫 React-native 已經是七年前的 [kaif.io](https://github.com/Yukaii/kaif-ios) rn app 了,歲月如梭啊,在這期間,我竟然連**一次**都沒有想在手機上跑自己程式的想法,看來果然是被蘋果生態系禁錮太久了 XD ~~(歡迎來到水果監獄)~~
為了要在 React-native 上使用 [codemirror](https://codemirror.net/) 這個網頁用的程式碼編輯器,我需要使用 [react-native-webview](https://github.com/react-native-webview/react-native-webview),而且為了實作可互動的界面,我同樣需要在 webview 中使用 React.js
要如何實作呢?我們先從 [react-native-webview](https://github.com/react-native-webview/react-native-webview) 的 API 開始看起。
## react-native-webview 的 source API
在 react-native-webview 提供兩種 API 可以執行 JavaScript 程式碼:
1. [`source` Attribute](https://github.com/react-native-webview/react-native-webview/blob/master/docs/Reference.md#reference): 可以給網址或是 HTML 原始碼
2. [injected JavaScript 系列](https://github.com/react-native-webview/react-native-webview/blob/master/docs/Reference.md#injectedjavascript):直接給 inline 的 JavaScript 程式碼,會在不同的生命週期事件執行
因為我想要讓我的 webview 能夠離線執行,所以勢必要把 JavaScript 和 HTML 一起打包進 React Native App 裡面。
翻了翻其他使用 webview 客製的案例,以 [`react-native-webview-leaflet`](https://github.com/reggie3/react-native-webview-leaflet)來說,就是直接把 [build 完的 html 原始碼](https://github.com/reggie3/react-native-webview-leaflet/tree/master/WebViewLeaflet/assets) commit 進 repo 裡,作為一位已經被**現代網頁開發工具**慣壞了的工程師,這種行為當然不可以發生!而且要怎麼 Hot Reload?
值得一題,我同時還使用了 [Expo](https://expo.io/) 這套 React Native 開發框架。Expo 把許多 React Native 的原生 Library 都包進框架裡面(包含 react-native-webview),帶來最無縫最容易最絲滑順暢的 React Native 開發體驗,要怎麼把自定的 WebView JavaScript Bundling 和 Expo 無縫整合呢?
## vite and `vite-plugin-singlefile`, 以及一點點的 gluing scripts
因為一些~~大人的~~因素,完整的程式碼還沒開源,我這邊就貼部分的程式碼出來。
首先先準備好 webview 要用的 html (`/webivew/index.html`):
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0,user-scalable=no"
/>
<title>CodeMirror</title>
</head>
<body>
<div id="app"></div>
<script src="./index.ts" type="module"></script>
</body>
</html>
```
以及對應的 vite 設定檔(`/vite.config.js`)
```javascript
import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";
export default defineConfig({
plugins: [viteSingleFile()],
build: {
target: "ios13",
rollupOptions: {
input: ["./webview/index.html"],
output: {
manualChunks: undefined,
},
},
},
});
```
此時每次 vite build 後會輸出到 `/dist/webview/index.html`,我再寫了一個簡單的腳本,複製 `index.html` 到 react-native 的 `/assets` 目錄去(~~欸對就這麼簡單還要寫 JavaScript 幹嘛 XDD 為了跨平臺辣~~)
```javascript
const fs = require("fs");
const path = require("path");
const distPath = path.join(__dirname, "../dist");
const webviewPath = path.join(distPath, "webview");
const indexPath = path.join(webviewPath, "index.html");
const outputPath = path.join(__dirname, "../assets/webviews/index.html");
if (!fs.existsSync(distPath) || !fs.existsSync(indexPath)) {
console.log("No dist folder or index.html found, skipping updateHTML");
process.exit(0);
}
fs.copyFileSync(indexPath, outputPath);
```
然後寫一個 react hook 去讀 HTML 內容:
```javascript
import useSWR from "swr";
import * as FileSystem from "expo-file-system";
import { useAssets } from "expo-asset";
export const useWebviewHTML = () => {
const [assets] = useAssets([require("../assets/webviews/index.html")]);
const webviewAssetUri = assets?.[0]?.localUri;
const { data: webviewHTML } = useSWR(
() => (webviewAssetUri ? "webview" : null),
async () => {
return await FileSystem.readAsStringAsync(webviewAssetUri!);
},
);
return webviewHTML;
};
export default useWebviewHTML;
```
最後丟給 `react-native-webview`:
```jsx
import { WebView } from "react-native-webview";
export default function WebviewComponent () {
// ...
const webviewHTML = useWebviewHTML();
// ...
return <WebView
source={{ html: webviewHTML }}
/>
}
```
只要 Assets 檔案有改,Reload app 的話 html 也會重新載入,超級舒適!!
## 就這樣了
難得的技術小筆記。其實最近弄 [Blast Launcher](https://github.com/BlastLauncher/blast) 比較多,方才打開草稿記錄夾,發現這篇也躺了快一個月,趕忙在還沒忘記的時候記錄一下。
關於流程其實也有改進的地方,比如複製檔案那邊,應該要讓 vite 直接輸出到 `assets` 資料夾就好,這樣 incremental build 也會有更舒服的體驗,不過當時沒有足夠的時間研究出來,就算是留下一個改進的伏筆了 (=ↀωↀ=)✧