├── .gitignore ├── README.md ├── functions └── pdf.ts ├── layers └── chrome_aws_lambda.zip ├── package.json ├── serverless.yml ├── tsconfig.json ├── webpack.config.js ├── yarn-error.log └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .serverless 2 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serverless-lambda-chrome 2 | 3 | This is a demo on running chromium puppeteer on AWS Lambda using Serverless framework. 4 | To get it running in your local serverless-offline environment 5 | ``` 6 | yarn install 7 | yarn serverless 8 | ``` 9 | It will be running on http://localhost:3003/dev/puppeteer/pdf 10 | 11 | To deploy to your AWS environment, you need to have your AWS account credentials stored properly, ie run `aws configure` and replace the profile name in `serverless.yml`. 12 | 13 | To deploy you will need to run 14 | ``` 15 | yarn sls deploy 16 | ``` 17 | 18 | In my setup, it will be serving at `https://labs.mianio.com/demo/puppeteer/pdf`. Note that you must send a GET request with `Accept: 'application/pdf'` header for API Gateway to respond properly. -------------------------------------------------------------------------------- /functions/pdf.ts: -------------------------------------------------------------------------------- 1 | import middy from "@middy/core"; 2 | import chromium from "chrome-aws-lambda"; 3 | import { APIGatewayEvent } from "aws-lambda"; 4 | import doNotWaitForEmptyEventLoop from "@middy/do-not-wait-for-empty-event-loop"; 5 | 6 | const handler = async (event: APIGatewayEvent) => { 7 | const executablePath = process.env.IS_OFFLINE 8 | ? null 9 | : await chromium.executablePath; 10 | const browser = await chromium.puppeteer.launch({ 11 | headless: true, 12 | args: chromium.args, 13 | defaultViewport: chromium.defaultViewport, 14 | executablePath 15 | }); 16 | const page = await browser.newPage(); 17 | const myContent = "happy new year!!"; 18 | await page.setContent(myContent); 19 | const stream = await page.pdf(); 20 | 21 | return { 22 | statusCode: 200, 23 | isBase64Encoded: true, 24 | headers: { 25 | "Content-type": "application/pdf" 26 | }, 27 | body: stream.toString("base64") 28 | }; 29 | }; 30 | 31 | export const generate = middy(handler).use(doNotWaitForEmptyEventLoop()); 32 | -------------------------------------------------------------------------------- /layers/chrome_aws_lambda.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crespowang/serverless-lambda-chrome/77a368b7339fb57b95ead70aa43cfcd53b8b770c/layers/chrome_aws_lambda.zip -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-lambda-chrome", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "git@github.com:crespowang/serverless-lambda-chrome.git", 6 | "author": "Crespo Wang ", 7 | "license": "MIT", 8 | "scripts": { 9 | "serverless": "sls offline start --port 3003 --stage dev --basePath / --prefix dev --location .webpack/service --offline" 10 | }, 11 | "dependencies": { 12 | "@middy/core": "^1.0.0-beta", 13 | "@middy/do-not-wait-for-empty-event-loop": "^1.0.0-beta", 14 | "aws-lambda": "^1.0.4", 15 | "chrome-aws-lambda": "^2.0.2", 16 | "puppeteer": "^2.0.0", 17 | "puppeteer-core": "^2.0.0", 18 | "serverless": "1.60.5", 19 | "serverless-offline": "v6.0.0-alpha.59" 20 | }, 21 | "devDependencies": { 22 | "@types/aws-lambda": "^8.10.39", 23 | "@types/node": "^13.1.4", 24 | "serverless-domain-manager": "^3.3.0", 25 | "serverless-webpack": "^5.3.1", 26 | "ts-loader": "^6.2.1", 27 | "typescript": "^3.7.4", 28 | "webpack": "^4.41.5", 29 | "webpack-node-externals": "^1.7.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-lambda-chrome 2 | frameworkVersion: "1.60.5" 3 | 4 | provider: 5 | name: aws 6 | versionFunctions: false 7 | runtime: nodejs12.x 8 | region: ap-southeast-2 9 | stage: dev 10 | profile: hotpuma 11 | environment: 12 | IS_OFFLINE: ${opt:offline} 13 | apiGateway: 14 | binaryMediaTypes: 15 | - "application/pdf" 16 | plugins: 17 | - serverless-webpack 18 | - serverless-domain-manager 19 | - serverless-offline 20 | 21 | layers: 22 | HeadlessChrome: 23 | name: HeadlessChrome 24 | compatibleRuntimes: 25 | - nodejs12.x 26 | description: Required for headless chrome 27 | package: 28 | artifact: layers/chrome_aws_lambda.zip 29 | custom: 30 | webpack: 31 | webpackConfig: webpack.config.js 32 | includeModules: 33 | forceExclude: 34 | - chrome-aws-lambda 35 | packager: "yarn" 36 | 37 | serverless-offline: 38 | location: .webpack/service 39 | 40 | customDomain: 41 | domainName: labs.mianio.com 42 | basePath: demo 43 | stage: ${self:provider.stage} 44 | createRoute53Record: true 45 | 46 | functions: 47 | generate-the-pdf: 48 | provisionedConcurrency: 2 49 | handler: functions/pdf.generate 50 | layers: 51 | - { Ref: HeadlessChromeLambdaLayer } 52 | events: 53 | - http: 54 | path: puppeteer/pdf 55 | method: get 56 | cors: true 57 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | // Target latest version of ECMAScript. 5 | 6 | "target": "es2019", 7 | // Search under node_modules for non-relative imports. 8 | "moduleResolution": "node", 9 | // Process & infer types from .js files. 10 | "allowJs": false, 11 | 12 | // Enable strictest settings like strictNullChecks & noImplicitAny. 13 | "strict": true, 14 | // Disallow features that require cross-file information for emit. 15 | "isolatedModules": false, 16 | // Import non-ES modules as default imports. 17 | "esModuleInterop": true, 18 | // Enable strict null checks 19 | "strictNullChecks": false, 20 | "skipLibCheck": true 21 | }, 22 | "include": ["**/*.ts"], 23 | "exclude": [".serverless", ".webpack", "node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | const path = require("path"); 3 | const slsw = require("serverless-webpack"); 4 | const nodeExternals = require("webpack-node-externals"); 5 | module.exports = { 6 | entry: slsw.lib.entries, 7 | target: "node", 8 | mode: slsw.lib.webpack.isLocal ? "development" : "production", 9 | node: { 10 | __dirname: true 11 | }, 12 | 13 | externals: [nodeExternals()], 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.ts$/, 18 | loader: "ts-loader" 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: [".ts", ".js"] 24 | }, 25 | output: { 26 | libraryTarget: "commonjs2", 27 | path: path.join(__dirname, ".webpack"), 28 | filename: "[name].js" 29 | }, 30 | plugins: [ 31 | new webpack.EnvironmentPlugin({ 32 | NODE_ENV: "development" 33 | }) 34 | ] 35 | }; 36 | --------------------------------------------------------------------------------