Hi! In a project in my last company, I ran into a weird issue with the modern JavaScript build system and to work around it, wrote an even weirder hack.
Sorry if you found the title being click-baity. Alternative title: Using constant assertions in TypeScript to keep constants in sync.
The Problem
The project was created using create-react-app (CRA) with TypeScript and it also had an Express server, that too in TypeScript. The project structure was simple (and an even more simplified version is shown here).
./
src/
App.tsx
server/
app.ts
then there was a constant.
const options = ["1 day", "1 week", "2 weeks", "1 month", "1 year"];
The constant options
, was required in client side to show a list of allowed options in a <select>
and the server needed it for validating the requests coming from client. Now using the DRY principle, I shouldn't be writing that same value of options
in two files - as the values at two places may get out of sync. So, it needed to be shared between src
and server
- that's where the problem started.
Turns out, create-react-app doesn't allow imports outside src
directory (See this Stack Overflow post), so I can't import options
from server
directory.
Hey Sid, why don't you do import from src
instead?
// server/app.ts
import { options } from '../src/App.tsx';
The build process in server
was simple. Take a .ts
file, and run it through tsc
to create a .js
file, keeping the same directory structure.
So, when I ran tsc
on my server/app.ts
, instead of creating a directory structure like:
./server-build/
app.js
it ended up creating:
./server-build/
server/
app.js
src/
app.js
Oops. Messing up build directory or build process just to share a constant? Not cool.
The Hack
A prior day, I came across constant assertions, a new TypeScript feature.
Turns out, in create-react-app, we can't import values outside src
directory, but we can import types.
// server/app.ts
// a const assertion
const options = ["1 day", "1 week", "2 weeks", "1 month", "1 year"] as const;
// export the type instead of value
export type Options = typeof options;
// src/App.tsx
import { Options } from '../server/app.ts';
const options: Options = ["1 day", "1 week", "2 weeks", "1 month", "1 year"];
How does it work? Lets say, I change "2 weeks" in server
to "3 weeks". What happens? Well, the client fails to compile! It fails as it's expecting options to have type Options
, but "3 weeks" doesn't exist in Options
at index 2. It works similarly if I change only in src
.
So, we've two variables in sync using TypeScript. Not very DRY, but it worksβ’οΈ.
See for yourself:
Top comments (0)