Skip to content

StackToolbox/aws-sam-webpack-plugin

Repository files navigation

npm

AWS SAM Webpack Plugin

A Webpack plugin that replaces the sam build step for AWS SAM CLI projects.

IMPORTANT

This project was created before AWS SAM CLI introduced support for building TypeScript projects. While it served a need at the time I would recommend using SAM CLI's TypeScript support.

I will no longer be providing updates to this package but if someone wants to create a PR to support new runtimes I will merge and release it.

Background

This plugin will build your AWS SAM CLI project using Webpack. You can use it to replace the sam build step if every function in your SAM template uses the nodejs18.x, nodejs20.x or nodejs22.x runtime. If your project uses other runtimes then look at Building Apps with SAM, TypeScript and VS Code Debugging.

I started this project for two reasons:

  1. SAM doesn't have good support for TypeScript
  2. SAM build is slow because it runs npm pack and npm install for every function in your project.

The goals for this projects are:

  1. Build your SAM project using Webpack (including support for watch mode)
  2. Support TypeScript and Babel
  3. Compatibility with running sam build
  4. Automatically generate VS Code debugging configuration

Usage with TypeScript

Installation

Create a package.json in your projects root folder using npm init or yarn init.

Install the development dependencies:

npm install webpack webpack-cli typescript ts-loader aws-sam-webpack-plugin @types/aws-lambda rimraf --save-dev

or

yarn add webpack webpack-cli typescript ts-loader aws-sam-webpack-plugin @types/aws-lambda rimraf -D

Install the production dependencies:

npm install aws-sdk --save

or

yarn add aws-sdk --save

webpack.config.js

Create a webpack.config.js file in your projects root folder and add this plugin. The entry points can be set automatically using the .entry() method from this plugin. The output should go to .aws-sam/build.

Tip: If you set entry to () => awsSamPlugin.entry() it will reload your SAM configuration every time webpack rebuilds. You can disable this by setting entry to awsSamPlugin.entry()

Example:

const path = require("path");
const AwsSamPlugin = require("aws-sam-webpack-plugin");

const awsSamPlugin = new AwsSamPlugin();

module.exports = {
  // Loads the entry object from the AWS::Serverless::Function resources in your
  // SAM config. Setting this to a function will
  entry: () => awsSamPlugin.entry(),

  // Write the output to the .aws-sam/build folder
  output: {
    filename: (chunkData) => awsSamPlugin.filename(chunkData),
    libraryTarget: "commonjs2",
    path: path.resolve("."),
  },

  // Create source maps
  devtool: "source-map",

  // Resolve .ts and .js extensions
  resolve: {
    extensions: [".ts", ".js"],
  },

  // Target node
  target: "node",

  // AWS recommends always including the aws-sdk in your Lambda package but excluding can significantly reduce
  // the size of your deployment package. If you want to always include it then comment out this line. It has
  // been included conditionally because the node10.x docker image used by SAM local doesn't include it.
  externals: process.env.NODE_ENV === "development" ? [] : ["aws-sdk"],

  // Set the webpack mode
  mode: process.env.NODE_ENV || "production",

  // Add the TypeScript loader
  module: {
    rules: [{ test: /\.tsx?$/, loader: "ts-loader" }],
  },

  // Add the AWS SAM Webpack plugin
  plugins: [awsSamPlugin],
};

tsconfig.json

Create a TypeScript config file that compiles .ts and .js files from the src folder.

Example:

{
  "compilerOptions": {
    "target": "es2015",
    "module": "commonjs",
    "sourceMap": true,
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

package.json (optional)

To make building simple I like to add some scripts to the package.json which handles building, building in watch mode and cleaning up.

{
  "scripts": {
    "build": "webpack-cli",
    "clean": "rimraf .aws-sam .vscode",
    "prebuild": "rimraf .aws-sam .vscode",
    "prewatch": "rimraf .aws-sam .vscode",
    "watch": "webpack-cli -w"
  }
}

You can set the NODE_ENV environment variable while executing the scripts to change how it's built:

NODE_ENV=development npm run-script build

src/{function}

Create a src folder with one sub-folder for each function. Place your handler and any test code in here.

template.yaml

Create a template.yaml in the project root. For the CodeUri use the functions folder (i.e. src/{folder}). Example:

MyFunction:
  Type: AWS::Serverless::Function
  Properties:
    CodeUri: src/my-function
    Handler: app.handler

Usage with Babel

Installation

Create a package.json in your projects root folder using npm init or yarn init.

Install the development dependencies:

npm install webpack webpack-cli aws-sam-webpack-plugin @babel/cli @babel/core @babel/plugin-proposal-class-properties @babel/preset-env @babel/preset-typescript @babel/plugin-transform-runtime babel-loader --save-dev

or

yarn add webpack webpack-cli aws-sam-webpack-plugin @babel/cli @babel/core @babel/plugin-proposal-class-properties @babel/preset-env @babel/preset-typescript @babel/plugin-transform-runtime babel-loader -D

Install the production dependencies:

npm install aws-sdk source-map-support @babel/runtime --save

or

yarn add aws-sdk source-map-support @babel/runtime --save

webpack.config.js

Create a webpack.config.js file in your projects root folder and add this plugin. The entry points can be set automatically using the .entry() method from this plugin. The output should go to .aws-sam/build.

Tip: If you set entry to () => awsSamPlugin.entry() it will reload your SAM configuration every time webpack rebuilds. You can disable this by setting entry to awsSamPlugin.entry()

Example:

const path = require("path");
const AwsSamPlugin = require("aws-sam-webpack-plugin");

const awsSamPlugin = new AwsSamPlugin();

module.exports = {
  // Loads the entry object from the AWS::Serverless::Function resources in your
  // SAM config. Setting this to a function will
  entry: () => awsSamPlugin.entry(),

  // Write the output to the .aws-sam/build folder
  output: {
    filename: (chunkData) => awsSamPlugin.filename(chunkData),
    libraryTarget: "commonjs2",
    path: path.resolve("."),
  },

  // Create source maps
  devtool: "source-map",

  // Resolve .ts and .js extensions
  resolve: {
    extensions: [".ts", ".js"],
  },

  // Target node
  target: "node",

  // AWS recommends always including the aws-sdk in your Lambda package but excluding can significantly reduce
  // the size of your deployment package. If you want to always include it then comment out this line. It has
  // been included conditionally because the node10.x docker image used by SAM local doesn't include it.
  externals: process.env.NODE_ENV === "development" ? [] : ["aws-sdk"],

  // Set the webpack mode
  mode: process.env.NODE_ENV || "production",

  // Add the TypeScript loader
  module: {
    rules: [
      { test: /\.jsx?$/, loader: "babel-loader" },
      { test: /\.tsx?$/, loader: "babel-loader" },
    ],
  },

  // Add the AWS SAM Webpack plugin
  plugins: [awsSamPlugin],
};

babel.config.js

Create a babel.config.js file at the project root

module.exports = {
  plugins: [
    "@babel/proposal-class-properties",
    [
      "@babel/plugin-transform-runtime",
      {
        regenerator: true,
      },
    ],
  ],
  presets: ["@babel/env", "@babel/typescript"],
};

package.json (optional)

To make building simple I like to add some scripts to the package.json which handle building, building in watch mode and cleaning up.

{
  "scripts": {
    "build": "webpack-cli",
    "clean": "rimraf .aws-sam .vscode",
    "prebuild": "rimraf .aws-sam .vscode",
    "prewatch": "rimraf .aws-sam .vscode",
    "watch": "webpack-cli -w"
  }
}

You can set the NODE_ENV environment variable while executing the commands to change how it's built:

NODE_ENV=development npm run-script build

src/{function}

Create a src folder with one sub-folder for each function. Place your handler and any test code in here.

template.yaml

Create a template.yaml in the project root. For the CodeUri use the functions folder (i.e. src/{folder}). Example:

MyFunction:
  Type: AWS::Serverless::Function
  Properties:
    CodeUri: src/my-function
    Handler: app.handler

Source Map Support

To enable source map support on Lambda make sure you set the environment variable NODE_OPTIONS to --enable-source-maps for your Lambda.

VS Code Debugging

To debug your Lambda using VS Code add the option -d 5858 when using sam local invoke to launch the Node debugger then switch to the debugger in VS Code. From the VS Code debugger select the Lambda function you want to debug and click run. As of VS Code version 1.51 and SAM CLI version 1.10.0 the debugger will stop at the bootstrap file. Click the continue button and it will go to the first break point.

You should be able to set breakpoints in your source code, step through the code and view values from the editor.

Building Multiple Projects (Experimental)

From v0.5.0 you can build multiple SAM projects from a single webpack.config.js by using the projects option. All of the SAM projects need to be located below a common point on the filesystem. This could be the same folder that contains your webpack.config.js or another folder like services (any name is ok).

The projects option accepts a JavaScript object where the key is a shortname for the project and the value can be:

  1. The path to the root folder for your SAM project. The plugin will look for a template.yaml or template.yml in that folder.
  2. The path to a SAM template. This allows you to use a different a template name. The folder the template is located in will be used as the root folder for that SAM project.

For example: The following will build two projects, projectA and projectB. For projectA the plugin will look for either template.yaml or template.yml in the folder ./services/project-a but for projectB it will only use project.yaml in the ./services/project-b folder.

const awsSamPlugin = new AwsSamPlugin({
  projects: {
    projectA: "./services/project-a",
    projectB: "./services/project-b/project.yaml",
  },
});

If you are upgrading from instructions prior to 0.5.0 you also need to modify the output section of your webpack.config.js so that Webpack uses the plugins .filename() method to determine the name of the output file. This will create a .aws-sam/build folder in correct SAM project root.

module.exports = {
  output: {
    filename: (chunkData) => awsSamPlugin.filename(chunkData),
    libraryTarget: "commonjs2",
    path: path.resolve("."),
  },
};

Once you have done this you should be able to execute Webpack from the root folder for your project (typically where you have your package.json, webpack.config.js, etc). In this example that folder would also contain the services folder.

Options

Name Type Default Description
outFile {String} app The name of the Javascript file to output without the .js extension. For example: index will generate index.js. By default it will use app
projects {Object} {"default":"."} A JavaScript object where the key is the name of the project and the value is the path to the SAM template or a folder containing a template.yaml or template.yml for the project
vscodeDebug {Boolean} true Also generate a .vscode/launch.json file for debugging Lambda with SAM CLI local S

vscodeDebug

Enable/disable automatically generating a .vscode/launch.json file. This file contains the VS Code debug configuration for all of the Lambdas from your template.yaml.

Maintainers


Rich Buggy