mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-20 11:19:56 +00:00
Add inline and watch helper scripts
This commit is contained in:
1 parent
e21f5ab6c7
commit
edd2d06627
4 files changed
+237
-3
No files matched your search
@@ -0,0 +1,157 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
let outputFile = null;
|
||||
let inputFile = null;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '-o' && i + 1 < args.length) {
|
||||
outputFile = args[++i];
|
||||
} else if (!args[i].startsWith('-')) {
|
||||
inputFile = args[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!inputFile) {
|
||||
console.error('Usage: node inline.js [-o OUTPUT_FILE] HTML_FILE');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const baseDir = path.dirname(path.resolve(inputFile));
|
||||
const html = fs.readFileSync(inputFile, 'utf8');
|
||||
|
||||
function fetchUrl(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = url.startsWith('https') ? https : http;
|
||||
client.get(url, res => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => resolve(data));
|
||||
res.on('error', reject);
|
||||
}).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function inlineCss(match) {
|
||||
const href = match.match(/href=["']([^"']+)["']/)[1];
|
||||
let css = '';
|
||||
if (href.startsWith('http')) {
|
||||
css = await fetchUrl(href);
|
||||
} else {
|
||||
const cssPath = path.join(baseDir, href);
|
||||
css = fs.readFileSync(cssPath, 'utf8');
|
||||
}
|
||||
return `<style>${css}</style>`;
|
||||
}
|
||||
|
||||
async function inlineJs(match) {
|
||||
const match2 = match.match(/src=["']([^"']+)["']/);
|
||||
if (!match2) return match;
|
||||
const src = match2[1];
|
||||
let js = '';
|
||||
if (src.startsWith('http')) {
|
||||
js = await fetchUrl(src);
|
||||
} else {
|
||||
const jsPath = path.join(baseDir, src);
|
||||
js = fs.readFileSync(jsPath, 'utf8');
|
||||
}
|
||||
return `<script>${js}</script>`;
|
||||
}
|
||||
|
||||
async function inlineImg(match) {
|
||||
const match2 = match.match(/src=["']([^"']+)["']/);
|
||||
if (!match2) return match;
|
||||
const src = match2[1];
|
||||
const ext = path.extname(src).toLowerCase();
|
||||
let mime = 'image/png';
|
||||
if (ext === '.svg') mime = 'image/svg+xml';
|
||||
else if (ext === '.jpg' || ext === '.jpeg') mime = 'image/jpeg';
|
||||
else if (ext === '.gif') mime = 'image/gif';
|
||||
else if (ext === '.webp') mime = 'image/webp';
|
||||
else if (ext === '.ico') mime = 'image/x-icon';
|
||||
let data;
|
||||
if (src.startsWith('http')) {
|
||||
data = await fetchUrl(src);
|
||||
} else {
|
||||
const imgPath = path.join(baseDir, src);
|
||||
data = fs.readFileSync(imgPath);
|
||||
}
|
||||
const base64 = Buffer.from(data).toString('base64');
|
||||
if (ext === '.svg') {
|
||||
const svgData = typeof data === 'string' ? data : data.toString('utf8');
|
||||
return match.replace(/src="[^"]+"/, `src="data:${mime};utf8,${encodeURIComponent(svgData)}"`);
|
||||
}
|
||||
return match.replace(/src="[^"]+"/, `src="data:${mime};base64,${base64}"`);
|
||||
}
|
||||
|
||||
async function inlineSvg(match) {
|
||||
const match2 = match.match(/href=["']([^"']+)["']/);
|
||||
if (!match2) return match;
|
||||
const href = match2[1];
|
||||
if (!href.toLowerCase().endsWith('.svg')) return match;
|
||||
let data;
|
||||
if (href.startsWith('http')) {
|
||||
data = await fetchUrl(href);
|
||||
} else {
|
||||
const svgPath = path.join(baseDir, href);
|
||||
data = fs.readFileSync(svgPath, 'utf8');
|
||||
}
|
||||
const base64 = Buffer.from(data).toString('base64');
|
||||
return match.replace(/href="[^"]+"/, `href="data:image/svg+xml;base64,${base64}"`);
|
||||
}
|
||||
|
||||
async function processHtml(content) {
|
||||
let result = content;
|
||||
const cssRegex = /<link[^>]+rel=["']stylesheet["'][^>]*>/g;
|
||||
const jsRegex = /<script[^>]+src=["'][^"']+["'][^>]*><\/script>/g;
|
||||
const imgRegex = /<img[^>]+src=["'][^"']+["'][^>]*>/g;
|
||||
const svgRegex = /<link[^>]+rel=["']icon["'][^>]*>/g;
|
||||
|
||||
const cssMatches = content.match(cssRegex) || [];
|
||||
for (const match of cssMatches) {
|
||||
if (!match.includes('data:')) {
|
||||
result = result.replace(match, await inlineCss(match));
|
||||
}
|
||||
}
|
||||
|
||||
const jsMatches = content.match(jsRegex) || [];
|
||||
for (const match of jsMatches) {
|
||||
if (!match.includes('data:')) {
|
||||
result = result.replace(match, await inlineJs(match));
|
||||
}
|
||||
}
|
||||
|
||||
const imgMatches = content.match(imgRegex) || [];
|
||||
for (const match of imgMatches) {
|
||||
if (!match.includes('data:')) {
|
||||
result = result.replace(match, await inlineImg(match));
|
||||
}
|
||||
}
|
||||
|
||||
const svgMatches = content.match(svgRegex) || [];
|
||||
for (const match of svgMatches) {
|
||||
if (!match.includes('data:')) {
|
||||
result = result.replace(match, await inlineSvg(match));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
processHtml(html).then(result => {
|
||||
if (outputFile) {
|
||||
fs.writeFileSync(outputFile, result);
|
||||
console.log(`Written to ${outputFile}`);
|
||||
} else {
|
||||
console.log(result);
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('Error:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const watchTargets = [];
|
||||
let command = [];
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '-w' && i + 1 < args.length) {
|
||||
watchTargets.push(args[++i]);
|
||||
} else if (args[i] === '--') {
|
||||
command = args.slice(i + 1);
|
||||
break;
|
||||
} else if (!args[i].startsWith('-')) {
|
||||
command = args.slice(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (watchTargets.length === 0 || command.length === 0) {
|
||||
console.error('Usage: node watch.js -w DIR_OR_FILE [-w DIR_OR_FILE] -- COMMAND');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let child = null;
|
||||
let restartTimeout = null;
|
||||
|
||||
function start() {
|
||||
if (child) {
|
||||
child.kill();
|
||||
}
|
||||
console.log(`Starting: ${command.join(' ')}`);
|
||||
child = spawn(command[0], command.slice(1), { stdio: 'inherit', shell: true });
|
||||
child.on('exit', (code) => {
|
||||
if (code !== null && code !== 0) {
|
||||
console.log(`Process exited with code ${code}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function watch(target) {
|
||||
const stats = fs.statSync(target);
|
||||
if (stats.isFile()) {
|
||||
fs.watchFile(target, (curr, prev) => {
|
||||
if (curr.mtime !== prev.mtime) {
|
||||
console.log(`File changed: ${target}`);
|
||||
clearTimeout(restartTimeout);
|
||||
restartTimeout = setTimeout(start, 100);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const dir = target;
|
||||
fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
||||
if (filename) {
|
||||
console.log(`File changed: ${filename}`);
|
||||
clearTimeout(restartTimeout);
|
||||
restartTimeout = setTimeout(start, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
start();
|
||||
watchTargets.forEach(target => watch(target));
|
||||
console.log(`Watching ${watchTargets.join(', ')} for changes...`);
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\nShutting down...');
|
||||
if (child) {
|
||||
child.kill('SIGTERM');
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -16,7 +16,7 @@ $(PROG): $(SOURCES) $(MONGOOSE_DIR)/mongoose.h
|
||||
|
||||
# Generate a single, self-contained dashboard.html from the UI in web_root/
|
||||
dashboard.html: $(wildcard web_root/*)
|
||||
monolith web_root/index.html -o $@
|
||||
node $(MONGOOSE_DIR)/resources/inline.js web_root/index.html -o $@
|
||||
|
||||
# Create an embedded filesystem containing compressed dashboard.html
|
||||
dashboard.html.c: dashboard.html
|
||||
@@ -28,7 +28,7 @@ purge:
|
||||
|
||||
# For development: rebuild and restart if any of the sources change
|
||||
watch:
|
||||
watchexec -r -w main.c -w mongoose_dashboard.c -w $(MONGOOSE_DIR)/mongoose.h -w web_root -e c,h,html,css,js,svg,png -- make
|
||||
node $(MONGOOSE_DIR)/resources/watch.js -w main.c -w mongoose_dashboard.c -w $(MONGOOSE_DIR)/mongoose.h -w web_root -- make
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.exe *.obj *.dSYM mongoose dashboard.*
|
||||
@@ -282,7 +282,7 @@
|
||||
</div>
|
||||
<div class="file-size">${f.size}</div>
|
||||
<div class="file-actions">
|
||||
<a onclick="window.open('files/${encodeURIComponent(f.name)}', '_blank')" target="_blank" class="btn btn-secondary">
|
||||
<a href="files/${encodeURIComponent(f.name)}" target="_blank" class="btn btn-secondary">
|
||||
<svg viewBox="0 0 24 24" class="nav-icon" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 3v12" />
|
||||
<path d="m7 10 5 5 5-5" />
|
||||
|
||||
Reference in new issue
Block a user