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.
|
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
|
## Testing
|
||||||
|
|
||||||
Run all tests (WASM integration + E2E):
|
Run all tests (WASM integration + E2E):
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
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_WEBSITE_ID="fake"
|
||||||
export UMAMI_SCRIPT_URL="data:,"
|
export UMAMI_SCRIPT_URL="data:,"
|
||||||
fi
|
;;
|
||||||
|
--dev)
|
||||||
|
DEV_MODE=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
WEB_DIR="$SCRIPT_DIR/web"
|
WEB_DIR="$SCRIPT_DIR/web"
|
||||||
@@ -40,5 +48,13 @@ if [ ! -f "$DIST_DIR/wasm/kobopatch.wasm" ]; then
|
|||||||
"$WASM_DIR/build.sh"
|
"$WASM_DIR/build.sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
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"
|
echo "Serving at http://localhost:8888"
|
||||||
node serve.mjs
|
node serve.mjs
|
||||||
|
fi
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import esbuild from 'esbuild';
|
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 { join, relative } from 'path';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
@@ -9,7 +9,23 @@ const repoDir = join(webDir, '..');
|
|||||||
const srcDir = join(webDir, 'src');
|
const srcDir = join(webDir, 'src');
|
||||||
const distDir = join(webDir, 'dist');
|
const distDir = join(webDir, 'dist');
|
||||||
const isDev = process.argv.includes('--dev');
|
const isDev = process.argv.includes('--dev');
|
||||||
|
const isWatch = process.argv.includes('--watch');
|
||||||
|
|
||||||
|
function copyDir(src, dst, skip = new Set()) {
|
||||||
|
mkdirSync(dst, { recursive: true });
|
||||||
|
for (const entry of readdirSync(src)) {
|
||||||
|
if (skip.has(entry)) continue;
|
||||||
|
const srcPath = join(src, entry);
|
||||||
|
const dstPath = join(dst, entry);
|
||||||
|
if (statSync(srcPath).isDirectory()) {
|
||||||
|
copyDir(srcPath, dstPath);
|
||||||
|
} else {
|
||||||
|
cpSync(srcPath, dstPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function build() {
|
||||||
// Clean dist/ (preserve wasm/ which is built separately)
|
// Clean dist/ (preserve wasm/ which is built separately)
|
||||||
if (existsSync(distDir)) {
|
if (existsSync(distDir)) {
|
||||||
for (const entry of readdirSync(distDir)) {
|
for (const entry of readdirSync(distDir)) {
|
||||||
@@ -26,25 +42,12 @@ await esbuild.build({
|
|||||||
format: 'iife',
|
format: 'iife',
|
||||||
target: ['es2020'],
|
target: ['es2020'],
|
||||||
outfile: join(distDir, 'bundle.js'),
|
outfile: join(distDir, 'bundle.js'),
|
||||||
minify: !isDev,
|
minify: !isDev && !isWatch,
|
||||||
sourcemap: isDev,
|
sourcemap: isDev || isWatch,
|
||||||
logLevel: 'warning',
|
logLevel: 'warning',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copy all of src/ to dist/, skipping js/ (bundled separately), css/ (minified), and index.html (generated)
|
// 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)) {
|
|
||||||
if (skip.has(entry)) continue;
|
|
||||||
const srcPath = join(src, entry);
|
|
||||||
const dstPath = join(dst, entry);
|
|
||||||
if (statSync(srcPath).isDirectory()) {
|
|
||||||
copyDir(srcPath, dstPath);
|
|
||||||
} else {
|
|
||||||
cpSync(srcPath, dstPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
copyDir(srcDir, distDir, new Set(['js', 'css', 'index.html']));
|
copyDir(srcDir, distDir, new Set(['js', 'css', 'index.html']));
|
||||||
|
|
||||||
// Minify CSS
|
// Minify CSS
|
||||||
@@ -52,7 +55,7 @@ mkdirSync(join(distDir, 'css'), { recursive: true });
|
|||||||
const cssSrc = readFileSync(join(srcDir, 'css', 'style.css'), 'utf-8');
|
const cssSrc = readFileSync(join(srcDir, 'css', 'style.css'), 'utf-8');
|
||||||
const { code: cssMinified } = await esbuild.transform(cssSrc, {
|
const { code: cssMinified } = await esbuild.transform(cssSrc, {
|
||||||
loader: 'css',
|
loader: 'css',
|
||||||
minify: !isDev,
|
minify: !isDev && !isWatch,
|
||||||
});
|
});
|
||||||
writeFileSync(join(distDir, 'css', 'style.css'), cssMinified);
|
writeFileSync(join(distDir, 'css', 'style.css'), cssMinified);
|
||||||
|
|
||||||
@@ -141,6 +144,28 @@ html = html.replace(
|
|||||||
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
|
// Dev server mode
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
|
|||||||
@@ -17,18 +17,19 @@ if (analyticsEnabled) {
|
|||||||
` <script defer src="${UMAMI_SCRIPT_URL}" data-website-id="${UMAMI_WEBSITE_ID}"></script>\n`;
|
` <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;
|
let cachedIndexHtml = null;
|
||||||
function getIndexHtml() {
|
function getIndexHtml() {
|
||||||
if (cachedIndexHtml) return cachedIndexHtml;
|
if (!noCache && cachedIndexHtml) return cachedIndexHtml;
|
||||||
const indexPath = join(DIST, 'index.html');
|
const indexPath = join(DIST, 'index.html');
|
||||||
if (!existsSync(indexPath)) return null;
|
if (!existsSync(indexPath)) return null;
|
||||||
let html = readFileSync(indexPath, 'utf-8');
|
let html = readFileSync(indexPath, 'utf-8');
|
||||||
if (analyticsSnippet) {
|
if (analyticsSnippet) {
|
||||||
html = html.replace('</head>', analyticsSnippet + '</head>');
|
html = html.replace('</head>', analyticsSnippet + '</head>');
|
||||||
}
|
}
|
||||||
cachedIndexHtml = html;
|
if (!noCache) cachedIndexHtml = html;
|
||||||
return cachedIndexHtml;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MIME = {
|
const MIME = {
|
||||||
|
|||||||
Reference in New Issue
Block a user