Fix iOS WebKit crash by shipping external app.js instead of inlined HTML.

Stop embedding the 1.4MB bundle in index.html to avoid std::overflow_error on launch, validate the iOS asset build, and surface load failures in the native shell.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-06-06 21:53:26 +08:00
parent d9d97552c8
commit db5465c646
5 changed files with 87 additions and 143 deletions
@@ -6,6 +6,7 @@ final class WebViewStore: ObservableObject {
@Published var isLoading = true
@Published var canGoBack = false
@Published var pageTitle = "WordLoop"
@Published var loadError: String?
weak var webView: WKWebView?
var schemeHandler: BundleWebSchemeHandler?
}
@@ -92,26 +93,23 @@ struct WordLoopWebView: UIViewRepresentable {
loadedEntryURL = entryURL
loadedAPIBaseURL = apiBaseURL
pendingAPIBaseURL = apiBaseURL
store.isLoading = true
Task { @MainActor in
store.isLoading = true
store.loadError = nil
}
if usesBundledFrontend, let bundleEntry = Self.bundledHTMLPayload() {
// loadHTMLString wordloop:// WKWebView /
webView.loadHTMLString(bundleEntry.html, baseURL: bundleEntry.baseURL)
if usesBundledFrontend,
let wwwRoot = ServerSettings.bundledWWWDirectory,
let indexURL = ServerSettings.bundledHomeURL {
let readAccess = ServerSettings.bundledReadAccessURL() ?? wwwRoot
webView.loadFileURL(indexURL, allowingReadAccessTo: readAccess)
} else if let remoteURL = ServerSettings.homeURL(for: apiBaseURL) {
webView.load(URLRequest(url: remoteURL, cachePolicy: .reloadIgnoringLocalCacheData))
} else {
webView.load(URLRequest(url: entryURL, cachePolicy: .reloadIgnoringLocalCacheData))
}
}
private static func bundledHTMLPayload() -> (html: String, baseURL: URL)? {
guard let wwwRoot = ServerSettings.bundledWWWDirectory,
let indexURL = ServerSettings.bundledHomeURL,
let html = try? String(contentsOf: indexURL, encoding: .utf8),
!html.isEmpty else {
return nil
}
return (html, wwwRoot)
}
func updateAPIBase(_ apiBaseURL: String, in webView: WKWebView) {
guard loadedAPIBaseURL != apiBaseURL else { return }
loadedAPIBaseURL = apiBaseURL
@@ -126,26 +124,43 @@ struct WordLoopWebView: UIViewRepresentable {
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
store.isLoading = true
store.canGoBack = webView.canGoBack
Task { @MainActor in
store.isLoading = true
store.canGoBack = webView.canGoBack
}
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
store.isLoading = false
store.canGoBack = webView.canGoBack
Task { @MainActor in
store.isLoading = false
store.canGoBack = webView.canGoBack
store.loadError = error.localizedDescription
}
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
store.isLoading = false
store.canGoBack = webView.canGoBack
Task { @MainActor in
store.isLoading = false
store.canGoBack = webView.canGoBack
store.loadError = error.localizedDescription
}
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
store.isLoading = false
store.canGoBack = webView.canGoBack
store.pageTitle = webView.title ?? "WordLoop"
if let apiBaseURL = pendingAPIBaseURL {
updateAPIBase(apiBaseURL, in: webView)
Task { @MainActor in
store.isLoading = false
store.canGoBack = webView.canGoBack
store.pageTitle = webView.title ?? "WordLoop"
if let apiBaseURL = pendingAPIBaseURL {
updateAPIBase(apiBaseURL, in: webView)
}
}
webView.evaluateJavaScript("document.getElementById('app')?.innerHTML?.length ?? 0") { [weak self] result, _ in
let appLen = (result as? NSNumber)?.intValue ?? 0
guard appLen <= 0 else { return }
Task { @MainActor in
self?.store.loadError = "页面未渲染,请删除 App 后重新安装"
}
}
}
}
@@ -197,7 +212,24 @@ struct WordLoopWebContainer: View {
)
.ignoresSafeArea(edges: .bottom)
if store.isLoading {
if let loadError = store.loadError {
VStack(spacing: 12) {
Text("页面加载失败")
.font(.headline)
Text(loadError)
.font(.caption)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
Button("重新加载") {
store.loadError = nil
store.webView?.reload()
}
.buttonStyle(.borderedProminent)
}
.padding(24)
.background(.ultraThinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
} else if store.isLoading {
ProgressView("加载中…")
.padding(20)
.background(.ultraThinMaterial)
File diff suppressed because one or more lines are too long