LambdaでEJSを使ってみる

今回はNode.jsでejsというテンプレートエンジンのパッケージを使ってEJSテンプレートからHTMLファイルを生成する方法をご紹介します。

環境

  • Lambda Node.js10
  • aws-cli/1.16.158

ejsの用途

ただ単に変数を置換するだけであれば、Javascriptの replace で可能ですが、テンプレート内で条件分岐やループを行いたい場合、ejsを使えば簡単にできます。

試してみる

ejsをLambdaから呼び出すためにLayer化します。(ZIPアップロードでも大丈夫です)

S3の構成

今回はお試しということでデプロイ時のソース置き場とテンプレート置き場とHTMlファイルの出力先を一つのバケットを使います。
それぞれ以下のキーを使用します。

  • デプロイ時のソース置き場
    • source
  • テンプレート置き場
    • template
  • HTMlファイルの出力先
    • output

プロジェクト構成

最終的な構成は以下となります。

.
├── cfn
│   └── template.yml
├── functions
│   └── generate_html
│       └── index.js
├── layer
│   └── nodejs
│       ├── node_modules
│       │   └── ejs
│       ├── package-lock.json
│       └── package.json
├── output
│   ├── ejs-layer.zip
│   └── packaged.yml
└── template
    └── template.ejs

HTMLテンプレートの作成

簡易的ですが、以下のテンプレートをS3に置いておきます。 template.ejs

<html>
    <head>
        <title>Classmethodの領収書</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <meta name="robots" content="noindex,nofollow">
        <meta name="googlebot" content="noindex,nofollow,noarchive">
    </head>
    <body>
        <h2>Classmethodの領収書</h2>
        <% if (user) { %>
        <h3><%=user.username%>様</h3>
        <% } %>
        <p>¥<%=amount%></p>
    </body>
</html>

S3へアップロード

$ aws s3 cp template/template.ejs s3://{バケット名}/template/

Layerの作成

zipファイルの作成

ejsのパッケージを含んだzipファイルを作成します。

$ mkdir -p layer/nodejs
$ cd layer/nodejs
$ npm init -y
$ npm install --save ejs
$ cd ../
$ zip -r ../output/ejs-layer.zip .

layer/nodejsの階層でディレクトリを作成して、/nodejs配下にejsをインストールします。
インストールできたらnodejs/配下をzip化します。

S3の source/layer/ 配下にアップロードします。

$ aws s3 cp /output/ejs-layer.zip s3://{バケット名}/source/layer/

デプロイはLambdaと同じSAMテンプレートで行うので先にLambdaを実装します。

Lambdaの作成

Lambdaのコード

generate_html/index.js

const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const ejs = require('ejs');
const bucket = process.env['BUCKET_NAME']

const getTemplate = async (template_name) => {
    const params = {
        Bucket: bucket,
        Key: `template/${template_name}`,
    };
    const data = await s3.getObject(params).promise();
    return new String(data.Body, 'utf8');
};

const putHtml = async (html) => {
    const file_name = Math.floor(new Date().getTime()/1000);
    const file_path = `output/${file_name}`
    const params = {
        Bucket: bucket,
        Key: file_path,
        Body: html,
        ContentType: 'text/html'
    };
    await s3.putObject(params).promise();
    return file_path;
};

exports.handler = async (event) => {
    console.log(event);
    const template = await getTemplate('template.ejs');
    let params = {
        amount: event.amount
    };
    params.user = event.user ? event.user : null;
    const html = ejs.render(template, params);
    const file_path = await putHtml(html);

    const response = {
        statusCode: 200,
        body: JSON.stringify(file_path),
    };
    return response;
};

  • まず getTemplate で先程準備したEJSテンプレートをS3からダウンロードします。
  • eventからパラメータの値を受け取り、 ejs.render() で置換します。
  • パラメータの置換した後に putHtml でS3のoutput/配下に出力したHTMLをアップロードします。

S3にアップロードするときの注意点ですが、 ContentType: 'text/html' と明示しておかないと、ブラウザから実際に出力したHTMLのページを開く時に閲覧でなくダウンロードになってしまいます。

SAMテンプレート

template.yml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Parameters:
  BucketName:
    Description: Load template and output destination of generated html file
    Type: String
    Default: {バケット名}
Resources:
  GenerateHtml:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: generate_html
      CodeUri: './functions/generate_html/index.js'
      Handler: index.handler
      MemorySize: 128
      Runtime: nodejs10.x
      Timeout: 300
      Policies:
        - AWSLambdaBasicExecutionRole
        - AmazonS3FullAccess
      Environment:
        Variables:
          BUCKET_NAME: !Ref BucketName
      Layers:
        - !Ref EjsLayer
  GenerateHtmlFuncLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${GenerateHtml}
      RetentionInDays: 7
  EjsLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: ejsLayer
      ContentUri:
        Bucket: !Ref BucketName
        Key: 'source/layer/ejs-layer.zip'
      CompatibleRuntimes:
        - nodejs10.x
      RetentionPolicy: Retain

Lambdaパッケージ化

$ sam package \
  --template-file cfn/template.yml \
  --s3-bucket {バケット名} \
  --s3-prefix source \
  --output-template-file ./output/packaged.yml

Lambdaデプロイ

$ sam deploy \
  --stack-name generate-html \
  --template-file ./output/packaged.yml \
  --capabilities CAPABILITY_NAMED_IAM

テスト

Lambdaコンソールから実際に実行してみます。
テスト を選択してテストイベントを作成します。

イベントのパラメータに以下を設定します。

ステータスが200で返ってきました。bodyにはS3出力先のキーが入っています。

S3コンソールからレスポンスに含まれたキーを開きます。

S3からダウンロードして開いてみると想定通りにHTMLが生成できています。

参考

ejs / npm