DEV Community

António Afonso
António Afonso

Posted on

Extending Glitch

If you're not familiar with it Glitch is “the friendly community where everyone can discover and create the best stuff on the web.” I found it a few months ago after a co-worker pointed it out to me and I fell in love with it right away.
I could go on and explain all the things that make me love Glitch (50% of it are the cute graphics though 😍) but it suffices to say that it brought a level of support to the iPad that I hadn't seen before in other development environments. Since nowadays I only use the iPad this was a “game changer”™ to me.
However, as great as it is, it still had a few issues that slowed me down so I decided to improve it by extending it. In this post I'll detail the two main areas I focused on — Text Editor improvements and Prettier support — but before I go on I'll first explain the basics of the extension.

How to Extend (bookmarklets)

Glitch is not open source so I'm not able to just fork, fix and create a pull request for it (like I did for CodeSandbox), I had to find other ways to run my code in it. The most common way is probably through browser extensions, they are little apps you can install on your browser that have great power over what's running on the browser. Unfortunately Mobile Safari does not support this mechanism so it was a no go for me.

The next best thing are bookmarklets, these are (like the name implies) bookmarks, but instead of loading a specific address in the browser they run code! This is possible because the browser understands multiple different types of URLs, the type (aka protocol) of the URL is the first thing you see before the colon. For example, in https://www.glitch.com, https is the type, which is the most common one, but there are many others, like ftp or mailto but also.. javascript! That's right, if you have a URL of javascript:alert('My first Bookmarklet!') and click on it you'll see the alert box, and since it's just a URL you can save it as a bookmark and click on it every time you want to run that code. This is the basis of all the extensions I did.

If you want to try it out you can install the extension here.

Text Editor Improvements on iOS

Key Bindings

As of now any key binding that uses Ctrl or Command (Cmd) does not work inside the text editor. Yes, that means, no undo (Cmd-Z), search (Cmd-F) or even block comment (Cmd-/). [NB: this only reveals the amount of value Glitch brings to me even though I wasn't able to undo.]

All of this is not Glitch's fault at all, Mobile Safari has really bad support for certain keys on input type="text" and textarea elements. As of now it's not possible to know when the person pressed the following keys: Left, Right, Up, Down arrows, Ctrl and Command as no key event is emitted.
You might have noticed that I didn't mention Alt, that's because this one emits events! It needs to, because on iOS (and MacOS) Alt-<key> will print an actual character (e.g.: in the US keyboard Alt-P will print π).

After learning this I made compromise: I replaced all Ctrl/Cmd key bindings with Alt ones, at the cost of not being able to print certain “uncommon” characters like Ω for Alt-Z in the US keyboard, but now I've gained back the ability to undo!

Text Selection

There was still another issue that was driving me crazy, whenever you selected some text in the editor it would just scroll down to some random part of the code, making it almost impossible to select text. After some debugging I realized this was a bug in CodeMirror's highlightSelectionMatches feature (CodeMirror is the component Glitch uses for text editing). For some reason when CodeMirror highlights text that matches the selection the editor jumps there as well. I haven't had the time yet to understand why this is happening, so I just disabled this feature.

Editor Jumping on Selection

Editor jumping on selection

Improving It

How does all of this translate into code you ask? Good question! CodeMirror provides a very comprehensive API so I used it to change the keybindings and to disable the highlightSelectionMatches feature. However, I needed to get a hold of the CodeMirror instance first, this was actually easier than I thought as CodeMirror sets the instance on the corresponding DOM element, hacky, but handy :-). As there's only one in the entire document I just document.querySelector('.CodeMirror').CodeMirror to get it. Here's the end result and final code:

Selection no longer makes editor jump and keybindings work

Selection no longer makes editor jump and keybindings work
let cm = document.querySelector('.CodeMirror').CodeMirror;
let extraKeys = cm.getOption('extraKeys');
cm.setOption('highlightSelectionMatches', false);
extraKeys['Alt-Z'] = 'undo';
extraKeys['Shift-Alt-Z'] = 'redo';
extraKeys['Alt-/'] = 'toggleCommentIndented';
extraKeys['Alt-F'] = 'findPersistent';
extraKeys['Shift-Alt-F'] = 'findPrev';
Enter fullscreen mode Exit fullscreen mode

Prettier Support

I use prettier on save everyday at work and can't really imagine my life without it now, so I had to have it on Glitch too.
Prettier usually runs on node but it also offers a standalone version that runs on the browser. This is what I used to implement this feature.

Once I started I quickly realized this was not going to be as simple as initial thought. Adding a new script tag through the bookmarklet was not working at all, and this was because https://glitch.com/edit implements a very tight Content Security Policy (CSP).

Content Security Policy

A CSP is a set of rules a webpage can define to restrict the type of resources that can be loaded. Glitch has a few ones that severly restricts the different ways to run external code:

  • script-src: this directive specifies not only which sources are permitted in a script tag but also limits the ability to create code from strings like eval() or Function(). Glitch only allows script sources from its own domain — https://glitch.com/ — and a few CDN domains to load static resources from, it also strictly forbiddens the creation of any code through eval. That was a bummer :(.
  • frame-src: this directive is similar to the previous one and it restricts the sources that a frame or an iframe can load. Glitch only allows for https://*.glitch.me https://*.glitch.staging.me https://*.glitch.development. The last two ones clearly included for development purposes of Glitch itself.
  • connect-src: this directive specifies which sources can be loaded through JavaScript APIs such as XMLHttpRequest and Fetch. In Glitch's case only its own api domain — https://api.glitch.com — and a few others like github are allowed.
  • default-src: this directive specifcies the default to use for any other directive that is not explicitly written out. In the case of Glitch this is set to 'self', which means its own domain.

All of this disallowed me to include any of the prettier code through a script tag or loading it through XHR and then evaling it.

Workaround

I included the frame-src directive above because I noticed that it actually allows the loading of any Glitch project that anyone has created. The reason it does this is so you can see Code and App side by side. Communication between the iframe and the main window is possible so I leveraged this to load and run the prettier code.

I created a Glitch project with the standalone version of prettier and a function to call it. I loaded this project on an iframe created by the bookmarklet and now I only needed to call that function.

iframe's Window

My first instinct was to access the iframe's window using iframe.contentWindow, however, you can only access its properties if both pages are in the same domain. The problem here is that the main window is on *.glitch.com and the iframe on *.glitch.me (where all Glitch apps run). If this hadn't been the case then I could have set window.document.domain = "glitch.com" on both pages to put them in the same domain and be done with it. However, this is only possible when they share the same second-level domain.

postMessage API

The alternative way to communicate between pages of different domains is the postMessage API. It's a simple API composed by a single method — window.postMessage(message, targetURI) — that when called sends a message event to that window. The targetURI parameter is the URL of the page you want to send the message to. This is a way to ensure you never send important data to another page by mistake, in our case we don't care so we use "*".
In my case I wanted the main window to send code to the iframe and receive the formatted code back. This is roughly how I coded it, first the bookmarklet and then the iframe:

const editor = getEditor();
const iframe = createAndLoadIframe("https://glitch-extensions.glitch.me");
editor.onSave(function(code) {
  // Send the code to the iframe window.
  iframe.contentWindow.postMessage(code, "*");
  // Prepare to receive the formatted code back.
  window.addEventListener("message", function(event) {
    // Replace the editor text with the formatted code we got back.
    editor.setValue(event.data);
  });
});
Enter fullscreen mode Exit fullscreen mode
Bookmarklet (running on https://www.glitch.com/edit)
<script src="prettier.js"></script>
<script>
  // Listen to messages coming from the bookmarklet.
  window.addEventListener("message", function(event) {
    // Format the code received using the function provided by prettier.js
    const formattedCode = prettier.format(event.data);
    // Send back the formatted code.
    event.source.postMessage(formattedCode, "*");
  });
</script>
Enter fullscreen mode Exit fullscreen mode
iframe (running on https://glitch-extensions.glitch.me)

As you can see this is an async API meaning that it may take some time between the person saving and getting the code formatted. However, since both pages are running on the browser this operation is so fast you won't notice any delay.

Auto Prettier (Experimental)

This is a feature I'm trying out to help me write code on the phone. Formatting code on the phone is a serious pain and to make things worse there's no easy way to trigger prettier on save. When turned on (it's off by default) it will auto format the code with prettier after 2s of inactivity.

Integration

Finally, I wanted these extensions to feel like they were part of the Glitch ecosystem. For this effect I added notifications and preferences using the Glitch look and feel:

Loading the extension

Notification of the extension being loaded

Notification of the extension being loaded

Prettierfying

Notification when prettierfying

Notification when prettierfying

Preferences

Additional user preferences for the extension

Additional user preferences for the extension

One of the risks of having this integration is the tight coupling it creates with Glitch. As a 3rd party this means Glitch can unilaterally break some of assumptions I’m making. But given the end result it’s a risk I’m willing to take for now.

Acknowledgements

I'd like to thank the Glitch team for an amazing product, and also for making source maps available for all the code. This allowed me to gain a better understanding of the underlying logic and to hook in and reuse parts of the application.

Top comments (4)

Collapse
 
pketh profile image
Pirijan Ketheswaran

As the designer and writer of much of the glitch editor ui, I'm really impressed. p.s. we're hiring an engineer to work on the editor, including helping us build out an official integrations system at jobs.lever.co/glitch/d4cc56d4-0f51... ;)

Collapse
 
aadsm profile image
António Afonso

Oh wow, thank you so much for your kind words! I was not expecting this :D.
That sounds pretty cool actually, I can’t wait to see what’s coming from that :)). I checked the job post but I don’t even have half the skills required :).

Collapse
 
pketh profile image
Pirijan Ketheswaran

In all job listings, the skills are only a guideline, having a portfolio of cool & relevant work beats everything :)

Collapse
 
athif23 profile image
At Indo

Cool.