DataStation Documentation Blog Community Subscribe
Blog

Languages you can run in the browser, part 1: Python, JavaScript, SQLite

Published on by

People often write about languages that compile to JavaScript. But what if you want to run languages in the browser? Without any API to proxy code and I/O to a standard language implementation on a server? Languages that compile to JavaScript cannot be run in the browser unless the compiler is written in JavaScript.

Recently I was trying to find such languages implemented in JavaScript as possible scripting options for the in-browser DataStation application. In this series I'll walk through a few useful and interesting languages running entirely within the browser, based on learnings building DataStation.

Python

Brython is a Python implementation written in JavaScript. To run a Python script in the browser using Brython, just add the following JavaScript files:

<script src="https://cdn.jsdelivr.net/npm/brython@3.9/brython.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/brython@3.9/brython_stdlib.js"></script>

The Brython docs assume you are going to script your app in Python so they only document how to use Brython as the type of a script tag. But if you're trying to run Python code given by the user you can call window.__BRYTHON__.python_to_js(program) and then eval the result. Here's some React pseudo-code:

function PythonEditor() {
  const [code, setCode] = React.useState('');
  const [pythonResult, setPythonResult] = React.useState('');
  React.useEffect(() => {
    const codeAsJS = window.__BRYTHON__.python_to_js(code);
    try {
      setPythonResult(eval(codeAsJS);
    } catch (e) {
      setPythonResult(e.stack);
    }
  }, [code]);

  return (
    <div>
      <textarea value={code} onChange={(e) => setCode(e.target.value)} />
      <div>Result: {pythonResult}</div>
    </div>
  );
}

JavaScript

Eval not your speed? Fair enough. You could try sandboxing the interpreter in an iframe. Or you could use JS-Interpreter, a JavaScript interpreter written in JavaScript.

SQLite

SQL.js is SQLite compiled to webassembly using emscripten. DataStation uses it for its in-memory SQL engine. It was tricky to get configured correctly! You need to self-host both the sql-wasm.js file they mention and also the sql-wasm.wasm. I couldn't get it working at all trying to use a CDN.

So first install this package through yarn/npm: yarn add sql.js. Then copy sql-wasm.js and sql-wasm.wasm into wherever you will host it:

cp node_modules/sql.js/dist/sql-wasm.js build
cp node_modules/sql.js/dist/sql-wasm.wasm build

Now you can follow the rest of the SQL.js tutorials pretty easily. Here's another pseudo-code React component for running SQL in-memory:

function SQLEditor() {
  const [code, setCode] = React.useState('SELECT 1');
  const [sqlResult, setSqlResult] = React.useState('');
  let sql = React.useRef(null);

  // Load the wasm file once.
  React.useEffect(() => {
    async function loadWasm() {
      sql.current = await window.initSqlJs({
        locateFile: (file) => file, // Where to find the sql-wasm.wasm file. This implementation assumes it's as /sql-wasm.wasm
      });
    };

    loadWasm();
  });
    
  React.useEffect(() => {
    if (!sql.current) { return; }

    try {
      const db = new sql.Database();
      const res = db.exec(code)[0];
      // Set the result to be an array of objects
      setSqlResult(res.values.map((row: Array) => {
        const formattedRow: { [k: string]: any } = {};
        res.columns.forEach((c: string, i: number) => {
          formattedRow[c] = row[i];
        });
        return formattedRow;
      }));
    } catch (e) {
      setSqlResult(e.stack);
    }
  }, [code, sql.current]);

  return (
    <div>
      <textarea value={code} onChange={(e) => setCode(e.target.value)} />
      <div>Result: {sqlResult}</div>
    </div>
  );
}

Recapping

In this post we looked at a few ways to run dynamic JavaScript, Python, and SQLite code in the browser without any server-side component. There are definitely security considerations to make before allowing dynamic code execution. In particular, a user could have access to read cookies, localstorage, and potentially make requests from a domain that you own. In some situations this is ok, for example in the in-browser DataStation app that has no special CORS access and no special cookies or localstorage to read. If it's also the case for your application, you should know about the languages you have available to you!

In the next post in this series we'll take a look at JavaScript implementations of Lua, PHP, and Basic.

Questions? Feedback? Feel free to reach the author at phil@multiprocess.io.

Or to stay in the loop about future posts!