From 4283c23be64bddbcc0be78ffa08ca5ea58e41ae6 Mon Sep 17 00:00:00 2001 From: Ashish Reddy Podduturi Date: Thu, 20 Aug 2026 22:22:52 -0700 Subject: [PATCH] fix: unbreak the login webview shipped in 2.5.0 Two defects compound into the black login view with "Error: Webview error" that customers hit on IdC sign-in in 2.5.0. Diagnosed from the extension log of a live repro: the logged failure is `Webview backend command failed: "setUiReady()" -> TypeError: Cannot read properties of undefined (reading 'start')`. 1. The login Vue bundle ships wrapped in a CommonJS helper. esbuild-loader 4.5.0 (from the 2.5.0 dependency remediation) infers `format: 'iife'` when the compile target is 'web' and the minifier target is not 'esnext', wrapping the whole bundle in a lazy CJS factory and rewriting top-level `this`, which also breaks `output.libraryTarget: 'this'`. Of the four Vue bundles in the shipped 2.5.0 VSIX, login/webview/vue/amazonq/index.js is the ONLY one wrapped -- feedback, codewhisperer and securityIssue shipped clean. Pinning the minimizer to target 'esnext' keeps esbuild as a pure minifier; the loader has already transpiled to es2021 and these bundles only run in the IDE's Chromium webview. This exact upgrade was reverted once before for the same breakage (91a54b0c1, "broke loading mynah-ui"). 2. setDidLoad dereferences `loadMetadata!.start`. The webview's 10-second load timeout clears loadMetadata on the assumption the load failed, so a page that reports readiness late -- 44 seconds after activation in the captured log -- crashes the setUiReady command instead of recording a slow-but-successful load, and VS Code surfaces that as the webview error banner. Emit without a duration and return instead. The rebuilt login bundle is verified free of the CJS wrapper. --- packages/core/src/webviews/main.ts | 20 ++++++++++++++++++- packages/webpack.vue.config.js | 31 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/core/src/webviews/main.ts b/packages/core/src/webviews/main.ts index 2b78eb42df..3c649b5be7 100644 --- a/packages/core/src/webviews/main.ts +++ b/packages/core/src/webviews/main.ts @@ -397,8 +397,26 @@ export abstract class VueWebview { protected setDidLoad(module: string) { this.loadMetadata?.loadTimeout?.dispose() + /** + * The metadata may already be gone by the time the frontend reports success: the 10s + * loadTimeout clears it (assuming the load failed), and a second page within the same + * webview reporting readiness arrives after the first consumed it. Both are late but + * successful loads. Emitting without a duration is fine; throwing is not -- this runs + * inside the webview's setUiReady command, so a throw here surfaces to the user as + * "Error: Webview error" on the login view (a customer-reported failure in 2.5.0). + */ + if (this.loadMetadata === undefined) { + telemetry.toolkit_didLoadModule.emit({ + passive: true, + module, + result: 'Succeeded', + reason: 'LoadReportedAfterMetadataCleared', + }) + return + } + // Represents time from intent to open, to confirmation of a successful load - const duration = globals.clock.Date.now() - this.loadMetadata!.start + const duration = globals.clock.Date.now() - this.loadMetadata.start telemetry.toolkit_didLoadModule.emit({ passive: true, diff --git a/packages/webpack.vue.config.js b/packages/webpack.vue.config.js index 1ca91ceed4..f34ffab9ae 100644 --- a/packages/webpack.vue.config.js +++ b/packages/webpack.vue.config.js @@ -10,6 +10,7 @@ const path = require('path') const glob = require('glob') const { VueLoaderPlugin } = require('vue-loader') +const { EsbuildPlugin } = require('esbuild-loader') const baseConfigFactory = require('./webpack.base.config') const { merge } = require('webpack-merge') const currentDir = process.cwd() @@ -71,6 +72,36 @@ module.exports = (env, argv) => { plugins: [new VueLoaderPlugin()], }) + // Keep esbuild as a pure minifier for these bundles, rather than letting it also choose an output + // format. + // + // esbuild-loader v4 infers `format: 'iife'` whenever the compiler target is 'web' (which this + // config sets) and the minifier target is anything other than 'esnext'. The base config's + // minifier targets es2021, so that inference fires here. The resulting IIFE wrapper rewrites + // top-level `this` into an internal exports object, which silently breaks + // `output.libraryTarget: 'this'` above: the bundle's exports are copied onto a dead local object + // instead of onto `window`, so nothing is exposed to the classic