├── .nvmrc ├── src ├── index.ts └── NodejsFunction.ts ├── .github ├── FUNDING.yml └── workflows │ └── CI.yml ├── .vscode ├── extensions.json └── settings.json ├── .yarnrc ├── .editorconfig ├── .gitignore ├── tsconfig.json ├── LICENSE ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 14.15.5 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./NodejsFunction"; 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: vvo 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint", "Orta.vscode-jest"] 3 | } 4 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | yarn-path ".yarn/releases/yarn-1.22.4.js" 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules/ 3 | 4 | # misc 5 | .DS_Store 6 | /dist/ 7 | TODO 8 | 9 | # debug 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | package-lock.json 14 | 15 | .env 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "search.exclude": { 4 | ".yarn": true, 5 | "yarn.lock": true 6 | }, 7 | "eslint.packageManager": "yarn", 8 | "eslint.alwaysShowStatus": true, 9 | "editor.defaultFormatter": "esbenp.prettier-vscode", 10 | "[markdown]": { 11 | "editor.formatOnSave": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "importHelpers": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "rootDir": "./src", 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "moduleResolution": "node", 16 | "baseUrl": "./", 17 | "paths": { 18 | "*": ["src/*", "node_modules/*"] 19 | }, 20 | "jsx": "react", 21 | "esModuleInterop": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Vincent Voyer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - next 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | ci: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v2 19 | - name: Read Node.js version to install from `.nvmrc` 20 | run: echo "##[set-output name=NVMRC;]$(cat .nvmrc)" 21 | id: nvm 22 | - name: Install required Node.js version 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: "${{ steps.nvm.outputs.NVMRC }}" 26 | - name: Get Yarn cache directory path 27 | id: yarn-cache 28 | run: echo "::set-output name=dir::$(yarn cache dir)" 29 | - name: Setup cache key and directory for node_modules cache 30 | uses: actions/cache@v1 31 | with: 32 | path: ${{ steps.yarn-cache.outputs.dir }} 33 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 34 | - name: Yarn install 35 | run: yarn --frozen-lockfile 36 | - name: Test 37 | run: yarn test 38 | - name: Release 39 | if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/next') 40 | run: yarn semantic-release 41 | env: 42 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 43 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-lambda-nodejs-webpack", 3 | "version": "0.0.0-development", 4 | "private": false, 5 | "description": "CDK Construct to build Node.js AWS lambdas using webpack", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/vvo/aws-lambda-nodejs-webpack" 9 | }, 10 | "license": "MIT", 11 | "author": "Vincent Voyer ", 12 | "main": "dist/index.js", 13 | "module": "dist/aws-lambda-nodejs-webpack.esm.js", 14 | "typings": "dist/index.d.ts", 15 | "files": [ 16 | "src/", 17 | "dist/", 18 | "LICENSE", 19 | "README.md" 20 | ], 21 | "scripts": { 22 | "build": "tsdx build", 23 | "format": "tsdx lint src --fix", 24 | "lint": "tsdx lint src", 25 | "prepare": "tsdx build", 26 | "semantic-release": "semantic-release", 27 | "start": "tsdx watch", 28 | "test": "echo 'no test, see roadmap'" 29 | }, 30 | "babel": { 31 | "presets": [ 32 | [ 33 | "@babel/preset-env", 34 | { 35 | "targets": { 36 | "node": "12" 37 | } 38 | } 39 | ] 40 | ] 41 | }, 42 | "prettier": { 43 | "trailingComma": "all" 44 | }, 45 | "dependencies": { 46 | "@babel/core": "7.12.16", 47 | "@babel/plugin-transform-runtime": "7.12.15", 48 | "@babel/preset-env": "7.12.16", 49 | "babel-loader": "8.2.2", 50 | "babel-plugin-source-map-support": "2.1.3", 51 | "cross-spawn": "7.0.3", 52 | "find-up": "5.0.0", 53 | "noop2": "2.0.0", 54 | "source-map-support": "0.5.19", 55 | "ts-loader": "8.0.17", 56 | "webpack": "5.22.0", 57 | "webpack-cli": "4.5.0" 58 | }, 59 | "devDependencies": { 60 | "@aws-cdk/aws-lambda": "1.89.0", 61 | "@aws-cdk/core": "1.89.0", 62 | "@types/cross-spawn": "6.0.2", 63 | "@typescript-eslint/eslint-plugin": "^4.15.0", 64 | "@typescript-eslint/parser": "^4.15.0", 65 | "eslint-plugin-import": "2.22.1", 66 | "eslint-plugin-jest": "24.1.3", 67 | "prettier-plugin-packagejson": "2.2.9", 68 | "semantic-release": "^17.3.9", 69 | "semantic-release-cli": "5.4.3", 70 | "tsdx": "0.14.1", 71 | "tslib": "2.1.0", 72 | "typescript": "4.1.5" 73 | }, 74 | "peerDependencies": { 75 | "@aws-cdk/aws-lambda": "^1.54.0", 76 | "@aws-cdk/core": "^1.54.0" 77 | }, 78 | "engines": { 79 | "node": ">=12" 80 | }, 81 | "eslint": { 82 | "env": { 83 | "es6": true, 84 | "jest": true, 85 | "node": true 86 | }, 87 | "parser": "@typescript-eslint/parser", 88 | "parserOptions": { 89 | "ecmaVersion": 2019, 90 | "sourceType": "module" 91 | }, 92 | "extends": [ 93 | "eslint:recommended", 94 | "plugin:jest/recommended", 95 | "plugin:import/recommended", 96 | "plugin:@typescript-eslint/eslint-recommended", 97 | "plugin:@typescript-eslint/recommended" 98 | ], 99 | "rules": { 100 | "arrow-body-style": [ 101 | "error", 102 | "always" 103 | ], 104 | "curly": "error", 105 | "import/order": [ 106 | "error", 107 | { 108 | "newlines-between": "always", 109 | "alphabetize": { 110 | "order": "asc" 111 | } 112 | } 113 | ] 114 | }, 115 | "settings": { 116 | "import/extensions": [ 117 | ".ts", 118 | ".js" 119 | ], 120 | "import/resolver": { 121 | "node": { 122 | "extensions": [ 123 | ".ts", 124 | ".js" 125 | ] 126 | } 127 | } 128 | } 129 | }, 130 | "renovate": { 131 | "extends": [ 132 | "config:js-lib", 133 | ":automergePatch", 134 | ":automergeBranch", 135 | ":automergePatch", 136 | ":automergeBranch", 137 | ":automergeLinters", 138 | ":automergeTesters", 139 | ":automergeTypes" 140 | ] 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Update from the maintainer**: Unless you have a good reason to use Webpack with AWS lambda, you should use the default CDK construct (https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-nodejs-readme.html). It works really well and is even faster than this Webpack based project, thanks to esbuild. I initially wrote this Webpack based construct because, at the time, the official Node.js construct was very slow. Since September 2021 I now use the default Node.js construct. 2 | 3 | Consider this package deprecated as I won't be maintaining it, but it works. 4 | 5 | # aws-lambda-nodejs-webpack ![npm](https://img.shields.io/npm/v/aws-lambda-nodejs-webpack) [![Mentioned in Awesome CDK](https://awesome.re/mentioned-badge.svg)](https://github.com/eladb/awesome-cdk) [![GitHub license](https://img.shields.io/github/license/vvo/aws-lambda-nodejs-webpack?style=flat)](https://github.com/vvo/aws-lambda-nodejs-webpack/blob/master/LICENSE) [![Tests](https://github.com/vvo/aws-lambda-nodejs-webpack/workflows/CI/badge.svg)](https://github.com/vvo/aws-lambda-nodejs-webpack/actions) ![npm](https://img.shields.io/npm/dy/aws-lambda-nodejs-webpack) 6 | 7 | --- 8 | 9 | _[CDK](https://aws.amazon.com/cdk/) Construct to build Node.js AWS lambdas using [webpack](https://webpack.js.org/)_ 10 | 11 | _Table of contents:_ 12 | 13 | - [Usage example](#usage-example) 14 | - [Features](#features) 15 | - [Why?](#why) 16 | - [Roadmap](#roadmap) 17 | - [How to make changes and test locally](#how-to-make-changes-and-test-locally) 18 | - [WTF happened to the rollup version?](#wtf-happened-to-the-rollup-version) 19 | - [Thanks](#thanks) 20 | 21 | ## Usage example 22 | 23 | ```bash 24 | yarn add aws-lambda-nodejs-webpack 25 | ``` 26 | 27 | ```js 28 | // infra/super-app-stack.js 29 | const sns = require("@aws-cdk/aws-sns"); 30 | const subscriptions = require("@aws-cdk/aws-sns-subscriptions"); 31 | const core = require("@aws-cdk/core"); 32 | const { NodejsFunction } = require("aws-lambda-nodejs-webpack"); 33 | 34 | module.exports = class SuperAppProductionStack extends core.Stack { 35 | constructor(scope, id, props) { 36 | super(scope, id, props); 37 | 38 | const slackNotificationsLambda = new NodejsFunction( 39 | this, 40 | "slack-notifications-lambda", 41 | { 42 | entry: "events/slack-notifications.js", // required 43 | }, 44 | ); 45 | } 46 | }; 47 | ``` 48 | 49 | ```js 50 | // events/slack-notifications.js 51 | import { WebClient as SlackWebClient } from "@slack/web-api"; 52 | 53 | export function handler(event) { 54 | const message = event.Records[0].Sns.Message; 55 | // do something with message 56 | } 57 | ``` 58 | 59 | ## Features 60 | 61 | - webpack 5 62 | - compiles and deploys to Node.js 14.x by default 63 | - fast, [no-docker](https://github.com/aws/aws-cdk/issues/9120) CDK construct 64 | - lambda output only contains the necessary files, no README, tests, ... 65 | - bundling happens in temporary directories, it never writes in your project directory 66 | - source map support 67 | - TypeScript support 68 | - Babel support (preset-env) 69 | - babel & webpack caching 70 | - node_modules are split into a single `vendor.js` file. Allowing you to debug your own code in AWS console most of the time 71 | 72 | ## Why? 73 | 74 | There's already a dedicated [aws-lambda-nodejs module for CDK](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-nodejs-readme.html) but I had two major issues with it: 75 | 76 | 1. **CDK uses docker** for anything lambda build related. This means it mounts your whole Node.js project (not just the lambda code) inside Docker before running a bundler like Parcel. This is perfectly fine and performant on Linux and Windows, but this is **extremely slow on macOS**: a single cdk synth would take 2 minutes for a 3 lines lambda. Since it might take a very long time for Docker on macOS to get as fast as on Linux. Even their latest update ([mutagen](https://docs.docker.com/docker-for-mac/mutagen/)), while significantly faster, still takes 20s just to mount a Node.js project. 77 | 2. aws-lambda-nodejs generates a single file bundle with every local and external module inlined. Which makes it very hard to debug and read on the AWS console. I wanted a lambda output that mimicks my project file organization. 78 | 79 | I want to be clear: I respect a LOT the work of the CDK team, and especially [@jogold](https://github.com/jogold/), author of aws-lambda-nodejs) that helped me a lot into debugging performance issues and explaining to me how everything works. 80 | 81 | ## Caveats 82 | 83 | ⚠️ the only configuration file from your project that we will read is `tsconfig.json` (not including compilerOptions, which are overwritten using https://www.npmjs.com/package/@tsconfig/node12). 84 | 85 | Other files won't be used (example: .babelrc). If you need to reuse part of your babel configuration, please open an issue with details on your usecase so we can build the best API for how to do this. 86 | 87 | ## How to make changes and test locally 88 | 89 | ``` 90 | // fork and clone 91 | cd aws-lambda-nodejs-webpack 92 | yarn 93 | yarn link 94 | yarn start 95 | 96 | # in another terminal and project where you want to test changes 97 | yarn link aws-lambda-nodejs-webpack 98 | # cdk commands will now use your local aws-lambda-nodejs-webpack 99 | ``` 100 | 101 | ## Thanks 102 | 103 | - the CDK team for this awesome project 104 | - [@jogold](https://github.com/jogold/) for his time while helping me debugging performance issues on Docker 105 | -------------------------------------------------------------------------------- /src/NodejsFunction.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as os from "os"; 3 | import * as path from "path"; 4 | import * as process from "process"; 5 | 6 | import * as lambda from "@aws-cdk/aws-lambda"; 7 | import * as cdk from "@aws-cdk/core"; 8 | import * as spawn from "cross-spawn"; 9 | import findUp from "find-up"; 10 | 11 | /** 12 | * Properties for a NodejsFunction 13 | */ 14 | export interface NodejsFunctionProps extends lambda.FunctionOptions { 15 | /** 16 | * Path to the entry file (JavaScript or TypeScript), relative to your project root 17 | */ 18 | readonly entry: string; 19 | 20 | /** 21 | * The name of the exported handler in the entry file. 22 | * 23 | * @default "handler" 24 | */ 25 | readonly handler?: string; 26 | 27 | /** 28 | * The runtime environment. Only runtimes of the Node.js family are 29 | * supported. 30 | * 31 | * @default - `NODEJS_14_X` if `process.versions.node` >= '14.0.0', 32 | * `NODEJS_12_X` otherwise. 33 | */ 34 | readonly runtime?: lambda.Runtime; 35 | 36 | /** 37 | * If you get "Module not found: Error: Can't resolve 'module_name'" errors, and you're not 38 | * actually using those modules, then it means there's a module you're using that is trying to 39 | * dynamically require other modules. This is the case with Knex.js. When this happens, pass all the modules 40 | * names found in the build error in this array. 41 | * 42 | * Example if you're only using PostgreSQL with Knex.js, use: 43 | * `modulesToIgnore: ["mssql", "pg-native", "pg-query-stream", "tedious"]` 44 | */ 45 | readonly modulesToIgnore?: string[]; 46 | 47 | /** 48 | * Externals not to be bundled with your lambda, default to all Node.js builtin modules and aws-sdk. Modules I use this for: @sentry/serverless for example 49 | */ 50 | readonly externals?: ["aws-sdk"]; 51 | 52 | /** 53 | * Whether to automatically reuse TCP connections when working with the AWS 54 | * SDK for JavaScript. 55 | * 56 | * This sets the `AWS_NODEJS_CONNECTION_REUSE_ENABLED` environment variable 57 | * to `1`. 58 | * 59 | * @see https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/node-reusing-connections.html 60 | * 61 | * @default true 62 | */ 63 | readonly awsSdkConnectionReuse?: boolean; 64 | } 65 | 66 | /** 67 | * A Node.js Lambda function bundled using Parcel 68 | */ 69 | export class NodejsFunction extends lambda.Function { 70 | constructor( 71 | scope: cdk.Construct, 72 | id: string, 73 | props: NodejsFunctionProps = { entry: "" }, 74 | ) { 75 | if (props.runtime && props.runtime.family !== lambda.RuntimeFamily.NODEJS) { 76 | throw new Error("Only `NODEJS` runtimes are supported."); 77 | } 78 | 79 | if (!/\.(js|ts)$/.test(props.entry)) { 80 | throw new Error( 81 | "Only JavaScript or TypeScript entry files are supported.", 82 | ); 83 | } 84 | 85 | const entryFullPath = path.resolve(props.entry); 86 | 87 | if (!fs.existsSync(entryFullPath)) { 88 | throw new Error(`Cannot find entry file at ${entryFullPath}`); 89 | } 90 | 91 | const userExternals = props.externals ?? []; 92 | const defaultExternals = ["aws-sdk"]; 93 | 94 | const handler = props.handler ?? "handler"; 95 | const defaultRunTime = 96 | nodeMajorVersion() >= 14 97 | ? lambda.Runtime.NODEJS_14_X 98 | : lambda.Runtime.NODEJS_12_X; 99 | const runtime = props.runtime ?? defaultRunTime; 100 | 101 | const outputDir = fs.mkdtempSync( 102 | path.join(os.tmpdir(), "aws-lambda-nodejs-webpack"), 103 | ); 104 | const webpackConfigPath = path.join(outputDir, "webpack.config.js"); 105 | 106 | const webpackBinPath = require.resolve("webpack-cli/bin/cli.js", { 107 | paths: [__dirname], 108 | }); 109 | 110 | const pluginsPath = path.join( 111 | webpackBinPath.slice(0, webpackBinPath.lastIndexOf("/node_modules")), 112 | "node_modules", 113 | ); 114 | 115 | const pluginsPaths = { 116 | webpack: findModulePath("webpack", pluginsPath), 117 | "babel-loader": findModulePath("babel-loader", pluginsPath), 118 | "@babel/preset-env": findModulePath("@babel/preset-env", pluginsPath), 119 | "@babel/plugin-transform-runtime": findModulePath( 120 | "@babel/plugin-transform-runtime", 121 | pluginsPath, 122 | ), 123 | "babel-plugin-source-map-support": findModulePath( 124 | "babel-plugin-source-map-support", 125 | pluginsPath, 126 | ), 127 | noop2: findModulePath("noop2", pluginsPath), 128 | "terser-webpack-plugin": findModulePath( 129 | "terser-webpack-plugin", 130 | pluginsPath, 131 | ), 132 | }; 133 | 134 | // NodeJs reserves '\' as an escape char; but pluginsPaths etc are inlined directly in the 135 | // TemplateString below, so will contain this escape character on paths computed when running 136 | // the Construct on a Windows machine, and so we need to escape these chars before writing them 137 | const escapePathForNodeJs = (path: string) => { 138 | return path.replace(/\\/g, "\\\\"); 139 | }; 140 | 141 | const webpackConfiguration = ` 142 | const { builtinModules } = require("module"); 143 | const { NormalModuleReplacementPlugin } = require("${escapePathForNodeJs( 144 | pluginsPaths["webpack"], 145 | )}"); 146 | const TerserPlugin = require("${escapePathForNodeJs( 147 | pluginsPaths["terser-webpack-plugin"], 148 | )}"); 149 | 150 | module.exports = { 151 | name: "aws-lambda-nodejs-webpack", 152 | mode: "none", 153 | entry: "${escapePathForNodeJs(entryFullPath)}", 154 | target: "node", 155 | resolve: { 156 | // next line allows resolving not found modules to local versions (require("lib/log")) 157 | modules: ["node_modules", "${escapePathForNodeJs(process.cwd())}"], 158 | extensions: [ ".ts", ".js" ], 159 | }, 160 | context: "${escapePathForNodeJs(process.cwd())}", 161 | devtool: "source-map", 162 | module: { 163 | rules: [ 164 | { 165 | test: /\\.js$/, 166 | exclude: /node_modules/, 167 | use: { 168 | loader: "${escapePathForNodeJs(pluginsPaths["babel-loader"])}", 169 | options: { 170 | babelrc: false, // do not use babelrc when present (could pollute lambda configuration) 171 | cwd: "${escapePathForNodeJs(process.cwd())}", 172 | cacheDirectory: "${escapePathForNodeJs( 173 | path.join( 174 | process.cwd(), 175 | "node_modules", 176 | ".cache", 177 | "aws-lambda-nodejs-webpack", 178 | "babel", 179 | ), 180 | )}", 181 | presets: [ 182 | [ 183 | "${escapePathForNodeJs(pluginsPaths["@babel/preset-env"])}", 184 | { 185 | "targets": { 186 | "node": "${ 187 | runtime.name.split("nodejs")[1].split(".")[0] 188 | }" 189 | }, 190 | loose: true, 191 | bugfixes: true, 192 | ignoreBrowserslistConfig: true // do not use browser list configuration, we build for node X that's it 193 | }, 194 | ] 195 | ], 196 | plugins: [ 197 | "${escapePathForNodeJs( 198 | pluginsPaths["@babel/plugin-transform-runtime"], 199 | )}", 200 | "${escapePathForNodeJs( 201 | pluginsPaths["babel-plugin-source-map-support"], 202 | )}" 203 | ] 204 | } 205 | } 206 | }, 207 | { 208 | test: /\\.ts$/, 209 | use: { 210 | loader: "${escapePathForNodeJs( 211 | findModulePath("ts-loader", pluginsPath), 212 | )}", 213 | options: { 214 | context: "${escapePathForNodeJs(process.cwd())}", 215 | configFile: "${escapePathForNodeJs( 216 | path.join(process.cwd(), "tsconfig.json"), 217 | )}", 218 | transpileOnly: true, 219 | // from: https://www.npmjs.com/package/@tsconfig/node14 220 | compilerOptions: { 221 | lib: ["es2020"], 222 | module: "commonjs", 223 | target: "es2020", 224 | baseUrl: ".", 225 | strict: true, 226 | esModuleInterop: true, 227 | skipLibCheck: true, 228 | forceConsistentCasingInFileNames: true 229 | } 230 | } 231 | }, 232 | exclude: /node_modules/, 233 | }, 234 | ] 235 | }, 236 | cache: { 237 | type: "filesystem", 238 | buildDependencies: { 239 | config: [__filename, "${escapePathForNodeJs( 240 | path.join(process.cwd(), "tsconfig.json"), 241 | )}"] 242 | }, 243 | cacheDirectory: "${escapePathForNodeJs( 244 | path.join( 245 | process.cwd(), 246 | "node_modules", 247 | ".cache", 248 | "aws-lambda-nodejs-webpack", 249 | "webpack", 250 | ), 251 | )}" 252 | }, 253 | // note: we specifically do not minify our code for Node.js 254 | // I have had horrible experience with code being minified for ES5 that would break on Node 12 255 | // If you have a good minifier that can minify to target Node 12 then open a PR 256 | optimization: { 257 | splitChunks: { 258 | cacheGroups: { 259 | vendor: { 260 | chunks: "all", 261 | filename: "vendor.js", // put all node_modules into vendor.js 262 | name: "vendor", 263 | test: /node_modules/, 264 | }, 265 | }, 266 | }, 267 | }, 268 | externals: [...builtinModules, ...${JSON.stringify([ 269 | ...defaultExternals, 270 | ...userExternals, 271 | ])}], 272 | output: { 273 | filename: "[name].js", 274 | path: "${escapePathForNodeJs(outputDir)}", 275 | libraryTarget: "commonjs2", 276 | }, 277 | ${(props.modulesToIgnore && 278 | ` 279 | plugins: [ 280 | new NormalModuleReplacementPlugin( 281 | /${escapePathForNodeJs(props.modulesToIgnore.join("|"))}/, 282 | "${escapePathForNodeJs(pluginsPaths["noop2"])}", 283 | ), 284 | ] 285 | `) || 286 | ""} 287 | };`; 288 | 289 | fs.writeFileSync(webpackConfigPath, webpackConfiguration); 290 | 291 | console.time(`aws-lambda-nodejs-webpack-${props.entry}`); 292 | const webpack = spawn.sync( 293 | webpackBinPath, 294 | ["--config", webpackConfigPath], 295 | { 296 | // we force CWD to the output dir to avoid being "polluted" by any babelrc or other configuration files 297 | // that could mess up with our own webpack configuration. If you need to reuse your babelrc then please open an issue 298 | cwd: outputDir, 299 | }, 300 | ); 301 | console.timeEnd(`aws-lambda-nodejs-webpack-${props.entry}`); 302 | 303 | if (webpack.status !== 0) { 304 | console.error( 305 | `webpack had an error when bundling. Return status was ${webpack.status}`, 306 | ); 307 | console.error(webpack.error); 308 | console.error( 309 | webpack?.output?.map(out => { 310 | return out?.toString(); 311 | }), 312 | ); 313 | console.error("webpack configuration was:", webpackConfiguration); 314 | process.exit(1); 315 | } 316 | 317 | super(scope, id, { 318 | ...props, 319 | runtime, 320 | code: lambda.Code.fromAsset(outputDir), 321 | handler: `main.${handler}`, 322 | }); 323 | 324 | // Enable connection reuse for aws-sdk 325 | if (props.awsSdkConnectionReuse ?? true) { 326 | this.addEnvironment("AWS_NODEJS_CONNECTION_REUSE_ENABLED", "1"); 327 | } 328 | 329 | this.addEnvironment("NODE_OPTIONS", "--enable-source-maps"); 330 | } 331 | } 332 | 333 | function nodeMajorVersion(): number { 334 | return parseInt(process.versions.node.split(".")[0], 10); 335 | } 336 | 337 | // this method forces resolving plugins relative to node_modules/aws-lambda-nodejs-webpack 338 | // otherwise they would be resolved to the user versions / undefined versions 339 | function findModulePath(moduleName: string, pluginsPath: string) { 340 | try { 341 | return require.resolve(moduleName, { paths: [__dirname] }); 342 | } catch (error) { 343 | const modulePath = findUp.sync(moduleName, { 344 | type: "directory", 345 | cwd: pluginsPath, 346 | }); 347 | 348 | if (modulePath === undefined) { 349 | throw new Error( 350 | `Can't find module named ${moduleName} via require.resolve or find-up`, 351 | ); 352 | } 353 | 354 | return modulePath; 355 | } 356 | } 357 | --------------------------------------------------------------------------------