Hi! We have an API setup on AWS with Lambdas and API Gateway. Our code runs on node 20 and we use TypeScript and ESM. All runs well on V3 but we can’t get it to work on V4. On V3 we use the serverless-plugin-typescript plugin but it requires Serverless V2 or V3.
So we moved to esbuild. The out of the box solution did not work at all with ESM for us. We were able to get it running with serverless-offline with the custom configuration below and using the serverless-esbuild plug in. However, it doesn’t work on AWS. The main problem we have is that esbuild seems to miss lots of code. For example, Prisma binaries.
If we disable bundling on esbuild, we get a message that we can’t have external and no bundling but we don’t have external declared anywhere. This happens with both the out of the box solution and the esbuild plugin. Setting external to false on the configuration below has no effect. We thought about adding the missing files manually through package.patterns on the serverles yml but is also also has no effect.
We are using severless 4.15.0, serverless-esbuild 1.55.0.
esbuild.config.cjs
// Based on https://dev.to/slootjes/optimizing-typescript-packages-in-serverless-framework-with-esbuild-1ol4
const {Buffer} = require('node:buffer');
const fs = require('node:fs');
const path = require('node:path');
const excludeVendorFromSourceMap = (includes = []) => ({
name: 'excludeVendorFromSourceMap',
setup(build) {
const emptySourceMap = '\n//# sourceMappingURL=data:application/json;base64,' + Buffer.from(JSON.stringify({
version: 3,
sources: [''],
mappings: 'A',
})).toString('base64');
build.onLoad({filter: /node_modules/}, async (args) => {
if (/\.[mc]?js$/.test(args.path)
&& !new RegExp(includes.join('|'), 'u').test(args.path.split(path.sep).join(path.posix.sep))
) {
return {
contents: `${await fs.promises.readFile(args.path, 'utf8')}${emptySourceMap}`,
loader: 'default',
};
}
});
},
});
module.exports = () => {
return {
format: 'esm',
platform: 'node',
target: 'node20',
sourcesContent: false,
keepNames: false,
bundle: true,
minify: true,
external: [],
outputFileExtension: '.mjs',
sourcemap: false, // Removing source maps as the bundle size is too large with them
banner: {
// https://github.com/evanw/esbuild/issues/1921
js: "import { createRequire } from 'module';const require = createRequire(import.meta.url);",
},
// Fix for __dirname is not defined in ES module scope
inject: ['cjs-shim.js'],
};
};
Where cjs-shim.js is the below.
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
global.__filename = __filename;
global.__dirname = __dirname;