Przeglądaj źródła

fix(desktop-electron): fix resource loading under file:// protocol (#17125)

Luke Parker 5 miesięcy temu
rodzic
commit
54e7baa6cf

+ 1 - 1
packages/desktop-electron/electron.vite.config.ts

@@ -27,7 +27,7 @@ export default defineConfig({
   },
   renderer: {
     plugins: [appPlugin],
-    publicDir: "../app/public",
+    publicDir: "../../../app/public",
     root: "src/renderer",
     build: {
       rollupOptions: {

+ 62 - 0
packages/desktop-electron/src/renderer/html.test.ts

@@ -0,0 +1,62 @@
+import { describe, expect, test } from "bun:test"
+import { join, dirname, resolve } from "node:path"
+import { existsSync } from "node:fs"
+import { fileURLToPath } from "node:url"
+
+const dir = dirname(fileURLToPath(import.meta.url))
+const root = resolve(dir, "../..")
+
+const html = async (name: string) => Bun.file(join(dir, name)).text()
+
+/**
+ * Electron loads renderer HTML via `win.loadFile()` which uses the `file://`
+ * protocol. Absolute paths like `src="/foo.js"` resolve to the filesystem root
+ * (e.g. `file:///C:/foo.js` on Windows) instead of relative to the app bundle.
+ *
+ * All local resource references must use relative paths (`./`).
+ */
+describe("electron renderer html", () => {
+  for (const name of ["index.html", "loading.html"]) {
+    describe(name, () => {
+      test("script src attributes use relative paths", async () => {
+        const content = await html(name)
+        const srcs = [...content.matchAll(/\bsrc=["']([^"']+)["']/g)].map((m) => m[1])
+        for (const src of srcs) {
+          expect(src).not.toMatch(/^\/[^/]/)
+        }
+      })
+
+      test("link href attributes use relative paths", async () => {
+        const content = await html(name)
+        const hrefs = [...content.matchAll(/<link[^>]+href=["']([^"']+)["']/g)].map((m) => m[1])
+        for (const href of hrefs) {
+          expect(href).not.toMatch(/^\/[^/]/)
+        }
+      })
+
+      test("no web manifest link (not applicable in Electron)", async () => {
+        const content = await html(name)
+        expect(content).not.toContain('rel="manifest"')
+      })
+    })
+  }
+})
+
+/**
+ * Vite resolves `publicDir` relative to `root`, not the config file.
+ * This test reads the actual values from electron.vite.config.ts to catch
+ * regressions where the publicDir path no longer resolves correctly
+ * after the renderer root is accounted for.
+ */
+describe("electron vite publicDir", () => {
+  test("configured publicDir resolves to a directory with oc-theme-preload.js", async () => {
+    const config = await Bun.file(join(root, "electron.vite.config.ts")).text()
+    const pub = config.match(/publicDir:\s*["']([^"']+)["']/)
+    const rendererRoot = config.match(/root:\s*["']([^"']+)["']/)
+    expect(pub).not.toBeNull()
+    expect(rendererRoot).not.toBeNull()
+    const resolved = resolve(root, rendererRoot![1], pub![1])
+    expect(existsSync(resolved)).toBe(true)
+    expect(existsSync(join(resolved, "oc-theme-preload.js"))).toBe(true)
+  })
+})

+ 8 - 9
packages/desktop-electron/src/renderer/index.html

@@ -4,20 +4,19 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1" />
     <title>OpenCode</title>
-    <link rel="icon" type="image/png" href="/favicon-96x96-v3.png" sizes="96x96" />
-    <link rel="icon" type="image/svg+xml" href="/favicon-v3.svg" />
-    <link rel="shortcut icon" href="/favicon-v3.ico" />
-    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-v3.png" />
-    <link rel="manifest" href="/site.webmanifest" />
+    <link rel="icon" type="image/png" href="./favicon-96x96-v3.png" sizes="96x96" />
+    <link rel="icon" type="image/svg+xml" href="./favicon-v3.svg" />
+    <link rel="shortcut icon" href="./favicon-v3.ico" />
+    <link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon-v3.png" />
     <meta name="theme-color" content="#F8F7F7" />
     <meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
-    <meta property="og:image" content="/social-share.png" />
-    <meta property="twitter:image" content="/social-share.png" />
-    <script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>
+    <meta property="og:image" content="./social-share.png" />
+    <meta property="twitter:image" content="./social-share.png" />
+    <script id="oc-theme-preload-script" src="./oc-theme-preload.js"></script>
   </head>
   <body class="antialiased overscroll-none text-12-regular overflow-hidden">
     <noscript>You need to enable JavaScript to run this app.</noscript>
     <div id="root" class="flex flex-col h-dvh"></div>
-    <script src="/index.tsx" type="module"></script>
+    <script src="./index.tsx" type="module"></script>
   </body>
 </html>

+ 8 - 9
packages/desktop-electron/src/renderer/loading.html

@@ -4,20 +4,19 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1" />
     <title>OpenCode</title>
-    <link rel="icon" type="image/png" href="/favicon-96x96-v3.png" sizes="96x96" />
-    <link rel="icon" type="image/svg+xml" href="/favicon-v3.svg" />
-    <link rel="shortcut icon" href="/favicon-v3.ico" />
-    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-v3.png" />
-    <link rel="manifest" href="/site.webmanifest" />
+    <link rel="icon" type="image/png" href="./favicon-96x96-v3.png" sizes="96x96" />
+    <link rel="icon" type="image/svg+xml" href="./favicon-v3.svg" />
+    <link rel="shortcut icon" href="./favicon-v3.ico" />
+    <link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon-v3.png" />
     <meta name="theme-color" content="#F8F7F7" />
     <meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
-    <meta property="og:image" content="/social-share.png" />
-    <meta property="twitter:image" content="/social-share.png" />
-    <script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>
+    <meta property="og:image" content="./social-share.png" />
+    <meta property="twitter:image" content="./social-share.png" />
+    <script id="oc-theme-preload-script" src="./oc-theme-preload.js"></script>
   </head>
   <body class="antialiased overscroll-none text-12-regular overflow-hidden">
     <noscript>You need to enable JavaScript to run this app.</noscript>
     <div id="root" class="flex flex-col h-dvh"></div>
-    <script src="/loading.tsx" type="module"></script>
+    <script src="./loading.tsx" type="module"></script>
   </body>
 </html>

+ 2 - 1
packages/desktop-electron/tsconfig.json

@@ -18,5 +18,6 @@
     "types": ["vite/client", "node", "electron"]
   },
   "references": [{ "path": "../app" }],
-  "include": ["src", "package.json"]
+  "include": ["src", "package.json"],
+  "exclude": ["src/**/*.test.ts"]
 }

+ 5 - 2
packages/ui/src/components/font.tsx

@@ -1,3 +1,4 @@
+import { Show } from "solid-js"
 import { Style, Link } from "@solidjs/meta"
 import inter from "../assets/fonts/inter.woff2"
 import ibmPlexMonoRegular from "../assets/fonts/ibm-plex-mono.woff2"
@@ -166,8 +167,10 @@ export const Font = () => {
         }
 ${monoNerdCss}
       `}</Style>
-      <Link rel="preload" href={inter} as="font" type="font/woff2" crossorigin="anonymous" />
-      <Link rel="preload" href={ibmPlexMonoRegular} as="font" type="font/woff2" crossorigin="anonymous" />
+      <Show when={typeof location === "undefined" || location.protocol !== "file:"}>
+        <Link rel="preload" href={inter} as="font" type="font/woff2" crossorigin="anonymous" />
+        <Link rel="preload" href={ibmPlexMonoRegular} as="font" type="font/woff2" crossorigin="anonymous" />
+      </Show>
     </>
   )
 }