Just about every developer that I meet has one, or more, side-projects that they work on. My primary side project (an Alexa skill called Movie Quiz) has helped me learn several new technologies over the years. Working on this skill has taught me everything from DynamoDB single-table design to CI/CD pipelines.
I recently learned about NestJS at work and the power of Inversion of Control and Dependency Injection. It just so happens that someone kindly added Dependency Injection to the framework that I use for my skill. Also, I've been having a difficult time getting some updates in the skill through certification, so it seemed like a good time to implement DI and write more some unit tests.
Issues with esbuild
Esbuild is a super fast typescript bundler. It gains this speed by completely ignoring the typescript. The problem with that is the Dependency Injection that I wanted to use relies on TypeScript Decorators. I had to turn on emitDecoratorMetadata and experimentalDecorators in my tsconfig file, but since esbuild just ignores that, all I get are errors.
Slowing Down esbuild
The solution was to get a plugin that negated all of the performance gained by ignoring TypeScript. The plugin runs each typescript file through tsc before passing it to esbuild.
First, install the esbuild-plugin-tsc
package:
pnpm install -D esbuild-plugin-tsc
Now import it and pass it into your esbuild configuration. I'm using esbuild with serverless stack, so the options are a little nested, but you can use plugins directly with esbuild as well.
import esbuildPluginTsc from 'esbuild-plugin-tsc';
...
const handler = new sst.Function(stack, 'Handler', {
nodejs: {
esbuild: {
plugins: [
esbuildPluginTsc({
tsconfigPath: path.join(HANDLER_ROOT, 'tsconfig.json'),
}),
],
}
}
});
Use the Right Format
In addition to using the plugin, I had to update my esbuild options to create commonjs formatted output. While this was frustrating to figure out, it was pretty easy to implement. Just set the format
setting to cjs
. In the serverless stack function this is done in the nodejs
configuration:
nodejs: {
format: 'cjs',
esbuild: {
...
}
}
Summary
In this article, you've seen how to use decorators in a TypeScript project that's bundled with esbuild. Hopefully, decorators will be standardized soon and we won't have to worry about all of this. In the meantime, I hope this article saves a few people from the frustration of getting this to work!