NW.js

Desktop application framework based on Chromium and Node.js. Direct integration of DOM APIs and Node.js APIs to package web apps as desktop applications. Differs from Electron in not separating main and renderer processes.

desktopJavaScriptNode.jsChromiumcross-platformhybrid

GitHub Overview

nwjs/nw.js

Call all Node.js modules directly from DOM/WebWorker and enable a new way of writing applications with all Web technologies.

Stars40,961
Watchers1,559
Forks3,882
Created:January 4, 2012
Language:JavaScript
License:MIT License

Topics

desktopjavascriptnode-webkitnodejsnwjsweb-application-framework

Star History

nwjs/nw.js Star History
Data as of: 7/19/2025, 10:37 AM

Framework

NW.js

Overview

NW.js is a framework for building cross-platform desktop applications using web technologies combined with Node.js. Through the integration of Node.js + Chromium, it enables developers to create native desktop applications using familiar web technologies like HTML, CSS, and JavaScript.

Details

As of 2025, NW.js is a stable desktop application development framework based on the latest version of Chromium. Originally known as "node-webkit," it is now developed as NW.js.

Key features of NW.js:

  • Node.js Integration: Direct access to Node.js modules from the DOM
  • Native APIs: Direct access to OS functionalities (file system, tray icons, etc.)
  • Lightweight Package: Smaller binary size compared to Electron
  • Debug Features: Powerful debugging environment with Chrome DevTools
  • Multi-platform: Support for Windows, macOS, and Linux
  • Global Shortcuts: Hotkey functionality that works system-wide

The main difference from Electron is the absence of main process and renderer process separation, adopting a simpler architecture. This allows developers to build desktop applications simply without worrying about complex inter-process communication.

Pros and Cons

Pros

  • Low Learning Curve: Direct utilization of web development skills
  • Development Efficiency: Rapid development with HTML and JavaScript
  • Direct Node.js Access: Direct use of Node.js modules from renderer
  • Lightweight: Simpler architecture compared to Electron
  • Rich API Set: Comprehensive access to native functionalities
  • Unified Code Management: No boundary between frontend and backend
  • Debug Environment: Excellent development experience with Chrome DevTools
  • Auto-update Features: Support for application update distribution

Cons

  • Security Risks: Potential vulnerabilities from direct DOM to Node.js access
  • Large Bundle Size: Large distribution files due to including Chromium engine
  • Memory Usage: Higher memory consumption compared to native apps
  • Electron Differentiation: Relatively smaller market share
  • No Process Separation: Difficult process management in complex apps
  • Security Model: Need to consider web security

Reference Links

Code Examples

Basic NW.js Application

// package.json - Application manifest
{
  "name": "hello-nwjs",
  "version": "1.0.0",
  "main": "index.html",
  "window": {
    "title": "Hello NW.js",
    "width": 800,
    "height": 600,
    "min_width": 400,
    "min_height": 300,
    "icon": "app.png"
  }
}
<!-- index.html - Main window -->
<!DOCTYPE html>
<html>
<head>
    <title>Hello NW.js World!</title>
    <meta charset="utf-8">
    <style>
        body {
            font-family: 'Arial', sans-serif;
            text-align: center;
            padding: 50px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            margin: 0;
        }
        .container {
            background: rgba(255, 255, 255, 0.1);
            padding: 30px;
            border-radius: 10px;
            backdrop-filter: blur(10px);
        }
        button {
            background: #4CAF50;
            color: white;
            border: none;
            padding: 15px 32px;
            margin: 10px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
        }
        button:hover {
            background: #45a049;
        }
        .info {
            margin: 20px 0;
            padding: 15px;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Hello NW.js World!</h1>
        <div class="info">
            <p>Node.js Version: <span id="node-version"></span></p>
            <p>Chromium Version: <span id="chromium-version"></span></p>
            <p>OS Platform: <span id="platform"></span></p>
        </div>
        
        <button onclick="showNotification()">Show Notification</button>
        <button onclick="openDevTools()">Developer Tools</button>
        <button onclick="minimizeWindow()">Minimize</button>
        <button onclick="closeApp()">Exit App</button>
    </div>

    <script>
        // Direct use of Node.js modules
        const os = require('os');
        const path = require('path');
        const fs = require('fs');
        
        // Display version information
        document.getElementById('node-version').textContent = process.version;
        document.getElementById('chromium-version').textContent = process.versions.chromium;
        document.getElementById('platform').textContent = os.platform();
        
        // Notification functionality
        function showNotification() {
            new Notification('NW.js Notification', {
                body: 'Hello from NW.js Application!',
                icon: 'app.png'
            });
        }
        
        // Open developer tools
        function openDevTools() {
            nw.Window.get().showDevTools();
        }
        
        // Minimize window
        function minimizeWindow() {
            nw.Window.get().minimize();
        }
        
        // Exit application
        function closeApp() {
            nw.App.quit();
        }
        
        // File system access example
        function readConfigFile() {
            const configPath = path.join(__dirname, 'config.json');
            try {
                const config = fs.readFileSync(configPath, 'utf8');
                console.log('Config file:', JSON.parse(config));
            } catch (error) {
                console.log('Config file not found');
            }
        }
        
        // Initialization on app startup
        readConfigFile();
    </script>
</body>
</html>

File Operations and System Integration

<!-- file-manager.html -->
<!DOCTYPE html>
<html>
<head>
    <title>File Manager - NW.js</title>
    <meta charset="utf-8">
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        .toolbar {
            margin-bottom: 20px;
            padding: 10px;
            background: #f0f0f0;
            border-radius: 5px;
        }
        .file-list {
            border: 1px solid #ddd;
            height: 400px;
            overflow-y: auto;
            padding: 10px;
        }
        .file-item {
            padding: 5px;
            border-bottom: 1px solid #eee;
            cursor: pointer;
        }
        .file-item:hover {
            background: #f5f5f5;
        }
        .directory {
            font-weight: bold;
            color: #0066cc;
        }
        .file {
            color: #333;
        }
        button {
            margin: 5px;
            padding: 8px 16px;
            border: none;
            background: #007cba;
            color: white;
            border-radius: 3px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <h1>NW.js File Manager</h1>
    
    <div class="toolbar">
        <button onclick="selectDirectory()">Select Folder</button>
        <button onclick="goHome()">Home Directory</button>
        <button onclick="goUp()">Parent Folder</button>
        <button onclick="createFolder()">New Folder</button>
        <input type="file" id="fileInput" multiple style="display: none;">
        <button onclick="document.getElementById('fileInput').click()">Select Files</button>
    </div>
    
    <div>Current Path: <span id="currentPath"></span></div>
    
    <div class="file-list" id="fileList">
        <!-- File list will be displayed here -->
    </div>
    
    <div id="status"></div>

    <script>
        const fs = require('fs');
        const path = require('path');
        const os = require('os');
        
        let currentDirectory = os.homedir();
        
        // Initialize
        updateFileList();
        
        function updateFileList() {
            const fileList = document.getElementById('fileList');
            const currentPathSpan = document.getElementById('currentPath');
            
            currentPathSpan.textContent = currentDirectory;
            fileList.innerHTML = '';
            
            try {
                const items = fs.readdirSync(currentDirectory);
                
                items.forEach(item => {
                    const itemPath = path.join(currentDirectory, item);
                    const stats = fs.statSync(itemPath);
                    
                    const div = document.createElement('div');
                    div.className = 'file-item';
                    
                    if (stats.isDirectory()) {
                        div.className += ' directory';
                        div.textContent = `📁 ${item}`;
                        div.onclick = () => {
                            currentDirectory = itemPath;
                            updateFileList();
                        };
                    } else {
                        div.className += ' file';
                        const size = (stats.size / 1024).toFixed(1);
                        div.textContent = `📄 ${item} (${size} KB)`;
                        div.onclick = () => openFile(itemPath);
                    }
                    
                    fileList.appendChild(div);
                });
            } catch (error) {
                fileList.innerHTML = `<div style="color: red;">Error: ${error.message}</div>`;
            }
        }
        
        function selectDirectory() {
            const input = document.createElement('input');
            input.type = 'file';
            input.nwdirectory = true;
            input.onchange = function() {
                if (this.value) {
                    currentDirectory = this.value;
                    updateFileList();
                }
            };
            input.click();
        }
        
        function goHome() {
            currentDirectory = os.homedir();
            updateFileList();
        }
        
        function goUp() {
            const parentDir = path.dirname(currentDirectory);
            if (parentDir !== currentDirectory) {
                currentDirectory = parentDir;
                updateFileList();
            }
        }
        
        function createFolder() {
            const folderName = prompt('Enter new folder name:');
            if (folderName) {
                const newFolderPath = path.join(currentDirectory, folderName);
                try {
                    fs.mkdirSync(newFolderPath);
                    updateFileList();
                    showStatus(`Folder "${folderName}" created`);
                } catch (error) {
                    showStatus(`Error: ${error.message}`, 'error');
                }
            }
        }
        
        function openFile(filePath) {
            // Open file with OS default application
            nw.Shell.openItem(filePath);
        }
        
        function showStatus(message, type = 'info') {
            const status = document.getElementById('status');
            status.textContent = message;
            status.style.color = type === 'error' ? 'red' : 'green';
            setTimeout(() => {
                status.textContent = '';
            }, 3000);
        }
        
        // File selection handling
        document.getElementById('fileInput').onchange = function() {
            const files = Array.from(this.files);
            console.log('Selected files:', files.map(f => f.path));
            showStatus(`${files.length} files selected`);
        };
        
        // Drag & drop support
        document.addEventListener('dragover', function(e) {
            e.preventDefault();
        });
        
        document.addEventListener('drop', function(e) {
            e.preventDefault();
            const files = Array.from(e.dataTransfer.files);
            console.log('Dropped files:', files.map(f => f.path));
            showStatus(`${files.length} files dropped`);
        });
    </script>
</body>
</html>

System Tray Integration and Global Shortcuts

// System tray and shortcut management class
class NWJSSystemIntegration {
    constructor() {
        this.tray = null;
        this.registeredShortcuts = [];
        this.gui = nw;
        this.win = this.gui.Window.get();
        
        this.init();
    }
    
    init() {
        // Window event setup
        this.win.on('minimize', () => {
            console.log('Window minimized');
        });
        
        this.win.on('close', () => {
            if (this.tray) {
                this.win.hide();
                return false; // Hide window instead of closing
            }
            this.cleanup();
        });
        
        // Initialization complete
        console.log('System integration initialized');
    }
    
    createTrayIcon() {
        if (this.tray) {
            console.log('Tray icon already created');
            return;
        }
        
        // Create tray icon
        this.tray = new this.gui.Tray({
            title: 'NW.js App',
            icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAM0lEQVRYhe3QMQEAAAjDMPxr/hMXAIEP0lPaAgAAAAAAAAAAAAAAAAAAAAAAAAAA8P4BAEoAHpvQZd8AAAAASUVORK5CYII='
        });
        
        // Create tray menu
        const menu = new this.gui.Menu();
        
        menu.append(new this.gui.MenuItem({
            label: 'Show App',
            click: () => this.showWindow()
        }));
        
        menu.append(new this.gui.MenuItem({
            label: 'Settings',
            click: () => {
                this.showWindow();
                console.log('Open settings (not implemented)');
            }
        }));
        
        menu.append(new this.gui.MenuItem({
            type: 'separator'
        }));
        
        menu.append(new this.gui.MenuItem({
            label: 'Exit',
            click: () => this.exitApp()
        }));
        
        this.tray.menu = menu;
        
        // Tray icon click event
        this.tray.on('click', () => {
            this.showWindow();
        });
        
        console.log('Tray icon created');
    }
    
    registerGlobalShortcuts() {
        const shortcuts = [
            {
                key: 'Ctrl+Shift+H',
                action: () => {
                    if (this.win.isVisible()) {
                        this.win.hide();
                    } else {
                        this.showWindow();
                    }
                }
            },
            {
                key: 'Ctrl+Shift+Q',
                action: () => this.exitApp()
            },
            {
                key: 'Ctrl+Shift+N',
                action: () => this.showNotification()
            }
        ];
        
        shortcuts.forEach(shortcut => {
            try {
                const globalShortcut = new this.gui.Shortcut({
                    key: shortcut.key,
                    active: shortcut.action,
                    failed: (msg) => {
                        console.error(`Failed to register shortcut "${shortcut.key}": ${msg}`);
                    }
                });
                
                this.gui.App.registerGlobalHotKey(globalShortcut);
                this.registeredShortcuts.push(globalShortcut);
            } catch (error) {
                console.error(`Shortcut registration error "${shortcut.key}":`, error);
            }
        });
        
        console.log(`${this.registeredShortcuts.length} shortcuts registered`);
    }
    
    showNotification() {
        const notification = new Notification('NW.js System Notification', {
            body: 'Notification sent from global shortcut',
            icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAM0lEQVRYhe3QMQEAAAjDMPxr/hMXAIEP0lPaAgAAAAAAAAAAAAAAAAAAAAAAAAAA8P4BAEoAHpvQZd8AAAAASUVORK5CYII=',
            tag: 'nwjs-notification'
        });
        
        notification.onclick = () => {
            this.showWindow();
        };
    }
    
    showWindow() {
        if (!this.win.isVisible()) {
            this.win.show();
        }
        this.win.restore();
        this.win.focus();
    }
    
    cleanup() {
        // Unregister shortcuts
        this.registeredShortcuts.forEach(shortcut => {
            try {
                this.gui.App.unregisterGlobalHotKey(shortcut);
            } catch (error) {
                console.error('Shortcut unregistration error:', error);
            }
        });
        
        // Remove tray icon
        if (this.tray) {
            this.tray.remove();
            this.tray = null;
        }
        
        console.log('System integration cleaned up');
    }
    
    exitApp() {
        this.cleanup();
        this.gui.App.quit();
    }
}

// Usage example
const systemIntegration = new NWJSSystemIntegration();
systemIntegration.createTrayIcon();
systemIntegration.registerGlobalShortcuts();

Native Menu and Window Management

// Native menu and window management system
class NWJSMenuManager {
    constructor() {
        this.gui = nw;
        this.win = this.gui.Window.get();
        this.menuBar = null;
        
        this.createMenuBar();
    }
    
    createMenuBar() {
        // Create main menu
        this.menuBar = new this.gui.Menu({ type: 'menubar' });
        
        // File menu
        const fileMenu = new this.gui.Menu();
        fileMenu.append(new this.gui.MenuItem({
            label: 'New Window',
            accelerator: 'CmdOrCtrl+N',
            click: () => this.createNewWindow()
        }));
        fileMenu.append(new this.gui.MenuItem({
            type: 'separator'
        }));
        fileMenu.append(new this.gui.MenuItem({
            label: 'Exit',
            accelerator: 'CmdOrCtrl+Q',
            click: () => this.gui.App.quit()
        }));
        
        // View menu
        const viewMenu = new this.gui.Menu();
        viewMenu.append(new this.gui.MenuItem({
            label: 'Fullscreen',
            accelerator: 'F11',
            click: () => this.toggleFullscreen()
        }));
        viewMenu.append(new this.gui.MenuItem({
            label: 'Developer Tools',
            accelerator: 'F12',
            click: () => this.win.showDevTools()
        }));
        viewMenu.append(new this.gui.MenuItem({
            type: 'separator'
        }));
        viewMenu.append(new this.gui.MenuItem({
            label: 'Always on Top',
            type: 'checkbox',
            checked: this.win.isAlwaysOnTop,
            click: () => this.toggleAlwaysOnTop()
        }));
        
        // Window menu
        const windowMenu = new this.gui.Menu();
        windowMenu.append(new this.gui.MenuItem({
            label: 'Minimize',
            accelerator: 'CmdOrCtrl+M',
            click: () => this.win.minimize()
        }));
        windowMenu.append(new this.gui.MenuItem({
            label: 'Maximize',
            click: () => this.toggleMaximize()
        }));
        windowMenu.append(new this.gui.MenuItem({
            type: 'separator'
        }));
        windowMenu.append(new this.gui.MenuItem({
            label: 'Center Window',
            click: () => this.centerWindow()
        }));
        
        // Help menu
        const helpMenu = new this.gui.Menu();
        helpMenu.append(new this.gui.MenuItem({
            label: 'About NW.js',
            click: () => {
                console.log(`NW.js ${process.versions.nw} - Node.js ${process.version}`);
            }
        }));
        
        // Add to menu bar
        this.menuBar.append(new this.gui.MenuItem({ label: 'File', submenu: fileMenu }));
        this.menuBar.append(new this.gui.MenuItem({ label: 'View', submenu: viewMenu }));
        this.menuBar.append(new this.gui.MenuItem({ label: 'Window', submenu: windowMenu }));
        this.menuBar.append(new this.gui.MenuItem({ label: 'Help', submenu: helpMenu }));
        
        // Apply menu bar
        this.win.menu = this.menuBar;
    }
    
    createNewWindow() {
        this.gui.Window.open('index.html', {
            title: 'New Window',
            width: 600,
            height: 400,
            position: 'center'
        });
    }
    
    toggleFullscreen() {
        if (this.win.isFullscreen) {
            this.win.leaveFullscreen();
        } else {
            this.win.enterFullscreen();
        }
    }
    
    toggleMaximize() {
        if (this.win.isMaximized) {
            this.win.unmaximize();
        } else {
            this.win.maximize();
        }
    }
    
    toggleAlwaysOnTop() {
        const newState = !this.win.isAlwaysOnTop;
        this.win.setAlwaysOnTop(newState);
    }
    
    centerWindow() {
        const screen = this.gui.Screen.screens[0];
        const x = Math.round((screen.bounds.width - this.win.width) / 2);
        const y = Math.round((screen.bounds.height - this.win.height) / 2);
        
        this.win.moveTo(x, y);
    }
}

// Usage example
const menuManager = new NWJSMenuManager();

Performance Optimization

// Performance monitoring and optimization
class NWJSPerformanceOptimizer {
    constructor() {
        this.memoryMonitor = null;
        this.performanceMetrics = {
            memory: [],
            cpu: [],
            fps: []
        };
        
        this.init();
    }
    
    init() {
        console.log('Performance Optimizer initializing...');
        
        // Start memory monitoring
        this.startMemoryMonitoring();
        
        // Garbage collection optimization
        this.optimizeGarbageCollection();
        
        console.log('Performance Optimizer initialization complete');
    }
    
    startMemoryMonitoring() {
        this.memoryMonitor = setInterval(() => {
            const memInfo = process.memoryUsage();
            
            this.performanceMetrics.memory.push({
                timestamp: Date.now(),
                rss: memInfo.rss,
                heapTotal: memInfo.heapTotal,
                heapUsed: memInfo.heapUsed,
                external: memInfo.external
            });
            
            // Keep maximum 1000 data points
            if (this.performanceMetrics.memory.length > 1000) {
                this.performanceMetrics.memory.shift();
            }
            
            // Warning if memory usage exceeds threshold
            const memoryMB = memInfo.heapUsed / 1024 / 1024;
            if (memoryMB > 500) { // Warning at 500MB+
                console.warn(`High memory usage detected: ${memoryMB.toFixed(2)} MB`);
                this.triggerMemoryCleanup();
            }
            
        }, 5000); // Monitor every 5 seconds
    }
    
    optimizeGarbageCollection() {
        // V8 engine garbage collection settings
        if (global.gc) {
            // Periodic garbage collection execution
            setInterval(() => {
                if (global.gc) {
                    global.gc();
                    console.log('Garbage collection executed');
                }
            }, 60000); // Every minute
        }
    }
    
    triggerMemoryCleanup() {
        console.log('Starting memory cleanup...');
        
        // Remove unnecessary DOM elements
        const unusedElements = document.querySelectorAll('.temp, .cached, [data-cleanup="true"]');
        unusedElements.forEach(element => {
            element.remove();
        });
        
        // Canvas element cleanup
        const canvases = document.querySelectorAll('canvas');
        canvases.forEach(canvas => {
            const ctx = canvas.getContext('2d');
            if (ctx) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
            }
        });
        
        // Force garbage collection
        if (global.gc) {
            global.gc();
        }
        
        console.log('Memory cleanup complete');
    }
    
    getPerformanceReport() {
        const latest = this.performanceMetrics.memory.slice(-1)[0];
        
        return {
            memory: {
                current: latest ? (latest.heapUsed / 1024 / 1024).toFixed(2) + ' MB' : 'N/A',
                peak: Math.max(...this.performanceMetrics.memory.map(m => m.heapUsed)) / 1024 / 1024,
                average: this.performanceMetrics.memory.reduce((sum, m) => sum + m.heapUsed, 0) 
                    / this.performanceMetrics.memory.length / 1024 / 1024
            },
            uptime: process.uptime()
        };
    }
    
    destroy() {
        if (this.memoryMonitor) {
            clearInterval(this.memoryMonitor);
            this.memoryMonitor = null;
        }
        
        console.log('Performance Optimizer stopped');
    }
}

// Usage example
const performanceOptimizer = new NWJSPerformanceOptimizer();

// Display performance report
setInterval(() => {
    const report = performanceOptimizer.getPerformanceReport();
    console.log('Performance Report:', report);
}, 30000); // Every 30 seconds

NW.js provides a powerful combination of web technologies and Node.js, offering new possibilities for desktop application development. With its simpler architecture different from Electron, it enables web developers to easily build native applications.