nextjs-components

Early last year, I began transcribing the react components from Vercel's component library site for use on my personal site. It was initially closed source but now in 2022, I decided to open source it. This is a reflection of how I tackled it and some thoughts and learnings I had along the way.

โ€œI just want to see some code!โ€

npm i nextjs-components@latest

Background

Here's a tiny bit of background of how I arrived at the idea to transcribe Vercel's components. I made my first site in 2018, with Create React App V1, started a separate Markdown blog with Gatsby V2 in 2019, and eventually rewrote the first site as an MDX blog with Next.js in 2021.

All along the way, I found myself doing a considerable amount of CSS work like figuring out which paradigms to use as well as designing my own components. I eventually decided that this class of work was not something I was interested in anymore (I was probably focused on Cognito or Dynamo at the time), but I still wanted some solution to make a pretty site.

Enter, Vercel design.

These were probably the best looking components I had come across, but they were/are unfortunately closed source. I wanted to use them so I began transcribing them directly from the site directly into my website repo, which was also closed source.

Why didn't I open source sooner?

I initially developed these components in my closed source website repository. I kept the repo closed source because I thought I was doing two cool new things:

  1. Deploying my site with the AWS CDK (v1.91.0, released Feb 23, 2021) which I don't think was popular at the time... so I thought I was doing something hip and new and I wanted to keep it secret. In retrospect, this was pretty naive.
  2. And of course, transcribing Vercel components. I occasionally came across random tweets where some developer would copy another developer's open source portfolio, only to pass it off as their own in an interview. This is pretty evil. So I kept my code closed because I didn't want others to potentially take advantage of my free labor in that same way. Today, I simply don't care as much and it's not like my code is super amazing or perfect. Copy away! ...And speaking of copying... look at this entire blog post ๐Ÿ˜†.
Note: This project is not affiliated with Vercel or Next.js

Transcribing

Back in my college years at the New School for Jazz and Contemporary music, I used to transcribe solos of old jazz greats like Hank Mobley and Bird to learn the language, learn how to improvise in a way that didn't sound like actual garbage, and pay my dues as a student of the art.

This process of transcibing Vercel's components comes from a similar place โ€” a place of respect, admiration, and learning from someone who designed and created these things before me.

In engineering terms, you can think of transcribing as reverse-engineering, but that sounds super lame.

CSS

I relied most heavily on the Chrome inspector for my transcription needs. The first step was usually copying over a large chunk of style declarations to a local css module.

Button CSS
Button CSS

Then I was able to conclude a few things from an element based on the DOM element.

In an example such as the following,

styled-jsx

jsx-3783837704 meant that styled-jsx was used

Global styles

geist-container meant that there was a global class declaration somewhere

CSS modules

badge_badge__LNH0p meant that a css module was used. In this case the file would be badge.module.css and the selector would be the derived by stripping away the module & hash in [module]_[local]__[hash:base64:5] โ€” so, .badge

JSX style={{...}}

--flex:1; --justify-content:center; --align-items:center; meant that css variables were set via the style attribute on a JSX element.

For dynamic class names, whether they were via props or state updated by user interactions, I used my best judgement and handled that in JSX logic with the help of clsx.

React Devtools

At times, there were complex behaviors that weren't easy to conclude just through the chrome inspector, so I reached for the React Devtools to view things like hooks usage, or context structures.

React Devtools
React Devtools

I noticed a few hook names that were shockingly close to react-aria's, so I added that as a reasonable dependency. It's also a great library in general ๐Ÿ‘.

Viewing source

There were times that I found myself needing to grok the bundled JS source (not pretty), when I couldn't figure out what I needed through the inspector or react devtools alone.

Inspecting bundled JavaScript source
Inspecting bundled JavaScript source

Result

The result of transcribing a single component looked something like...

npm

Bundling and publishing this project to a public registry, like npm, from scratch was actually new to me. I had previously only worked around existing CD pipelines, like Jenkins โ†’ Artifactory, or GitHub actions to npm, and hadn't dealt with the end-to-end process myself, so this was cool to figure out.

TypeScript support

I started with TypeScript from the beginning. This was a no brainer.

Babel over SWC, unfortunately

I had initially reached for swc for .tsxto .js compilation needs, and it was fast A.F.

2555.26% times faster than babel... (is my math correct here? ๐Ÿคจ)

But there isn't any existing support/plugins for transforming styled-jsx, so I needed to use babel instead. It was significantly slower, but realistically very much an acceptable trade off at my scale.

๐Ÿ’ญ The TypeScript compiler is probably worth looking into as well.

Picking a name

Choosing some cool hip package name was not a rabbit hole that I wanted to waste my time going down, so I picked a name on a whim and called it a day โ€” nextjs-components.

Publishing

After compiling and copying over files to an output folder, npm publish was all I needed to get nextjs-components live (an npm account is required of course). There are some missing automation pieces that I'm looking to add in the near future.

Takeaways

Editor component

Figuring out how to build the live Editor component was one of the cooler things I discovered. Wiring it up as an MDX component was also an interesting challenge.

Code Editor
() => {
const [count, setDogs] = useState(1);
const increment = () => setDogs((d) => d + 1);
const decrement = () => setDogs((d) => Math.max(d - 1, 1));
const dogs = Array(count).fill("๐Ÿถ").join("");
return (
<>
<Container center>
<Text h2>{dogs}</Text>
</Container>
<Container direction={['column', 'row', 'row']} hcenter>
<Container left>
<Button onClick={() => alert("How dare you!")}variant={""} type={"error"}>
Bonk!
</Button>
</Container>
<Container left>
<Button type={"warning"} disabled>
No touchy
</Button>
</Container>
<Container left>
<Button onClick={increment} variant={"shadow"} type={"success"} prefix={<Plus/>}>
Add
</Button>
</Container>
<Container>
<Button onClick={decrement} variant={"ghost"} type={"warning"} suffix={<Minus/>}>
Remove
</Button>
</Container>
</Container>
</>
)
}

See implementation, powered by react-live

Friction

There were a few components like Entity and micro-interactions like focus ring and hover behavior on Button that were very tedious and tricky to nail down.

There are also several inconsitencies on the Vercel design page with one being multiple implementations of Button despite there being only one documented component. So I had decide on what I wanted to avoid transcribing.

Next Steps

This project is still a work-in-progress (v0.1.0-rc50 at the time of writing this). There are still several complex components that I haven't gotten around to transcribing, and there are some components that are in-progress.

See the repo projects for work being tracked.

I'll do my best to keep this up to date in between full time work and life priorities

Give it a star โญ๏ธ if you like what you see!

If you have any questions or comments, please don't hesitate to reach out directly! ๐Ÿ™

Thanks for reading! ๐Ÿ™‡๐Ÿป

Comments