Bruin VS Code Extension: The Architectural Challenge of Integrating Vue.js Webviews
How we built a rich, interactive VS Code extension using Vue.js webviews, bridging Node.js extension code with a modern frontend through message passing.
If you're a data engineer, your typical day involves constant context switching: writing code in VS Code, running pipelines from the terminal, checking logs in a CLI, and jumping to a browser to visualize lineage or job status. This fragmentation kills productivity and breaks your flow.
Bruin was founded to solve exactly this problem. We believe data teams deserve the same seamless, unified experience that software engineers enjoy, one place to think, build, run, and debug. That's why we created the Bruin VS Code Extension, which brings the full power of the Bruin CLI (asset management, pipeline execution, quality checks) directly into your editor.
Native VS Code UI components work fine for simple status bars or tree views. But for rich experiences like interactive data lineage graphs, live query previews, and asset editors, we needed more power. That's where Webviews powered by Vue.js come in.
The Node.js extension runs in VS Code's host process. It handles workspace context, registers commands, manages panels, and acts as a secure bridge to the local Bruin CLI.
The Vue app runs inside a sandboxed webview (essentially a browser iframe). It renders everything the user sees: lineage graphs, asset details, connections and databases, query results, etc. The app is built with modern tooling: Vue 3, TypeScript, Vite, Pinia, and compiles to static assets that the extension serves.
Challenge: Hot reloading during development requires careful setup.
Solution: We use Vite's dev server for the Vue app, but webviews need special handling. The extension serves built assets via webview.asWebviewUri(), so we run vite build --watch during development and manually refresh the webview panel. (more details in the next blog post)
# Development workflow
npm run dev:watch # Runs TypeScript compiler + Vite watch mode concurrently
Challenge: Large message payloads make the UI feel laggy, and sending messages when webviews aren't ready wastes resources.
Solution: Be mindful about when and how often you send messages:
Check webview visibility: Only send messages when the webview panel is visible (webview.visible). Don't send until components are mounted and ready to receive data
Use debouncing: Debounce rapid file changes (180ms for graph updates, 500ms for asset detection) to prevent message flooding
Keep payloads small: Aim for <10KB when possible. For large data like lineage graphs, use duplicate detection on the client side to skip processing identical messages