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.
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.
Topics
Star History
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
- NW.js Official Site
- NW.js Documentation
- NW.js GitHub Repository
- NW.js Builder
- NW.js Tutorial
- Node.js Official Site
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.