better-firebase-functions-esbuild
v7.1.1
Published
esbuild plugin for optimized Firebase Cloud Functions builds. Per-function entry points with tree shaking.
Maintainers
Readme
better-firebase-functions-esbuild
esbuild helper/plugin for better-firebase-functions.
This package discovers your Firebase function files using the same config already present in your BFF entry point, then builds one bundled output file per function.
Install
npm install -D better-firebase-functions-esbuild esbuildHow discovery works
The plugin executes your entry point in a special BFF build-discovery mode. That means it reuses the exact runtime config already passed to exportFunctions() or exportFunctionsAsync():
functionDirectoryPathsearchGlobfuncNameFromRelPath__dirname
In the common case you only provide entryPoint to the build helper. No duplicated glob config is required.
Recommended usage: buildFunctions()
Keep your runtime config in the entry point:
// src/index.ts
import { exportFunctions } from 'better-firebase-functions';
exportFunctions({
__filename,
exports,
functionDirectoryPath: './functions',
searchGlob: '**/*.func.js',
});Then build with esbuild:
import { buildFunctions } from 'better-firebase-functions-esbuild';
import { resolve } from 'path';
await buildFunctions({
entryPoint: resolve(__dirname, 'src/index.ts'),
outdir: resolve(__dirname, 'dist'),
target: 'node20',
verbose: true,
});Output layout
If the source trigger is:
src/functions/auth/on-create.func.ts...the bundled output will be:
dist/main.js
dist/functions/auth/on-create.func.jsThe runtime layout is preserved, so main.js can keep using the same functionDirectoryPath and searchGlob you configured in the entry point.
Manual discovery helpers
discoverFunctionEntryPoints()
Returns a runtime-oriented map of { functionName: absoluteSourcePath }.
import { discoverFunctionEntryPoints } from 'better-firebase-functions-esbuild';
const entryPoints = await discoverFunctionEntryPoints({
entryPoint: resolve(__dirname, 'src/index.ts'),
});discoverBuildEntryPoints()
Returns build-ready esbuild entry points whose output paths already mirror the runtime BFF layout.
import { discoverBuildEntryPoints } from 'better-firebase-functions-esbuild';
const { entryPoints, discovery } = await discoverBuildEntryPoints({
entryPoint: resolve(__dirname, 'src/index.ts'),
});
await esbuild.build({
entryPoints: {
main: resolve(__dirname, 'src/index.ts'),
...entryPoints,
},
outdir: 'dist',
bundle: true,
platform: 'node',
format: 'cjs',
});Logging plugin: bffEsbuildPlugin()
esbuild's plugin API cannot add entry points dynamically, so the plugin only logs discovery information. Use buildFunctions() for actual per-function bundling.
import { build } from 'esbuild';
import { bffEsbuildPlugin } from 'better-firebase-functions-esbuild';
await build({
entryPoints: { main: 'src/index.ts' },
outdir: 'dist',
bundle: true,
platform: 'node',
plugins: [
bffEsbuildPlugin({
entryPoint: resolve(__dirname, 'src/index.ts'),
verbose: true,
}),
],
});Fallback manual overrides
If your entry point cannot be executed directly during the build, you can still pass discovery overrides manually:
await buildFunctions({
entryPoint: resolve(__dirname, 'src/index.ts'),
functionDirectoryPath: './functions',
searchGlob: '**/*.func.js',
funcNameFromRelPath: myCustomNameGenerator,
outdir: 'dist',
});These overrides are a fallback only. The preferred approach is keeping the discovery config in the entry point and letting the plugin load it automatically.
Notes
- Your
searchGlobshould describe runtime files, usually compiled.jsfiles. - During build-discovery, BFF automatically expands
.js/.cjs/.mjsglobs to match source.ts/.cts/.mtsfiles too. - If you use a custom
funcNameFromRelPath, esbuild discovery reuses it automatically because it is loaded from the entry point itself.
