awwsmm.com
- The Learning ProcessLearning about TypeScript, Next.js, the GitHub API, and more
Boy have I learned a lot over the past few days.
As you can see from the recent round of commits I've made, I have been working on a few things...
Requiring
Commit
s to have a defineddate
This commit moves from a
Commit
with adate: string | undefined
field to one with adate: string
field.Originally, I tried to make my model class (
Commit
) match the GitHub API as closely as possible, and sinceGET /repos/{owner}/{repo}/commits
can return commit objects with possiblynull
author
s as well ascommitter
s, it's possible for a commit returned from the GitHub API to not have a date at all.That... seems unlikely, so I now assume that all commits will indeed have dates, and throw an
Error
in theCommit
constructor
ifdate
isundefined
. This can be tested during development, and (combined with the next change) means that I've removed all explicitundefined
types from the current codebase.Require
CommitGroup
s to have at least oneCommit
This commit changes a lot of things. Firstly, it requires that
CommitGroup
s have at least oneCommit
in them. It does this by throwing anError
from theconstructor
if an attempt is made to create aCommitGroup
with an empty array ofCommit
s.This is really helpful because if a
CommitGroup
must have at least oneCommit
and (from the last update) if aCommit
must have a definitedate
, thenCommitGroup
s can have definitestart()
andend()
timestamps. This means thatstart()
andend()
can have typeDate
rather thanDate | undefined
. That change removes the last explicitundefined
types from the codebase. This means we shouldn't have to do much more "?.
" / "??
" checking.Ease serialisation by moving from methods to fields
The reason I haven't updated this project in a few days is because I tried to do too much all at once (I fall into that rabbit hole sometimes). One of the things I was trying to do was to return
class
es (rather than plain objects) from thegetStaticProps
methods in my dynamic route definition files (/pages/blog/[slug].md
and/pages/projects/[id].md
).I ran into some issues here (specifically around defining custom
toJSON()
methods) and everything went a bit haywire so I've backed that out of this group of commits for now.Anyway, the point is: serialisation is confusing enough as-is without including a bunch of methods and custom
class
es. So I'm trying to store as much data in object fields, rather than in methods which calculate some value. To that end, I moved all of the methods inProjectUpdate
to fields in these updates, which means thatLogEntry
andCommitGroup
also have these as fields now instead of methods (start
instead ofstart()
for example).This made it easier to make the next change, as well.
Make
CommitGroup
class immutableAs
start
andend
are now fields ofCommitGroup
rather than methods, and asCommit
s are themselves immutable (containing onlyreadonly
fields), it made sense to try to makeCommitGroup
s immutable as well.In general, immutability of objects is something worth working toward. It makes the state of an object easier to reason about and can eliminate race conditions in multithreaded environments (so, not JavaScript, really). However, immutability can make your program less performant, as you have to create an entirely new object whenever you want to update a field.
This was easy enough -- just making the array of
commits
readonly
, and makingadd()
return a newCommitGroup
rather than mutating the current one makes the class effectively immutable. We just need to remember to save the result ofadd()
to a new variable when updating aCommitGroup
.Try not to hit the GitHub rate limit
Finally, I made one small band-aid kind of change, where I don't query GitHub for commits when in development mode. This is easy enough to check in Next.js
if (process.env.NODE_ENV === "development") { // do development stuff } else { // do other stuff }
so I simply return an empty array of
Commit
s when in development mode. A better solution here would be to- query GitHub for commits
- save those commits to a temporary cache file
- don't query a second time if a cache file is present
- delete the cache file on a
git push
This would give a better idea of what the site will look like when it goes live, but it's a bit more involved, so I'll do that later. For now, I just return an empty array.
Potpourri
Finally, in this commit I did some refactoring, moving some classes around in a way that seemed to be a bit more organised.
And in the last few commits this round, I hit a bug where I was trying to use
String.replaceAll
, but I was targeting an older ES version which doesn't support that method.So, all in all, lots done here, but lots more to do.