I have always found Codepen’s online code preview system to be magical. It can show how code works in real time as what you see is what you get. Both code demonstration and test function are very convenient and quick. Just recently, I have a business in hand that needs the ability similar to Codepen. After some research, I developed a demo with basic online running code ability.

Online experience address: jrainlau. Making. IO/online – code…

Since the business only needs to execute JS code, demo only has the ability to run JS code.

A, principle

As we know, the browser uses its own engine to process HTML, CSS, and JS resources, and the processing starts as soon as the page loads. If we want to run these resources dynamically, we can use DOM manipulation for HTML and CSS, and we can use eval or new Function() for JS. But these operations are complex and insecure (eval and new Function() are prone to accidents), so is there a way to run them safely and elegantly? Let’s see what the famous Codepen did.

I simply wrote a button inside the Codepen, binding the style and click event, and you can see that the white area shows the result we want. When you open the console and look at it, you can see that the entire white area is an IFrame and the HTML content in it is the code we just edited.

As you can imagine, it works a little like Document.write, writing content directly into an HTML file and embedding it in other web pages as an iframe to achieve code preview logic. So what are the benefits of using iframe? Iframe can stand on its own as a host-isolated sandbox environment, where code runs without affecting the host in most cases, effectively ensuring security. With HTML5’s new sandbox attribute, iframe can be defined with more elaborate permissions, such as whether it is allowed to run scripts, whether it is allowed to pop over, and so on.

Second, implementation method

The most important step in achieving a codepen-like effect is how to inject code into iframe. Since we need to use the relevant API to manipulate iframe, the browser can only use iframe links in the same domain for security reasons, otherwise it will report cross-domain errors.

Start with 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>
Copy the code

Next we can use ifame. ContentDocument to retrieve the contents of the iframe and then manipulate it:

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

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

Then we just need to find an input field, save the code as a variable, and call iframedoc.write () to dynamically write the code to 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 prints the Console information in the IFrame directly. How does this work? The answer is simple. We can hijack the console API in the iframe page and post postMessage messages to the parent page while keeping the console output function. The parent page listens to the message and outputs the message to the page to implement the Console panel.

In ifame.html, we write a piece of JS code outside (because the parent page call to iframedoc.write () overwrites everything inside ) :

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

rewriteConsole('log');
rewriteConsole('info');
rewriteConsole('debug');
rewriteConsole('warn');
rewriteConsole('error');
rewriteConsole('table');
Copy the code

In addition, we can set the sandbox property of the iframe to limit some of its permissions, but there is a danger of nesting dolls. If the API of window.parent. It is not safe for an iframe to overwrite the content of the parent page, or even the sandbox property, so we need to block this API from the iframe:

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

Before calling the parent page’s iframedoc.write (code), we need to replace the custom code entered by the user and change all parent-document to window.disableparent. When the user calls the parent. Document API and actually runs window.disableParent in iframe, an error is reported that the window.parent attribute cannot be called! , effectively avoid the security risks of nesting dolls.

Implement the monaco-Editor editing module and Console panel module

The online-code-runner I built is based on the Monaco-Editor to implement the editing module and the Console panel module. Next, I will briefly describe how they are implemented respectively.


For editing modules, this is a simple Monaco-Editor that simply sets 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',}}});Copy the code

After clicking the “Execute code” button, the contents of the edit module can be read from editor.getValue() and handed over to iframe to run.


For the Console panel, which is another read-only Monaco-Editor, there are two main issues that are a little bit of an effort. One is how to insert new content one by one. The second is how to generate different background colors depending on the console type.

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

The solution to problem 2 is that we have hijacked the console logic in iframe and told the parent page whether console [type] is log or warn or something else in postMessage. So the parent page knows the type from the console[type] here.

We can then call the editor.deltaDecorations method to set the background colors for rows and columns:

const deltaDecorations = []

// Each time a new Console message is pushed, insert a message into deltaDecorations for later use
// startLine and endLine represent the start and endLine numbers of this new message
// '${info.type}Decoration' specifies the className of the console[type] background color, which corresponds to the CSS
deltaDecorations.push({
  range: new monaco.Range(startLine, 1, endLine, 1),
  options: { isWholeLine: true.className: `${info.type}Decoration`}});Copy the code

Then we can define the background color CSS corresponding to different consle[type] :

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

The specific code can be seen here: github.com/jrainlau/on…

Five, the summary

By analyzing the implementation of Codepen, this paper developed an online code preview system dedicated to executing JavaScript code using iframe and monaco-Editor. In addition to the role of code preview, display, for some management systems, often need to artificially write some post-script processing system data, just can use this way to build a code preview system, real-time and safe preview post-script, very useful.