Azure Functions + esbuild = ๐๐คฏ
Reducing bundle size and build time with an awesome new tool.
I first discovered esbuild
as a builder for Angular in this Reddit post. I was amazed! Seeing this, I thought: "There must be other ways to utilize this!"
What is esbuild
?
esbuild
calls itself "An extremely fast JavaScript bundler". I can definitely confirm that! And the main reason for that is, that it's written in Go - unlike our ol' reliable tsc
. Also, it's plugin API makes it extremely flexible. Did I mention that it also has native TypeScript support?
What advantages does this bring?
- Reduced bundle size:
esbuild
has two features that enable this - bundling and code-splitting. These features allow us to compile all external dependencies (from our node_modules folder) into the final bundle. Becauseesbuild
can also minify the bundle, this decreases bundle size drastically - Reduced build time: Like I mentioned before -
esbuild
is extremely fast. Combined with a watch mode this makes for crazy fast development speed. Build time for my Function App with 28 Functions: 800ms ๐คฏ Now, this is only half the truth.esbuild
doesn't do type checking so we have to runtsc --noEmit
at least in our CI pipeline (which isn't quite as fast unfortunately).
How do I build Azure Functions with it?
Great question! Here's how you can do it:
First, install esbuild
using npm: npm i -D esbuild
Then, create a build script in the root of the project:
// build.mjs -> .mjs so we can use TLA (Top-level-await)
import { build } from 'esbuild';
await build({
entryPoints: ['my-func/index.ts'],
format: 'esm',
splitting: true,
minify: true,
bundle: true,
platform: 'node',
target: 'node12'
sourcemap: false,
watch: false,
outdir: 'dist',
outExtension: { '.js' : '.mjs' }
});
Now, simply adjust the scriptFile
property in every function.json
to account for the new file extension and you're good to go.
Or are you? ๐ง While writing this article, I fell into some traps and rabbitholes. Like for example:
__dirname
and__filename
not being available in ESMesbuild
not being able to convert CJS requires to ESM imports- The output directory not being correct when there is only one Azure Function
What now?
These pitfalls and rabbitholes caused me to develop a tool that catches all of those and allows for easy extension of the esbuild
config that I think is a good default. You can also either use it from the CLI or via code (which I would recommend).
Let's have a look:
First, install it in your project using npm: npm i -D esbuild-azure-functions
.
Then, create a build script in the root of the project:
// build.mjs -> .mjs so we can use TLA (Top-level-await)
import { build, BuilderConfigType } from 'esbuild-azure-functions';
const config: BuilderConfigType = {
project: process.cwd(),
esbuildOptions: {
outdir: 'myoutdir'
}
};
await build(config);
And that's the basic config. By default, the tool looks for every index.ts
file in the project
directory. For a more detailed documentation, checkout the GitHub repo below!
Benchmark
To wrap it up, here are some of the number I've observed
Bundle size
Bundle size decreased massively. Most of that probably comes from the fact that we don't just throw the node_modules
folder into the app package.
Now, I've included zip file sizes for both Linux and Windows. I run my Node.js Azure Functions on Windows and Windows produces a zip file that's quite a lot smaller than one created on Linux.
Build time
As mentioned above, build time is very fast.
I hope you liked this blog post! If you have any questions, feel free to hit me up!
Cheers!