CodePen’s online code preview system has always been amazing. It’s easy to show code in real time, whether it’s code demonstrations or test functions. Recently, I have a business that needs the ability similar to CodePen. After some research, I developed a demo with the basic ability of running code online.

Online experience address: https://jrainlau.github.io/on…

Because the business only needs to execute JS code, so Demo only has the running ability of JS code.

A, principle

As we know, the browser uses its own engine to process HTML, CSS, and JS resources, which starts when the page loads. If we want to run these resources dynamically, we can use DOM manipulation for HTML and CSS, or eval or new Function() for JS. But all of these operations are complex and unsafe (eval and new Function() are prone to accidents), so is there a way to run them elegantly and safely on the fly? Let’s take a look at what the famous Codepen did.

I simply wrote a button in CodePen, bound with the style and click event, and you can see that the white area is already showing the result we want. When you open the console and self-imbibe, you can see that the whole white area is an iframe, and the HTML content in it is the code we just edited.

It is easy to imagine that it works a bit like document.write, writing content directly to an HTML file and then embedding it in other web pages as an iframe to realize the logic of code preview. So what are the benefits of using an iframe? An iframe can stand on its own as a sandbox environment isolated from the host, and the code running in it will not affect the host in most cases, effectively ensuring security. With HTML5’s new sandbox property, you can give your iframe more detailed permissions, such as whether to allow it to run scripts, whether to allow it to popover, and so on.

Second, the way to achieve

The most important step in achieving a CodePen-like effect is how to inject code into an iframe. Since we need to use the relevant API to manipulate the iFrame, for the sake of browser security, we can only use the iFrame link in the same domain, otherwise we will report a cross-domain error.

First prepare an index. HTML and iframe. HTML and run them using a static resource server, assuming both run at localhost:8080. Then we in the index. The HTML inserted inside an iframe, the link is localhost: 8080 / iframe., HTML code is as follows:

<iframe src="localhost:8080/iframe.html"></iframe>

Next we can use iFrame. ContentDocument to get the contents of the iFrame and manipulate it:

<script> const ifrme = document.querySelector('iframe'); const iframeDoc = iframe.contentDocument; iframeDoc.open(); IframeDoc. Write (' <body> <style>button {color: RGB (0, 0, 0);} red }</style> <button>Click</button> <script> document.querySelector('button').addEventListener(() => { console.log('on-click! ') }) <\/script> </body> `); iframeDoc.close(); // Finally, call 'close()' to turn off the write switch </script>

After the operation, we can on localhost: 8080 / index. See as HTML Codepen the same effect as shown:

All we need to do is find an input field, save the code as a variable, and call IFrameDoc.write () to dynamically write the code to the IFrame and run it in real time.

Console output and security

If you look at the Codepen page, you can see that there is a Console panel that outputs Console information directly from the iframe. How does this work? The answer is simple. We can hijack an API such as Console in an iframe page and send relevant messages to the parent page via postMessage, while retaining the original console output function. After the parent page listens to the message, it will sort out the information and output it to the page to realize the Console panel.

In iframe.html, we write a JS code outside of (because the parent page calling iframedoc.write () will overwrite everything in ) :

function rewriteConsole(type) { const origin = console[type]; console[type] = (... args) => { window.parent.postMessage({ from: 'codeRunner', type, data: args }, '*'); origin.apply(console, args); }; } rewriteConsole('log'); rewriteConsole('info'); rewriteConsole('debug'); rewriteConsole('warn'); rewriteConsole('error'); rewriteConsole('table');

In addition, we will set the sandbox property of the iframe to restrict some of its permissions, but there is a danger that if we execute the window.parent.document API in the iframe, We can ask the iframe to override the contents of the parent page, or even override the sandbox property, which is definitely not safe, so we need to block the related API in the iframe:

Object.defineProperty(window, 'disableParent', {get() {throw new Error(' Cannot call the window.parent property! '); }, set() {}, });

Before calling IFrameDoc.write (code) on the parent page, we need to replace the custom code entered by the user, changing all Parent. Document to Window.DisableParent. When the user calls the Parent. Document API, what is actually running on the IFrame is window.disableParent. This will result in an error that the window.parent property cannot be called! , effectively avoid the safety hazards of Matryoshka dolls.

Use Monaco-Editor to implement the edit module and Console panel module

The online-code-runner I built is based on the monaco-editor to implement the edit module and the Console panel module. I will briefly describe how they are implemented in the following sections.


For the editing module, it is a simple monaco-editor, and you can simply set its style:

monaco.editor.create(document.querySelector('#editor'), {
  {
    language: 'javascript',
    tabSize: 2,
    autoIndent: true,
    theme: 'github',
    automaticLayout: true,
    wordWrap: 'wordWrapColumn',
    wordWrapColumn: 120,
    lineHeight: 28,
    fontSize: 16,
    minimap: {
      size: 'fill',
    },
  },
});

After clicking the “Execute Code” button, you can use the Editor.getValue () to read out the contents of the edit module and then hand it to the iframe to run.


For the Console panel, which is another read-only Monaco-Editor, there are two main issues that are a bit of a hassle. One is how to insert new additions one by one; The second is how to generate different background colors for different Console types.

The solution to problem 1 is simple. You just define a string variable infos, add the information to the infos whenever you listen for postMessage() from the iframe, and call editor.setValue().

The solution to problem two is that we’ve hijacked the console’s logic in the iframe, telling the parent page consle[type] whether it’s log or warn or something at the time of the postMessage. So the parent page can know what type it is based on the console[type] here.

Next, we can call the Editor.DelteDecorations method to set the background color for a row or column:

Const deltaDecorations = [] // Inserts a message to deltaDecorations whenever a new consle message is pushed in, And then we'll use the // where startLine and endLine represent the start and endLine numbers of this new message, // '${info.type}Decoration' // 'console[type]' // CSS deltadecorations.push ({range: new monaco.Range(startLine, 1, endLine, 1), options: { isWholeLine: true, className: `${info.type}Decoration` }, });

We can then define the CSS for the different consle[type] background colors:

.warnDecoration { background: #ffd900; width: 100% ! important; } .errorDecoration { background: #ff3300; width: 100% ! important; }

Specific code can look here: https://github.com/jrainlau/o…

Five, the summary

By analyzing the implementation way of CodePen, this paper developed a set of online code preview system dedicated to executing JavaScript code by cooperating with Monaco – Editor in the way of iframe. In addition to the function of code preview and display, for some management systems, people often need to write some post-script to deal with the data in the system, just can use the way in this article to build a code preview system, real-time and safe post-script preview, very useful.