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
和 module
A 模块设置为外部依赖。如果你的应用程序使用了其他模块,可以根据需要添加到 --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; }