Back in the day, "building a website" used to mean crafting a beautiful soup of markup, styles, and scripts, and once you were happy with the result, uploading the files to a web server.
These days, with the move away from document-based web pages to SPAs and other highly interactive websites, the word "build" has taken on a new meaning: before we can see our site work, we need to compile, bundle, and minify our sources with tools like Webpack and Babel.
Because of this build step and the increased complexity of our apps, Continuous Integration tools and processes have become an essential part of web development. But builds themselves are not why CI is valuable, they're just a side effect of the approach we as an industry have gravitated towards, and they have some drawbacks.
Slow, fragile, (sometimes) unnecessary
The intended goal of CI is to ensure that the code we produce is of high quality and works as expected, and that the process is automated and doesn't consume precious developer time.
Unfortunately, for many large web applications, the majority of the CI time is spent on building the code, instead of verifying its quality and correctness.
One of the first frameworks for agile development, Extreme Programming, puts the ideal CI/CD time to be around ten minutes for a project. This is achievable, but when multiplied by failing or fragile builds, ten minutes can turn into a game of waiting, fixing, and rebuilding, resulting in slow feedback, developer frustration, and wasted time.
We've recently experimented with (and written about) using modern browser features such as native ES Modules and their support for importing packages directly from a CDN like unpkg.com, removing the need to bundle our source code prior to delivery.
What we've discovered is that removing the build step doesn't only speed up the local development process, but also helps us achieve CI/CD pipelines that take significantly less time and end up giving us more confidence in the code we're shipping.
Build once, run anywhere
From the CI point of view this means that our CI has gone from checking whether the build is successful and produces functional code, to simply checking the quality of the code being introduced by the developer, resulting in a quick feedback loop. On Sail CI, our entire CI pass takes only a minute,
most of which is spent installing yarn
devDependencies such as linters and test runners. As the project scales, we predict the total duration of the the CI pass will not grow significantly as the number of
dependencies doesn't grow as we add code and tests.
One of the surprising side effects of the removal of the build step was the added confidence it gave us in shipping code. We found whilst working with runpkg that "It works on my machine" was almost a reality (browser differences aside, which could be solved by running browser automation tests as part of the CI pipeline). By pulling built ES6 modules from unpkg we knew that when we pushed our work to production, the code running there would directly mirror what was on our local machine, as it hadn't been touched by a bundler.
For deployment we settled on ZEIT's Now for its simplicity. The entire application is hosted as static assets on ZEIT's CDN for free!
After the Sail CI step passes, Now's Github App integration handles deploys of every branch to a testing environment, and master branch pushes deploys to runpkg.com. Once configured these deploys were incredibly fast and allowed us as developers great confidence that we would not break the app.
Now worked as a great compliment to our build-less system and I would highly recommend it for build-less projects.
What we found was that CI/CD pipelines for build-less projects simplified what we wanted out of our processes. It allowed us to focus on what was really important in our CI pipeline, i.e. code quality and quick feedback. This resulted in increased confidence in the code we're writing and, that when the code gets into production there should be no issues.
As always when working with bleeding-edge methodologies such as ES modules, some problems crop up. For us, we found that our Cypress e2e tests were not able to run in headless mode without bundling our ES modules, and we opted for running our e2e tests locally as git pre-push hooks instead. As the build-free approach becomes more common, we expect the tooling ecosystem to support it natively.
There are also some inherent limitations to the build-free approach. When working with TypeScript or ReasonML, or huge projects that may need a bundling step, we'll need to fall back on running our builds on CI.
But for apps like runpkg, the build-free approach and lightning fast CI/CD are huge benefits, and something we'll continue to experiment with. Check out the runpkg project on GitHub to see how this approach works in practice.
So give it a go, the technology is there and the CI/CD tools (mostly) work!