1.利用 esbuild 编译 typeScript 到javascript(有依赖)
我们可以使用typeScript来开发Lambda函数。但是lambda本身运行不了typeScript,需要我们通过esbulid,先build成Javascript,我们下面的例子会把依赖的代码,都打入到javascript中。

下面是typeScript的例子:
lambda.ts

package.json
{
"name": "aws-lambda-layer",
"version": "1.0.0",
"description": "aws lambda layer demo",
"main": "index.js",
"scripts": {
"build": "esbuild lambda.ts --bundle --minify --sourcemap --platform=node --target=es2020 --format=esm --outfile=dist/index.mjs",
"postbuild": "cd dist && zip -r index.zip index.mjs*"
},
"author": "",
"license": "ISC",
"dependencies": {
"@types/aws-lambda": "^8.10.109",
"esbuild": "^0.15.16",
"lambda-local": "^2.0.3",
"aws-sdk": "^2.1279.0",
"class-transformer": "^0.5.1",
"mongodb": "^4.12.1",
"axios": "^1.2.0"
}
}
注意 “scripts” 里面的参数
–bundle:会把 dependencies相关的依赖包都打进build好的javascript里面。
在 esbuild 中,bundle 是一个构建选项,用于指示 esbuild 打包所有代码并输出一个捆绑后的文件。具体来说,它将编译器的工作和模块加载器的工作结合起来,使得在浏览器或 Node.js 中直接加载生成的文件时,可以无需再去加载其他模块或脚本文件,这个打包后的 bundle.js 文件包含了所有相关的代码。
–sourcemap:会生成map映射文件
在终端执行:npm run build

可以看到在dist目录下,生成了mjs 和 map 以及zip包。可以看到mjs size有9.2MB,这是因为把依赖包都打进去了。

另外 –minify 这个参数会压缩代码,把代码变成一行。
去掉 –minify 参数以后的build出的 index.mjs

有 –minify 参数 build出的 index.mjs

重要
加了 –bundle参数以后,lambda执行总会出这个错误。


加上 –format=esm 能解决这个问题,不知道原因
esbuild src/service/test.ts --bundle --external:axios --external:aws-sdk --external:mongodb --external:class-transformer --external:@aws-sdk/client-secrets-manager --external:zip --platform=node --target=es2020 --format=esm --outfile=dist/index.mjs",




一个完整的例子
esbuild src/service/test.ts --bundle --external:axios --external:aws-sdk --external:mongodb --external:class-transformer --external:@aws-sdk/client-secrets-manager --external:zip --platform=node --target=es2020 --format=esm --outfile=dist/index.mjs
2.利用 esbuild 编译 typeScript 到javascript(无依赖)
方法一:去掉–bundle
上面那种情况,可以看到把依赖都打到javascript中了,所以size很大。我们有时候只想编译文件,不想把依赖打进去。然后我们的依赖是通过layer层做出来。我们的javascript 直接关联lambda layer层,引用lambda layer层的依赖。

可以修改下package.json的build的命令
修改后的package.json
build 里面 去掉了 –bundle 和 –sourcemap
{
"name": "aws-lambda-layer",
"version": "1.0.0",
"description": "aws lambda layer demo",
"main": "index.js",
"scripts": {
"build": "esbuild lambda.ts --minify --platform=node --target=es2020 --format=esm --outfile=dist/index.mjs",
"postbuild": "cd dist && zip -r index.zip index.mjs*"
},
"author": "",
"license": "ISC",
"dependencies": {
"@types/aws-lambda": "^8.10.109",
"esbuild": "^0.15.16",
"lambda-local": "^2.0.3",
"aws-sdk": "^2.1279.0",
"class-transformer": "^0.5.1",
"mongodb": "^4.12.1",
"axios": "^1.2.0"
}
}
再次npm run build,可以看到index.mjs 变成313kb了。


但是方法一有个问题,假如要build的ts文件,里面引用了别的moudle,CommonUtil,如果去掉了 –bundle,则CommonUtil也不会打进编译好的index.mjs代码里面了。这个时候我们可以用下面的方法二,通过external参数具体指定除外的模块,可以精确的除外某个moudle,保留不想除外的moudle。

方法二:
在使用 esbuild 构建 Node.js 应用时,可以通过设置 external 选项来告诉 esbuild 哪些模块是外部依赖,不应该被打包进最终的输出文件中。这样可以减小输出文件的大小,并且可以避免一些不必要的重复代码。
你可以将以下的 build 命令添加到你的 package.json 文件中的 scripts 字段中:
{
"name": "my-app",
"version": "1.0.0",
"scripts": {
"build": "esbuild index.js --bundle --external:fs --external:path --external:moduleA --platform=node --format=esm --outfile=dist/bundle.js"
},
"dependencies": {
// ...
}
}
在上面的 build 命令中,我们使用了 --external 选项来将 fs、path 和 moduleA 模块设置为外部依赖。如果你的应用程序使用了其他模块,可以根据需要添加到 --external 选项中。
3.制作lambda layer
先在本地做好layer层用到的nodejs modules。
npm install mongodb npm install aws-sdk npm install axios

把node_modules, package.json,package-lock.json一起打成压缩包,并且命名成nodejs.zip
通过lambda控制台创建layer,并且上传nodejs.zip
4.lambda函数引用该layer层




5.文件制作layer层
有时候,我们希望把一些证明文件,普通文件等等,做到layer层里面,以供lamda函数引用,可以这样做。
比如下面的例子,我们希望连接把mongodb用到的pem文件打到laryer层里面。
pem
|---lib
|---rds-combined-ca-bundle.pem

然后打成pem.zip压缩包,在layer做成的控制台正常上传zip文件,做成就可以了

在lambda函数里面,正常引用这个layer层。
这样这个文件会放在lamda函数,下面的路径下面。
/opt/lib/rds-combined-ca-bundle.pem
lamda函数的例子:
import { MongoClient } from 'mongodb';
import { Axios } from 'axios';
import AWS from 'aws-sdk';
import * as fs from 'fs';
import {
SecretsManagerClient,
GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";
const secret_name = "secrets-docdb-01";
// This will persist DB connectionn if Lambda container still "warm" on invocation, see: https://docs.aws.amazon.com/lambda/latest
const cachedDb: Map<string, MongoClient> = new Map();
const client = new SecretsManagerClient({
region: "ap-northeast-1",
});
export const handler = async (event) => {
// 这块通过fs打印一下/opt/lib目录下面的文件
let filenames = fs.readdirSync("/opt/lib");
console.log("\nFilenames in directory:");
filenames.forEach((file) => {
console.log("File:", file);
});
let response;
// 从AWS Secret里面取得documentdb的用户名和密码
try {
response = await client.send(
new GetSecretValueCommand({
SecretId: secret_name,
VersionStage: "AWSCURRENT", // VersionStage defaults to AWSCURRENT if unspecified
})
);
} catch (error) {
throw error;
}
const secret = response.SecretString;
const parsedResult = JSON.parse(secret);
console.log(parsedResult.username);
const uri= 'mongodb://'+ parsedResult.username + ':' + parsedResult.password+ '@xxxxx.ap-northeast-1.docdb.amazonaws.com:27017';
// DB接続取得
let conn = await connectToDatabase(uri);
// Config Mongo Client'
console.log("conn");
const db = conn.db('accomDocDb02'); // which DB
console.log(db);
const collection = db.collection('lambdatest'); // Which "collection"/table
// Write to DB
const dbWrite = await collection.insertOne({ 'lambdatestMsg': 'test' });
console.log(dbWrite);
console.log('******lambda tets start******');
console.log(event);
console.log('Axios:' + Axios.toString);
console.log('MongoClient:' + MongoClient.name);
console.log('S3:' + AWS.S3.toString);
console.log('******lambda test end******');
};
async function connectToDatabase(uri) {
console.log("=> connect to database");
if (!cachedDb.has(uri)) {
console.log("=> creating connection...");
// 注意这里面pem的证书,是从layer层路径来的
const connection = await MongoClient.connect(uri, { ssl: true, sslCA: '/opt/lib/rds-combined-ca-bundle.pem', retrywrites: false }).then(c => c);
cachedDb.set(uri, connection);
console.log("=> connection created...");
}
return cachedDb.get(uri) ?? null;
}