Lesson 05-Electron Advanced Features

Performance Optimization

Process Memory Management

Main Process Memory Optimization

// Memory leak detection example
const { app, BrowserWindow } = require('electron');
const heapdump = require('heapdump');

app.on('ready', () => {
  // Periodically generate heap snapshots (development environment)
  if (process.env.NODE_ENV === 'development') {
    setInterval(() => {
      const filename = `heapdump-${Date.now()}.heapsnapshot`;
      heapdump.writeSnapshot(filename);
    }, 60000); // Generate every minute
  }

  // Window management optimization
  const windows = new Set();
  function createWindow() {
    const win = new BrowserWindow({ /* ... */ });
    windows.add(win);

    win.on('closed', () => {
      windows.delete(win); // Explicitly release window reference
      win = null; // Aid garbage collection
    });
  }
});

Renderer Process Memory Optimization

// Renderer process memory management example
class ResourceManager {
  constructor() {
    this.cache = new Map();
    this.maxCacheSize = 100;
  }

  loadImage(url) {
    if (this.cache.has(url)) {
      return this.cache.get(url);
    }

    const img = new Image();
    img.src = url;
    this.cache.set(url, img);

    // LRU cache strategy
    if (this.cache.size > this.maxCacheSize) {
      const firstKey = this.cache.keys().next().value;
      const firstImg = this.cache.get(firstKey);
      firstImg.src = ''; // Release resource
      this.cache.delete(firstKey);
    }

    return img;
  }
}

Renderer Process Performance Optimization

Virtual List Implementation

// Large dataset virtual list component
class VirtualList {
  constructor(container, itemHeight, totalItems, renderItem) {
    this.container = container;
    this.itemHeight = itemHeight;
    this.totalItems = totalItems;
    this.renderItem = renderItem;
    this.visibleItems = [];
    this.scrollTop = 0;

    this.init();
  }

  init() {
    // Set container height
    this.container.style.height = `${this.totalItems * this.itemHeight}px`;

    // Create placeholder for visible area
    this.placeholder = document.createElement('div');
    this.placeholder.style.height = '100%';
    this.container.appendChild(this.placeholder);

    // Bind scroll event
    this.container.addEventListener('scroll', () => {
      this.handleScroll();
    });

    // Initial render
    this.handleScroll();
  }

  handleScroll() {
    const newScrollTop = this.container.scrollTop;
    if (Math.abs(newScrollTop - this.scrollTop) < this.itemHeight) return;

    this.scrollTop = newScrollTop;
    this.renderVisibleItems();
  }

  renderVisibleItems() {
    const visibleCount = Math.ceil(this.container.clientHeight / this.itemHeight);
    const startIndex = Math.floor(this.scrollTop / this.itemHeight);
    const endIndex = Math.min(startIndex + visibleCount, this.totalItems);

    // Reuse existing DOM elements
    this.visibleItems.forEach((item, index) => {
      const actualIndex = startIndex + index;
      if (actualIndex < endIndex) {
        this.updateItem(item, actualIndex);
      } else {
        this.container.removeChild(item.element);
      }
    });

    // Add new elements
    for (let i = this.visibleItems.length; i < endIndex - startIndex; i++) {
      const actualIndex = startIndex + i;
      const element = document.createElement('div');
      element.style.position = 'absolute';
      element.style.top = `${actualIndex * this.itemHeight}px`;
      element.style.height = `${this.itemHeight}px`;

      this.container.appendChild(element);
      this.visibleItems.push({
        element,
        index: actualIndex
      });

      this.renderItem(element, actualIndex);
    }

    // Remove excess elements
    while (this.visibleItems.length > endIndex - startIndex) {
      const lastItem = this.visibleItems.pop();
      this.container.removeChild(lastItem.element);
    }
  }

  updateItem(item, index) {
    // Update existing element content
    this.renderItem(item.element, index);
  }
}

Startup Speed Optimization

Preload Script Optimization

// preload.js - On-demand loading strategy
const { contextBridge, ipcRenderer } = require('electron');

// Expose only necessary APIs
contextBridge.exposeInMainWorld('electronAPI', {
  // Basic functionality
  readFile: (path) => ipcRenderer.invoke('read-file', path),

  // Deferred loading API
  getAdvancedAPI: () => {
    return new Promise((resolve) => {
      // Load heavy module on demand
      import('./heavyModule').then(module => {
        resolve(module.createAdvancedAPI(ipcRenderer));
      });
    });
  }
});

Deferred Loading Strategy

// Main process deferred loading example
let heavyModule = null;

ipcMain.handle('heavy-operation', async (event, data) => {
  if (!heavyModule) {
    // Dynamically load heavy module
    heavyModule = await import('./heavyModule');
  }
  return heavyModule.processData(data);
});

// Renderer process deferred loading example
async function loadHeavyComponent() {
  // Show loading indicator
  showLoadingIndicator();

  // Dynamically import component
  const HeavyComponent = await import('./HeavyComponent');

  // Hide loading indicator
  hideLoadingIndicator();

  // Render component
  renderComponent(HeavyComponent);
}

Resource Compression and Caching

Resource Compression Configuration

// webpack.config.js - Production environment optimization
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  mode: 'production',
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true, // Multi-threaded compression
        terserOptions: {
          compress: {
            drop_console: true // Remove console logs
          }
        }
      }),
      new CssMinimizerPlugin()
    ],
    splitChunks: {
      chunks: 'all', // Code splitting
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        }
      }
    }
  },
  performance: {
    hints: 'warning',
    maxEntrypointSize: 512000, // Entry file size limit
    maxAssetSize: 512000 // Asset file size limit
  }
};

Caching Strategy Implementation

// Service Worker caching strategy
// sw.js
const CACHE_NAME = 'electron-app-cache-v1';
const ASSETS = [
  '/',
  '/index.html',
  '/styles/main.css',
  '/scripts/main.js'
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(ASSETS))
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // Return cached response if hit
        if (response) return response;

        // Otherwise fetch from network
        return fetch(event.request)
          .then(response => {
            // Cache cachable resources
            if (event.request.url.match(/\.(js|css|png|jpg|jpeg|gif|ico)$/)) {
              const clone = response.clone();
              caches.open(CACHE_NAME)
                .then(cache => cache.put(event.request, clone));
            }
            return response;
          });
      })
  );
});

GPU and Hardware Acceleration

Hardware Acceleration Configuration

// Main process hardware acceleration configuration
const { app } = require('electron');

app.disableHardwareAcceleration(); // Disable hardware acceleration (for debugging)

// Or control via command-line arguments
// electron --enable-features=HardwareAcceleration

// Renderer process CSS hardware acceleration
.optimized-element {
  transform: translateZ(0); // Trigger GPU acceleration
  will-change: transform;   // Hint browser for optimization
}

// WebGL hardware acceleration verification
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');

if (!gl) {
  console.error('WebGL unavailable, hardware acceleration may be disabled');
} else {
  console.log('WebGL context created successfully, hardware acceleration available');
}

Native Module Development

Node.js Native Module Development

C++ Addon Example

// hello.cc - Basic C++ addon
#include <node_api.h>

napi_value Hello(napi_env env, napi_callback_info info) {
  napi_value greeting;
  napi_create_string_utf8(env, "Hello from C++", NAPI_AUTO_LENGTH, &greeting);
  return greeting;
}

NAPI_MODULE_INIT() {
  napi_value fn;
  napi_create_function(env, nullptr, 0, Hello, nullptr, &fn);
  napi_set_named_property(env, exports, "hello", fn);
  return exports;
}

Build Configuration

# binding.gyp - Build configuration
{
  "targets": [
    {
      "target_name": "hello",
      "sources": ["hello.cc"],
      "include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
      "dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
      "cflags_cc": ["-std=c++17"]
    }
  ]
}

Precompiled Native Module Integration

Using prebuild-install

# Install precompiled module
npm install prebuild-install

# Add install script to package.json
{
  "scripts": {
    "install": "prebuild-install || node-gyp rebuild"
  }
}

Cross-Platform Precompilation Configuration

// package.json configuration example
{
  "name": "native-addon",
  "version": "1.0.0",
  "binary": {
    "module_name": "nativeAddon",
    "module_path": "./lib/binding/{configuration}/{node_abi}-{platform}-{arch}",
    "host": "https://github.com/username/native-addon/releases/download",
    "remote_path": "./{version}",
    "package_name": "{node_abi}-{platform}-{arch}.tar.gz"
  },
  "scripts": {
    "install": "prebuild-install || node-gyp rebuild",
    "rebuild": "node-gyp rebuild --build-from-source"
  }
}

Native Module Cross-Platform Compilation

Multi-Platform Compilation Script

#!/bin/bash
# build-all.sh - Cross-platform compilation script

# Clean old builds
rm -rf build/

# Compile Windows version
npm run rebuild -- --arch=x64 --platform=win32

# Compile macOS version
npm run rebuild -- --arch=x64 --platform=darwin

# Compile Linux version
npm run rebuild -- --arch=x64 --platform=linux

# Package binaries for all platforms
mkdir -p dist/
cp -r build/Release/*.node dist/

Conditional Compilation Example

// platform_utils.cc - Platform-specific code
#include <node_api.h>
#include <string>

napi_value GetPlatformInfo(napi_env env, napi_callback_info info) {
  napi_value result;
  napi_create_string_utf8(env, GetPlatformString().c_str(), NAPI_AUTO_LENGTH, &result);
  return result;
}

std::string GetPlatformString() {
#if defined(_WIN32)
  return "Windows";
#elif defined(__APPLE__)
  return "macOS";
#elif defined(__linux__)
  return "Linux";
#else
  return "Unknown";
#endif
}

NAPI_MODULE_INIT() {
  napi_value fn;
  napi_create_function(env, nullptr, 0, GetPlatformInfo, nullptr, &fn);
  napi_set_named_property(env, exports, "getPlatform", fn);
  return exports;
}

Native Module Security and Permissions

Secure Communication Example

// secure_addon.cc - Secure communication example
#include <node_api.h>
#include <string>

napi_value SafeOperation(napi_env env, napi_callback_info info) {
  size_t argc = 1;
  napi_value args[1];
  napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

  // Validate input parameters
  char buffer[256];
  size_t buffer_size = sizeof(buffer);
  napi_get_value_string_utf8(env, args[0], buffer, buffer_size, nullptr);

  // Parameter safety check
  if (strlen(buffer) > 100) {
    napi_throw_error(env, nullptr, "Input too long");
    return nullptr;
  }

  // Perform secure operation
  std::string result = "Processed: " + std::string(buffer);

  napi_value output;
  napi_create_string_utf8(env, result.c_str(), NAPI_AUTO_LENGTH, &output);
  return output;
}

NAPI_MODULE_INIT() {
  napi_value fn;
  napi_create_function(env, nullptr, 0, SafeOperation, nullptr, &fn);
  napi_set_named_property(env, exports, "safeOperation", fn);
  return exports;
}

Native Module Performance Optimization

Efficient Memory Management

// memory_efficient.cc - Efficient memory management example
#include <node_api.h>
#include <vector>

napi_value ProcessLargeData(napi_env env, napi_callback_info info) {
  // Get input data
  size_t argc = 1;
  napi_value args[1];
  napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

  // Use direct buffer access
  void* data;
  size_t length;
  napi_get_arraybuffer_info(env, args[0], &data, &length);

  // Directly process binary data (avoid copying)
  unsigned char* buffer = static_cast<unsigned char*>(data);
  for (size_t i = 0; i < length; i++) {
    buffer[i] = buffer[i] ^ 0xFF; // Simple data processing example
  }

  // Return input buffer directly (zero-copy)
  napi_value result;
  napi_create_external_arraybuffer(
    env,
    data,
    length,
    nullptr, // No finalizer callback
    nullptr,
    &result
  );
  return result;
}

NAPI_MODULE_INIT() {
  napi_value fn;
  napi_create_function(env, nullptr, 0, ProcessLargeData, nullptr, &fn);
  napi_set_named_property(env, exports, "processLargeData", fn);
  return exports;
}

Advanced IPC Communication

Structured Data Transmission

Protocol Buffers Integration

// message.proto
syntax = "proto3";

message DataMessage {
  int32 id = 1;
  string content = 2;
  repeated float values = 3;
  map<string, string> metadata = 4;
}
// Protocol Buffers integration example
const protobuf = require('protobufjs');
const path = require('path');

// Load proto file
protobuf.load(path.join(__dirname, 'message.proto'), (err, root) => {
  if (err) throw err;

  // Get message type
  const DataMessage = root.lookupType('DataMessage');

  // Encode message
  const payload = { id: 1, content: 'test', values: [1.1, 2.2], metadata: { key: 'value' } };
  const errMsg = DataMessage.verify(payload);
  if (errMsg) throw Error(errMsg);

  const message = DataMessage.create(payload);
  const buffer = DataMessage.encode(message).finish();

  // Send binary data via IPC
  window.electronAPI.sendData(buffer);

  // Decode at receiver
  window.electronAPI.onDataReceived((buffer) => {
    const message = DataMessage.decode(buffer);
    const object = DataMessage.toObject(message, {
      longs: String,
      enums: String,
      bytes: String
    });
    console.log('Received:', object);
  });
});

Inter-Process Event Broadcasting

Event Broadcasting System

// Main process event broadcasting
const { ipcMain, BrowserWindow } = require('electron');

// Store all window references
const windows = new Set();

function createWindow() {
  const win = new BrowserWindow({ /* ... */ });
  windows.add(win);

  win.on('closed', () => {
    windows.delete(win);
  });
}

// Broadcast event to all windows
ipcMain.on('broadcast-event', (event, eventName, ...args) => {
  windows.forEach(win => {
    if (!win.isDestroyed()) {
      win.webContents.send(eventName, ...args);
    }
  });
});

// Renderer process listening for broadcasts
window.electronAPI.onBroadcastEvent((eventName, ...args) => {
  console.log(`Received broadcast: ${eventName}`, args);
});

Shared Memory and Efficient Transmission

Shared Memory Implementation

// shared_memory.cc - Shared memory example
#include <node_api.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

napi_value CreateSharedMemory(napi_env env, napi_callback_info info) {
  // Create shared memory object
  int shm_fd = shm_open("/electron_shm", O_CREAT | O_RDWR, 0666);
  ftruncate(shm_fd, 4096); // 4KB shared memory
  void* ptr = mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);

  // Return file descriptor to JS layer (via Node.js addon)
  napi_value result;
  napi_create_int32(env, shm_fd, &result);
  return result;
}

napi_value WriteToSharedMemory(napi_env env, napi_callback_info info) {
  size_t argc = 2;
  napi_value args[2];
  napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

  int shm_fd;
  size_t data_size;
  napi_get_value_int32(env, args[0], &shm_fd);
  napi_get_value_uint32(env, args[1], &data_size);

  void* ptr = mmap(0, data_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
  // Write data to shared memory...

  return nullptr;
}

NAPI_MODULE_INIT() {
  napi_value create_fn, write_fn;
  napi_create_function(env, nullptr, 0, CreateSharedMemory, nullptr, &create_fn);
  napi_create_function(env, nullptr, 0, WriteToSharedMemory, nullptr, &write_fn);

  napi_value exports;
  napi_get_named_property(env, exports, "createSharedMemory", &create_fn);
  napi_set_named_property(env, exports, "writeToSharedMemory", &write_fn);
  return exports;
}

Secure Communication and Validation

IPC Data Validation

// Main process secure validation
const { ipcMain } = require('electron');

// Whitelist validation
const allowedCommands = new Set(['read-file', 'write-file']);

ipcMain.handle('secure-command', async (event, command, ...args) => {
  // 1. Validate command against whitelist
  if (!allowedCommands.has(command)) {
    throw new Error(`Command ${command} is not allowed`);
  }

  // 2. Validate argument types
  if (command === 'read-file') {
    if (typeof args[0] !== 'string') {
      throw new Error('Invalid argument type for read-file');
    }
    // Path safety checks...
  }

  // 3. Execute operation
  return await performOperation(command, ...args);
});

// Renderer process secure call
async function safeCommand(command, ...args) {
  try {
    return await window.electronAPI.secureCommand(command, ...args);
  } catch (error) {
    console.error('Secure command failed:', error.message);
    throw error;
  }
}

IPC Performance Optimization

Batch Data Processing

// Batch data transmission optimization
class DataBatcher {
  constructor(sendFn, batchSize = 100, flushInterval = 100) {
    this.sendFn = sendFn;
    this.batchSize = batchSize;
    this.flushInterval = flushInterval;
    this.batch = [];
    this.timer = null;
  }

  add(data) {
    this.batch.push(data);

    // Send immediately if batch size reached
    if (this.batch.length >= this.batchSize) {
      this.flush();
      return;
    }

    // Start timer if not already started
    if (!this.timer) {
      this.timer = setTimeout(() => this.flush(), this.flushInterval);
    }
  }

  flush() {
    if (this.batch.length === 0) return;

    // Send batch data
    this.sendFn(this.batch);

    // Reset state
    this.batch = [];
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
  }
}

// Usage example
const batcher = new DataBatcher((batch) => {
  window.electronAPI.sendBatchData(batch);
});

// Add data
batcher.add({ id: 1, value: 'test' });

Zero-Copy Data Transmission

// Zero-copy transmission implementation
const { ipcRenderer } = require('electron');

// Send large binary data (zero-copy)
function sendLargeData(buffer) {
  // Use Transferable object to avoid copying
  ipcRenderer.postMessage('large-data', buffer, [buffer.buffer]);
}

// Receiver handling
ipcRenderer.on('large-data', (event, buffer) => {
  // Directly use received buffer
  console.log('Received buffer:', buffer.byteLength);
});

// Main process counterpart
ipcMain.on('large-data', (event, buffer) => {
  // Directly access buffer data
  const dataView = new DataView(buffer);
  // Process data...
});
Share your love