Utilisation de TypeScript et de la prise en charge de Jest : un didacticiel AWS SAM

Publié: 2022-03-11

Outil puissant pour créer des applications sans serveur, le modèle d'application sans serveur AWS (SAM) s'associe fréquemment à JavaScript : 62 % des développeurs de moyennes et grandes entreprises choisissent JavaScript pour leur code sans serveur. Cependant, TypeScript gagne en popularité et dépasse de loin JavaScript en tant que troisième langage le plus apprécié des développeurs.

Bien que le passe-partout JavaScript ne soit pas difficile à trouver, le démarrage de projets AWS SAM avec TypeScript est plus complexe. Le didacticiel suivant montre comment créer un projet AWS SAM TypeScript à partir de zéro, ainsi que la façon dont les différentes parties fonctionnent ensemble. Les lecteurs n'ont besoin que d'une certaine familiarité avec les fonctions AWS Lambda pour suivre.

Démarrage de notre projet AWS SAM TypeScript

Le travail de base de notre application sans serveur comprend divers composants. Nous allons d'abord configurer l'environnement AWS, notre package npm et la fonctionnalité Webpack, puis nous pourrons créer, invoquer et tester notre fonction Lambda pour voir notre application en action.

Préparer l'environnement

Pour configurer l'environnement AWS, nous devons installer les éléments suivants :

  1. CLI AWS
  2. CLI AWS SAM
  3. Node.js et npm

Notez que ce tutoriel nécessite l'installation de Docker lors de l'étape 2 ci-dessus pour tester notre application localement.

Initialiser un projet vide

Créons le répertoire du projet, aws-sam-typescript-boilerplate et un sous-dossier src pour contenir le code. Depuis le répertoire du projet, nous allons configurer un nouveau package npm :

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

Cette commande créera un fichier package.json dans notre projet.

Ajouter la configuration Webpack

Webpack est un bundler de modules principalement utilisé pour les applications JavaScript. Étant donné que TypeScript compile en JavaScript brut, Webpack préparera efficacement notre code pour le navigateur Web. Nous allons installer deux bibliothèques et un chargeur personnalisé :

  • webpack : bibliothèque de base
  • webpack-cli : utilitaires de ligne de commande pour Webpack
  • ts-loader : chargeur TypeScript pour Webpack
 npm i --save-dev webpack webpack-cli ts-loader

La commande de génération de l'AWS SAM CLI, sam build , ralentit le processus de développement car elle tente d'exécuter npm install pour chaque fonction, ce qui entraîne une duplication. Nous utiliserons une commande build alternative de la bibliothèque aws-sam-webpack-plugin pour accélérer notre environnement.

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

Par défaut, Webpack ne fournit pas de fichier de configuration. Créons un fichier de configuration personnalisé nommé webpack.config.js dans le dossier racine :

 /* 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] };

Examinons maintenant les différentes parties :

  • entry : cela charge l'objet d'entrée (où Webpack commence à créer le bundle) à partir de la ressource AWS::Serverless::Function .
  • output : cela pointe vers la destination de la sortie de génération (dans ce cas, .aws-sam/build ). Ici, nous spécifions également la bibliothèque cible en tant que commonjs2 , qui attribue la valeur de retour du point d'entrée à module.exports . Ce point d'entrée est le point par défaut pour les environnements Node.js.
  • devtool : cela crée une carte source, app.js.map , dans notre destination de sortie de construction. Il mappe notre code d'origine sur le code exécuté dans le navigateur Web et facilitera le débogage si nous définissons la variable d'environnement NODE_OPTIONS sur --enable-source-maps pour notre Lambda.
  • resolve : cela indique à Webpack de traiter les fichiers TypeScript avant les fichiers JavaScript.
  • target : Cela indique à Webpack de cibler Node.js comme notre environnement. Cela signifie que Webpack utilisera la fonction Node.js require pour charger les morceaux lors de la compilation.
  • module : cela applique le chargeur TypeScript à tous les fichiers qui répondent à la condition de test . En d'autres termes, il garantit que tous les fichiers avec une extension .ts ou .tsx seront gérés par le chargeur.
  • plugins : cela aide Webpack à identifier et à utiliser notre aws-sam-webpack-plugin .

Dans la première ligne, nous avons désactivé une règle ESLint particulière pour ce fichier. Les règles ESLint standard que nous configurerons plus tard découragent l'utilisation de l'instruction require . Nous préférons require d' import dans Webpack, nous ferons donc une exception.

Configurer la prise en charge de TypeScript

L'ajout de la prise en charge de TypeScript améliorera l'expérience du développeur en :

  • Empêcher les messages d'avertissement concernant les déclarations de type manquantes.
  • Fournir une validation de type.
  • Offrant l'auto-complétion à l'intérieur de l'IDE.

Tout d'abord, nous allons installer TypeScript pour notre projet localement (ignorez cette étape si TypeScript est installé globalement) :

 npm i --save-dev typescript

Nous inclurons les types des bibliothèques que nous utilisons :

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

Nous allons maintenant créer le fichier de configuration TypeScript, tsconfig.json , à la racine du projet :

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

Ici, nous suivons la configuration par défaut recommandée par la communauté TypeScript. Nous avons ajouté include pour ajouter les fichiers sous le dossier src au programme et exclude pour éviter la compilation TypeScript pour le dossier node_modules - nous ne toucherons pas directement à ce code.

Créer une fonction Lambda

Nous n'avons pas écrit de code Lambda pour notre application sans serveur jusqu'à présent, alors allons-y. Dans le dossier src que nous avons créé précédemment, nous allons créer un sous-dossier test-lambda contenant un fichier app.ts avec cette fonction 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; };

Cette simple fonction d'espace réservé renvoie une réponse 200 avec un corps. Nous pourrons exécuter le code après une étape supplémentaire.

Inclure le fichier de modèle AWS

AWS SAM nécessite un fichier de modèle pour transpiler notre code et le déployer dans le cloud. Créez le fichier template.yaml dans le dossier racine :

 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

Ce fichier de modèle génère une fonction Lambda accessible à partir d'une API HTTP GET. Notez que la version référencée sur la ligne Runtime: peut nécessiter une personnalisation.

Exécutez l'application

Pour exécuter l'application, nous devons ajouter un nouveau script dans le fichier package.json pour construire le projet avec Webpack. Le fichier peut contenir des scripts existants, comme un script de test vide. Nous pouvons ajouter le script de construction comme ceci :

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

Si vous exécutez npm run build à partir de la racine du projet, vous devriez voir le dossier build, .aws-sam , créé. Ceux d'entre nous dans un environnement Mac peuvent avoir besoin de rendre les fichiers cachés visibles en appuyant sur Commande + Maj + . pour voir le dossier.

Nous allons maintenant démarrer un serveur HTTP local pour tester notre fonction :

 sam local start-api

Lorsque nous visitons le point de terminaison de test dans un navigateur Web, nous devrions voir un message de réussite.

Le navigateur Web affiche le lien "127.0.0.1:3000/test" dans la barre d'adresse. Sous la barre d'adresse, la page Web est vide à l'exception d'un message indiquant '{"message": "La demande a réussi."}.

La console doit indiquer que la fonction est montée dans un conteneur Docker avant son exécution, c'est pourquoi nous avons installé Docker plus tôt :

 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

Améliorer notre flux de travail de développement pour un environnement professionnel

Notre projet est opérationnel, l'ajout de quelques touches de finition assurera une expérience de développeur exceptionnelle qui stimulera la productivité et la collaboration.

Optimisez la construction avec le rechargement à chaud

Il est fastidieux d'exécuter la commande build après chaque changement de code. Le rechargement à chaud résoudra ce problème. Nous pouvons ajouter un autre script dans notre package.json pour surveiller les modifications de fichiers :

 "watch": "webpack-cli -w"

Ouvrez un terminal séparé et exécutez npm run watch . Désormais, votre projet se compilera automatiquement lorsque vous modifierez un code. Modifiez le message du code, actualisez votre page Web et consultez le résultat mis à jour.

Améliorez la qualité du code avec ESLint et Prettier

Aucun projet TypeScript ou JavaScript n'est complet sans ESLint et Prettier. Ces outils maintiendront la qualité et la cohérence du code de votre projet.

Commençons par installer les dépendances principales :

 npm i --save-dev eslint prettier

Nous ajouterons quelques dépendances d'assistance afin qu'ESLint et Prettier puissent travailler ensemble dans notre projet TypeScript :

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

Ensuite, nous ajouterons notre linter en créant un fichier de configuration ESLint, .eslintrc , à l'intérieur de la racine du projet :

 { "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"] } } } }

Notez que la section extends de notre fichier doit conserver la configuration du plugin Prettier comme dernière ligne afin d'afficher les erreurs Prettier comme des erreurs ESLint visibles dans notre éditeur. Nous suivons les paramètres recommandés par ESLint pour TypeScript, avec quelques préférences personnalisées ajoutées dans la section des rules . N'hésitez pas à parcourir les règles disponibles et à personnaliser davantage vos paramètres. Nous avons choisi d'inclure :

  • Une erreur si nous n'utilisons pas de chaînes entre guillemets simples.
  • Un avertissement lorsque nous ne fournissons aucun cas default dans les instructions switch .
  • Un avertissement si nous réassignons un paramètre d'une fonction.
  • Un avertissement si nous appelons une instruction await dans une boucle.
  • Une erreur pour les variables inutilisées, qui rendent le code illisible et sujet aux bogues au fil du temps.

Nous avons déjà configuré notre configuration ESLint pour fonctionner avec le formatage Prettier. (Plus d'informations sont disponibles dans le projet GitHub eslint-config-prettier .) Maintenant, nous pouvons créer le fichier de configuration Prettier, .prettierrc :

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

Ces paramètres proviennent de la documentation officielle de Prettier ; vous pouvez les modifier à votre guise. Nous avons mis à jour les propriétés suivantes :

  • trailingComma : nous avons changé cela de es5 à none pour éviter les virgules de fin.
  • semi : Nous avons changé cela de false à true car nous préférons avoir un point-virgule à la fin de chaque ligne.

Enfin, il est temps de voir ESLint et Prettier en action. Dans notre fichier app.ts , nous allons changer le type de variable de response de const à let . L'utilisation de let n'est pas une bonne pratique dans ce cas puisque nous ne modifions pas la valeur de response . L'éditeur doit afficher une erreur, la règle non respectée et des suggestions pour corriger le code. N'oubliez pas d'activer ESLint et Prettier sur votre éditeur s'ils ne sont pas déjà configurés.

L'éditeur affiche une ligne de code attribuant une valeur à la variable "let response". La ligne affiche une ampoule jaune à côté, et le mot "réponse" a un soulignement rouge et une erreur pop-up au-dessus. La fenêtre contextuelle d'erreur définit d'abord la variable "response" et indique : "let response : { statusCode : nombre ; corps : chaîne ; }." Sous la définition, le message d'erreur indique : "'response' n'est jamais réaffecté. Utilisez plutôt 'const'. eslint(prefer-const)." Sous le message d'erreur, deux options s'affichent : "Afficher le problème" ou "Résolution rapide".

Maintenir le code avec Jest Testing

De nombreuses bibliothèques sont disponibles pour les tests, telles que Jest, Mocha et Storybook. Nous utiliserons Jest dans notre projet pour plusieurs raisons :

  • C'est rapide à apprendre.
  • Il nécessite une configuration minimale.
  • Il offre des tests instantanés faciles à utiliser.

Installons les dépendances requises :

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

Ensuite, nous allons créer un fichier de configuration Jest, jest.config.js , à la racine du projet :

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

Nous personnalisons trois options dans notre fichier :

  • roots : Ce tableau contient les dossiers qui seront recherchés pour les fichiers de test - il ne vérifie que sous notre sous-dossier src .
  • testMatch : ce tableau de modèles glob inclut les extensions de fichier qui seront considérées comme des fichiers Jest.
  • transform : Cette option nous permet d'écrire nos tests en TypeScript en utilisant le package ts-jest .

Créons un nouveau dossier __tests__ dans src/test-lambda . À l'intérieur, nous ajouterons le fichier handler.test.ts , où nous créerons notre premier test :

 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); }); });

Nous allons revenir à notre fichier package.json et le mettre à jour avec le script de test :

 "test": "jest"

Lorsque nous allons au terminal et npm run test , nous devrions être accueillis par un test de réussite :

Le haut de la console affiche un indicateur vert "Pass" et le nom du fichier de test, "src/test-lambda/__tests__/handler.test.ts". La ligne suivante indique "Test de démonstration". La ligne suivante affiche une coche verte suivie de "Ceci est la preuve de concept que le test fonctionne. (1 ms)." Après une ligne vide, la première ligne indique : "Suites de tests : 1 réussie, 1 au total." Le deuxième se lit comme suit : "Tests : 1 réussi, 1 au total." Le troisième indique : "Instantanés : 0 au total". Le quatrième indique : "Temps : 0,959 s." La dernière ligne indique : "Ran all test suites."

Gérer le contrôle de source avec .gitignore

Nous devons configurer Git pour exclure certains fichiers du contrôle de code source. Nous pouvons créer un fichier .gitignore en utilisant gitignore.io pour ignorer les fichiers qui ne sont pas nécessaires :

 # 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

Ready, Set, Build : notre plan de réussite

Nous avons maintenant un projet passe-partout AWS SAM complet avec TypeScript. Nous nous sommes concentrés sur l'obtention des bases et le maintien d'une qualité de code élevée avec le support ESLint, Prettier et Jest. L'exemple de ce didacticiel AWS SAM peut servir de modèle, mettant votre prochain grand projet sur la bonne voie dès le début.

Le blog Toptal Engineering exprime sa gratitude à Christian Loef pour avoir examiné les exemples de code présentés dans cet article.

Le logo AWS avec le mot "PARTNER" et le texte "Advanced Tier Services" en dessous.
En tant que partenaire conseil avancé du réseau de partenaires Amazon (APN), Toptal offre aux entreprises un accès à des experts certifiés AWS, à la demande, partout dans le monde.