From 3a9618400178a10158fc153fe88e9260ae25d476 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Fri, 24 Jul 2026 18:40:19 +0700 Subject: [PATCH] Fix native build: access Camera via runtime plugin, not @capacitor/camera import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Local CAPACITOR_BUILD failed: Rollup couldn't resolve import "@capacitor/camera" from CameraCapture.tsx — the package is listed in package.json but isn't present in node_modules on the build machine. Root pattern (same class as the @capacitor/core crash): a package that must NOT be resolved at build time was being imported by specifier. Fix: - CameraCapture.tsx no longer imports '@capacitor/camera' at all. The Camera plugin is accessed via the runtime-injected window.Capacitor.Plugins.Camera (Capacitor injects it natively), using string enum values ('dataUrl' / 'CAMERA'). No bare specifier remains in the bundle, and it degrades gracefully if the plugin is absent. - vite.config: @capacitor/camera moved back to `external` (belt-and-braces; it's no longer imported anyway). @capacitor/core stays BUNDLED (static import in platform.ts — the white-screen fix). nostr-signer-capacitor-plugin and the 4 uninstalled @capacitor/* plugins remain external. Verified: no @capacitor/camera specifier and no unresolved @capacitor/core import in the built bundle; no static imports of any externalized package remain in src/. Bump to 1.0.67. Co-authored-by: shakespeare.diy --- package.json | 2 +- src/components/CameraCapture.tsx | 29 ++++++++++++++++++++++++++--- vite.config.ts | 32 ++++++++++++++++++-------------- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index df1eee2..b7a1c12 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "skate-spots", "private": true, - "version": "1.0.66", + "version": "1.0.67", "type": "module", "homepage": "https://skate-spots.com", "scripts": { diff --git a/src/components/CameraCapture.tsx b/src/components/CameraCapture.tsx index 28b4eb4..179f30a 100644 --- a/src/components/CameraCapture.tsx +++ b/src/components/CameraCapture.tsx @@ -42,11 +42,34 @@ async function isNative(): Promise { } } +// The Camera plugin is provided by Capacitor at runtime on native. We access it +// through the runtime-injected `window.Capacitor.Plugins.Camera` rather than a +// static/dynamic `import('@capacitor/camera')`, so the bundle contains no bare +// `@capacitor/camera` specifier (which a native WebView can't resolve, and which +// isn't installed on every build machine). CameraResultType/CameraSource are +// plain string enums in the plugin, so we pass their string values directly. +interface CapacitorCameraPlugin { + getPhoto(options: { + resultType: string; + source: string; + quality: number; + correctOrientation: boolean; + }): Promise<{ dataUrl?: string }>; +} + +function getNativeCamera(): CapacitorCameraPlugin | null { + const cap = (window as unknown as { + Capacitor?: { Plugins?: { Camera?: CapacitorCameraPlugin } }; + }).Capacitor; + return cap?.Plugins?.Camera ?? null; +} + async function captureNative(): Promise { - const { Camera, CameraResultType, CameraSource } = await import('@capacitor/camera'); + const Camera = getNativeCamera(); + if (!Camera) throw new Error('Camera plugin unavailable'); const photo = await Camera.getPhoto({ - resultType: CameraResultType.DataUrl, - source: CameraSource.Camera, + resultType: 'dataUrl', // CameraResultType.DataUrl + source: 'CAMERA', // CameraSource.Camera quality: 90, correctOrientation: true, }); diff --git a/vite.config.ts b/vite.config.ts index 443af0b..15adf71 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -44,22 +44,26 @@ export default defineConfig(() => ({ transformMixedEsModules: true, }, rollupOptions: { - // IMPORTANT: @capacitor/core and @capacitor/camera MUST be bundled (they are - // installed, statically imported, and their JS API layer is real code the - // WebView needs). Leaving them external emitted a bare `import "@capacitor/core"` - // specifier that a native WebView cannot resolve → white-screen boot crash. - // They are therefore intentionally ABSENT from this list. + // Bundling policy — the distinction that matters is STATIC vs DYNAMIC import, + // and whether the package resolves on the build machine: // - // Kept external: - // - nostr-signer-capacitor-plugin: installed from GitHub with a broken package - // entry point, so it CANNOT be bundled. It's only ever used via a guarded - // dynamic import() at runtime on native (see lib/capacitorAmberSigner.ts, - // wrapped in try/catch → returns null on failure), so external is correct - // and safe — a resolution failure degrades gracefully instead of crashing. - // - @capacitor/app|haptics|keyboard|status-bar: NOT installed and never - // statically imported (accessed via window.Capacitor.Plugins), so keeping - // them external prevents a stray import from breaking the build. + // BUNDLED (absent from this list): + // - @capacitor/core — imported STATICALLY (lib/platform.ts). It MUST be + // bundled: leaving it external emitted a bare `import "@capacitor/core"` + // specifier the native WebView can't resolve → white-screen boot crash. + // Capacitor guarantees core is installed, so bundling always resolves. + // + // EXTERNAL (listed below): + // - @capacitor/camera — used only via a GUARDED dynamic import() on native + // (components/CameraCapture.tsx). It may not be installed on every build + // machine, and it's optional/native-only, so it must stay external. The + // dynamic import() remains a runtime call that degrades gracefully. + // - nostr-signer-capacitor-plugin — installed from GitHub with a broken + // package entry point; cannot be bundled. Guarded dynamic import() only. + // - @capacitor/app|haptics|keyboard|status-bar — NOT installed and never + // statically imported (accessed via window.Capacitor.Plugins at runtime). external: [ + '@capacitor/camera', 'nostr-signer-capacitor-plugin', '@capacitor/app', '@capacitor/haptics',