Over a year ago we set out on a mission at Optimizely to unify our product design and get a handle on our ever-increasing CSS payload. Fellow UI Engineer Tom Genoni spearheaded this effort in 2014 and created our Sass framework called OUI.
We first integrated OUI into a small part of the Optimizely application and gradually added it to the entire A/B Testing product in the months that followed. We have since developed an entire new product, and a handful of smaller ones, using the framework. This increase in scope presented unique challenges and required us to improve our implementation strategy.
In Part 1, Tom wrote about the high-level steps it took to build and evangelize OUI. In this post I’ll discuss the Sass architecture decisions we made and processes we added that have allowed us to scale OUI.
The Anatomy of OUI
OUI was created outside of the Optimizely codebase and lives in its own repository on GitHub. The architecture allows developers to easily modify the default styles by overriding variables and to create partials (components and objects) that play nicely with the framework.
This figure above shows how we typically integrate OUI into an application. In practice, the `my_app.scss` file typically looks like this:
//  Import OUI and app functions and mixins
//  Import OUI and app variables
//  Import OUI and app partials
//  Import OUI trumps
@import 'oui/partials/trumps/layo ut';
A few notes about the SCSS above:
- We first import OUI’s mixins and functions followed by any custom ones we need.
- OUI’s variables load right before our app’s variables. This allows us to amend or override existing variables and introduce custom ones.
- This is the meat of the file. The first partial imports all of OUI’s base rules, components, and objects whereas the the second imports code custom to our product. By listing them here we ensure they pick up all default variables along with any added or changed by our app’s variables.
- Trumps, our utility classes, are loaded last because they perform a specific job and should not be overwritten.
Version 1: Basic Integration of OUI with npm
Hosting OUI on GitHub gives us the freedom to easily integrate it into projects using npm, and at first we only used it to pull in the most-recent OUI commit from GitHub that would work in the Optimizely application:
npm install --save git://github.com/optimizely/oui.git#commit-hash
In fact, any GitHub repository can be installed as a dependency using this method. And specifying the commit hash is a lightweight way to prevent breaking changes in OUI from automatically being pulled in. Using this process, references to OUI in `my_app.scss` are pointed to the `node_modules/` directory, the default install location for npm.
Alternatively we could symlink `oui/` to `path/to/node_modules/oui/`.
This basic approach worked for a few months, but questions arose as we began adding OUI to other projects. How can applications automatically pull in OUI bug fixes and other non-breaking changes? How can we provide context when upgrading to a new version of OUI? And how can we keep documentation up-to-date while moving quickly? We knew we could do better!
Version 2: Advanced Implementation of OUI
To make OUI truly robust and developer friendly, we built on our previous work and took advantage of a number technical tools and best practices. Though these steps are more advanced, we found them to be indispensable.
- Using npm and Semantic Versioning
- Adding a change log
- Implementing a build system
- Creating a living documentation solution
OUI has been in development for over a year, but breaking changes are still common. A breaking change, such as renaming flexbox classes, can require hours of careful find and replaces to support in a large codebase.
As previously mentioned, we initially pulled in OUI from GitHub using npm and included a commit hash to prevent breaking changes from automatically getting fetched. This solution is slow because it requires updating the commit hash in an application’s `package.json` for each new change. Ideally, backwards compatible changes such as bug fixes and new components should automatically be pulled in.
We accomplished this by publishing OUI on npm, following Semantic Versioning in our version numbers, and configuring NPM to accept minor and patch changes. Contributors to OUI identify breaking changes and change version numbers accordingly.
Keeping a Change Log
Upgrading to the latest version of OUI within a project can be difficult without context or clean release notes. We created a change log (based on Keep a Changelog) to track all commits and pull requests. This makes it easy to generate helpful release notes that help developers implementing OUI know exactly what they have to fix or change when bumping up a full version.
Linting and Compiling SCSS with Travis CI
We use scss-lint to maintain a consistent style and run node-sass to ensure the SCSS compiles. We have integrated the basic lint and compile checks into GitHub pull requests using GitHub’s Travis CI integration.
This ensures the code is clean and prevents glaring bugs from being merged. It also has the added benefit of automating part of the code review process, allowing reviewers to focus on the important parts.
Creating Living Documentation
We have dozens of engineers that use OUI. To ensure everyone can use the framework, we must have well-documented code. We use ScribeSass, a node.js module built in-house, to auto-generate a documentation website (not live yet) based on comments in the SCSS files code. We’ll be releasing it as an open-source project soon.
We introduced OUI to the Optimizely codebase over a year ago. The architecture decisions that make OUI flexible and the investment in tooling and processes has allowed us to integrate it in five repositories, easily onboard new contributors, and enable a team of software engineers to write HTML without introducing new CSS.