Mon Jun 26 2023

Refactoring! The musical

by e(Liz)abeth (Mars)ton

Don’t worry, I haven’t gone anywhere! I’m currently hard at work refactoring the blog toolkit I authored in order to create this blog, which I named sbstr8.

Refactoring, as other engineers probably know, is the art of generalizing or otherwise mangling code into a new shape. I’m doing a major refactor, but I’ve committed to not break the build at every step!

First of all, I made myself a fresh git branch.

git checkout -b refactor

And the whole time, to ensure I haven’t broken the build, I have the site up and running in development mode at http://localhost:3000 with npm run dev. Crucially, I never break the build; each commit is a functional snapshot of this website! Yes, this is a flex :3

In this new git branch, I’m moving source files individually from e.g. /src/components/foo.tsx into /src/sbstr8/components/foo.tsx.

Were I using VS Code, there’d probably be some fancy tool to move and refactor files, but I’m neovim all the way, baby, which means I need to improvise with some regular expressions.

sed is of course the usual tool that one would use under a unix-like OS, but venerable sed lacks previews, and besides, it’s boring.

So instead, I’m using sad, an amazing, efficient CLI tool that lets you opt in and out of each change.

Under the hood it uses delta for diff visualization and the lovely and powerful fzf for listing, with fd for speed and sanity.

The full incantation (from the repo root) looks like:

pushd src; fd | sad '@/components/foo' '@/sbstr8/components/foo’; popd

I exploit the fact that I’m using @-prefixed import paths to build regular expressions that neatly target just the imports.

When I’m satisfied, I stage the result with git add -p, walking through the changes (thanks to delta again for the highlighting.)

Sometimes I’m also renaming components as I go; for this, I do another find/replace (messier and more ad-hoc, a pattern enabled by sad) search for e.g. Foo and approve or disapprove of each offered conversion (to Bar, say.)

Then, I git mv the files so they are actually in the right place:

git mv src/components/foo.tsx src/sbstr8/components/bar.tsx

I then commit the change. Crucially, each change is atomic. So I can roll back the refactoring of any given file.

The next step is to add Jest snapshot tests and Storybook stories under ./src/sbstr8, so I can see what is changing and how.

Then, I’ll copy the ./sbstr8 directory back into the sbstr8 repo. At this point, I’ll also strip out style information (I don’t want every sbstr8 site looking like; I want the blog engine to be nearly ‘headless’ (visually unopinionated).

When everything is kosher over at sbstr8, I’ll merge into from sbstr8.

And this is the fancy part! At this stage, I’ll revert most of the commits I made above.

As I revert each commit, I’ll wind up with:

  1. a stylized, component under src/components/, and
  2. an unstyled base component under src/sbstr8/components.

I’ll then copy the snapshot jest tests and stories (which I will need for the next step) from the ./src/sbstr8/components directory into ./src/components.

I’ll then use those tests as a so-called ‘harness’ while I modify each of the components to add styling to the now-unstyled sbstr8 base component.

There will be some other steps as well, with a similar philosophy, for e.g. handling sitemap.ts. This absolutely will take another day or two.

But! The payoff for this elaborate dance is pretty epic: I’ll have an unbroken build the entire time.

And that’s how you do a major refactor with both efficiency and caution.

Don’t believe me? You’re reading these very words in the refactored branch, halfway through refactoring!

Photo Wikimedia Commons. CC0 1.0. Universal Public Domain Dedication

Calligraphic embellishment of the name 'Liz'