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