How We Built EQCSS & Why You Should Try Building Your Own Polyfills Too
The Backstory
In 2013, I was creating the frontend of a responsive web app that had a lot of data to display. I had done a lot of responsive design using @media
queries, but as I found myself trying to re-use components from one layout in another layout, I wished I could make my responsive breakpoints correspond to the width of the elements instead of the width of the browser.
This is something CSS can’t currently do, so I was copying and pasting a lot of styles from template to template, changing mostly just the breakpoints. I searched for existing workarounds, mainly tools and JavaScript plugins, to help me automate this process or output the duplicate code for me — but these all seemed like imperfect solutions to the problem.
I had heard about Less, a CSS preprocessor that lets you author CSS that includes extra features like variables and functions that aren’t part of standard CSS. You can add a small JavaScript plugin on your website that will allow the browser to read this non-standard CSS, and all your non-standard code would magically translate to styles that the browser understood. I started to wonder if there was a way I could extend CSS in the same way to solve my problem!
The Birth of a Plugin
Somewhere along the way, my paths crossed with an amazing and creative coder named Maxime. I had been a big fan of some of Maxime’s past projects, and he had knowledge and understanding of CSS and JavaScript far beyond mine. One day, when I was thinking about my challenges with CSS, I sent him the following message:
I need a way of writing CSS styles that lets me:
- specify different styles based on the current width of an element
- specify different styles based on the current height of an element
- keep an element vertically centered within its parent element at all times
- keep an element horizontally centered within its parent element at all times
- specify different styles based on the text length of an element
- specify different styles based on number of child elements an element contains
- Bonus: to allow me to navigate up the DOM using the
<
selectorIf I had a library like this I believe I could design layouts that would be truly bulletproof and unbreakable. I need
@element
queries!Is it possible to write these styles in a way that looks familiar to people who write CSS, but gets read and executed by JavaScript?
Is JavaScript able to parse text (maybe called
.jss
file or<script type="text/jss">
where I could write CSS blocks, but wrap them with special@element
queries, which could be read by JavaScript, and have the computed styles applied to the page?@element('.widget-box', min-height: 500px) { .widget-box { background: red; } .widget-box a { font-size: 18pt; } }
or
@element('#username', min-length: 20) { #username { font-size: 8pt; } #username < label { border: 1px solid red; } }
For this to be truly useful, it needs to have a small learning curve for people who already know CSS but don’t know JavaScript. They should be able to add the JavaScript library to a site and write the custom styles and have it work without needing any custom JavaScript. I guess that makes this more like a polyfill than a plugin : )
Is something like this possible?
— Tommy, December 5, 2014
I wasn’t sure what kind of answer I would get back. I had already tried building a few plugins on my own without much success. Being a JavaScript beginner, I was very limited in what I was able to build on my own, and all of the solutions I had attempted to create myself had ended up adding more work. For a solution to be truly valuable, it should reduce my overall workload and make it easier to develop — it should be removing constraints, not adding them!
Quickly, I got an answer back from Maxime:
The answer to all your questions is yes. It’s possible. :)
I don’t see one mission in your description, but three:
You want to extend the CSS capabilities to do what media queries don’t do yet: apply some style to an element depending on its size or the length of its text content
You want to extend CSS selectors to add a parent selector.
You wand to extend regular flow-related css properties by adding a way to vertical align anything into anything. Those are the 3 holy grails of CSS, you’re setting the bar very high :D
— Maxime, December 5, 2014
In the weeks that followed, through emails bouncing between Canada, France, and the United States, Maxime and I worked out what this new syntax would look like. We wrote and shared code in a language that didn’t exist yet, talked about potential problems and workarounds, and in the end, he built the first version of the EQCSS JavaScript plugin according to what I thought I needed.
Very shortly, I was able to put this plugin to use on the websites I was working on, and by January 2015, it was first used in production. We continued to experiment with it, adding new features and improving support and performance over the following months. Since the plugin was originally written, I’ve learned enough JavaScript to be able to troubleshoot, repair, maintain, and even add new features of my own.
Why I Built a Plugin
When I think of the reasons why I spent so much time and effort creating a solution like this, I think there were a few different motivations. The first was that I wanted a solution to the problems I was facing in my work every day; something I could put to use immediately that would start to save me time from the first day I used it.
Another motivation was to discover how malleable the web was as a platform. Was it possible to modify and extend one of the foundational languages of the web (CSS) and add new features to it yourself? How far could you take it?
Those reasons were enough to get me started initially, but now that we have a solution that competes well against other solutions, there’s also the additional motivation: can we refine our solution and present a more standardized method for solving similar problems so that everyone can benefit?
The Challenges of Building a Plugin
There have been a number of challenges I have faced when creating this project. Some with the syntax itself, some with writing a plugin, some with maintaining support with different browsers as they change features, and some in the human arena, helping people to understand the concepts and make the most of what the plugin has to offer.
Syntax Challenges
Some of the syntax challenges we faced were trying to keep all of the syntax limited to just one language: CSS. We didn’t want the user to have to add anything extra to their HTML markup for the code they were writing in their CSS to work, and we wanted to avoid the user needing to write custom JavaScript themselves to get started.
Another challenge was designing the syntax to be expressive enough to handle the use cases we needed to support right away while providing sufficient flexibility to allow users to write in their own additional functionality as needed. This extra flexibility has been a benefit to us when testing and adding new features because a prototype can be produced by writing custom code with the plugin in a way that translates closely to the code that would need to be added to the plugin to extend it. The plugin can be used to prototype new features for itself, and since we have introduced this additional flexibility, the speed at which we’ve been able to add new features has increased.
When inventing any new language feature, like we were doing with CSS, it’s important to design your syntax in a way that makes it future-proof, in case similarly-named features are added to the language in the future that work differently from your implementation. With our plugin, the custom syntax can be read from CSS, but the syntax can also be loaded directly from the plugin as a separate script type that the browser won’t try to read as CSS. Because of this custom script type, new terms in our syntax could coexist in the same codebase alongside other languages that include similar terms without them conflicting.
Plugin Challenges
One of our goals when trying to build a plugin (something I had never done at this scale before) was that we wanted to keep the file size reasonably small and keep the source code straightforward enough to allow anyone to read, edit and extend the plugin for their needs. It was also important to me that the features we added work in Internet Explorer 8. The amount of IE8-specific code this required ended up comprising a large portion of the overall codebase, but we were able to structure the plugin in a way that all of the IE8-specific code could be quarantined into its own file. This additional file only needs to be included in projects where IE8 support is required and can safely be omitted in projects where IE8 support is not needed.
Browser Challenges
When designing a plugin that needs to work in web browsers, you begin to view web browsers as moving targets. We had originally built and tested the plugin in Chrome, Safari, Firefox and Internet Explorer, and it at first it was legacy versions of Internet Explorer that imposed the strictest limitations on the plugin. But in early 2016, after the plugin had been in production for a year, we received a bug report that in new versions of Firefox, some pages with the plugin were suffering a major performance issue! We hadn’t changed anything in our code — but after investigating different Firefox releases for this bug, it seemed like something had changed in the way Firefox thought about the page scroll event, and it was triggering recalculations in our plugin many more times than necessary.
The proposed solution for fixing Firefox was to add a debounce mechanism to our plugin — a piece of code that could set an upper limit on how frequently a recalculation could be requested. This would solve our problem in Firefox, but in a way that affected how the plugin would work in all browsers, potentially opening up unforeseen issues. Making things worse, while testing solutions in prerelease versions of Firefox the problem seemed to be absent from versions of Firefox that were months away from release. It was stressful knowing other people were using our plugin and that unless we released a patch, Firefox users around the world would see a degraded experience for months. In the end, after a lot of testing, we released a patch adding a debounce mechanism, fixing the bug for Firefox users and increasing performance in other browsers as well.
Module Challenges
Originally, we had built the plugin to work like a polyfill (or a shim). It was designed to run directly in the browser, which made it easy to host on a CDN. It wasn’t long before we began getting requests from Webpack users who were building projects using JavaScript modules and wanted a version of plugin packaged as such. Thankfully, we were able to wrap the existing plugin with code from a UMD module template, which turned it into a module. The plugin can now be loaded by module loaders like Webpack and Browserify. Just as before, if you load the plugin outside of a module loader (like linking to the file directly in the browser) the plugin will still attach itself to the global object (the browser) just like it did before and run normally.
Documentation Challenges
The last area that has been a challenge when building a new plugin is finding (or creating) vocabulary that allows you to talk about how these new concepts work and how others can benefit from these ideas in their projects. Over time, through talking with others, writing a specification, documentation, and many articles, this gap is being bridged, but any plugin dealing with a new technology or concept would face a similar communication gap at the outset.
Why Should You do the Same?
After my experience building a plugin, I am more enthusiastic than ever about doing the same thing all over again! It feels like magic to do something you believed was impossible, and it’s very rewarding to experience the reaction of developers who are facing the same problems during the moment they realize how their problems can finally be solved.
Sharing your solutions with the community is a win/win/win situation.
- Everybody benefits from reading your code and using your techniques in their work
- You benefit by having a standardized reference to start from in the future
- Often other people will suggest features and report edge cases you have missed that help you improve your solution
There’s no downside! Once you have a solution, if it’s a language feature like mine, you can move forward by writing a specification for the new feature and begin the process of proposing it to be included in the standard for that language.
Other things you can pursue once you have a plugin are ways of leveraging that solution as a way to research new ideas or concepts or using your plugin as a springboard for further plugin experiments. Since creating my plugin to solve my primary need for element queries, I’ve been able to use the increased power and flexibility of EQCSS to research and experiment CSS solutions in other areas as well. I’ve done experiments with things like aspect ratios (something CSS currently has no property for) and ways to extend the Attribute selector in CSS, plus plenty of other ideas.
Out of these experiments, new plugins are being created, and hopefully they can also be leveraged to explore and discover even more ideas.
My Only Regret
After this transformative experience, the only thing that stings at all has been the realization that every feature we got functioning in the most cutting-edge browsers was something we also made work in IE8. That means in all of the years since IE8 was released it has been possible to do all of this, but until a couple of open-source developers collaborated and spent a few weeks building a plugin, it wasn’t being done.
If I have one regret about this entire process, it’s that the idea didn’t occur sooner! We could have been building websites this way for years already if somebody had taken the effort to create a plugin like this years ago.
Looking Forward
So what does that mean for right now? What solutions are already possible today, and don’t require much work to realize, but simply don’t exist yet? If you have an idea about a solution for something, it makes sense to explore it and try building it sooner rather than later!
My entire outlook on web development has changed after this experience, and it’s the kind of feeling that makes you jump out of bed in the morning feeling excited by the things you might create by the time you go to sleep that don’t currently exist right now.
So I’ll ask you: What challenges do you face, and what ideas have you had about solving them?