ask-sdk-jsx-for-aplを使って、TypeScriptでAlexa APLを作る

AlexaLive 2020でask-sdk-jsx-for-aplが発表され、APLをついにReactライクに作れるようになりました。

Reactめっちゃ書いてるマンとしては見逃せないので、早速触ってみます。

Install

以下の3ライブラリを追加します。

$ npm install -S ask-sdk-jsx-for-apl react react-dom

また、TypeScriptでReactを使うには@typesも必要なので追加しましょう。

$ npm install -D @types/react @types/react-dom

Check and update tsconfig.json

tsconfig.jsonの設定が以下のようになっているか確認しましょう。

  • compilerOptions.jsx = 'react'
  • compilerOptions.esModuleInterop = true

こんな感じになっていればとりあえずOKです。

{
    "compilerOptions": {
      "module": "commonjs",
      "target": "es6",
      "noImplicitAny": true,
      "noImplicitReturns": true,
      "noFallthroughCasesInSwitch": true,
      "outDir": "./dist",
      "esModuleInterop": true,
      "jsx": "react",
      "allowJs": false
    },
    "include": [
        "./src/**/*"
    ]
  }

Create APL by React

ということで早速APLを書いていきます。

import React from 'react';
import { APL, MainTemplate, Container, Text } from 'ask-sdk-jsx-for-apl';

/**
 * Class component
 */
export class LaunchAplDocument extends React.Component {
    private readonly launchMessage = 'Welcome to my first JSX for APL skill!';
    render() {
        return (
            <APL theme="dark">
                <MainTemplate>
                    <Container
                        alignItems="center"
                        justifyContent="spaceAround">
                        <Text
                            text={this.launchMessage}
                            fontSize="50px"
                            color="rgb(251,184,41)" />
                    </Container>
                </MainTemplate>
            </APL>
        );
    }
}

/**
 * Functional Component
 */
export const LaunchAplDocumentFC: React.FC = () => {
    const launchMessage = 'Welcome to my first JSX for APL skill!';
    return (
        <APL theme="dark">
            <MainTemplate>
                <Container
                    alignItems="center"
                    justifyContent="spaceAround">
                    <Text
                        text={launchMessage}
                        fontSize="50px"
                        color="rgb(251,184,41)" />
                </Container>
            </MainTemplate>
        </APL>
    );
}

作成したAPLは、AplDocumentクラスを利用してaddDirectiveします。

import React from 'react'
import { RequestHandler } from 'ask-sdk-core';
import { AplDocument } from 'ask-sdk-jsx-for-apl';
import { LaunchAplDocument } from './LaunchRequest.apl';

export const LaunchRequestHandler:  RequestHandler = {
    async canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === "LaunchRequest"
    },
    async handle(handlerInput) {
        return handlerInput.responseBuilder
            .speak("Hello! It's a nice development. How are you?")
            .reprompt("How are you?")
            .addDirective(
                new AplDocument(
                    <LaunchAplDocument />
                ).getDirective()
            )
            .getResponse()        
    }
}

export default LaunchRequestHandler

Trouble shooting

Error: TS2352: Conversion of type ‘RegExp’ to type ‘LaunchAplDocument’ may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to ‘unknown’ first.

.tsxでJSXを書いていない場合、このエラーに遭遇します。

.ts ファイルを必要に応じて .tsxにリネームしましょう。

Left: LaunchRequest.ts (get Error)
Right: LaunchRequest.tsx (No Error)

Error: TS2686: ‘React’ refers to a UMD global, but the current file is a module. Consider adding an import instead.

JSXが記述されているファイルでは、必ずimport React from 'react'しましょう。

Left: import React (No Error)
Right:nothing (Get Error)

Test your APL Component by Jest

Reactなので、もちろんJestでテストできます。

import React from 'react';
import {
    APL,
    MainTemplate,
    Container,
    Text,
    AplDocument
} from 'ask-sdk-jsx-for-apl'

const LaunchAplDocumentFC: React.FC = () => {
    const launchMessage = 'Welcome to my first JSX for APL skill!';
    return (
        <APL theme="dark">
            <MainTemplate>
                <Container
                    alignItems="center"
                    justifyContent="spaceAround">
                    <Text
                        text={launchMessage}
                        fontSize="50px"
                        color="rgb(251,184,41)" />
                </Container>
            </MainTemplate>
        </APL>
    );
}

describe('AplDocument', () => {
    const directive = new AplDocument(<LaunchAplDocumentFC />).getDirective()
    it('should get RenderDocument Directive', () => {
        expect(directive).toEqual({
            "document": {
              "import": [],
              "mainTemplate": {
                "items": [
                  {
                    "alignItems": "center",
                    "items": [
                      {
                        "color": "rgb(251,184,41)",
                        "fontSize": "50px",
                        "items": [],
                        "text": "Welcome to my first JSX for APL skill!",
                        "type": "Text"
                      }
                    ],
                    "justifyContent": "spaceAround",
                    "type": "Container"
                  }
                ],
                "parameters": []
              },
              "theme": "dark",
              "type": "APL",
              "version": "1.3"
            },
            "type": "Alexa.Presentation.APL.RenderDocument"
          })
    })
})

Use snapshot testing to check the component update

Jestには戻り値が変わったかどうかを検知できるスナップショットテストがあります。

import React from 'react';
import {
    APL,
    MainTemplate,
    Container,
    Text,
    AplDocument
} from 'ask-sdk-jsx-for-apl'

const LaunchAplDocumentFC: React.FC = () => {
    const launchMessage = 'Welcome to my first JSX for APL skill!';
    return (
        <APL theme="dark">
            <MainTemplate>
                <Container
                    alignItems="center"
                    justifyContent="spaceAround">
                    <Text
                        text={launchMessage}
                        fontSize="50px"
                        color="rgb(251,184,41)" />
                </Container>
            </MainTemplate>
        </APL>
    );
}

describe('AplDocument', () => {
    const directive = new AplDocument(<LaunchAplDocumentFC />).getDirective()
    it('should match snapshot', () => {
        expect(directive).toMatchSnapshot()
    })
})

一度テストを実行してスナップショットを作っておきます。

その後、Textのcolorをrgb(251,184,41) から rgb(255,184,41)に変更してみると、以下のようにJestでエラーがでます。


    Snapshot name: `AplDocument should match snapshot 1`

    - Snapshot  - 1
    + Received  + 1

    @@ -14,11 +14,11 @@
            "items": Array [
              Object {
                "alignItems": "center",
                "items": Array [
                  Object {
    -               "color": "rgb(251,184,41)",
    +               "color": "rgb(255,184,41)",
                    "fontSize": "50px",
                    "items": Array [],
                    "text": "Welcome to my first JSX for APL skill!",
                    "type": "Text",
                  },

      29 |     const directive = new AplDocument(<LaunchAplDocumentFC />).getDirective()
      30 |     it('should match snapshot', () => {
    > 31 |         expect(directive).toMatchSnapshot()
         |                           ^
      32 |     })
      33 |     it('should get RenderDocument Directive', () => {

想定している変更であれば、-uオプションを使ってスナップショットテストを更新しましょう。もしそうでない場合は・・・バグなので要修正です。

Comment