skip to Main Content

Issue with AWS CDK, TypeScript Lambda, and Lambda Layers – chromium.executablePath Not a Function

I am working on an AWS Lambda function in TypeScript using AWS CDK for deployment. My Lambda function utilizes Puppeteer and a custom Chromium build to generate PDFs. However, I am encountering a TypeError related to chromium.executablePath.

Layer Dependencies:

My package.json includes the following dependencies for the Lambda Layer:

"dependencies": {
  "@sparticuz/chromium": "^106.0.2",
  "puppeteer-core": "^18.0.5"
}

Lambda Function:

Here is the relevant part of my Lambda function:

const puppeteer = require("/opt/nodejs/puppeteer-core");
const chromium = require("/opt/nodejs/@sparticuz/chromium");

async function createPdf(fileLinks: Record<string, string>, logoDataUri: string, bucketName: string, key: string): Promise<Buffer> {
  await chromium.font("https://raw.githack.com/googlei18n/noto-emoji/master/fonts/NotoColorEmoji.ttf"); // Add Font

  const browser = await puppeteer.launch({
    args: chromium.args,
    defaultViewport: chromium.defaultViewport,
    executablePath: await chromium.executablePath(),
    headless: chromium.headless,
  });

  // ... rest of the function
}

// Function logic continues...

Error Encountered:

When invoking the Lambda, I receive the following error:

{
  "errorType": "TypeError",
  "errorMessage": "chromium.executablePath is not a function",
  "trace": [
    "TypeError: chromium.executablePath is not a function",
    "    at createPdf (/var/task/index.js:70735:36)",
    "    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)",
    "    at async Runtime.handler (/var/task/index.js:70601:21)"
  ]
}

CDK Definitions:

Here’s how I defined the layer and the Lambda function in my CDK stack:

this.chromiumLayer = new LayerVersion(this, "chromiumLayer", {
  code: Code.fromAsset(join(__dirname, "../../src/layers/chromium")),
  compatibleRuntimes: [Runtime.NODEJS_20_X],
  compatibleArchitectures: [Architecture.X86_64, Architecture.ARM_64],
  description: "Chromium layer for Lambda",
});

this.createDownloadPDF = new NodejsFunction(this, "createDownloadPDF", {
  // ... configuration properties
  layers: [this.chromiumLayer],
  bundling: {
    externalModules: ["aws-sdk", "@sparticuz/chromium", "puppeteer-core"],
    // ... other bundling options
  },
});

tsconfig:

"paths": {
  "/opt/nodejs/puppeteer-core": ["./src/layers/chromium/nodejs/node_modules/puppeteer-core"],
  "/opt/nodejs/@sparticuz/chromium": ["./src/layers/chromium/nodejs/node_modules/@sparticuz/chromium"]
}

Question:

  1. Why am I getting the TypeError: chromium.executablePath is not a function error?
  2. Is there a specific way to bundle or configure the Lambda layers in AWS CDK for this kind of setup?
  3. Are there additional steps I need to take to correctly integrate @sparticuz/chromium with Puppeteer in a Lambda environment?

Any insights or solutions to address this error would be greatly appreciated. Thank you!

2

Answers


  1. Chosen as BEST ANSWER

    Discoveries

    I found that using a NodejsFunction offered other problems that I mitigated through using a JS lambda function

    Working Solution:

    CDK Code:

    this.chromiumLayer = new LayerVersion(this, "chromiumLayer", {
      code: Code.fromAsset(join(__dirname, "../../src/layers/chromium")),
      compatibleRuntimes: [Runtime.NODEJS_18_X],
      compatibleArchitectures: [Architecture.X86_64, Architecture.ARM_64],
      description: "Chromium layer for Lambda",
    });
    
    this.createDownloadPDF = new aws_lambda.Function(this, "createDownloadPDF", {
      runtime: aws_lambda.Runtime.NODEJS_18_X,
      code: aws_lambda.Code.fromAsset(join(__dirname, "./createDownloadPDF")),
      handler: "index.handler",
      memorySize: 1024,
      logRetention: RetentionDays.ONE_WEEK,
      timeout: Duration.minutes(10),
      retryAttempts: 2,
      description: "Create the instruction PDF for users to download from Etsy",
      environment: {
        OUTPUT_BUCKET: importedBucketName,
      },
      layers: [this.chromiumLayer],
    });
    

    Lambda JS Code:

    const puppeteer = require("/opt/nodejs/node_modules/puppeteer-core");
    const chromium = require("/opt/nodejs/node_modules/@sparticuz/chromium");
    
    async function createPdf(fileLinks, logoDataUri, bucketName, key) {
      let browser;
      try {
        // Launching browser
        browser = await puppeteer.launch({
          args: chromium.args,
          defaultViewport: chromium.defaultViewport,
          executablePath: await chromium.executablePath(),
          headless: chromium.headless,
        });
    
        const page = await browser.newPage();
    
    }
    

    Lambda Layer Package.json:

      "dependencies": {
        "@sparticuz/chromium": "119.0.2",
        "puppeteer-core": "21.5.0"
      },
    

  2. I tried so many ways and the only one that worked with Nodejs.18+ was to download the chromium binaries at runtime.

    This requires no layers, external dependencies, or anything else. The main disadvantage is the cold start: now your lambda needs to download and decompress a ~58MB binary from a remote location (I recommend uploading to S3).

    I describe the solution here: https://open.substack.com/pub/konarskis/p/puppeteer-aws-lambda

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search