Electron Integration Guide
This guide covers building a desktop visual editor using Canvus SDK inside an Electron application. The demo-electron/ directory provides a complete reference implementation.
Architecture Overview
The Electron demo implements a three-layer architecture:
ββββββββββββββββββββββββββββββββββββββββββββββββββ
β Main Process (main.cjs) β
β - File I/O (open/read HTML files from disk) β
β - CDP debugger attachment (DOM.enable, β
β CSS.enable, CSS.forcePseudoState) β
ββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Preload Script (preload.cjs) β
β - contextBridge exposes ipcRenderer channels β
β - open-file, read-file, force-pseudo-state β
ββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Renderer Process (Vite + React) β
β - Canvus Workspace + SDK interactions β
β - Host-driven HTML importing β
β - UI controls (file open, preview toggle) β
ββββββββββββββββββββββββββββββββββββββββββββββββββKey Concepts
File-Based HTML Importing
Unlike the web demo which uses hardcoded HTML strings, the Electron demo reads real HTML files from disk:
// Main process: IPC handler for file dialog
ipcMain.handle('open-file', async () => {
const result = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [{ name: 'HTML Files', extensions: ['html', 'htm'] }]
})
if (result.canceled) return null
return {
filePath: result.filePaths[0],
fileContent: fs.readFileSync(result.filePaths[0], 'utf8')
}
})Host-Driven Importing Workflow
The demo-electron/src/importer.ts module implements the full host-driven importing workflow described in the HTML/CSS Importing Guide:
- Parse the HTML string using
DOMParser - Extract & inject CSS β inline
<style>blocks and external<link>stylesheets - Handle
@propertyrules β extracted todocument.head(Chromium Shadow DOM limitation) - Resolve relative URLs β rewrites
src,href,srcset, and CSSurl()references - Register nodes β each top-level
<body>child becomes a root-level workspace node - Extract & execute scripts β runs via
executeScopedScript()and flags nodes withmarkNodeHasJS()
import { importHTMLDocument } from './importer'
const result = await importHTMLDocument(workspace, htmlString, {
baseUrl: filePath, // Resolves relative paths
clearWorkspace: true, // Clear existing nodes first
defaultPageWidth: 1200, // Initial node width
})
console.log(`Imported ${result.styleTagsCount} style blocks`)
console.log(`External stylesheets:`, result.externalStylesheets)
console.log(`Scripts executed:`, result.scriptsExecuted)CDP-Based Pseudo-State Forcing
The Electron main process attaches the Chrome DevTools Protocol debugger to force CSS pseudo-states (:hover, :active, :focus) on elements inside the Shadow DOM:
// Main process: Attach CDP debugger on window creation
win.webContents.debugger.attach('1.3')
await win.webContents.debugger.sendCommand('DOM.enable')
await win.webContents.debugger.sendCommand('CSS.enable')The renderer process hooks into the SDKβs onForcePseudoState callback:
const ws = new Workspace(container, {
onForcePseudoState(nodeId, state, enabled) {
// Forward to Electron main process via IPC
window.electronAPI.forcePseudoState({ nodeId, stateName: state, enabled })
},
})The main process then resolves the CDP node ID and forces the pseudo-class:
ipcMain.handle('force-pseudo-state', async (event, { nodeId, stateName, enabled }) => {
const dbg = mainWindow.webContents.debugger
// Evaluate to get the DOM element reference
const evalResult = await dbg.sendCommand('Runtime.evaluate', {
expression: `window.ws.getContentRoot('${nodeId}')`,
returnByValue: false
})
// Get the CDP nodeId
const { nodeId: cdpNodeId } = await dbg.sendCommand('DOM.requestNode', {
objectId: evalResult.result.objectId
})
// Force the pseudo-class state
await dbg.sendCommand('CSS.forcePseudoState', {
nodeId: cdpNodeId,
forcedPseudoClasses: enabled ? [`:${stateName}`] : [],
})
})CDP pseudo-state forcing applies real CSS pseudo-classes, unlike the SDKβs .canvus-state-hover class approach which only works with custom CSS. CDP forcing works with all existing stylesheets.
Running the Electron Demo
# From the project root
npm run demo:electronThis starts the Vite dev server and launches the Electron window. The demo includes:
- File β Open HTML dialog for importing real HTML documents
- Preview Mode toggle for testing interactive content
- CSS properties panel for editing styles
- Selection inspector showing node tree and breadcrumbs
E2E Testing
The Electron demo includes Playwright E2E tests:
cd demo-electron
npx playwright testTests verify:
- Workspace mounting and node rendering
- Drill-down selection and direct node class assertions
- Node interaction behaviors inside the Shadow DOM
The Electron demoβs importer.ts is a host application module, not part of the SDK. It demonstrates the importing pattern described in the HTML/CSS Importing Guide but lives outside the SDK boundary.