├── .gitignore ├── README.md ├── babel.config.js ├── functions └── pdf.ts ├── package.json ├── serverless.yml ├── tsconfig.json ├── webpack.config.js ├── yarn-error.log └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .serverless 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serverless-lambda-puppeteer 2 | 3 | This is a demo on running 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 | Note that you must send a GET request with `Accept: 'application/pdf'` header for API Gateway to respond properly. 18 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | targets: { 7 | node: true 8 | } 9 | } 10 | ], 11 | "@babel/typescript" 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /functions/pdf.ts: -------------------------------------------------------------------------------- 1 | import middy from "middy"; 2 | import chromium from "chrome-aws-lambda"; 3 | import puppeteer from "puppeteer-core"; 4 | import { 5 | cors, 6 | doNotWaitForEmptyEventLoop, 7 | httpHeaderNormalizer, 8 | httpErrorHandler 9 | } from "middy/middlewares"; 10 | 11 | const handler = async (event: any) => { 12 | const executablePath = event.isOffline 13 | ? "./node_modules/puppeteer/.local-chromium/mac-674921/chrome-mac/Chromium.app/Contents/MacOS/Chromium" 14 | : await chromium.executablePath; 15 | 16 | const browser = await puppeteer.launch({ 17 | args: chromium.args, 18 | executablePath 19 | }); 20 | 21 | const page = await browser.newPage(); 22 | 23 | await page.goto("https://www.google.com", { 24 | waitUntil: ["networkidle0", "load", "domcontentloaded"] 25 | }); 26 | 27 | const pdfStream = await page.pdf(); 28 | 29 | return { 30 | statusCode: 200, 31 | isBase64Encoded: true, 32 | headers: { 33 | "Content-type": "application/pdf" 34 | }, 35 | body: pdfStream.toString("base64") 36 | }; 37 | }; 38 | 39 | export const generate = middy(handler) 40 | .use(httpHeaderNormalizer()) 41 | .use(cors()) 42 | .use(doNotWaitForEmptyEventLoop()) 43 | .use(httpErrorHandler()); 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-lambda-puppeteer", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Crespo Wang ", 6 | "license": "MIT", 7 | "scripts": { 8 | "serverless": "sls offline start --port 3003 --stage dev --basePath / --prefix dev --location .webpack/service" 9 | }, 10 | "dependencies": { 11 | "chrome-aws-lambda": "^1.19.0", 12 | "middy": "^0.28.4", 13 | "puppeteer-core": "1.19.0", 14 | "serverless": "1.49.0", 15 | "serverless-domain-manager": "^3.2.6", 16 | "serverless-offline": "^6.0.0-alpha.0", 17 | "serverless-webpack": "^5.3.1", 18 | "typescript": "^3.5.3" 19 | }, 20 | "devDependencies": { 21 | "@babel/cli": "^7.5.5", 22 | "@babel/core": "^7.5.5", 23 | "@babel/preset-env": "^7.5.5", 24 | "@babel/preset-typescript": "^7.3.3", 25 | "@types/puppeteer-core": "^1.9.0", 26 | "babel-loader": "^8.0.6", 27 | "puppeteer": "1.19.0", 28 | "webpack": "^4.39.1", 29 | "webpack-node-externals": "^1.7.2" 30 | } 31 | } -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-pdf-puppeteer 2 | frameworkVersion: "1.49.0" 3 | 4 | provider: 5 | name: aws 6 | versionFunctions: false 7 | runtime: nodejs8.10 8 | region: ap-southeast-2 9 | stage: dev 10 | profile: hotpuma 11 | 12 | apiGateway: 13 | binaryMediaTypes: 14 | - "application/pdf" 15 | plugins: 16 | - serverless-webpack 17 | - serverless-offline 18 | - serverless-domain-manager 19 | 20 | custom: 21 | webpack: 22 | webpackConfig: webpack.config.js 23 | includeModules: true 24 | packager: "yarn" 25 | 26 | serverless-offline: 27 | location: .webpack/service 28 | 29 | customDomain: 30 | domainName: labs.mianio.com 31 | basePath: demo 32 | stage: ${self:provider.stage} 33 | createRoute53Record: true 34 | 35 | functions: 36 | give-me-the-pdf: 37 | handler: functions/pdf.generate 38 | events: 39 | - http: 40 | path: puppeteer/pdf 41 | method: get 42 | cors: true 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | // Target latest version of ECMAScript. 5 | 6 | // We don't use tsc for compiling ts code into js, instead we use babel-node for running APIs, so target won't make any differences 7 | "target": "es2017", 8 | // Search under node_modules for non-relative imports. 9 | "moduleResolution": "node", 10 | // Process & infer types from .js files. 11 | "allowJs": false, 12 | // Don't emit; allow Babel to transform files. 13 | "noEmit": true, 14 | // Enable strictest settings like strictNullChecks & noImplicitAny. 15 | "strict": true, 16 | // Disallow features that require cross-file information for emit. 17 | "isolatedModules": false, 18 | // Import non-ES modules as default imports. 19 | "esModuleInterop": true, 20 | // Enable strict null checks 21 | "strictNullChecks": false, 22 | "skipLibCheck": true 23 | }, 24 | "include": ["**/*.ts"], 25 | "exclude": [".serverless", ".webpack", "node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /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: "babel-loader", 19 | options: { 20 | presets: [ 21 | [ 22 | "@babel/preset-env", 23 | { 24 | targets: { 25 | node: true 26 | } 27 | } 28 | ], 29 | "@babel/typescript" 30 | ] 31 | }, 32 | include: [__dirname], 33 | exclude: /node_modules/ 34 | } 35 | ] 36 | }, 37 | resolve: { 38 | extensions: [".ts", ".js"] 39 | }, 40 | output: { 41 | libraryTarget: "commonjs2", 42 | path: path.join(__dirname, ".webpack"), 43 | filename: "[name].js" 44 | }, 45 | plugins: [ 46 | new webpack.EnvironmentPlugin({ 47 | NODE_ENV: "development" 48 | }) 49 | ] 50 | }; 51 | --------------------------------------------------------------------------------