How to fix npm packages
Web development is full of external dependencies and frontend developers and NodeJs developers are especially depend on node_modules
which we know are the heaviest objects in the universe.
Recently I faced with a critical issue with one of my code packages - NextJs
caused a compiler error, so I was unable to build my app with TypeScript.
Since NextJs
is a core dependency of my project, I cannot replace it with something else, so I have to find a workaround on fixing npm package in a quick way. Issue details will be explained below, but first let's define general goals on the fix:
- Solution has to be explicit, universal and elegant
- Solution has to be
git tracked
- Without Pull Request on GitHub repo (no time to wait for maintainers attention and approval)
- Without forking GitHub repo to create my own version of package (I will not maintain this package as good as maintainers of official NextJs framework)
- Solution has to be easy maintained after package version upgrade
Patch-package to the rescue!
Hopefully, some good developers from a community already took care about my case - patch-package
is an npm package which patches source code of any package as if I would directly commit changes to package while still git ignoring node_modules.
Solution demo
Solution was very simple and fully aligned with general goals defined abode.
Screenshot below shows a full needed changes to my package to build the app without compiler errors. (if you ignore description comments, solution is just a few lines of code).
Issue use case
In my situation, the issue was with global typings .d.ts
files placed deeply inside git ignored source code of npm dependency - typings of CSS modules are defined in an incompatible way with my tsconfig.json
restrictions.
Original NextJs global.d.ts
file makes all classes imports from *.module.scss
in form of string record:
const classes: { readonly [key: string]: string }
What is wrong with it? It is weakly typed - you can refer to any arbitrary class name in your code and by default TypeScript compiler will be fine with that allowing potential runtime bugs, especially in future:
import style from './index.module.scss';
// no error by compiler π
<div className={style.nameWithTypo} />
// no error by compiler π
<div className={style.anotherTypoMissedHere} />
// no error by compiler π
<div className={style.previouslyDefinedButRemovedOrRenamedNow} />
Hopefully, we can make TypeScript compiler more stricter and force us to check all arbitrary members of objects via enabled option noUncheckedIndexedAccess
in tsconfig.json
. With this option, all our classes from module css files are potentially undefined.
So now, every object member checked by compiler and raise compile errors in cases like that:
In code above, style.autoMargin
potentially undefined
and therefore not allowed to be used as key for literal object.
Of course I do not want to check in runtime existence of my class name. Also I do not want to lie to my compiler via terrible non-null assertion operator
:
So, the solution is to change module css typings
declare module '*.module.scss' {
const classes: any
export default classes
}
Now we don't need to handle potentially undefined values π
But wait, your classes: any
is even worse than a string record
Not exactly, because I use other technique to keep my CSS modules names relevant - TypeScript plugin typescript-plugin-css-modules
.
typescript-plugin-css-modules
shows a fully typed object of CSS classes availableThis way, my favourite IDE VS Code helps me to know relevant CSS classes and highlight issues like typos.
I believe this approach is a good balance between development convenience and type reliability. For something more important than classnames, I would rely on real compiler check (no lies via :any
or !
or as SomeType
) or even runtime check like with a panic
util above.