Initial commit step0
This commit is contained in:
2
step0/.gitignore
vendored
Normal file
2
step0/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
build/
|
||||||
11
step0/CMakeLists.txt
Normal file
11
step0/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
cmake_minimum_required(VERSION 4.0.0)
|
||||||
|
project(cjs-lite)
|
||||||
|
|
||||||
|
add_executable(app app.c)
|
||||||
|
|
||||||
|
# Set specific executable flags
|
||||||
|
target_link_options(app PRIVATE
|
||||||
|
-Wl,--no-entry
|
||||||
|
-Wl,--no-growable-memory
|
||||||
|
-Wl,--initial-memory=536870912
|
||||||
|
)
|
||||||
1
step0/README.md
Normal file
1
step0/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# C + JavaScript (lite)
|
||||||
38
step0/Web.cmake
Normal file
38
step0/Web.cmake
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
set(CMAKE_SYSTEM_NAME WASI)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR WASM)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR_FAMILY WASM)
|
||||||
|
set(WASM ON)
|
||||||
|
set(WEB ON)
|
||||||
|
|
||||||
|
# Find clang/clang++ as compiler
|
||||||
|
find_program(CMAKE_C_COMPILER clang)
|
||||||
|
find_program(CMAKE_CXX_COMPILER clang++)
|
||||||
|
find_program(CMAKE_ASM_COMPILER llvm-mc)
|
||||||
|
|
||||||
|
# Some options that affect the generated output
|
||||||
|
set(WASM_OPTS -mbulk-memory -matomics -fignore-exceptions -msimd128)
|
||||||
|
|
||||||
|
add_compile_definitions(
|
||||||
|
$<$<COMPILE_LANGUAGE:C,CXX>:__wasi__>
|
||||||
|
$<$<COMPILE_LANGUAGE:C,CXX>:__WASM__>
|
||||||
|
)
|
||||||
|
add_compile_options(${WASM_OPTS})
|
||||||
|
add_link_options(-nostdlib ${WASM_OPTS})
|
||||||
|
|
||||||
|
set(CMAKE_CROSSCOMPILING TRUE)
|
||||||
|
set(CMAKE_EXECUTABLE_SUFFIX_CXX ".wasm")
|
||||||
|
set(CMAKE_EXECUTABLE_SUFFIX_C ".wasm")
|
||||||
|
|
||||||
|
set(BUILD_SHARED_LIBS OFF)
|
||||||
|
|
||||||
|
set(CMAKE_C_COMPILER_TARGET unknown-unknown-wasm32-web)
|
||||||
|
set(CMAKE_CXX_COMPILER_TARGET unknown-unknown-wasm32-web)
|
||||||
|
set(CMAKE_ASM_COMPILER_TARGET unknown-unknown-wasm32-web)
|
||||||
|
|
||||||
|
# adjust the default behavior of the FIND_XXX() commands:
|
||||||
|
# search programs in the host environment
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
|
||||||
|
# search headers and libraries in the target environment
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
4
step0/app.c
Normal file
4
step0/app.c
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
__attribute__((export_name("hello")))
|
||||||
|
int hello() {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
BIN
step0/favicon.ico
Normal file
BIN
step0/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
33
step0/index.html
Normal file
33
step0/index.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>step0</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: black;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
#input {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="canvas"></canvas>
|
||||||
|
<script src="main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
70
step0/main.js
Normal file
70
step0/main.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
const debug = function() { console.debug(...arguments); };
|
||||||
|
const log = function() { console.log(...arguments) };
|
||||||
|
const error = function() { console.error(...arguments) };
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('./bin/app.wasm');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch WASM: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasmBytes = await response.arrayBuffer();
|
||||||
|
const wasmCompiled = await WebAssembly.compile(wasmBytes);
|
||||||
|
const wasmModule = await WebAssembly.instantiate(wasmCompiled, { });
|
||||||
|
|
||||||
|
// Set canvas size to match display size
|
||||||
|
function resizeCanvas() {
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeCanvas();
|
||||||
|
window.addEventListener('resize', resizeCanvas);
|
||||||
|
|
||||||
|
var result = wasmModule.exports.hello();
|
||||||
|
log("Finished", result);
|
||||||
|
} catch (error) {
|
||||||
|
error('runtime error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
|
|
||||||
|
// ------- debug hot-reload code -------
|
||||||
|
{
|
||||||
|
const debugCtl = {
|
||||||
|
connect: () => {
|
||||||
|
const ws = new WebSocket(`${window.location.href}control`);
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
log('Connected to control WebSocket');
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(event.data);
|
||||||
|
log('Received message:', message);
|
||||||
|
|
||||||
|
if (message.type === 'refresh') {
|
||||||
|
log('Reloading page...');
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
error('Error parsing WebSocket message:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = () => {
|
||||||
|
log('WebSocket disconnected, reconnecting in 2s...');
|
||||||
|
setTimeout(debugCtl.connect, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = (error) => {
|
||||||
|
error('WebSocket error:', error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
debugCtl.connect();
|
||||||
|
}
|
||||||
|
// -------------------------------------
|
||||||
33
step0/package-lock.json
generated
Normal file
33
step0/package-lock.json
generated
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "step0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"ws": "^8.18.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
|
||||||
|
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
step0/package.json
Normal file
5
step0/package.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"ws": "^8.18.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
133
step0/server.js
Normal file
133
step0/server.js
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
// ===== Configuration =====
|
||||||
|
var config = {
|
||||||
|
port: 8080,
|
||||||
|
};
|
||||||
|
//
|
||||||
|
// =========================
|
||||||
|
let rootDir = __dirname;
|
||||||
|
if (!rootDir.endsWith('/')) rootDir += '/';
|
||||||
|
const binDir = rootDir + `build`;
|
||||||
|
const watchPaths = [rootDir, binDir];
|
||||||
|
|
||||||
|
const http = require('http');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
const WebSocket = require('ws');
|
||||||
|
|
||||||
|
const mimeTypes = {
|
||||||
|
'.html': 'text/html',
|
||||||
|
'.js': 'text/javascript',
|
||||||
|
'.wasm': 'application/wasm',
|
||||||
|
'.css': 'text/css',
|
||||||
|
'.json': 'application/json'
|
||||||
|
};
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
let norm = path.normalize(req.url);
|
||||||
|
console.log(`${req.method} ${norm}`);
|
||||||
|
|
||||||
|
// Serve config endpoint
|
||||||
|
if (norm === '/config') {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify(config));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve files
|
||||||
|
let filePath;
|
||||||
|
if (norm.startsWith('/bin/')) {
|
||||||
|
filePath = path.join(binDir, norm.slice(5));
|
||||||
|
} else if (norm == '/') {
|
||||||
|
filePath = rootDir + 'index.html';
|
||||||
|
} else {
|
||||||
|
filePath = rootDir + norm;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extname = String(path.extname(filePath)).toLowerCase();
|
||||||
|
const contentType = mimeTypes[extname] || 'application/octet-stream';
|
||||||
|
|
||||||
|
fs.readFile(filePath, (error, content) => {
|
||||||
|
if (error) {
|
||||||
|
if (error.code === 'ENOENT') {
|
||||||
|
res.writeHead(404, { 'Content-Type': 'text/html' });
|
||||||
|
res.end('<h1>404 - File Not Found</h1>', 'utf-8');
|
||||||
|
} else {
|
||||||
|
res.writeHead(500);
|
||||||
|
res.end(`Server Error: ${error.code}`, 'utf-8');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Type': contentType,
|
||||||
|
'Content-Length': content.byteLength,
|
||||||
|
'Access-Control-Expose-Headers': 'Content-Length',
|
||||||
|
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||||
|
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||||
|
});
|
||||||
|
res.end(content, 'utf-8');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const wss = new WebSocket.Server({ noServer: true });
|
||||||
|
wss.on('connection', () => { });
|
||||||
|
server.on('upgrade', (request, socket, head) => {
|
||||||
|
if (request.url === '/control') {
|
||||||
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||||
|
wss.emit('connection', ws, request);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function broadcastRefresh() {
|
||||||
|
const message = JSON.stringify({ type: 'refresh' });
|
||||||
|
let clientCount = 0;
|
||||||
|
|
||||||
|
wss.clients.forEach((client) => {
|
||||||
|
if (client.readyState === WebSocket.OPEN) {
|
||||||
|
client.send(message);
|
||||||
|
clientCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (clientCount > 0) {
|
||||||
|
console.log(`Sent refresh to ${clientCount} client(s)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch files for changes
|
||||||
|
const watchers = [];
|
||||||
|
watchPaths.forEach((watchPath) => {
|
||||||
|
try {
|
||||||
|
const watcher = fs.watch(watchPath, { recursive: true }, (eventType, filename) => {
|
||||||
|
console.log(`File changed: ${filename} (${eventType})`);
|
||||||
|
if (filename) {
|
||||||
|
console.log(`File changed: ${filename} (${eventType})`);
|
||||||
|
broadcastRefresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
watchers.push(watcher);
|
||||||
|
console.log(`Watching: ${path.resolve(watchPath)}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to watch ${watchPath}:`, error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(config.port, () => {
|
||||||
|
console.log(`Server running at http://localhost:${config.port}/`);
|
||||||
|
console.log(`WebSocket listening on ws://localhost:${config.port}/control`);
|
||||||
|
console.log(`WASM binary path configured as: ${binDir}`);
|
||||||
|
|
||||||
|
const shouldLaunchBrowser = process.argv.includes('--launch');
|
||||||
|
if (shouldLaunchBrowser) {
|
||||||
|
exec(`librewolf --new-window http://localhost:${config.port}/`, (error) => {
|
||||||
|
if (error) {
|
||||||
|
console.error(`Failed to launch LibreWolf: ${error.message}`);
|
||||||
|
} else {
|
||||||
|
console.log('LibreWolf launched');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user