Trabalhando com suporte a TypeScript e Jest: um tutorial do AWS SAM

Publicados: 2022-03-11

Uma ferramenta poderosa para criar aplicativos sem servidor, o AWS Serverless Application Model (SAM) frequentemente combina com JavaScript: 62% dos desenvolvedores em empresas de médio e grande porte escolhem JavaScript para seu código sem servidor. No entanto, o TypeScript está crescendo em popularidade e supera em muito o JavaScript como a terceira linguagem mais amada pelos desenvolvedores.

Embora o clichê do JavaScript não seja difícil de encontrar, iniciar projetos do AWS SAM com o TypeScript é mais complexo. O tutorial a seguir mostra como criar um projeto do AWS SAM TypeScript do zero e como as diferentes partes funcionam juntas. Os leitores precisam estar apenas um pouco familiarizados com as funções do AWS Lambda para acompanhar.

Iniciando nosso projeto do AWS SAM TypeScript

A base do nosso aplicativo sem servidor inclui vários componentes. Primeiro configuraremos o ambiente da AWS, nosso pacote npm e a funcionalidade do Webpack – então podemos criar, invocar e testar nossa função Lambda para ver nosso aplicativo em ação.

Prepare o Ambiente

Para configurar o ambiente AWS, precisamos instalar o seguinte:

  1. AWS CLI
  2. CLI do AWS SAM
  3. Node.js e npm

Observe que este tutorial requer a instalação do Docker durante a etapa 2 acima para testar nosso aplicativo localmente.

Inicializar um projeto vazio

Vamos criar o diretório do projeto, aws-sam-typescript-boilerplate e uma subpasta src para armazenar o código. No diretório do projeto, vamos configurar um novo pacote npm:

 npm init -y # -y option skips over project questionnaire

Este comando criará um arquivo package.json dentro do nosso projeto.

Adicione a configuração do Webpack

Webpack é um empacotador de módulos usado principalmente para aplicativos JavaScript. Como o TypeScript compila para JavaScript simples, o Webpack preparará efetivamente nosso código para o navegador da web. Vamos instalar duas bibliotecas e um carregador personalizado:

  • webpack: biblioteca principal
  • webpack-cli: utilitários de linha de comando para Webpack
  • ts-loader: carregador TypeScript para Webpack
 npm i --save-dev webpack webpack-cli ts-loader

O comando build da AWS SAM CLI, sam build , retarda o processo de desenvolvimento porque tenta executar npm install para cada função, causando duplicação. Usaremos um comando de compilação alternativo da biblioteca aws-sam-webpack-plugin para acelerar nosso ambiente.

 npm i --save-dev aws-sam-webpack-plugin

Por padrão, o Webpack não fornece um arquivo de configuração. Vamos criar um arquivo de configuração personalizado chamado webpack.config.js na pasta raiz:

 /* eslint-disable @typescript-eslint/no-var-requires */ const path = require('path'); const AwsSamPlugin = require('aws-sam-webpack-plugin'); const awsSamPlugin = new AwsSamPlugin(); module.exports = { entry: () => awsSamPlugin.entry(), output: { filename: (chunkData) => awsSamPlugin.filename(chunkData), libraryTarget: 'commonjs2', path: path.resolve('.') }, devtool: 'source-map', resolve: { extensions: ['.ts', '.js'] }, target: 'node', mode: process.env.NODE_ENV || 'development', module: { rules: [{ test: /\.tsx?$/, loader: 'ts-loader' }] }, plugins: [awsSamPlugin] };

Agora vamos examinar as várias partes:

  • entry : carrega o objeto entry (onde o Webpack começa a construir o pacote) do recurso AWS::Serverless::Function .
  • output : aponta para o destino da saída da compilação (neste caso, .aws-sam/build ). Aqui também especificamos a biblioteca de destino como commonjs2 , que atribui o valor de retorno do ponto de entrada a module.exports . Esse ponto de entrada é o padrão para ambientes Node.js.
  • devtool : isso cria um mapa de origem, app.js.map , em nosso destino de saída de compilação. Ele mapeia nosso código original para o código em execução no navegador da Web e ajudará na depuração se definirmos a variável de ambiente NODE_OPTIONS como --enable-source-maps para nosso Lambda.
  • resolve : Isso diz ao Webpack para processar arquivos TypeScript antes dos arquivos JavaScript.
  • target : Isso diz ao Webpack para direcionar o Node.js como nosso ambiente. Isso significa que o Webpack usará a função require do Node.js para carregar partes quando compilar.
  • module : aplica o carregador TypeScript a todos os arquivos que atendem à condição de test . Em outras palavras, garante que todos os arquivos com extensão .ts ou .tsx serão manipulados pelo carregador.
  • plugins : Isso ajuda o Webpack a identificar e usar nosso aws-sam-webpack-plugin .

Na primeira linha, desabilitamos uma regra ESLint específica para este arquivo. As regras ESLint padrão que configuraremos posteriormente desencorajam o uso da instrução require . Preferimos require a import no Webpack, então abriremos uma exceção.

Configurar o suporte ao TypeScript

Adicionar suporte ao TypeScript melhorará a experiência do desenvolvedor ao:

  • Evitando mensagens de aviso sobre declarações de tipo ausentes.
  • Fornecendo validação de tipo.
  • Oferecendo autocompletar dentro do IDE.

Primeiro, instalaremos o TypeScript para nosso projeto localmente (pule esta etapa se você tiver o TypeScript instalado globalmente):

 npm i --save-dev typescript

Incluiremos os tipos para as bibliotecas que estamos usando:

 npm i --save-dev @types/node @types/webpack @types/aws-lambda

Agora, criaremos o arquivo de configuração do TypeScript, tsconfig.json , na raiz do projeto:

 { "compilerOptions": { "target": "ES2015", "module": "commonjs", "sourceMap": true, "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, }, "include": ["src/**/*.ts", "src/**/*.js"], "exclude": ["node_modules"] }

Aqui estamos seguindo a configuração padrão recomendada pela comunidade TypeScript. Adicionamos include para anexar os arquivos na pasta src ao programa e exclude para evitar a compilação TypeScript para a pasta node_modules —não tocaremos nesse código diretamente.

Criar uma função Lambda

Não escrevemos nenhum código Lambda para nosso aplicativo sem servidor até agora, então vamos começar. Na pasta src que criamos anteriormente, criaremos uma subpasta test-lambda contendo um arquivo app.ts com esta função Lambda:

 import { APIGatewayEvent } from 'aws-lambda'; export const handler = async (event: APIGatewayEvent) => { console.log('incoming event is', JSON.stringify(event)); const response = { statusCode: 200, body: JSON.stringify({ message: 'Request was successful.' }) }; return response; };

Essa função de espaço reservado simples retorna uma resposta 200 com um corpo. Seremos capazes de executar o código após mais uma etapa.

Incluir o arquivo de modelo da AWS

O AWS SAM requer um arquivo de modelo para transpilar nosso código e implantá-lo na nuvem. Crie o arquivo template.yaml na pasta raiz:

 AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: AWS SAM Boilerplate Using TypeScript Globals: Function: Runtime: nodejs14.x # modify the version according to your need Timeout: 30 Resources: TestLambda: Type: AWS::Serverless::Function Properties: Handler: app.handler FunctionName: "Test-Lambda" CodeUri: src/test-lambda/ Events: ApiEvent: Type: Api Properties: Path: /test Method: get

Esse arquivo de modelo gera uma função Lambda acessível a partir de uma API HTTP GET. Observe que a versão referenciada na linha Runtime: pode precisar de personalização.

Execute o aplicativo

Para executar a aplicação, devemos adicionar um novo script no arquivo package.json para construir o projeto com o Webpack. O arquivo pode ter scripts existentes, como um script de teste vazio. Podemos adicionar o script de construção assim:

 "scripts": { "build": "webpack-cli" }

Se você executar npm run build na raiz do projeto, deverá ver a pasta build, .aws-sam , criada. Aqueles de nós em um ambiente Mac podem precisar tornar os arquivos ocultos visíveis pressionando Command + Shift + . para ver a pasta.

Vamos agora iniciar um servidor HTTP local para testar nossa função:

 sam local start-api

Quando visitamos o endpoint de teste em um navegador da Web, devemos ver uma mensagem de sucesso.

O navegador da web mostra o link "127.0.0.1:3000/test" na barra de endereços. Abaixo da barra de endereço, a página da Web está em branco, exceto por uma mensagem que diz '{"message": "A solicitação foi bem-sucedida."}.

O console deve mostrar que a função é montada em um contêiner do Docker antes de ser executada, e é por isso que instalamos o Docker anteriormente:

 Invoking app.handler (nodejs14.x) Skip pulling image and use local one: public.ecr.aws/sam/emulation-nodejs14.x:rapid-1.37.0-x86_64. Mounting /Users/mohammadfaisal/Documents/learning/aws-sam-typescript-boilerplate/.aws-sam/build/TestLambda as /var/task:ro, delegated inside runtime container

Aprimorando nosso fluxo de trabalho de desenvolvimento para um ambiente profissional

Nosso projeto está funcionando, adicionar alguns toques finais garantirá uma experiência de desenvolvedor excepcional que aumentará a produtividade e a colaboração.

Otimize a compilação com o Hot Reloading

É tedioso executar o comando build após cada alteração de código. O recarregamento a quente resolverá esse problema. Podemos adicionar outro script em nosso package.json para observar as alterações no arquivo:

 "watch": "webpack-cli -w"

Abra um terminal separado e execute npm run watch . Agora, seu projeto será compilado automaticamente quando você alterar qualquer código. Modifique a mensagem do código, atualize sua página da Web e veja o resultado atualizado.

Melhore a qualidade do código com ESLint e Prettier

Nenhum projeto TypeScript ou JavaScript está completo sem ESLint e Prettier. Essas ferramentas manterão a qualidade e a consistência do código do seu projeto.

Vamos instalar as dependências principais primeiro:

 npm i --save-dev eslint prettier

Adicionaremos algumas dependências auxiliares para que o ESLint e o Prettier possam trabalhar juntos em nosso projeto TypeScript:

 npm i --save-dev \ eslint-config-prettier \ eslint-plugin-prettier \ @typescript-eslint/parser \ @typescript-eslint/eslint-plugin

Em seguida, adicionaremos nosso linter criando um arquivo de configuração ESLint, .eslintrc , dentro da raiz do projeto:

 { "root": true, "env": { "es2020": true, "node": true, "jest": true }, "parser": "@typescript-eslint/parser", "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended" ], "ignorePatterns": ["src/**/*.test.ts", "dist/", "coverage/", "test/"], "parserOptions": { "ecmaVersion": 2018, "sourceType": "module", "ecmaFeatures": { "impliedStrict": true } }, "rules": { "quotes": ["error", "single", { "allowTemplateLiterals": true }], "default-case": "warn", "no-param-reassign": "warn", "no-await-in-loop": "warn", "@typescript-eslint/no-unused-vars": [ "error", { "vars": "all", "args": "none" } ] }, "settings": { "import/resolver": { "node": { "extensions": [".js", ".jsx", ".ts", ".tsx"] } } } }

Observe que a seção extends do nosso arquivo deve manter a configuração do plugin Prettier como a última linha para exibir erros Prettier como erros ESLint visíveis em nosso editor. Estamos seguindo as configurações recomendadas do ESLint para TypeScript, com algumas preferências personalizadas adicionadas na seção de rules . Sinta-se à vontade para navegar pelas regras disponíveis e personalizar ainda mais suas configurações. Optamos por incluir:

  • Um erro se não usarmos strings entre aspas simples.
  • Um aviso quando não fornecemos maiúsculas e minúsculas default nas instruções switch .
  • Um aviso se reatribuirmos qualquer parâmetro de uma função.
  • Um aviso se chamarmos uma instrução await dentro de um loop.
  • Um erro para variáveis ​​não utilizadas, que tornam o código ilegível e propenso a erros ao longo do tempo.

Já configuramos nossa configuração ESLint para trabalhar com a formatação Prettier. (Mais informações estão disponíveis no projeto GitHub eslint-config-prettier .) Agora, podemos criar o arquivo de configuração do Prettier, .prettierrc :

 { "trailingComma": "none", "tabWidth": 4, "semi": true, "singleQuote": true }

Essas configurações são da documentação oficial da Prettier; você pode modificá-los como desejar. Atualizamos as seguintes propriedades:

  • trailingComma : Alteramos isso de es5 para none para evitar vírgulas à direita.
  • semi : Mudamos isso de false para true porque preferimos ter um ponto e vírgula no final de cada linha.

Finalmente, é hora de ver o ESLint e o Prettier em ação. Em nosso arquivo app.ts , alteraremos o tipo de variável de response de const para let . Usar let não é uma boa prática neste caso, pois não modificamos o valor de response . O editor deve exibir um erro, a regra quebrada e sugestões para corrigir o código. Não se esqueça de habilitar o ESLint e o Prettier em seu editor se eles ainda não estiverem configurados.

O editor exibe uma linha de código atribuindo um valor à variável "let response". A linha mostra uma lâmpada amarela ao lado e a palavra "resposta" tem um sublinhado vermelho e um pop-up de erro acima dela. O pop-up de erro primeiro define a variável "resposta" e lê: "let response: { statusCode: number; body: string; }." Abaixo da definição, a mensagem de erro diz: "'resposta' nunca é reatribuída. Use 'const' em vez disso. eslint(prefer-const)." Abaixo da mensagem de erro, duas opções são: "Exibir problema" ou "Correção rápida".

Manter código com teste Jest

Muitas bibliotecas estão disponíveis para teste, como Jest, Mocha e Storybook. Usaremos o Jest em nosso projeto por alguns motivos:

  • É rápido para aprender.
  • Requer configuração mínima.
  • Ele oferece testes de instantâneos fáceis de usar.

Vamos instalar as dependências necessárias:

 npm i --save-dev jest ts-jest @types/jest

Em seguida, criaremos um arquivo de configuração Jest, jest.config.js , dentro da raiz do projeto:

 module.exports = { roots: ['src'], testMatch: ['**/__tests__/**/*.+(ts|tsx|js)'], transform: { '^.+\\.(ts|tsx)$': 'ts-jest' } };

Estamos personalizando três opções em nosso arquivo:

  • roots : Este array contém as pastas que serão pesquisadas por arquivos de teste—ele verifica apenas abaixo de nossa subpasta src .
  • testMatch : Esta matriz de padrões glob inclui as extensões de arquivo que serão consideradas arquivos Jest.
  • transform : Esta opção nos permite escrever nossos testes em TypeScript usando o pacote ts-jest .

Vamos criar uma nova pasta __tests__ dentro src/test-lambda . Dentro dele, adicionaremos o arquivo handler.test.ts , onde criaremos nosso primeiro teste:

 import { handler } from '../app'; const event: any = { body: JSON.stringify({}), headers: {} }; describe('Demo test', () => { test('This is the proof of concept that the test works.', async () => { const res = await handler(event); expect(res.statusCode).toBe(200); }); });

Retornaremos ao nosso arquivo package.json e o atualizaremos com o script de teste:

 "test": "jest"

Quando vamos ao terminal e executamos npm run test , devemos ser recebidos com um teste aprovado:

A parte superior do console mostra um indicador verde "Pass" e o nome do arquivo de teste, "src/test-lambda/__tests__/handler.test.ts." A próxima linha diz: "Teste de demonstração". A próxima linha mostra uma marca de seleção verde seguida de "Esta é a prova de conceito de que o teste funciona. (1 ms)." Após uma linha em branco, a primeira linha diz: "Conjuntos de teste: 1 aprovado, 1 total". O segundo diz: "Testes: 1 aprovado, 1 total." O terceiro diz: "Snapshots: 0 total." O quarto diz: "Tempo: 0,959 s." A última linha diz: "Ran all test suites."

Manipular o controle de origem com .gitignore

Devemos configurar o Git para excluir determinados arquivos do controle de origem. Podemos criar um arquivo .gitignore usando gitignore.io para pular arquivos que não são necessários:

 # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* .pnpm-debug.log* # Runtime data pids *.pid *.seed *.pid.lock npm-debug.log package.lock.json /node_modules .aws-sam .vscode # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional ESLint cache .eslintcache

Preparar, definir, construir: nosso plano para o sucesso

Agora temos um projeto clichê completo do AWS SAM com TypeScript. Nós nos concentramos em acertar o básico e manter a alta qualidade do código com suporte a ESLint, Prettier e Jest. O exemplo deste tutorial do AWS SAM pode servir como um modelo, colocando seu próximo grande projeto nos trilhos desde o início.

O Toptal Engineering Blog agradece a Christian Loef por revisar os exemplos de código apresentados neste artigo.

O logotipo da AWS com a palavra "PARTNER" e o texto "Advanced Tier Services" abaixo.
Como um parceiro de consultoria avançada na Amazon Partner Network (APN), a Toptal oferece às empresas acesso a especialistas certificados pela AWS, sob demanda, em qualquer lugar do mundo.