January 17, 2022: DataStation 0.6.0 is released! Read more
DataStation The Open-Source Data IDE
Blog

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

Published on by

DataStation is an open-source data IDE for developers. It allows you to easily build graphs and tables with data pulled from SQL databases, logging databases, metrics databases, HTTP servers, and all kinds of text and binary files. Need to join or munge data? Write embedded scripts as needed in Python, JavaScript, Ruby, R, or Julia. All in one application.

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.