RunJS is a browser-based JavaScript playground built with React and Vite. Write code in Monaco, run it in a sandboxed preview runtime, and share snippets through the URL.
- Monaco editor with a custom Dracula-based theme and ligatures.
- Live execution in an isolated iframe runtime (
sandbox="allow-scripts"). - Captured output for
console.log,console.warn,console.error, andconsole.info. - URL-synced code state (debounced), so snippets can be shared as a link.
- One-click "Copy link" action in the header.
- Session history stored in
localStorage, with rename, reopen, and delete actions. - Resizable editor/preview layout (horizontal on desktop, vertical on mobile).
- Web app manifest and platform icons for installable/mobile-friendly behavior.
- React 19
- Vite 8
- TypeScript
- Tailwind CSS 4
- Monaco Editor
- Zustand
- ESLint
- Node.js (current LTS recommended)
- npm
npm installnpm run devOpen the URL shown in the terminal (typically http://localhost:5173).
npm run dev- start Vite dev servernpm run build- create production buildnpm run preview- preview production build locallynpm run lint- run ESLint checksnpm run lint:fix- run ESLint and auto-fix issues
- The editor content is stored in a Zustand store.
- Changes are debounced and encoded into the
codeURL parameter. - The preview builds an HTML runtime and injects your code into a
try/catchblock. - Console calls are intercepted in the iframe and sent to the app via
postMessage. - Output is rendered in the preview panel with type-aware styling.
- Starting a new session stores the previous snippet in session history (deduped by payload) for later retrieval.
Inside the preview runtime, a few global helper functions are available in addition to console.*:
log(...args)- same behavior asconsole.log(...), output appears in the preview panel.warn(...args)- same behavior asconsole.warn(...).error(...args)- same behavior asconsole.error(...).info(...args)- same behavior asconsole.info(...).expect(value)- creates async assertions withtoBe(...)(strict equality) andtoEqual(...)(deep equality).
perf calls your function and prints:
=>[perf] Function name: <label>duration: <ms>msmemory usage: <delta> MB(orunavailable in this browser/runtime)
perf returns the wrapped function result. If the function is async/Promise-like, perf returns that Promise and reports metrics in a finally block.
Sync example:
perf(
() => {
for (let i = 0; i < 1_000_000; i++) {
// do something
}
},
{ label: 'loop' },
);Async example:
await perf(
async () => {
await new Promise((resolve) => setTimeout(resolve, 250));
},
{ label: 'fetch simulation' },
);Supported options:
label(string) - custom function name shown by the helper. Defaults tofn.nameoranonymous.
expect can receive either a direct value or a callback. If a callback is provided, it is awaited before each assertion.
Available matchers:
expect(value).toBe(expected)- checks strict equality (===) and returns a Promise.expect(value).toEqual(expected)- checks deep structural equality and returns a Promise.expect(value).stringMatching(expected)- checks that a string containsexpected(whenexpectedis a string) or matches it (whenexpectedis a RegExp).expect(value).objectContaining(expectedObject)- checks that all expected keys exist in the received object and their values are deep-equal.expect(value).arrayContaining(expectedArray)- checks that each expected item exists in the received array using deep equality.
Examples:
expect(2 + 2).toBe(4);
expect({ id: 1, tags: ['a'] }).toEqual({ id: 1, tags: ['a'] });
expect(() => Promise.resolve({ id: 1 })).toEqual({ id: 1 });
expect('Hello World').stringMatching('World');
expect('Version v1.2.3').stringMatching(/v\d+\.\d+\.\d+/);
expect({ id: 1, user: { name: 'Max' } }).objectContaining({ user: { name: 'Max' } });
expect([{ id: 1 }, { id: 2 }]).arrayContaining([{ id: 2 }]);- Create a branch for your change.
- Run
npm run lintbefore opening a PR. - Keep changes focused and include a clear PR description.