Building a rich text editor with syntax highlighting is not a simple task. There's a few library for highlighting a code, but integrating it inside live editor needs more work and prone to performance problem
Another way to embed code is using Github Gist. It automatically highlight your code for free so you don't have to experiment with various client-side syntax highlighting library. Embedding gist into a page is easy. Github gives you copy-pasteable snippet that you can include anywhere in your HTML file. When you refresh the page, the code will be embedded with syntax highlighting.
The problem is Github Gist embed script doesn't play well with rich text editor. I can't simply inject the script tag into the DOM. It was confusing at first. Then I look my browser console and found this warning:
Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.
Turns out, Github uses
This is why injecting the script tag didn't work. Chrome simply doesn't allow loading script that has
document.write inside, unless the script was loaded explicitly. Explicitly-loaded scripts are any scripts that is already exists when browser start parsing HTML and render the DOM.
I was lucky to have an inspiration when building this. I can just see how Medium handles this particular case, and then adopt it in my framework. The answer turns out is quite simple: Iframe.
When embedding from 3rd party, Medium creates a new media endpoint / URL that contains HTML, CSS, and JS needed to load the embed. In their editor they can use source attribute in iframe to render the embed.
This means that any 3rd party embed inserted into the editor needs to be saved in database first, so we can use the iframe via media endpoint.
I'm not really a fan of this method because I have to write custom server handler for media endpoint. Thankfully, there is a new attribute in HTML5 that we can use to render arbitrary content inside an iframe: srcdoc.
<iframe srcdoc='<script src="..."></script>'></iframe>
srcdoc has great support across all major browser except IE and MS Edge. It's not supported in IE and still under consideration in MS Edge at the time of writing. But for me, this is good enough because I don't plan to support all browsers anyway. Technically, I can use iframe src attribute because it supports data URI which has better browser coverage, but it has character limitation.
If you're wondering why injecting iframe using DOM API works but scripts didn't, it's because loading an iframe is similar to loading a new page. Any content inside an iframe is marked as explicitly-added content, similar to loading a fresh HTML. So
document.write calls is allowed inside this frame.
Using an iframe also protects the current page from any DOM and style modification from external script because both are contained inside the frame.
As you can see from github embed above, it looks like we copy the actual tag from github gist page, and render the markup without iframe. This is actually achieved by removing any border and scrollbar on the iframe, because by default iframe will be rendered as a small box with border and scrollbar.
Before this, I never thought I would use an iframe in anything. I always think that iframe is only an escape hatch for the old web, like creating layout using <table>. It's not. Iframe is still an escape hatch, but a really useful one.
Long live iframe!