Added --dev flag to ./serve-locally.sh
This commit is contained in:
@@ -183,6 +183,12 @@ This serves the app at `http://localhost:8888`. The script automatically:
|
||||
|
||||
You can delete the entire `web/dist/` folder and re-run `serve-locally.sh` to regenerate everything.
|
||||
|
||||
To automatically rebuild when source files change:
|
||||
|
||||
```bash
|
||||
./serve-locally.sh --dev
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run all tests (WASM integration + E2E):
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${1:-}" == "--fake-analytics" ]]; then
|
||||
DEV_MODE=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--fake-analytics)
|
||||
export UMAMI_WEBSITE_ID="fake"
|
||||
export UMAMI_SCRIPT_URL="data:,"
|
||||
fi
|
||||
;;
|
||||
--dev)
|
||||
DEV_MODE=true
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
WEB_DIR="$SCRIPT_DIR/web"
|
||||
@@ -40,5 +48,13 @@ if [ ! -f "$DIST_DIR/wasm/kobopatch.wasm" ]; then
|
||||
"$WASM_DIR/build.sh"
|
||||
fi
|
||||
|
||||
echo "Serving at http://localhost:8888"
|
||||
node serve.mjs
|
||||
if [ "$DEV_MODE" = true ]; then
|
||||
echo "Serving at http://localhost:8888 (dev mode, watching for changes)"
|
||||
NO_CACHE=1 node serve.mjs &
|
||||
SERVER_PID=$!
|
||||
trap "kill $SERVER_PID 2>/dev/null" EXIT
|
||||
node build.mjs --watch
|
||||
else
|
||||
echo "Serving at http://localhost:8888"
|
||||
node serve.mjs
|
||||
fi
|
||||
|
||||
159
web/build.mjs
159
web/build.mjs
@@ -1,5 +1,5 @@
|
||||
import esbuild from 'esbuild';
|
||||
import { cpSync, mkdirSync, readFileSync, writeFileSync, existsSync, rmSync, readdirSync, statSync } from 'fs';
|
||||
import { cpSync, mkdirSync, readFileSync, writeFileSync, existsSync, rmSync, readdirSync, statSync, watch } from 'fs';
|
||||
import { join, relative } from 'path';
|
||||
import { createHash } from 'crypto';
|
||||
import { execSync } from 'child_process';
|
||||
@@ -9,29 +9,8 @@ const repoDir = join(webDir, '..');
|
||||
const srcDir = join(webDir, 'src');
|
||||
const distDir = join(webDir, 'dist');
|
||||
const isDev = process.argv.includes('--dev');
|
||||
const isWatch = process.argv.includes('--watch');
|
||||
|
||||
// Clean dist/ (preserve wasm/ which is built separately)
|
||||
if (existsSync(distDir)) {
|
||||
for (const entry of readdirSync(distDir)) {
|
||||
if (entry !== 'wasm') {
|
||||
rmSync(join(distDir, entry), { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build JS bundle
|
||||
await esbuild.build({
|
||||
entryPoints: [join(srcDir, 'js', 'app.js')],
|
||||
bundle: true,
|
||||
format: 'iife',
|
||||
target: ['es2020'],
|
||||
outfile: join(distDir, 'bundle.js'),
|
||||
minify: !isDev,
|
||||
sourcemap: isDev,
|
||||
logLevel: 'warning',
|
||||
});
|
||||
|
||||
// Copy all of src/ to dist/, skipping js/ (bundled separately), css/ (minified), and index.html (generated)
|
||||
function copyDir(src, dst, skip = new Set()) {
|
||||
mkdirSync(dst, { recursive: true });
|
||||
for (const entry of readdirSync(src)) {
|
||||
@@ -45,29 +24,53 @@ function copyDir(src, dst, skip = new Set()) {
|
||||
}
|
||||
}
|
||||
}
|
||||
copyDir(srcDir, distDir, new Set(['js', 'css', 'index.html']));
|
||||
|
||||
// Minify CSS
|
||||
mkdirSync(join(distDir, 'css'), { recursive: true });
|
||||
const cssSrc = readFileSync(join(srcDir, 'css', 'style.css'), 'utf-8');
|
||||
const { code: cssMinified } = await esbuild.transform(cssSrc, {
|
||||
async function build() {
|
||||
// Clean dist/ (preserve wasm/ which is built separately)
|
||||
if (existsSync(distDir)) {
|
||||
for (const entry of readdirSync(distDir)) {
|
||||
if (entry !== 'wasm') {
|
||||
rmSync(join(distDir, entry), { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build JS bundle
|
||||
await esbuild.build({
|
||||
entryPoints: [join(srcDir, 'js', 'app.js')],
|
||||
bundle: true,
|
||||
format: 'iife',
|
||||
target: ['es2020'],
|
||||
outfile: join(distDir, 'bundle.js'),
|
||||
minify: !isDev && !isWatch,
|
||||
sourcemap: isDev || isWatch,
|
||||
logLevel: 'warning',
|
||||
});
|
||||
|
||||
// Copy all of src/ to dist/, skipping js/ (bundled separately), css/ (minified), and index.html (generated)
|
||||
copyDir(srcDir, distDir, new Set(['js', 'css', 'index.html']));
|
||||
|
||||
// Minify CSS
|
||||
mkdirSync(join(distDir, 'css'), { recursive: true });
|
||||
const cssSrc = readFileSync(join(srcDir, 'css', 'style.css'), 'utf-8');
|
||||
const { code: cssMinified } = await esbuild.transform(cssSrc, {
|
||||
loader: 'css',
|
||||
minify: !isDev,
|
||||
});
|
||||
writeFileSync(join(distDir, 'css', 'style.css'), cssMinified);
|
||||
minify: !isDev && !isWatch,
|
||||
});
|
||||
writeFileSync(join(distDir, 'css', 'style.css'), cssMinified);
|
||||
|
||||
// Copy worker files from src/js/ (not bundled, served separately)
|
||||
mkdirSync(join(distDir, 'js'), { recursive: true });
|
||||
// Copy worker files from src/js/ (not bundled, served separately)
|
||||
mkdirSync(join(distDir, 'js'), { recursive: true });
|
||||
|
||||
// Copy wasm_exec.js as-is
|
||||
const wasmExecSrc = join(srcDir, 'js', 'wasm_exec.js');
|
||||
if (existsSync(wasmExecSrc)) {
|
||||
// Copy wasm_exec.js as-is
|
||||
const wasmExecSrc = join(srcDir, 'js', 'wasm_exec.js');
|
||||
if (existsSync(wasmExecSrc)) {
|
||||
cpSync(wasmExecSrc, join(distDir, 'js', 'wasm_exec.js'));
|
||||
}
|
||||
}
|
||||
|
||||
// Copy patch-worker.js with WASM hash injected
|
||||
const workerSrc = join(srcDir, 'js', 'patch-worker.js');
|
||||
if (existsSync(workerSrc)) {
|
||||
// Copy patch-worker.js with WASM hash injected
|
||||
const workerSrc = join(srcDir, 'js', 'patch-worker.js');
|
||||
if (existsSync(workerSrc)) {
|
||||
let workerContent = readFileSync(workerSrc, 'utf-8');
|
||||
const wasmFile = join(distDir, 'wasm', 'kobopatch.wasm');
|
||||
if (existsSync(wasmFile)) {
|
||||
@@ -78,13 +81,13 @@ if (existsSync(workerSrc)) {
|
||||
);
|
||||
}
|
||||
writeFileSync(join(distDir, 'js', 'patch-worker.js'), workerContent);
|
||||
}
|
||||
}
|
||||
|
||||
// Get git version string
|
||||
let versionStr = 'unknown';
|
||||
let versionLink = 'https://github.com/nicoverbruggen/kobopatch-webui';
|
||||
const nixpacksCommit = process.env.SOURCE_COMMIT;
|
||||
try {
|
||||
// Get git version string
|
||||
let versionStr = 'unknown';
|
||||
let versionLink = 'https://github.com/nicoverbruggen/kobopatch-webui';
|
||||
const nixpacksCommit = process.env.SOURCE_COMMIT;
|
||||
try {
|
||||
if (nixpacksCommit) {
|
||||
versionStr = nixpacksCommit.slice(0, 7);
|
||||
versionLink = `https://github.com/nicoverbruggen/kobopatch-webui/commit/${nixpacksCommit}`;
|
||||
@@ -106,41 +109,63 @@ try {
|
||||
versionLink = `https://github.com/nicoverbruggen/kobopatch-webui/commit/${hash}`;
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
} catch {}
|
||||
|
||||
// Generate cache-busted index.html
|
||||
const bundleContent = readFileSync(join(distDir, 'bundle.js'));
|
||||
const bundleHash = createHash('md5').update(bundleContent).digest('hex').slice(0, 8);
|
||||
// Generate cache-busted index.html
|
||||
const bundleContent = readFileSync(join(distDir, 'bundle.js'));
|
||||
const bundleHash = createHash('md5').update(bundleContent).digest('hex').slice(0, 8);
|
||||
|
||||
const cssContent = readFileSync(join(distDir, 'css/style.css'));
|
||||
const cssHash = createHash('md5').update(cssContent).digest('hex').slice(0, 8);
|
||||
const cssContent = readFileSync(join(distDir, 'css/style.css'));
|
||||
const cssHash = createHash('md5').update(cssContent).digest('hex').slice(0, 8);
|
||||
|
||||
let html = readFileSync(join(srcDir, 'index.html'), 'utf-8');
|
||||
let html = readFileSync(join(srcDir, 'index.html'), 'utf-8');
|
||||
|
||||
// Remove all <script src="js/..."> tags
|
||||
html = html.replace(/\s*<script src="js\/[^"]*"><\/script>\n/g, '');
|
||||
// Add the bundle script before </body>
|
||||
html = html.replace(
|
||||
// Remove all <script src="js/..."> tags
|
||||
html = html.replace(/\s*<script src="js\/[^"]*"><\/script>\n/g, '');
|
||||
// Add the bundle script before </body>
|
||||
html = html.replace(
|
||||
'</body>',
|
||||
` <script src="/bundle.js?h=${bundleHash}"></script>\n</body>`
|
||||
);
|
||||
);
|
||||
|
||||
// Update CSS cache bust
|
||||
html = html.replace(
|
||||
// Update CSS cache bust
|
||||
html = html.replace(
|
||||
/css\/style\.css\?[^"]*/,
|
||||
`css/style.css?h=${cssHash}`
|
||||
);
|
||||
);
|
||||
|
||||
// Inject version string and link
|
||||
html = html.replace('<span id="commit-hash"></span>', `<span id="commit-hash">${versionStr}</span>`);
|
||||
html = html.replace(
|
||||
// Inject version string and link
|
||||
html = html.replace('<span id="commit-hash"></span>', `<span id="commit-hash">${versionStr}</span>`);
|
||||
html = html.replace(
|
||||
'href="https://github.com/nicoverbruggen/kobopatch-webui"',
|
||||
`href="${versionLink}"`
|
||||
);
|
||||
);
|
||||
|
||||
writeFileSync(join(distDir, 'index.html'), html);
|
||||
writeFileSync(join(distDir, 'index.html'), html);
|
||||
|
||||
console.log(`Built to ${distDir} (bundle: ${bundleHash}, css: ${cssHash}, version: ${versionStr})`);
|
||||
console.log(`Built to ${distDir} (bundle: ${bundleHash}, css: ${cssHash}, version: ${versionStr})`);
|
||||
}
|
||||
|
||||
await build();
|
||||
|
||||
// Watch mode: rebuild on source changes
|
||||
if (isWatch) {
|
||||
let rebuildTimer = null;
|
||||
|
||||
watch(srcDir, { recursive: true }, (eventType, filename) => {
|
||||
if (rebuildTimer) clearTimeout(rebuildTimer);
|
||||
rebuildTimer = setTimeout(async () => {
|
||||
console.log(`\nChange detected: ${filename}`);
|
||||
try {
|
||||
await build();
|
||||
} catch (err) {
|
||||
console.error('Build failed:', err.message);
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
|
||||
console.log('Watching src/ for changes...');
|
||||
}
|
||||
|
||||
// Dev server mode
|
||||
if (isDev) {
|
||||
|
||||
@@ -17,18 +17,19 @@ if (analyticsEnabled) {
|
||||
` <script defer src="${UMAMI_SCRIPT_URL}" data-website-id="${UMAMI_WEBSITE_ID}"></script>\n`;
|
||||
}
|
||||
|
||||
// Cache the processed index.html at startup
|
||||
// Cache the processed index.html (disabled when NO_CACHE is set, e.g. during --dev)
|
||||
const noCache = !!process.env.NO_CACHE;
|
||||
let cachedIndexHtml = null;
|
||||
function getIndexHtml() {
|
||||
if (cachedIndexHtml) return cachedIndexHtml;
|
||||
if (!noCache && cachedIndexHtml) return cachedIndexHtml;
|
||||
const indexPath = join(DIST, 'index.html');
|
||||
if (!existsSync(indexPath)) return null;
|
||||
let html = readFileSync(indexPath, 'utf-8');
|
||||
if (analyticsSnippet) {
|
||||
html = html.replace('</head>', analyticsSnippet + '</head>');
|
||||
}
|
||||
cachedIndexHtml = html;
|
||||
return cachedIndexHtml;
|
||||
if (!noCache) cachedIndexHtml = html;
|
||||
return html;
|
||||
}
|
||||
|
||||
const MIME = {
|
||||
|
||||
Reference in New Issue
Block a user