How to fix npm packages
![How to fix npm packages](/content/images/size/w2000/2022/06/2022-06-19-12-17-25.png)
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.
![](https://colonel.shnyra.com/content/images/2022/06/image.png)
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.
![](https://colonel.shnyra.com/content/images/2022/06/image-3.png)
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).
![](https://colonel.shnyra.com/content/images/2022/06/image-1.png)
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:
import clsx from 'clsx';
import style from './style.module.scss';
<Button
className={clsx({
[style.autoMargin]: props.fullWidth,
// β ^ A computed property name must be of type 'string', 'number', 'symbol', or 'any'.ts(2464)
})}
/>
{children}
</Button>
// error is equivalent to
const demo = { [undefined]: true };
// β ^ A computed property name must be of type 'string', 'number', 'symbol', or 'any'.ts(2464)
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
:
const panic = <T extends unknown>(value: T): NonNull<T> => {
if (value === undefined || value === null) {
throw new Error('value undefined');
}
return value;
}
// π runtime check via panic looks like over engineering
<div className={panic(style.autoMargin)} />
// π non-null assertion is a lie to compiler, do not spoil the code
<div className={style.autoMargin!} />
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.
![](https://colonel.shnyra.com/content/images/2022/06/image-2.png)
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.