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.
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:
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';
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 likeeval()
orFunction()
. 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 forhttps://*.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 asXMLHttpRequest
andFetch
. 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:
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
Prettierfying
Preferences
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)
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... ;)
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 :).
In all job listings, the skills are only a guideline, having a portfolio of cool & relevant work beats everything :)
Cool.