Aws-cdk: Best way to integrate with webpack

Created on 28 Jan 2019  ·  6Comments  ·  Source: aws/aws-cdk

Hello,

I'm looking for a way to include a webpack step during deploy. Are there any plans to provide a way to hook in the asset prepare / deploy phase?

@aws-cdassets feature-request managemendevenv

Most helpful comment

Hi @RomainMuller,

My use case is the following:

  • I want to use babel
  • I want to have small bundles containing only the run-time code required by my functions, one bundle per function
  • I don't want to write each time a specific build script based on a "known, fixed location" for the lambdas
  • I want to have my run-time code close to my constructs, something like this:
├── constructs
|   ├── CoolConstruct          # construct with a lambda.Function called `Importer`
|       ├── lib
|           ├── index.js
|           ├── importer
|               ├── index.js   # code for the lambda.Function called `Importer`

So far, I came up with the following solution:

  • Created a WebpackFunction construct like this:
const fs = require('fs');
const { join, dirname } = require('path');
const lambda = require('@aws-cdk/aws-lambda');

const { replace } = require('ramda');

const fnPath = replace(/\.[^.]+$/, '.js');

class WebpackFunction extends lambda.Function {
  constructor(scope, id, { path, ...props }) {
    const entryKey = fnPath(props.handler);
    const entryValue = join(path, entryKey);
    const outputPath = join(path, '.webpack');

    // In order to be able to synthesize the stack when creating
    // the webpack config the asset path needs to exists because it
    // is checked for a `lambda.Code.asset`. We use the original path
    // first to avoid this issue.
    const targetPath = fs.existsSync(outputPath) ? outputPath : dirname(entryValue);

    super(scope, id, {
      code: lambda.Code.asset(targetPath), // Set asset path to webpack output path
      runtime: lambda.Runtime.NodeJS810,
      ...props,
    });

    // Will be used to build the webpack config entry and output
    // properties by synthesizing the stack and extracting those
    // metadatas
    this.node.addMetadata('webpack:entry:key', entryKey);
    this.node.addMetadata('webpack:entry:value', entryValue);
    this.node.addMetadata('webpack:outputPath', outputPath);
  }
}

module.exports = WebpackFunction;
  • I use this construct just like the lambda.Function construct but I specify path (= path to the folder containing the file where my handler is exported) instead of code
  • In my webpack.config.js I'm requiring the app, synthesizing all the stacks and extracting the webpack:xxx metadata. I then use this info to generate an array of webpack configurations based on a base webpack config (the whole process could be easily converted to a webpack plugin cdk-webpack-plugin, can share my code if you want to have a look at the process)
  • I can now run webpack && cdk deploy

I wonder if there is a more elegant way to achieve this?

A plugin system with something like a hook during asset preparation would be ideal (an async function that receives some context, the original path and could return a new path?)

All 6 comments

One way to go is to have webpack called during your build (by having a specific build script). Now this is not an ideal solution of course, but this should work right now.

I'd like to get a better understanding of your exact use-case, if you can elaborate on that a bit? I want to have a (pluggable) mechanism for supporting various asset types, and that will help educate the design.

Hi @RomainMuller,

My use case is the following:

  • I want to use babel
  • I want to have small bundles containing only the run-time code required by my functions, one bundle per function
  • I don't want to write each time a specific build script based on a "known, fixed location" for the lambdas
  • I want to have my run-time code close to my constructs, something like this:
├── constructs
|   ├── CoolConstruct          # construct with a lambda.Function called `Importer`
|       ├── lib
|           ├── index.js
|           ├── importer
|               ├── index.js   # code for the lambda.Function called `Importer`

So far, I came up with the following solution:

  • Created a WebpackFunction construct like this:
const fs = require('fs');
const { join, dirname } = require('path');
const lambda = require('@aws-cdk/aws-lambda');

const { replace } = require('ramda');

const fnPath = replace(/\.[^.]+$/, '.js');

class WebpackFunction extends lambda.Function {
  constructor(scope, id, { path, ...props }) {
    const entryKey = fnPath(props.handler);
    const entryValue = join(path, entryKey);
    const outputPath = join(path, '.webpack');

    // In order to be able to synthesize the stack when creating
    // the webpack config the asset path needs to exists because it
    // is checked for a `lambda.Code.asset`. We use the original path
    // first to avoid this issue.
    const targetPath = fs.existsSync(outputPath) ? outputPath : dirname(entryValue);

    super(scope, id, {
      code: lambda.Code.asset(targetPath), // Set asset path to webpack output path
      runtime: lambda.Runtime.NodeJS810,
      ...props,
    });

    // Will be used to build the webpack config entry and output
    // properties by synthesizing the stack and extracting those
    // metadatas
    this.node.addMetadata('webpack:entry:key', entryKey);
    this.node.addMetadata('webpack:entry:value', entryValue);
    this.node.addMetadata('webpack:outputPath', outputPath);
  }
}

module.exports = WebpackFunction;
  • I use this construct just like the lambda.Function construct but I specify path (= path to the folder containing the file where my handler is exported) instead of code
  • In my webpack.config.js I'm requiring the app, synthesizing all the stacks and extracting the webpack:xxx metadata. I then use this info to generate an array of webpack configurations based on a base webpack config (the whole process could be easily converted to a webpack plugin cdk-webpack-plugin, can share my code if you want to have a look at the process)
  • I can now run webpack && cdk deploy

I wonder if there is a more elegant way to achieve this?

A plugin system with something like a hook during asset preparation would be ideal (an async function that receives some context, the original path and could return a new path?)

Indeed. @jogold I would love that plugin.
Also, another use-case is for frontend where deploy of API stack needs to happen first so that the generated APIG URL, Cognito URL etc can be baked into the frontend webpack bundle.

Usually, I'm doing this with the serverless framework and I output the above data as an .env file.
Then as a separate CLI command build (webpack) and then deploy to s3.

What would be amazing would be a construct that would handle this all during cdk deploy
A) Taking in the values of deployed constructs
B) Firing the webpack build
C) Using the s3deploy construct to deploy built/baked assets.

Would LOVE to work on this if someone thinks it's possible and might be able to point me in the direction.

Hi.
While it's not a plugin, this is how I have solved Webpack bundling during deployment: https://dev.to/mamaar/aws-cdk-4249

@jogold:

synthesizing all the stacks and extracting the webpack:xxx metadata

Do you mind sharing this bit of the code?

Thanks.

@jogold:

synthesizing all the stacks and extracting the webpack:xxx metadata

Do you mind sharing this bit of the code?

Thanks.

This is issue is old and I stopped exploring with Webpack for the CDK. See @aws-cdk/aws-lambda-nodejs of the current recommended approach.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nzspambot picture nzspambot  ·  3Comments

slipdexic picture slipdexic  ·  3Comments

sudoforge picture sudoforge  ·  3Comments

eladb picture eladb  ·  3Comments

pepastach picture pepastach  ·  3Comments