fix module loading in browser commands production

This commit is contained in:
Chris Wanstrath 2025-10-08 12:40:59 -07:00
parent 606bcc9ad9
commit cba61cb061
4 changed files with 289 additions and 11 deletions

View File

@ -1,7 +1,33 @@
//// ////
// version: 49fd142 // version: 606bcc9
var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: (newValue) => all[name] = () => newValue
});
};
// src/js/completion.ts
var exports_completion = {};
__export(exports_completion, {
initCompletion: () => initCompletion
});
// src/js/dom.ts // src/js/dom.ts
var exports_dom = {};
__export(exports_dom, {
scrollback: () => scrollback,
content: () => content2,
cmdLine: () => cmdLine,
cmdInput: () => cmdInput,
$$: () => $$,
$: () => $
});
var content2 = $("content"); var content2 = $("content");
var cmdLine = $("command-line"); var cmdLine = $("command-line");
var cmdInput = $("command-textbox"); var cmdInput = $("command-textbox");
@ -24,6 +50,35 @@ var $$ = (tag, innerHTML = "") => {
return el; return el;
}; };
// src/js/commands.ts
var exports_commands = {};
__export(exports_commands, {
mode: () => mode,
commands: () => commands,
cacheCommands: () => cacheCommands
});
// src/js/shell.ts
var exports_shell = {};
__export(exports_shell, {
runCommand: () => runCommand
});
// src/js/scrollback.ts
var exports_scrollback = {};
__export(exports_scrollback, {
setStatus: () => setStatus,
replaceOutput: () => replaceOutput,
latestId: () => latestId,
insert: () => insert,
initScrollback: () => initScrollback,
handleOutput: () => handleOutput,
appendOutput: () => appendOutput,
addOutput: () => addOutput,
addInput: () => addInput,
addErrorMessage: () => addErrorMessage
});
// src/shared/utils.ts // src/shared/utils.ts
function randomId() { function randomId() {
return Math.random().toString(36).slice(7); return Math.random().toString(36).slice(7);
@ -160,7 +215,32 @@ function handleOutput(msg) {
addOutput(id, result.output); addOutput(id, result.output);
} }
// src/js/websocket.ts
var exports_websocket = {};
__export(exports_websocket, {
startConnection: () => startConnection,
send: () => send,
close: () => close
});
// src/js/session.ts
var exports_session = {};
__export(exports_session, {
sessionStore: () => sessionStore,
sessionId: () => sessionId,
initSession: () => initSession,
handleSessionUpdate: () => handleSessionUpdate,
handleSessionStart: () => handleSessionStart
});
// src/js/event.ts // src/js/event.ts
var exports_event = {};
__export(exports_event, {
once: () => once,
on: () => on,
off: () => off,
fire: () => fire
});
function fire(eventName, detail) { function fire(eventName, detail) {
window.dispatchEvent(new CustomEvent(eventName, { detail })); window.dispatchEvent(new CustomEvent(eventName, { detail }));
} }
@ -169,6 +249,14 @@ function on(eventName, handler) {
window.addEventListener(eventName, listener); window.addEventListener(eventName, listener);
return () => window.removeEventListener(eventName, listener); return () => window.removeEventListener(eventName, listener);
} }
function once(eventName, handler) {
const listener = handler;
window.addEventListener(eventName, listener, { once: true });
return () => window.removeEventListener(eventName, listener);
}
function off(eventName, handler) {
window.removeEventListener(eventName, handler);
}
// src/js/session.ts // src/js/session.ts
var sessionId = randomId(); var sessionId = randomId();
@ -186,7 +274,19 @@ function handleSessionUpdate(msg) {
fire("session:update", msg.data); fire("session:update", msg.data);
} }
// src/js/dispatch.ts
var exports_dispatch = {};
__export(exports_dispatch, {
dispatchMessage: () => dispatchMessage
});
// src/js/webapp.ts // src/js/webapp.ts
var exports_webapp = {};
__export(exports_webapp, {
currentAppUrl: () => currentAppUrl,
cacheApps: () => cacheApps,
apps: () => apps
});
var apps = []; var apps = [];
function cacheApps(a) { function cacheApps(a) {
apps.length = 0; apps.length = 0;
@ -194,8 +294,23 @@ function cacheApps(a) {
apps.sort(); apps.sort();
window.dispatchEvent(new CustomEvent("apps:change")); window.dispatchEvent(new CustomEvent("apps:change"));
} }
function currentAppUrl() {
const project = sessionStore.get("project") || "root";
if (!apps.includes(project))
return;
const hostname = sessionStore.get("hostname") || "localhost";
const s = hostname.startsWith("localhost") ? "" : "s";
return `http${s}://${project}.${hostname}`;
}
// src/js/stream.ts // src/js/stream.ts
var exports_stream = {};
__export(exports_stream, {
handleStreamStart: () => handleStreamStart,
handleStreamReplace: () => handleStreamReplace,
handleStreamEnd: () => handleStreamEnd,
handleStreamAppend: () => handleStreamAppend
});
function handleStreamStart(msg) { function handleStreamStart(msg) {
const id = msg.id; const id = msg.id;
const status = document.querySelector(`[data-id="${id}"].input .status`); const status = document.querySelector(`[data-id="${id}"].input .status`);
@ -213,6 +328,12 @@ function handleStreamReplace(msg) {
} }
function handleStreamEnd(_msg) {} function handleStreamEnd(_msg) {}
// src/js/game.ts
var exports_game = {};
__export(exports_game, {
handleGameStart: () => handleGameStart
});
// src/shared/game.ts // src/shared/game.ts
class GameContext { class GameContext {
ctx; ctx;
@ -387,6 +508,12 @@ class GameContext {
} }
// src/js/focus.ts // src/js/focus.ts
var exports_focus = {};
__export(exports_focus, {
initFocus: () => initFocus,
focusInput: () => focusInput,
focusHandler: () => focusHandler
});
function initFocus() { function initFocus() {
window.addEventListener("click", focusHandler); window.addEventListener("click", focusHandler);
focusInput(); focusInput();
@ -609,6 +736,9 @@ async function receive(e) {
console.log("<- receive", data); console.log("<- receive", data);
await dispatchMessage(data); await dispatchMessage(data);
} }
function close() {
ws?.close(1000, "bye");
}
function retryConnection() { function retryConnection() {
connected = false; connected = false;
if (retries >= MAX_RETRIES) { if (retries >= MAX_RETRIES) {
@ -623,6 +753,12 @@ function retryConnection() {
} }
// src/js/history.ts // src/js/history.ts
var exports_history = {};
__export(exports_history, {
resetHistory: () => resetHistory,
initHistory: () => initHistory,
addToHistory: () => addToHistory
});
var history = ["one", "two", "three"]; var history = ["one", "two", "three"];
var idx = -1; var idx = -1;
var savedInput = ""; var savedInput = "";
@ -724,6 +860,10 @@ function handleCompletion(e) {
} }
// src/js/cursor.ts // src/js/cursor.ts
var exports_cursor = {};
__export(exports_cursor, {
initCursor: () => initCursor
});
var cursor = "Û"; var cursor = "Û";
var cmdCursor; var cmdCursor;
var enabled = true; var enabled = true;
@ -761,6 +901,10 @@ function buildBlankCursorLine() {
} }
// src/js/drop.ts // src/js/drop.ts
var exports_drop = {};
__export(exports_drop, {
initDrop: () => initDrop
});
function initDrop() { function initDrop() {
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
document.body.addEventListener(eventName, preventDefaults, false); document.body.addEventListener(eventName, preventDefaults, false);
@ -783,6 +927,10 @@ function handleDrop(e) {
} }
// src/js/editor.ts // src/js/editor.ts
var exports_editor = {};
__export(exports_editor, {
initEditor: () => initEditor
});
var INDENT_SIZE = 2; var INDENT_SIZE = 2;
function initEditor() { function initEditor() {
document.addEventListener("input", handleAdjustHeight); document.addEventListener("input", handleAdjustHeight);
@ -961,6 +1109,11 @@ function insertMoreIndentedNewline(editor) {
} }
// src/js/form.ts // src/js/form.ts
var exports_form = {};
__export(exports_form, {
submitHandler: () => submitHandler,
initForm: () => initForm
});
function initForm() { function initForm() {
document.addEventListener("submit", submitHandler); document.addEventListener("submit", submitHandler);
} }
@ -997,6 +1150,10 @@ var submitHandler = async (e) => {
}; };
// src/js/gamepad.ts // src/js/gamepad.ts
var exports_gamepad = {};
__export(exports_gamepad, {
initGamepad: () => initGamepad
});
var BUTTONS_TO_KEYS = { var BUTTONS_TO_KEYS = {
0: "z", 0: "z",
1: "x", 1: "x",
@ -1045,7 +1202,18 @@ function handleGamepad() {
requestAnimationFrame(handleGamepad); requestAnimationFrame(handleGamepad);
} }
// src/js/hyperlink.ts
var exports_hyperlink = {};
__export(exports_hyperlink, {
initHyperlink: () => initHyperlink
});
// src/js/browser.ts // src/js/browser.ts
var exports_browser = {};
__export(exports_browser, {
openBrowser: () => openBrowser,
isBrowsing: () => isBrowsing
});
var HEIGHT2 = 540; var HEIGHT2 = 540;
var WIDTH2 = 960; var WIDTH2 = 960;
var controls = $("browser-controls"); var controls = $("browser-controls");
@ -1219,6 +1387,10 @@ async function handleClick2(e) {
} }
// src/js/input.ts // src/js/input.ts
var exports_input = {};
__export(exports_input, {
initInput: () => initInput
});
function initInput() { function initInput() {
cmdInput.addEventListener("keydown", inputHandler); cmdInput.addEventListener("keydown", inputHandler);
cmdInput.addEventListener("paste", pasteHandler); cmdInput.addEventListener("paste", pasteHandler);
@ -1270,6 +1442,11 @@ function clearInput() {
} }
// src/js/resize.ts // src/js/resize.ts
var exports_resize = {};
__export(exports_resize, {
resize: () => resize,
initResize: () => initResize
});
var content3 = document.getElementById("content"); var content3 = document.getElementById("content");
function initResize() { function initResize() {
window.addEventListener("resize", resize); window.addEventListener("resize", resize);
@ -1294,11 +1471,21 @@ function resizeCinema() {
} }
// src/js/statusbar.ts // src/js/statusbar.ts
var exports_statusbar = {};
__export(exports_statusbar, {
status: () => status,
projectWww: () => projectWww,
projectName: () => projectName,
projectCwd: () => projectCwd,
initStatusbar: () => initStatusbar
});
var projectName = $("project-name"); var projectName = $("project-name");
var projectCwd = $("project-cwd"); var projectCwd = $("project-cwd");
var projectWww = $("project-www"); var projectWww = $("project-www");
var statusbar = $("statusbar"); var statusbar = $("statusbar");
var statusbarMsg = $("statusbar-msg"); var statusbarMsg = $("statusbar-msg");
var STATUS_MSG_LENGTH = 3000;
var timer;
function initStatusbar() { function initStatusbar() {
registerEvents(); registerEvents();
} }
@ -1313,6 +1500,19 @@ function registerEvents() {
updateCwd(data.cwd); updateCwd(data.cwd);
}); });
} }
function status(msg) {
showStatusMsg();
statusbarMsg.textContent = msg;
if (timer)
clearTimeout(timer);
timer = setTimeout(hideStatusMsg, STATUS_MSG_LENGTH);
}
function showStatusMsg() {
statusbar.classList.add("showing-msg");
}
function hideStatusMsg() {
statusbar.className = "";
}
function updateProjectName(project) { function updateProjectName(project) {
projectName.textContent = project; projectName.textContent = project;
updateWww(project); updateWww(project);
@ -1339,9 +1539,13 @@ function displayProjectPath(path) {
} }
// src/js/vram.ts // src/js/vram.ts
var exports_vram = {};
__export(exports_vram, {
startVramCounter: () => startVramCounter
});
var vramCounter = $("vram-size"); var vramCounter = $("vram-size");
var startVramCounter = () => { var startVramCounter = () => {
const timer = setInterval(() => { const timer2 = setInterval(() => {
const count = parseInt(vramCounter.textContent) + 1; const count = parseInt(vramCounter.textContent) + 1;
let val = count + "KB"; let val = count + "KB";
if (count < 10) if (count < 10)
@ -1349,12 +1553,12 @@ var startVramCounter = () => {
vramCounter.textContent = val; vramCounter.textContent = val;
if (count >= 64) { if (count >= 64) {
vramCounter.textContent += " OK"; vramCounter.textContent += " OK";
clearInterval(timer); clearInterval(timer2);
} }
}, 15); }, 15);
}; };
// src/js/main.ts // src/js/main.tmp.ts
initCompletion(); initCompletion();
initCursor(); initCursor();
initDrop(); initDrop();
@ -1371,3 +1575,32 @@ initSession();
initStatusbar(); initStatusbar();
startConnection(); startConnection();
startVramCounter(); startVramCounter();
Object.assign(window, {
__pluto_modules: {
browser: exports_browser,
commands: exports_commands,
completion: exports_completion,
cursor: exports_cursor,
dispatch: exports_dispatch,
dom: exports_dom,
drop: exports_drop,
editor: exports_editor,
event: exports_event,
focus: exports_focus,
form: exports_form,
game: exports_game,
gamepad: exports_gamepad,
history: exports_history,
hyperlink: exports_hyperlink,
input: exports_input,
resize: exports_resize,
scrollback: exports_scrollback,
session: exports_session,
shell: exports_shell,
statusbar: exports_statusbar,
stream: exports_stream,
vram: exports_vram,
webapp: exports_webapp,
websocket: exports_websocket
}
});

View File

@ -2,11 +2,47 @@
SHA=$(git rev-parse --short HEAD) SHA=$(git rev-parse --short HEAD)
# Build to a temporary file # Discover all modules in src/js (excluding main.ts and temp files)
bun build ./src/js/main.js \ MODULES=$(ls src/js/*.ts | sed 's|src/js/||' | sed 's|.ts$||' | grep -v -E '^(main|.*\.tmp)$' | sort)
# Create temporary exports file
echo "// Auto-generated exports for dynamically loaded browser commands" > ./src/js/exports.tmp.ts
# Generate import statements
for mod in $MODULES; do
echo "import * as $mod from \"./$mod\"" >> ./src/js/exports.tmp.ts
done
echo "" >> ./src/js/exports.tmp.ts
echo "Object.assign(window, {" >> ./src/js/exports.tmp.ts
echo " __pluto_modules: {" >> ./src/js/exports.tmp.ts
# Generate module list with proper formatting
FIRST=true
for mod in $MODULES; do
if [ "$FIRST" = true ]; then
printf " %s" "$mod" >> ./src/js/exports.tmp.ts
FIRST=false
else
printf ", %s" "$mod" >> ./src/js/exports.tmp.ts
fi
done
printf ",\n" >> ./src/js/exports.tmp.ts
echo " }" >> ./src/js/exports.tmp.ts
echo "})" >> ./src/js/exports.tmp.ts
# Create temporary main.ts that includes exports
cat ./src/js/main.ts ./src/js/exports.tmp.ts > ./src/js/main.tmp.ts
# Build from temporary main
bun build ./src/js/main.tmp.ts \
--outfile ./public/bundle.tmp.js \ --outfile ./public/bundle.tmp.js \
--target browser --target browser
# Clean up temporary files
rm ./src/js/exports.tmp.ts ./src/js/main.tmp.ts
# If bundle.js doesn't exist yet, fake it it # If bundle.js doesn't exist yet, fake it it
if [ ! -f ./public/bundle.js ]; then if [ ! -f ./public/bundle.js ]; then
touch ./public/bundle.js touch ./public/bundle.js

12
src/build.ts Normal file
View File

@ -0,0 +1,12 @@
////
// build process helpers
export function rewriteJsImports(code: string): string {
if (process.env.NODE_ENV === "production") {
return code
.replace(/import\s+\*\s+as\s+(\w+)\s+from\s+["']@\/js\/\w+["']/g, 'const $1 = window.__pluto_modules.$1')
.replace(/import\s+{([^}]+)}\s+from\s+["']@\/js\/(\w+)["']/g, 'const {$1} = window.__pluto_modules.$2')
} else {
return code.replace(/from (["'])@\/js\//g, "from $1../js/")
}
}

View File

@ -7,6 +7,7 @@ import { prettyJSON } from "hono/pretty-json"
import color from "kleur" import color from "kleur"
import type { Message } from "./shared/types" import type { Message } from "./shared/types"
import { rewriteJsImports } from "./build"
import { NOSE_ICON, NOSE_BIN, NOSE_DATA, NOSE_DIR, NOSE_ROOT_BIN, DEFAULT_PROJECT } from "./config" import { NOSE_ICON, NOSE_BIN, NOSE_DATA, NOSE_DIR, NOSE_ROOT_BIN, DEFAULT_PROJECT } from "./config"
import { transpile, isFile, tilde, isDir } from "./utils" import { transpile, isFile, tilde, isDir } from "./utils"
import { serveApp, apps, initWebapps } from "./webapp" import { serveApp, apps, initWebapps } from "./webapp"
@ -134,11 +135,7 @@ app.get("/source/:name", async c => {
const path = await sessionRun(sessionId, () => commandPath(name)) const path = await sessionRun(sessionId, () => commandPath(name))
if (!path) return c.text("Command not found", 404) if (!path) return c.text("Command not found", 404)
let code = await transpile(path) return new Response(rewriteJsImports(await transpile(path)), {
// rewrite imports so they work on the server and in the browser
code = code.replace(/from (["'])@\/js\//g, "from $1../js/")
return new Response(code, {
headers: { headers: {
"Content-Type": "text/javascript" "Content-Type": "text/javascript"
} }