The web is amazing.
Amazingly complex, that is.
The web has become the most fragile part of most software projects, the slice of the stack that is the most likely to break or require costly updates in the long run.
Set up a new project with all that a fresh web project requires—bundling, testing, frameworks–and you’re off to a running start.
Until your coworker tries to clone the repository and gets eight security advisories during
npm install on a four-hour-old project.
Another coworker doesn’t even get past the install step. One of the testing dependencies is “opinionated” and won’t install. You’re both using desktop Linux—but their flavour is different enough for install to fail.
A week passes and your
webpack config has already become an inscrutable document whose meaning is arcane lore.
Switch work on another project for a few weeks and when try to get it up and running on another machine you discover that…
…It won’t run any more,
…the dependencies all have security advisories listed,
…Typescript suddenly complains about code it used to think was fine,
…the bundler bails with an obscure webpack error,
…and tests are failing,
…all happening even though you haven’t touched an actual line of code yet.
These days, there’s no such thing as a small or medium-sized web development project. Everything has the complexity of a major investment of time.
Node is a bad fit for most web development projects. It is an especially bad fit for projects that aren’t using Node for the server in the first place.
Imagine if everybody had to wrangle Ruby or Rust installation and hundreds of arcane gems or crates every time they wanted to add code to a website?
Web Development with Node is a complexity tax on all software development
The biggest pain point in many PHP or Ruby on Rails projects is the front-end tooling.
You know that feeling of dread: starting a new PHP, Ruby, or Python web project and trying to navigate Node, just to be able to work on the front end.
—Oh, no. Why do we have all of these security alerts? All we’re using the JS for is to validate some forms! Most of these alerts are for server-side Node libraries. The framework can’t be including them all in the bundle, right?
—Why doesn’t the testing framework work any more? It used to work in our other projects.
—Crap! The new version of the form-handling library has changed its API. Again. Now I’m juggling three different APIs across four projects even though I’m using the same library in all of them.
—Damn. There’s a new version of Typescript out. Now I need to figure out what they’ve changed.
Most of us have stories and none of them are of the happy “they all lived happily ever after” kind.
It’s a pain, but node, bundling, and the rest is just a hard requirement for modern web development, right?
It doesn’t have to be this way.
What if it were painless?
Imagine starting a new server-side project…
You don’t need to install a Node-based test runner—just a couple of text files and you can run the tests in genuine browsers.
No matter how much time passes, working tests will only fail if you actually change something.
You have manageable processes for your dependencies that are tied into your testing. Even when something does break, you almost always know exactly where.
You only bundle the code for deployment if your testing shows the project needs it for loading speed.
No additional tooling is needed. Just what comes with your Operating System, shell, and your Ruby, PHP, or Python framework. HTML is our test runner and any framework that works with HTML can play.
There is a little bit of Node, but it’s contained entirely in a Google Action and exists only to give the action the same capabilities you have during development.
Web development can be much less complicated.
This isn’t a fantasy
Web servers and browsers are amazing tools in their own right. One feature in particular is about to change the way many of us code for the web.
Import maps give you many of the conveniences you expect from node package management and bundling, but without the complexity and overhead that comes with Node and
Where they shine in particular is in testing, where their ability to remap module paths on the fly simplify many tasks, from mocking to dependency management.
Import maps will be the new default for Ruby on Rails, are supported in Symfony using the experimental
AssetMapper component, and they are a great match for web development projects that aren’t built with Node, whether they be in Python, PHP, Ruby, Rust, or Go.
What Uncluttered offers you
Uncluttered is a 20 000 word text-oriented course that:
- Will guide through setting up a web development project that combines everything you need for modern web development, but without locking you in – or out of – other approaches in the future.
- It includes a link to a GitHub project template I’ve made, that you can use to get up and running.
- Provides a thorough introduction to Import Maps and how they can be used to help you with testing, dependency management, and structuring your project.
- Introduces you to test-driven development as it applies to the web including an overview of how you can apply the approach to solve concrete problems in your web development workflow.
- Outlines several import map strategies you can use to prevent dependency hell.
- In addition to a web course that’s hosted by Teachable, you can also download an EPUB or PDF version of the course for offline reading.
Mocha for testing in the browser and out
This means that if, for any reason, the browser-based setup becomes too limiting for your project, there are a number of migration strategies for you that you can use without rewriting a single test.
How to set your browsers up for reliable testing
Browsers are designed to be end-user software whose settings and state is tailored to each individual. Reliable web software testing requires predictable state.
Those two aren’t entirely compatible. The course has a lesson that talks you through approaches towards session management in the big browsers and offers potential automated solutions.
Test-driven development without the religious fanaticism
TDD is a solution. It fixes certain problems. It will not work for everything.
But, it really does work for very many problems.
The course will introduce you to the basic concepts behind unit, integrated, integration, and end-to-end testing – I even have a go at explaining why the terminology is so confusing. I go over mocking or test doubling – how those two terms represent two different approaches in testing and which one I recommend and why.
Dependency management from the future
Until import maps were introduced, we only had one realistic option for dependencies in web development:
Bundle them all together into statically linked files like an overworked Unix developer from the 1970s.
JS is descended from the Smalltalks, Schemes, and Common LISPs of history. Languages that are dynamic at every level and in every way and for whom static binary deployment means taking away much of what make them special.
Pretending that it’s a statically typed language in the style of C or C++ will only lead to defective software and miserable developers.
In true dynamic language fashion, Import Maps are a new primitive in web development. It lets you dynamically, conditionally, and on-demand remap any JS module to another.
It’s the building block of future dependency management solutions.
And instead of being a hard requirement for deployment, bundlers become a performance optimisation – something you can introduce granularly to solve a specific problem you know you have, and whose complexity you can otherwise avoid.
Uncluttered: free yourself from Node with import maps and test-driven web development is a web-based course that will guide you through test-driven web development using just the web browser, a web server, some HTML, and some JS modules that run natively in the browser without building.
Instead of the usual video course or thick book, it’s structured as a series of bite-sized written lessons, averaging at around 500 words or 2–4 minutes of reading.
Some of the lessons show you one of the steps in setting up a Node-free development environment.
Others are examples of how test-driven development would work in practice. A few of them are context—nuggets of advice based on years of web development experience.
It’s an even mix of practice and context. Some lessons show you how to do a thing. Others present food for thought.
It has plenty of code examples throughout and shows you how import maps give you web development superpowers.
Import maps are the best thing to happen to test-driven web development in years.
You can drop in and read a short lesson in between the many other things we all have going in our lives.
What matters is what works for your, for your project, for your workplace.
Who is Baldur Bjarnason?
I’ve been making websites for over twenty-five years, working as a web developer (front-end, back-end, or full-stack) for both companies and charities, small and large. I’ve written two books on topics related to software development and have been writing about web development online for years.
In the past I’ve worked with Node, Ruby, Python, and PHP projects, so I have first-hand experience with the disconnect that can exist between Node-based front ends and servers written in other languages. I’ve even had the misfortune of once working on an ecommerce site whose core components were written in C and then wrapped in PERL.
It wasn’t pretty.
I’ve seen things and my goal with my books and courses is to make sure you never have to.
PART ONE: SETUP
- WTF is test-driven development?
- You’re going to need a web server
- Loosely coupled systems
- Setting up the project
- Set up the web server (don’t just curl|sh random scripts)
- Don’t over-engineer basic setup
- The browsers are our runtime
- The browsers are our primary web development tool
- EXAMPLE: browser launch scripts
- EXAMPLE: the mocha HTML file
- Continuous Integration (CI)
PART TWO: ES MODULES AND IMPORT MAPS
- On code smell and gut feeling
- We can do better
- Managing dependencies in code is bad
- Import maps bring loose coupling to ES modules and it’s genuine magic
- We can use import maps right now, today
PART THREE: TESTING!
- Where to begin?
- EXAMPLE: the actual test
- And we’re d—oh, wait, there’s more?
- Classicist testing
- Mockist testing
- Which school to choose?
- EXAMPLE: mocking sources of randomness
PART FOUR: DEPENDENCIES
- We have to talk about dependencies
- Consider avoiding external dependencies altogether
- Implicit dependency testing
- EXAMPLE: the Mockist approach
- Update and test one module at a time
- Split your dependencies into layers
PART FIVE: INTO PRODUCTION
- Deployment strategies for import maps
- If your platform has a strategy, use that
- Progressive Enhancement
- Polyfill and commit
- Bundle using
esbuildand a plugin
- Which should you choose?
- Then what happens?