circuit

How To Embed VSCode Into A Browser With React


https://github.com/Open-EdTech/react-run-code

Why I wanted a runnable VSCode clone

To explain why TypeScript is important, you need to explain why developer tooling is important. To explain why developer tooling is important you will need a code editing environment. Fortunately the editor for VSCode (monaco editor) is open source, and feels just like VSCode. By embedding the monaco editor in my web page I can explain TypeScript much better than any other website.

Plus, the monaco editor can transpile TypeScript to JavaScript, we can run the JavaScript in the browser and output the results for an interactive educational experience.

Embedding Monaco Editor

Monaco editor is not a react component, you need to bring it into react. Thankfully someone has written a library that does this in a graceful way (and avoids some webpack headaches of other available libraries).

Running Code

By default, the monaco editor does not run code. It does syntax highlighting, auto-completion, red squiggly lines, hover information, etc. So how are we running code?

Basically we do this:

Running TypeScript

I'm pretty sure can't just run TypeScript code like that, we need to run it as JavaScript. The monaco editor has a TypeScript compiler that it uses to check your TypeScript code. You can use that to emit the output from a single model, a model is basically just a file in VSCode.

Then you take the emitted JavaScript and run it.

Building a Console

The console-feed react component was such a time saver (and it looks awesome!)

This component reads from the console's messages and outputs the formatting shown in the gif. We modified this component to clear the logs on each run to prevent logs from piling up endlessly.

Supporting Multiple Consoles

We want multiple editors per page, by default their consoles would all print the same message because we are just reading logs from the console. How do we isolate console messages by the editor that sent the message?

We make each editor output their unique editor ID as the last argument in an overridden console.log to distinguish between message sources.

We only log the message to a console component if the last argument provided matched the ID of the console component.

Grading User Input

I had an idea that it might be useful to “grade” user code in a way that doesn't involve any calls to any servers. The issue is that you will have a tough time calling an API from code that is running inside of Function(code)() . Also, since the grading process happens entirely on the client side there is no way to make the grading “unhackable”.

So we do something simple. If the message from the console is “Problem solved”, the problem is solved. By solved I mean that the owner of the website can use a callback function that executes custom logic when this message is logged.

console.log(“Problem solved”) executes user defined function.

Supporting Multiple Files

Tabs

Tabs do not come with monaco editor. I styled the tabs and implemented tab creation and deletion.

I also used React-Dnd to rearrange tabs the same way you would in VSCode.

Import and Export

I took a very hacky approach to module bundling (there are known bugs). I generate a dependency graph using regular expressions.

Then perform a topological sort to stack the files on top of each other. I get to use some really nice (and well tested!) code from this LeetCode problem's discussion section.

The models are shared across the page so I can import from different editors on the same page.

The hacky way of doing things doesn't always work. If you open up a file named “0.ts” it will show you the code that got generated so you can diagnose the problem. Here we got screwed up by a duplicate declaration.

Customizing Files

I included some different options for the files, you can customize to determine which tab is initially selected, if the file should be read only, if the file should be shown, and more.

Quickly Writing Interactive Content

To create the initial state for the editor you can create an empty editor, make a new file, and copy the configuration for modelsInfo to the clipboard using the green <> .

import React from "react";
import Editor from "react-run-code";

function App() {
  return <Editor id="10" modelsInfo={[]} />;
}

export default App;

You can now go into your source code and paste [{"value":"console.log(\"make a new file\")","filename":"new.ts","language":"typescript"}] in place of [] for the prop modelsInfo={[]}.

Next Steps

I have tested the component a lot manually, but I need to learn Jest and get some of those type of tests up and running! I still am debating if I should try to implement runtime bundling, I saw a good blog post on the subject.

Summary

The distance between just another blog and something that can be really special (an educational platform?) is just the time you put into building internal tools to quickly create content rich interactive components. The component isn't perfect but it has worked well enough for me to start using it.

Try it out on npm.

npm: https://www.npmjs.com/package/react-run-code

GitHub: https://github.com/Open-EdTech/react-run-code




Continue Learning