I have always felt that Codepen's online code preview system is amazing. It can display the running effect of the code in real time, whether it is code demonstration or test function. It is very convenient and fast. It just happens that I have a business need to use a capability similar to Codepen recently. After some research, I developed a demo with basic online code running capabilities.
Online experience address: https://jrainlau.github.io/online-code-runner/
Since the business only needs to execute JS code, the demo only has the ability to run JS code.
1. Principle
We know that the browser processes html, css and js resources through its own engine, and the processing process starts when 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 a eval
complicated and unsafe (060f658601baea and new Function()
are prone to accidents), so is there any way to be both elegant and convenient, and safe to run dynamically? Let's take a look at how the famous Codepen does it.
I simply wrote a button in Codepen, bound the style and click event, and you can see that the white area has shown the result we want. After opening the console and self-absorption observation, it can be found that the entire white area is a iframe
, and the html content in it is the code we just edited.
It is not difficult to imagine that its operating principle is a bit similar to document.write
. It writes the content directly into a certain html file, and then embeds it into other web pages in the form of an iframe to realize the logic of code preview. So what are the benefits of using iframes? The iframe can independently become a sandbox environment isolated from the host, and the code running in it will not affect the host in most cases, which can effectively ensure security. With the new sandbox
attribute of HTML5, you can define more precise permissions for the iframe, such as whether to allow it to run scripts, whether to allow it to pop up and so on.
Second, the realization method
To achieve a Codepen-like effect, the most important step is how to inject the code into the iframe. Because we need to use related APIs to manipulate iframes, for security reasons, browsers can only use iframe link same domain, otherwise a cross-domain error will be reported.
First, prepare a index.html
and iframe.html
, use a static resource server to run them, suppose they all run at localhost:8080
. Then we insert an iframe into index.html
localhost:8080/iframe.html
, the code is as follows:
<iframe src="localhost:8080/iframe.html"></iframe>
Next, we can use iframe.contentDocument
to get the content of the iframe, and then manipulate it:
<script>
const ifrme = document.querySelector('iframe');
const iframeDoc = iframe.contentDocument;
iframeDoc.open(); // 需要先调用 `open()`,打开“写”的开关
iframeDoc.write(`
<body>
<style>button { color: red }</style>
<button>Click</button>
<script>
document.querySelector('button').addEventListener(() => {
console.log('on-click!')
})
<\/script>
</body>
`);
iframeDoc.close(); // 最后调用 `close()`,关闭“写”的开关
</script>
After the run is completed, we can localhost:8080/index.html
see the same effect and Codepen previously exhibited inside:
In the follow-up, we only need to find an input box, save the written code as a variable, and then call iframeDoc.write()
to dynamically write the code to the iframe and run it in real time.
3. Console output and security
Observing the Codepen page, you can see that there is a Console panel, which can directly output console
How is this achieved? The answer is simple, we can hijack console
and other APIs in the iframe page, and while retaining the original console output function, output the relevant information postMessage
, and the parent page listens to the message after it After sorting the information, output it to the page to realize the Console panel.
In iframe.html
, we write a piece of js code outside of <body></body>
iframeDoc.write()
will cover all the content in <body></body>
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
attribute to the iframe to restrict some of its permissions, but there is a hidden danger of a doll, that is, if the window.parent.document
related API is executed in the iframe, the iframe can be allowed to rewrite the content of the parent page, or even rewrite the sandbox
attribute. It must be insecure, so we need to block this related API in the iframe:
Object.defineProperty(window, 'disableParent', {
get() {
throw new Error('无法调用 window.parent 属性!');
},
set() {},
});
In the call to the parent page iframeDoc.write(code)
before, we need to put custom code entered by the user code
once replace
, all among parent.document
into window.disableParent
. When the user invokes parent.document
time related API, the actual operation of the iframe is window.disableParent
, then it will direct error can not call window.parent property! , Effectively avoiding the hidden dangers of nesting dolls.
Fourth, use monaco-editor to implement the editing module and the Console panel module
online-code-runner I built is based on monaco-editor to implement the editing module and the Console panel module. Next, I will briefly describe how they are implemented.
For the editing module, it is a simple monaco-editor, just 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, editor.getValue()
, and then handed over to the iframe to run.
For the Console panel, it is another read-only . There are two main problems and it will be a little bit difficult. One is how to insert the newly added content one by one; the other is how to generate different background colors console
The solution to problem one is very simple. You only need to define a string type variable infos
. Whenever 060f658601c1c6 from iframe is postMessage()
, infos
, and finally call editor.setValue()
.
The solution to question two, we have hijacked the logic of console
postMessage
, we also tell the parent page consle[type]
is log
or warn
or something else, so the parent page can know the specific type console[type]
Next, we can call the editor.deltaDecorations
method to set the background color of a certain row and certain column:
const deltaDecorations = []
// 每当有新的 consle 消息推送过来时,都往 deltaDecorations 里插入一条信息,后面会用到
// 这里的 startLine 和 endLine 代表着这条新的消息的起始行号和结束行号,需要自行记录
// `${info.type}Decoration` 为不同 `console[type]` 的背景色对应的 className,对应着具体的 CSS
deltaDecorations.push({
range: new monaco.Range(startLine, 1, endLine, 1),
options: { isWholeLine: true, className: `${info.type}Decoration` },
});
Then we can define the background color CSS corresponding to consle[type]
.warnDecoration {
background: #ffd900;
width: 100% !important;
}
.errorDecoration {
background: #ff3300;
width: 100% !important;
}
The specific code can be viewed here: https://github.com/jrainlau/online-code-runner/blob/main/src/components/Console.vue#L62-L71
V. Summary
This article analyzes the implementation of Codepen and uses iframe to cooperate with monaco-editor to develop an online code preview system dedicated to executing JavaScript code. In addition to the function of code preview and display, for some management systems, it is often necessary to manually write some post-scripts to process the data in the system. Just use the method of this article to build a code preview system, which is real-time and safe. The post script is previewed locally, which is very useful.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。