我正在尝试制作两个AWS Lambda函数(以打字稿编写)。这两个函数共享用于与API交互的相同代码。为了不必将相同的代码复制到两个不同的Lambda中,我想将共享的代码移至本地模块,并使两个Lambda都依赖于所述模块。
我最初尝试在两个lambda之间注视代码是使用monorepo和lerna。我当前的项目结构如下:
- lerna.json
- package.json
- packages
- api
- package.json
- lambdas
- funcA
- package.json
- func B
- package.json
lerna.json:
{
"packages": [
"packages/api",
"packages/lambdas/*"
],
"version": "1.0.0"
}
在每个用于Lambda函数的package.json中,我都可以这样包含本地api模块:
"dependencies": {
"@local/api": "*"
}
有了这个,我已经能够将通用代码移到其自己的模块中。但是,我现在不确定如何捆绑功能以部署到AWS Lambda。 lerna是否有办法创建可以部署的捆绑软件?
答案 0 :(得分:1)
我已经为这个问题苦苦挣扎了一段时间,最后我不得不为此做点什么。
我使用了一个名为 slice-node-modules 的小包,如发现的 here in this similar question,它在一段时间内足以满足我的目的。随着我将更多项目整合到 monorepos 中,并开始使用作为兄弟姐妹而不是外部发布的共享依赖项,我遇到了这种方法的缺点。
我创建了一个名为 lerna-to-lambda 的新工具,专门针对我的用例量身定制。我用最少的文档公开发布了它,希望足以帮助处于类似情况的其他人。其要点是,在安装所有依赖项后,在捆绑步骤中运行 l2l
,并将所需内容复制到输出目录中,然后准备好使用 SAM 或其他方式部署到 Lambda .
例如,在自述文件中,您的 Lambda 函数的 package.json
中可能有这样的内容:
"scripts": {
...
"clean": "rimraf build lambda",
"compile": "tsc -p tsconfig.build.json",
"package": "l2l -i build -o lambda",
"build": "yarn run clean && yarn run compile && yarn run package"
},
在本例中,compile
步骤是将源目录中的 TypeScript 文件编译为 build
目录中的 JavaScript 文件。然后 package
步骤将 build
中的所有代码以及 Lambda 的所有依赖项(aws-sdk
除外)打包到目录 lambda
中,这就是您要部署的到 AWS。如果有人使用纯 JavaScript 而不是 TypeScript,他们可以在打包前将必要的 .js 文件复制到 build
目录中。
我意识到这个问题已经存在 2 年多了,从那时起您可能已经找到了自己的解决方案和/或变通方法。但因为它仍然与我相关,我认为它仍然与外面的人相关,所以我正在分享。
答案 1 :(得分:0)
运行lerna bootstrap
将在每个“包”中创建一个node_modules文件夹。这将包括您所有的lerna管理的依赖项以及该特定程序包的外部依赖项。
从那时起,每个lambda的部署将与您使用lerna无关。部署程序包将需要包含该特定lambda的代码和该lambda的node_modules文件夹-您可以将其压缩并手动上传,或使用SAM或CloudFormation之类的方法。
编辑:正如您正确指出的那样,您最终会在node_modules文件夹中使用符号链接,这使打包内容变得很尴尬。要解决此问题,您可以在打包进行部署之前运行以下操作:
cp -rL lambdas/funcA/node_modules lambdas/funcA/packaged/node_modules
-L
将强制将符号链接的目录复制到该文件夹中,然后可以对其进行压缩。
答案 2 :(得分:0)
由于cp -rL
在Mac上不起作用,因此我不得不提出类似的建议。
如果您的所有软件包都属于一个范围(@org),这是一个工作流程:
在您的lerna存储库的package.json中:
"scripts": {
"deploy": "lerna exec \"rm -rf node_modules\" && lerna bootstrap -- --production && lerna run deploy && lerna bootstrap"
}
在包含lambda函数的包中:
"scripts":{
"deploy": "npm pack && tar zxvf packagename-version.tgz && rm -rf node_modules/@org && cp -r node_modules/* package/node_modules && cd package && npm dedupe"
}
现在将“ packagename-version”和“ @org”替换为项目的相应值。还将所有依赖包添加到“ bundledDependencies”。
在lerna mono存储库的根目录中运行npm run deploy
后,您最终会在包含lambda函数的包中找到一个文件夹“ package”。它具有运行功能所需的所有依赖关系。从那里拿走。
我曾希望使用npm pack
可以使我利用.npmignore文件,但看来这是行不通的。如果有人知道如何使它工作,请告诉我。
答案 3 :(得分:0)
我在安装过程中使用了自定义脚本来复制依赖项。这将允许我使用相同的代码开发和部署应用程序。
Project structure 在 lambda_a 的 package.json 文件中,我有以下行:
"scripts": {
"install": "node ./install_libs.js @libs/library_a"
},
@libs/library_a 可由 lambda 代码使用以下语句使用:
const library_a = require('@libs/library_a')
对于 SAM 构建,我使用来自 lmbdas folder 的以下命令:
export SAM_BUILD=true && sam build
install_libs.js
console.log("Starting module installation")
var fs = require('fs');
var path = require('path');
var {exec} = require("child_process");
if (!fs.existsSync("node_modules")) {
fs.mkdirSync("node_modules");
}
if (!fs.existsSync("node_modules/@libs")) {
fs.mkdirSync("node_modules/@libs");
}
const sam_build = process.env.SAM_BUILD || false
libs_path = "../../"
if (sam_build) {
libs_path = "../../" + libs_path
}
process.argv.forEach(async function (val, index, array) {
if (index > 1) {
var currentLib = libs_path + val
console.log(`Building lib ${currentLib}`)
await exec(`cd ${currentLib} && npm install` , function (error, stdout, stderr){
if (error) {
console.log(`error: ${error.message}`);
return;
}
console.log(`stdout: ${stdout}`);
console.log('Importing module : ' + currentLib);
copyFolderRecursiveSync(currentLib, "node_modules/@libs")
});
}
});
function copyFolderRecursiveSync(source, target) {
var files = [];
// Check if folder needs to be created or integrated
var targetFolder = path.join(target, path.basename(source));
if (!fs.existsSync(targetFolder)) {
fs.mkdirSync(targetFolder);
}
// Copy
if (fs.lstatSync(source).isDirectory()) {
files = fs.readdirSync(source);
files.forEach(function (file) {
var curSource = path.join(source, file);
if (fs.lstatSync(curSource).isDirectory()) {
copyFolderRecursiveSync(curSource, targetFolder);
} else {
copyFileSync(curSource, targetFolder);
}
});
}
}
function copyFileSync(source, target) {
var targetFile = target;
// If target is a directory, a new file with the same name will be created
if (fs.existsSync(target)) {
if (fs.lstatSync(target).isDirectory()) {
targetFile = path.join(target, path.basename(source));
}
}
fs.writeFileSync(targetFile, fs.readFileSync(source));
}