Over the years, our engineering teams at HubSpot have run into issues with deploying, updating, and sharing front-end assets (e.g. Javascript, CSS, images, etc). Sure, web development has come a long way, and the latest browsers, libraries, and tools enable us to build incredibly rich and complex applications. But in many ways, webapp development is still quite young.
In particular, the tooling needed to help larger-scale development teams share front-end code and inter-operate across applications is pretty much non-existent.
It is true that we’re making progress, such as vastly improved code collaboration via Github pull requests. And the impressive surge of front-end package managers like Yeoman/Bower, npm, Jam, and Volo. These kinds of tools are great and are quickly becoming indispensable. But unfortunately, as they are right now, they still have a long way to go before they can solve some of the issues that larger-scale development teams run into, including:
- Having consistent styles and reusing Javascript components across several applications (without resorting to copy & paste!)
- Accidentally breaking other applications when updating shared CSS and Javascript
- Dealing with the subtle annoyances of higher-level front-end languages like Coffeescript and SASS (e.g. having to check in compiled output, keep everyone on the same version, run 3 different watchers, etc)
- Getting everyone to consistently use best performance practices
So we did what any self-respecting team that discovers a new problem does, we built our own solution.
So what is it?
So what exactly are we talking about? It's a long story, but here's a sneak peek:
- A dependency management system for Javascript and CSS that allows our developers to share code and still move lightning-fast (with far less worry about breaking other people's applications)
- A local server and package manager that installs and updates dependencies for you, plus lots of other conveniences (like auto compiling SASS and coffeescript so you don't need to run any watchers)
- Build tools that automatically "link" your project's code to specific dependencies and optimize everything (concatenate, minify, and far-futures caching headers)
- A server-side runtime that allows those front-end dependencies to be updated on-the-fly without any re-deploying (plus easy debugging)
But before we get to all the details, it is worthwhile to explain why this matters. And fair warning, this will take a little while, so we've broken it up into parts:
- The what's and how's of front-end dependency management.
- The goals and core concepts for Asset Bender. (TL;DRers and know-it-alls, you can wait for part 2 or see the code on github)
- More details about Asset Bender's internals.
- The path forward.
Scale you say?
In the beginning, most front-end development teams have one source code repository. Life is good in those days. You don't have any complicated setup scripts, dependencies are just copied right into the repository, and there is only one set of code that needs to run in production.
But those days can't last forever. As your application and development team (hopefully) grows, you'll eventually hit a point where one source repository doesn't make sense anymore. Maybe it is just for logistical reasons or maybe it is because there are too many coders stepping on each others toes (everyone loves merge conflicts!), but it eventually will happen.
I realize that there are lots of small companies that will never outgrow a single codebase and will never have have to worry about this. That's awesome, live it up! But for those of us that have to work with other people...
Everything looks good to me
When you get to the point of needing two or more front-end codebases, you'll quickly end up having dependencies between them. For example, the second project might share some base styles (CSS) with the first project. Or maybe the second project uses some jQuery plugins that are defined in the first.
So what happens when those shared bits of code are updated? Do your developers realize that when they adjust the base styles in the first project, they might be breaking things in the other project? Everything might look fine when they are testing locally, but who knows what havoc it might wreck on other applications when it hits production?
Copy and paste to the rescue!
You might be thinking of some ways to get around this. Why not just make a copy of the styles for each project? But all that does is just trade one problem for another. Now any updates and bug fixes that happen in one place need to be replicated. Come on, we know that's not going to happen, developers are lazy! :)
Another approach might be to refactor out all the shared pieces into another project. This does help organize things, and might help your developers realize that they need to be more careful when committing to the shared project, but it doesn't actually solve the core problem. You'll still only have a single instance of the shared assets in production. And deploying any breaking changes will require immediate updates to both dependent projects. In other words, all projects still need to move in lockstep and update as a unit (despite our initial desire to keep them separate!).
The real solution isn't copying or organization. It is versioning. Instead of forcing both projects A and B to update at the same time to keep up with changes to shared project C, let project B rely on version 1 of project C while project A updates to version 2 of project C. Since we're keeping multiple copies of project C around, A and B can stick with the old version of C and wait to be updated as needed.
The buzzword for this is dependency management.
Has this been solved before?
Yes and no.
Yes, there are lots of mature dependency management solutions out there (Maven, Pip, NPM, etc). But all are primarily built to deal with the issues that arise when running code on desktops and servers. They simply weren't built to deal with browsers that are interpreting code and resolving dependencies at runtime.
Even though the core concepts are the same, dependency management of front-end assets is quite different. The web is simply a whole different ballgame:
- Performance on the web is a big deal. And to do that well, you need to limit the size (and number) of your front-end assets, share code whenever possible, and aggressively use HTTP caching. However, you probably couldn’t care less about the size of the code that is running on your servers.
- On the web, dependencies (aka the Javascript, CSS, and images your page links to) are resolved at runtime, independently by each and every visitor that hits your page. But server-side dependencies are only resolved once when the project was built and/or deployed.
- Front-end webapp code tends to move faster than its desktop/server counterparts. There’s no need to package up and ship executables around, so it is easy (and preferred) to incrementally change content, styles, or code as necessary.
- Unlike server-side code, CSS (and HTML if you exclude server-side template languages) is static. And it is delivered to the client verbatim, unlike server-side code that can respond dynamically (different logic, fetching data from databases/APIs etc)
- BONUS: Sharing CSS is immensely different than sharing code. Unlike "real" code, CSS does not have specific API boundaries. Instead it has an amorphous set of styles (Flying spaghetti monster anyone?) that a web page may or may not intersect with. And because of this, subtle changes to your HTML structure and/or CSS can easily break things. In other words, CSS dependencies are far more fragile and difficult to predict than server-side (and Javascript) dependencies.
So yeah. Even though dependency management is an established and understood practice, something new and different is needed to deal with the constraints that come with the web.
Read on to part 2 to start learning about Asset Bender—HubSpot’s tool that deals with these issues, and makes our front-end developers’ lives so much easier.