Can we create a local JavaScript application without Electron or a framework?
Yes, a local script can launch the browser with an HTML page as its interface.
Let's be clear, a framework can be very useful for building a local JavaScript application, providing many useful functions. But when you want to create a very simple application, with a JavaScript script that uses an HTML page as its interface and allows interaction with that interface, it's better to avoid the cumbersome nature of a framework, especially Electron, whose newer versions are incompatible with older ones. Downloading an application that has to include the 600 MB of Chromium to ensure compatibility is impractical.
Ideally, you would be able to use the browser already installed on your desktop to display the HTML interface, and we'll show you how in this simple demonstration.
This demonstration loads the list of files in a directory and displays the contents of a file by clicking on its name.
In the previous article, "How works a Node.js file server", we showed how a script can interact with an HTML page loaded in a browser. But to make it function similarly to a typical local application, we will add the code that allows the script to launch the browser and interface page itself.
exec('start chrome --app=http://localhost:3000')
The "--app" option allows the page to be displayed by the Chrome browser alone, without the browser's own interface.
This command works on Windows. The demonstration uses process.platform to detect the operating system, and if it's macOS or Linux, it executes the appropriate command for that OS.
Here is the complete code for the local JavaScript server:
const http = require('http');
const fs = require('fs');
const path = require('path');
const WebSocket = require('ws');
const { exec } = require('child_process');
const server = http.createServer((req, res) => {
let filePath = req.url === '/' ? './index.html' : `.${req.url}`;
const extname = path.extname(filePath);
const mimeTypes = {
'.html': 'text/html',
'.css': 'text/css',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
};
const contentType = mimeTypes[extname] || 'application/octet-stream';
fs.readFile(filePath, (error, content) => {
if (error) {
res.writeHead(404);
res.end("Fichier non trouvé");
} else {
res.writeHead(200, { 'Content-Type': contentType });
res.end(content, 'utf-8');
}
});
});
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws) => {
ws.on('message', (message) => {
const data = JSON.parse(message);
if (data.type === 'LIST_DIR') {
const dirPath = data.path || '.';
fs.readdir(dirPath,
{ withFileTypes: true },
(err, files) => {
if (err) return ws.send(
JSON.stringify({ type: 'ERROR', message: err.message })
);
const fileList = files.map(f => ({ name: f.name, isDir: f.isDirectory() }));
//broadcast({ type: 'DIR_CONTENT', , currentPath: });
ws.send(
JSON.stringify({
type: 'DIR',
files: fileList,
currentPath: path.resolve(dirPath)
})
);
});
}
if (data.type === 'READ_FILE') {
fs.readFile(data.path, 'utf8', (err, content) => {
if (err) return ws.send(JSON.stringify({
type: 'ERROR', message: err.message
}));
ws.send(
JSON.stringify({
type: 'FILE',
filecontent:content,
fileName: path.basename(data.path)
})
);
});
};
}); // message
}); // connection
server.listen(3000, () => {
console.log("Start...");
switch(process.platform) {
case "win32":
exec('start chrome --app=http://localhost:3000');
//exec('start chrome http://localhost:3000');
break
case "darwin":
exec('open -a "Google Chrome" http://localhost:3000')
break
case "linux":
exec('google-chrome http://localhost:3000')
break;
}
});
The name of the HTML interface file is assigned to the filepath variable at the beginning of the script.
Next, we define the MIME types for common extensions; otherwise, our JavaScript server wouldn't be able to load the corresponding files, even if these files are loaded from the HTML interface, such as the stylesheet.
The following code is the WebSocket mechanism that interacts with the HTML page, as already described in the article "First steps with WebSocket and Node.js."
It uses the `ws` library. This library isn't included in Node.js, but it has been added to the demo archive. If this library isn't recognized by your system, you can also install it with this command.
npm install ws
The HTML code for the demo is quite simple:
<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>No Electron, no framework - Scriptol.fr/Scriptol.com</title> <link rel="stylesheet" href="style.css"></head>
<body> <div id="explorer"> <h3>File List</h3> <input type="text" id="pathInput" placeholder="." style="width: 80%;"> <button onclick="listDir()">Go</button> <div id="fileList"></div> </div>
<div id="viewer"> <h3 id="fileName"></h3> <hr> <div id="fileContent"></div> </div>
<script>
const ws = new WebSocket('ws://localhost:3000');
let currentPath = '';
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'DIR') {
currentPath = data.currentPath;
const listDiv = document.getElementById('fileList');
document.getElementById('pathInput').value = currentPath;
listDiv.innerHTML = "<p><small>${currentPath}</small></p>";
data.files.forEach(file => {
const el = document.createElement('div');
el.className = file.isDir ? 'dir' : 'file';
el.textContent = (file.isDir ? '📠' : '📄 ') + file.name;
if (!file.isDir) {
el.onclick = () => readFile(file.name);
}
listDiv.appendChild(el);
});
}
if (data.type === 'FILE') {
document.getElementById('fileName').textContent = data.fileName;
document.getElementById('fileContent').textContent = data.filecontent;
}
if (data.type === 'ERROR') {
alert("Erreur: " + data.message);
}
};
function listDir() {
const path = document.getElementById('pathInput').value;
ws.send(JSON.stringify({ type: 'LIST_DIR', path: path }));
}
function readFile(name) {
ws.send(JSON.stringify({ type: 'READ_FILE', path: currentPath + '/' + name }));
}
</script>
</body>
</html>
The interface consists of two windows: the file list on the left, and the contents of a file on the right.
To try the demo, download the archive:
Open the archive and transfer the directory it contains, noelectron, to the storage device. Open the directory in the command line and type:
node server.js
This displays the interface. Tap on "go" to display the current directory and click on a filename to display the contents on the right.

