Mdbook REPL

This is a mdbook real time playground for some programming languages which you can directly execute them in the browser without any server. It's fast and easy to use.

This is mostly inspired by mdbook rust playground, but it's only limited to rust and it's using https://play.rust-lang.org as its backend compiler server. So I want to make a playground for other languages based on webassembly.

Below is an example of a python code block that can be executed directly in the browser:

# Python codeblock

print("Hello, world!")

All the code is editable and runnable. You can change the code and run it again. The execution is really fast.

Attention 💥

This playground is still in development and not many languages are supported yet. If you have any ideas or suggestions, please let me know.

Usage

This preprocessor is designed to be used with mdbook. If you want to use this repl in your own web project, you can have a look at For Developers section.

Installation

There two ways to install this preprocessor.

You can install it with cargo if you have rust installed:

cargo install mdbook-repl

Or you can download the pre built binary from github release page. You should put the binary in your machine's PATH after installation.

You can check your installation by running:

mdbook-repl --version

Configuration

After installation, you need to add some configurations to your book.toml file. Below is an example of the full configuration options for this preprocessor:

[preprocessor.repl]
# iframe url, default is https://mr-addict.github.io/mdbook-repl/embed/
src = "https://mr-addict.github.io/mdbook-repl/embed/"

# python is disabled by default and loading is lazy
python.enable = true
python.loading = "lazy"

# typescript is disabled by default and loading is lazy
typescript.enable = true
typescript.loading = "lazy"

# javascript is disabled by default and loading is lazy
javascript.enable = true
javascript.loading = "lazy"
  • src: The url of the repl iframe, the default value is https://mr-addict.github.io/mdbook-repl/embed/. You can also deploy your own repl server for better performance, see For Developers section.
  • language.enable: Enable the language for the repl, default value is false.
  • language.loading: The loading of the language, can be eager or lazy, default value is lazy.

For example if you only care about python codeblock, you can only enable python and disable the others:

[preprocessor.repl]
python.enable = true

Options

You can also specific some options for the each codeblock.

readonly

readonly option will make the codeblock not editable. You can use this option if you want to show some code examples that should not be changed.

```javascript,readonly
// javascript codeblock

console.log("Hello, world!");
```

And the codeblock will not be eidtable:

// javascript codeblock

console.log("Hello, world!");

norepl

norepl option will make the codeblock not rendered by the preprocessor. You can use this option if you want to show some code examples that should not be executed.

```javascript,norepl
// javascript codeblock

console.log("Hello, world!");
```

And it will not be rendered by this preprocessor:

// javascript codeblock

console.log("Hello, world!");

Shortcuts

You can use CTRL + R to run the codeblock. This is useful when you want to run the codeblock without clicking the run button.

Language Extensions

This preprocessor only recongnizes specific extensions for sepecific language. For example, you can only use python or py codeblock for python code.

Here is the full list of extensions:

LanguageExtensions
Pythonpython, py
TypeScripttypescript, ts
JavaScriptjavascript, js

Performance

There is no doubt that the execution of the code is really fast compared with backend server playgrounds. However, the bottleneck is the loading time of the codeblock. The loading time is depending on language.

The smallest one is javascrpt, because javascipt is natively supported by the browser. However others languages like python and typescript need some complied webassembly runtime to be loaded first.

So the loading option is lazy and all lanugages are disabled by default. Also, most of the examples in this docs are using javascript by default to make the loading time faster.

Below is the relative size of the extra needed runtime for each language:

LanguageSize
Python5.5MB
TypeScript718kb
JavaScript0

Limitations

There are some limitations when using this playground, because of the nature of the technologies used in this playground. So, don't expect to use this playground as a full-featured IDE.

The best usage of this playground is to test and learn programming languages for educational purposes, not for production.

If you find any other issues, don't hesitate to report it to the GitHub repository.

User input

For example, you can not use the input function to get user input in python. Though it is possible to play arround to make user input work, but it's not worth it. The following code won't work:

# This will not work

input('Enter your name: ')

Interrupt

And now there is also no easy way for interrupt your code, so don't write bad code!!

For Developers

Actually, you can use repl in your own web project other than mdbook. What mdbook-repl does is to preprocess your markdown code blocks and replace them with some js and css.

The core of mdbook-repl is the iframe. The iframe is used to display the output of the code. The js and css are used to communicate with the iframe.

The iframe url is https://mr-addict.github.io/mdbook-repl/embed/. You can also deploy the iframe in your own server. You can find the source code of the iframe in the github repository.

API

When the iframe is loaded, it will send a message to the parent window. The message is a json string. The json object has the following properties:

{
  "repl": {
    "id": "",
    "dimensions": {
      "width": 800,
      "height": 600
    },
    "editor": {
      "readonly": false,
      "theme": "light",
      "language": "python",
      "code": "# This is a default python code\n\nprint('Hello world')",
      "defaultCode": "# This is a default python code\n\nprint('Hello world')"
    },
    "output": {
      "data": [],
      "status": "loading"
    }
  }
}

What you should do fist is to send some basic information to the iframe including id and editor data. The id is used to identify the editor if you have more that one iframes in you page. The id is empty at first. When new information updated, id will be sent with it. The editor information is used to initialize the editor.

The dimensions is used to set the width and height of the iframe. The output data is used to display the output of the code. The status can be idle, loading, running or finished. The data is an array of objects. Each object has a color and msg. The color is used to set the color of the message which can be normal or red. The msg is used to display the message.

Example

Here is an example of how to use the mdbook-repl in your own project:

<style>
  iframe {
    border: none;
    width: 100%;
  }
</style>
<iframe src="http://localhost:4173/" width="100%" allow="clipboard-write"></iframe>
<script>
  const id = "ac2f5a2";
  const lang = "python";
  const theme = "light";
  const readonly = false;
  const code = "# Python\n\nprint('Hello world')";

  const iframe = document.querySelector("iframe");

  const postmessage = (msg) => iframe.contentWindow.postMessage({ repl: msg }, "*");

  window.addEventListener("message", (event) => {
    const replData = event.data.repl;
    if (event.source === window || !replData) return;

    // if the id is empty, then it's the first time the iframe is loaded
    if (replData.id === "") {
      postmessage({ id, editor: { theme, lang, code, readonly, defaultCode: code } });
      return;
    }

    if (replData.id !== id) return;

    // update the iframe height
    iframe.style.height = replData.dimensions.height + "px";
  });
</script>

💥 Attention

You need to add allow="clipboard-write" to the iframe to make the clipboard work.

Python

Python is a programming language that lets you work quickly and integrate systems more effectively . It's easy to learn and use.

This playground uses pyodide to run python code in the browser. Currently, it's using pyodide 0.25.0 which is using python 3.11.3. You can check the version by running the following code:

import sys

print(sys.version)

Examples

Here are some basic examples of using Python that you can do in this playground.

Basic Example

message = "Hello, world!"

print(message)

Example with Function

def greet(name):
    return "Hello, " + name

print(greet("world"))

Example with Class

class Greeter:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return "Hello, " + self.name

g = Greeter("world")
print(g.greet())

Example with List Comprehension

squares = [x * x for x in range(10)]

print(squares)

Example with async/await:

import asyncio

async def delay(ms):
    await asyncio.sleep(ms / 1000)

async def main():
    print("Start")
    await delay(1000)
    print("End")

await main()

Packages

There are many built-in packages available in pyodide. You should have a look at the list of packages before you use them.

For example, you can use regex pakcage to match a pattern in a string:

import regex

pattern = r'(\d{4})-(\d{2})-(\d{2})'

match = regex.match(pattern, '2022-12-31')

print(match.groups())

It may take some time when you first import a package, but after that, it should be faster.

And here is another example of using numpy package to calculate the mean of a list of numbers:

import numpy as np

numbers = [1, 2, 3, 4, 5]

mean = np.mean(numbers)

print(mean)

Attention 💥

Some of the packages may not work as expected due to the limitations of browser environment.

Typescript

Typescript is a superset of JavaScript that adds static typing to the language. Though you can run javascript code in the browser, but you need to compile typescript to javascript first. Then you can run the compiled javascript code in the browser.

This playground uses babel/standalone in service worker to compile typescript code to javascript and then execute compiled javascript code to get the result.

let message: string = "Hello, world!";

console.log(message);

Examples

Here are some basic examples of using TypeScript that you can do in this playground.

Basic Example

let message: string = "Hello, world!";

console.log(message);

Example with Function

function greet(name: string) {
  return "Hello, " + name;
}

console.log(greet("world"));

Example with Interface

interface Person {
  first: string;
  last: string;
}

function greeter(person: Person) {
  return "Hello, " + person.first + " " + person.last;
}

console.log(greeter({ first: "Jane", last: "Doe" }));

async/await:

async function delay(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function main() {
  console.log("Start");
  await delay(1000);
  console.log("End");
}

main();

Javascript

Javascript is a programming language that is used to make web pages interactive. Browser is able to run javascript code directly. But the repl code still exected in service worker to prevent the page from freezing.

let message = "Hello, world!";

console.log(message);

Examples

Here are some basic examples of using JavaScript that you can do in this playground.

Basic Example

let message = "Hello, world!";

console.log(message);

Example with Function

function greet(name) {
  return "Hello, " + name;
}

console.log(greet("world"));

Example with Class

class Greeter {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return "Hello, " + this.name;
  }
}

let g = new Greeter("world");
console.log(g.greet());

Example with Array

let squares = Array.from({ length: 10 }, (_, i) => i * i);

console.log(squares);

Example with async/await:

async function delay(ms) {
  await new Promise((resolve) => setTimeout(resolve, ms));
}

async function main() {
  console.log("Start");
  await delay(1000);
  console.log("End");
}

main();