العمل مع دعم TypeScript و Jest: برنامج تعليمي لـ AWS SAM

نشرت: 2022-03-11

يُعد AWS Serverless Application Model (SAM) أداة قوية لبناء تطبيقات بدون خادم ، ويقترن كثيرًا مع JavaScript: يختار 62٪ من المطورين عبر الشركات المتوسطة والكبيرة JavaScript للتعليمات البرمجية بدون خادم. ومع ذلك ، تزداد شعبية TypeScript وتتفوق كثيرًا على JavaScript باعتبارها ثالث أكثر اللغات المحببة للمطورين.

بينما ليس من الصعب العثور على JavaScript boilerplate ، فإن بدء مشروعات AWS SAM باستخدام TypeScript أكثر تعقيدًا. يوضح البرنامج التعليمي التالي كيفية إنشاء مشروع AWS SAM TypeScript من البداية بالإضافة إلى كيفية عمل الأجزاء المختلفة معًا. يجب أن يكون القراء على دراية إلى حد ما بوظائف AWS Lambda لمتابعة ذلك.

بدء مشروع AWS SAM TypeScript

يتضمن العمل الأساسي لتطبيقنا بدون خادم مكونات مختلفة. سنقوم أولاً بتهيئة بيئة AWS وحزمة npm ووظيفة Webpack - ثم يمكننا إنشاء واستدعاء واختبار وظيفة Lambda الخاصة بنا لرؤية تطبيقنا قيد التنفيذ.

جهز البيئة

لإعداد بيئة AWS ، نحتاج إلى تثبيت ما يلي:

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

لاحظ أن هذا البرنامج التعليمي يتطلب تثبيت Docker أثناء الخطوة 2 أعلاه لاختبار تطبيقنا محليًا.

بدء مشروع فارغ

لنقم بإنشاء دليل المشروع ، و aws-sam-typescript-boilerplate ، ومجلد فرعي src للاحتفاظ بالكود. من دليل المشروع ، سنقوم بإعداد حزمة npm جديدة:

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

سيقوم هذا الأمر بإنشاء ملف package.json داخل مشروعنا.

أضف تكوين Webpack

Webpack عبارة عن مجمّع وحدات يُستخدم بشكل أساسي لتطبيقات JavaScript. نظرًا لأن TypeScript يُترجم إلى JavaScript عادي ، فإن Webpack سيعد الكود الخاص بنا بشكل فعال لمتصفح الويب. سنقوم بتثبيت مكتبتين ومحمل مخصص:

  • حزمة الويب: مكتبة أساسية
  • webpack-cli: أدوات مساعدة لسطر الأوامر لـ Webpack
  • محمل ts: محمل TypeScript لـ Webpack
 npm i --save-dev webpack webpack-cli ts-loader

يؤدي أمر إنشاء AWS SAM CLI ، sam build ، إلى إبطاء عملية التطوير لأنه يحاول تشغيل npm install لكل وظيفة ، مما يتسبب في حدوث ازدواجية. سنستخدم أمر إنشاء بديل من مكتبة البرنامج المساعد aws-sam-webpack-plugin لتسريع بيئتنا.

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

بشكل افتراضي ، لا يوفر Webpack ملف تكوين. لنقم بإنشاء ملف تهيئة مخصص باسم webpack.config.js في المجلد الجذر:

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

الآن دعنا نفحص الأجزاء المختلفة:

  • entry : يؤدي هذا إلى تحميل كائن الإدخال (حيث يبدأ Webpack في إنشاء الحزمة) من AWS::Serverless::Function Resource.
  • output : يشير هذا إلى وجهة مخرجات البناء (في هذه الحالة ، .aws-sam/build ). هنا نحدد أيضًا المكتبة الهدف على أنها commonjs2 ، والتي تعين قيمة الإرجاع لنقطة الإدخال إلى module.exports . نقطة الإدخال هذه هي نقطة الدخول الافتراضية لبيئات Node.js.
  • devtool : يؤدي هذا إلى إنشاء خريطة مصدر ، app.js.map ، في وجهة مخرجات البناء الخاصة بنا. يقوم بتعيين الكود الأصلي الخاص بنا إلى الكود الذي يتم تشغيله في متصفح الويب وسيساعد في تصحيح الأخطاء إذا قمنا بتعيين متغير البيئة NODE_OPTIONS على --enable-source-maps لـ Lambda.
  • resolve : هذا يخبر Webpack بمعالجة ملفات TypeScript قبل ملفات JavaScript.
  • target : هذا يخبر Webpack باستهداف Node.js كبيئتنا. هذا يعني أن Webpack سيستخدم وظيفة require Node.js لتحميل الأجزاء عند تجميعها.
  • module : يتم تطبيق أداة تحميل TypeScript على جميع الملفات التي تفي بشرط test . بمعنى آخر ، فإنه يضمن أن جميع الملفات ذات الامتداد .ts أو .tsx سيتم معالجتها بواسطة المُحمل.
  • plugins : يساعد هذا Webpack في التعرف aws-sam-webpack-plugin .

في السطر الأول ، قمنا بتعطيل قاعدة ESLint معينة لهذا الملف. قواعد ESLint المعيارية التي سنقوم بتكوينها لاحقًا لا تشجع على استخدام العبارة require . نفضل require import في Webpack لذلك سنقوم بإجراء استثناء.

قم بإعداد دعم TypeScript

ستؤدي إضافة دعم TypeScript إلى تحسين تجربة المطور من خلال:

  • منع رسائل التحذير من فقدان تعريفات النوع.
  • توفير التحقق من النوع.
  • تقديم الإكمال التلقائي داخل IDE.

أولاً ، سنقوم بتثبيت TypeScript لمشروعنا محليًا (تجاوز هذه الخطوة إذا كان TypeScript مثبتًا عالميًا):

 npm i --save-dev typescript

سنقوم بتضمين أنواع المكتبات التي نستخدمها:

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

الآن ، سننشئ ملف تكوين TypeScript ، tsconfig.json ، في جذر المشروع:

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

نحن هنا نتبع التكوين الافتراضي الذي أوصى به مجتمع TypeScript. لقد أضفنا include لإلحاق الملفات الموجودة ضمن مجلد src بالبرنامج exclude لتجنب تجميع node_modules لمجلد node_modules - لن نلمس هذا الرمز مباشرةً.

قم بإنشاء دالة Lambda

لم نكتب أي كود Lambda لتطبيقنا الذي لا يحتوي على خادم حتى الآن ، لذا دعنا ننتقل. في المجلد src الذي أنشأناه سابقًا ، سننشئ مجلدًا فرعيًا test-lambda يحتوي على ملف app.ts مع وظيفة 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; };

ترجع وظيفة العنصر النائب البسيط هذه استجابة 200 بجسم. سنكون قادرين على تشغيل الكود بعد خطوة واحدة أخرى.

قم بتضمين ملف قالب AWS

يتطلب AWS SAM ملف قالب لتحويل الكود الخاص بنا ونشره على السحابة. قم بإنشاء ملف template.yaml في المجلد الجذر:

 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

ينشئ ملف القالب هذا وظيفة Lambda يمكن الوصول إليها من واجهة برمجة تطبيقات HTTP GET. لاحظ أن الإصدار المشار إليه في Runtime: line قد يحتاج إلى التخصيص.

قم بتشغيل التطبيق

لتشغيل التطبيق ، يجب علينا إضافة برنامج نصي جديد في ملف package.json لبناء المشروع باستخدام Webpack. قد يحتوي الملف على نصوص حالية ، مثل نص اختبار فارغ. يمكننا إضافة نص البناء مثل هذا:

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

إذا قمت بتشغيل npm run build من جذر المشروع ، فسترى مجلد البناء ، .aws-sam ، الذي تم إنشاؤه. قد يحتاج أولئك منا في بيئة Mac إلى جعل الملفات المخفية مرئية بالضغط على Command + Shift +. لرؤية المجلد.

سنبدأ الآن خادم HTTP محليًا لاختبار وظيفتنا:

 sam local start-api

عندما نزور نقطة نهاية الاختبار في متصفح الويب ، يجب أن نرى رسالة نجاح.

يعرض متصفح الويب الارتباط "127.0.0.1:3000/test" في شريط العنوان. أسفل شريط العناوين ، تكون صفحة الويب فارغة باستثناء الرسالة التي تقرأ '{"message": "تم الطلب بنجاح."}.

يجب أن تُظهر وحدة التحكم أنه يتم تثبيت الوظيفة في حاوية Docker قبل تشغيلها ، ولهذا السبب قمنا بتثبيت Docker سابقًا:

 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

تعزيز سير عمل التطوير لدينا لإعداد احترافي

مشروعنا جاهز للعمل ، وستضمن إضافة بعض اللمسات النهائية تجربة مطور استثنائية من شأنها تعزيز الإنتاجية والتعاون.

قم بتحسين الإنشاء باستخدام إعادة التحميل السريع

من الممل تشغيل أمر الإنشاء بعد كل تغيير في التعليمات البرمجية. إعادة التحميل السريع سيصلح هذه المشكلة. يمكننا إضافة برنامج نصي آخر في package.json الخاص بنا لمشاهدة تغييرات الملف:

 "watch": "webpack-cli -w"

افتح محطة منفصلة وقم npm run watch . الآن ، سيتم تجميع مشروعك تلقائيًا عند تغيير أي رمز. قم بتعديل رسالة الرمز ، وقم بتحديث صفحة الويب الخاصة بك ، واطلع على النتيجة المحدثة.

تحسين جودة الكود مع ESLint وأجمل

لا يكتمل أي مشروع TypeScript أو JavaScript بدون ESLint و Prettier. ستحافظ هذه الأدوات على جودة واتساق كود مشروعك.

لنقم بتثبيت التبعيات الأساسية أولاً:

 npm i --save-dev eslint prettier

سنضيف بعض التبعيات المساعدة حتى يتمكن ESLint و Prettier من العمل معًا في مشروع TypeScript:

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

بعد ذلك ، سنضيف linter عن طريق إنشاء ملف تكوين ESLint ، .eslintrc ، داخل جذر المشروع:

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

لاحظ أن قسم الامتدادات في ملفنا يجب أن يحافظ على تكوين البرنامج المساعد extends باعتباره السطر الأخير لعرض أخطاء Prettier كأخطاء ESLint مرئية في محررنا. نحن نتبع إعدادات ESLint الموصى بها لـ TypeScript ، مع إضافة بعض التفضيلات المخصصة في قسم rules . لا تتردد في تصفح القواعد المتاحة وتخصيص إعداداتك بشكل أكبر. اخترنا تضمين:

  • خطأ إذا لم نستخدم سلاسل ذات علامات اقتباس مفردة.
  • تحذير عندما لا نقدم حالة default في عبارات switch .
  • تحذير إذا قمنا بإعادة تعيين أي معلمة للدالة.
  • تحذير إذا استدعينا تعليمة await داخل حلقة.
  • خطأ للمتغيرات غير المستخدمة ، مما يجعل الشفرة غير قابلة للقراءة وعرضة للأخطاء بمرور الوقت.

لقد قمنا بالفعل بإعداد تكوين ESLint الخاص بنا للعمل مع تنسيق أجمل. (يتوفر المزيد من المعلومات في مشروع eslint-config-prettier GitHub.) الآن ، يمكننا إنشاء ملف تكوين Prettier ، .prettierrc :

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

هذه الإعدادات من وثائق Prettier الرسمية ؛ يمكنك تعديلها كما تريد. قمنا بتحديث الخصائص التالية:

  • trailingComma : قمنا بتغيير هذا من es5 إلى none لتجنب الفواصل الزائدة.
  • semi : قمنا بتغيير هذا من false إلى true لأننا نفضل وجود فاصلة منقوطة في نهاية كل سطر.

أخيرًا ، حان الوقت لمشاهدة ESLint و Prettier أثناء العمل. في ملف app.ts بنا ، سنغير نوع متغير response من const إلى let . استخدام let ليس ممارسة جيدة في هذه الحالة لأننا لا نقوم بتعديل قيمة response . يجب أن يعرض المحرر الخطأ والقاعدة المعطلة والاقتراحات لإصلاح الكود. لا تنس تمكين ESLint و Prettier على المحرر الخاص بك إذا لم يتم إعدادهما بالفعل.

يعرض المحرر سطرًا من التعليمات البرمجية يعين قيمة للمتغير "let response". يعرض السطر مصباحًا أصفر اللون بجانبه ، وكلمة "استجابة" بها تسطير أحمر ونافذة منبثقة للخطأ أعلاها. تحدد النافذة المنبثقة للخطأ أولاً المتغير "استجابة" وتقرأ: "دع الاستجابة: {statusCode: number؛ body: string؛}." أسفل التعريف ، تقرأ رسالة الخطأ: "لم تتم إعادة تعيين" الاستجابة "مطلقًا. استخدم" const "بدلاً من ذلك. eslint (التفضيل)." أسفل رسالة الخطأ ، يتم قراءة خيارين: "عرض المشكلة" أو "الإصلاح السريع".

الحفاظ على الكود مع اختبار الدعابة

تتوفر العديد من المكتبات للاختبار ، مثل Jest و Mocha و Storybook. سنستخدم Jest في مشروعنا لعدة أسباب:

  • إنه سريع التعلم.
  • يتطلب الحد الأدنى من الإعداد.
  • يوفر اختبار لقطة سهل الاستخدام.

لنقم بتثبيت التبعيات المطلوبة:

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

بعد ذلك ، سننشئ ملف تكوين Jest ، jest.config.js ، داخل جذر المشروع:

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

نقوم بتخصيص ثلاثة خيارات في ملفنا:

  • roots : تحتوي هذه المصفوفة على المجلدات التي سيتم البحث عنها عن ملفات الاختبار — فهي تتحقق فقط أسفل المجلد الفرعي src الخاص بنا.
  • testMatch : تتضمن هذه المجموعة من أنماط الكرة الأرضية امتدادات الملفات التي سيتم اعتبارها ملفات Jest.
  • transform : يتيح لنا هذا الخيار كتابة اختباراتنا في TypeScript باستخدام حزمة ts-jest .

لنقم بإنشاء مجلد __tests__ جديد داخل src/test-lambda . داخل ذلك ، سنضيف الملف handler.test.ts ، حيث سننشئ اختبارنا الأول:

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

سنعود إلى ملف package.json الخاص بنا ونقوم بتحديثه بنص الاختبار:

 "test": "jest"

عندما نذهب إلى المحطة npm run test ، يجب أن يتم الترحيب بنا باختبار ناجح:

يُظهر الجزء العلوي من وحدة التحكم مؤشر "Pass" أخضر واسم ملف الاختبار ، "src / test-lambda / __ tests __ / handler.test.ts." يقرأ السطر التالي ، "اختبار تجريبي". يعرض السطر التالي علامة اختيار خضراء متبوعة بـ "هذا دليل على أن الاختبار يعمل. (1 مللي ثانية)." بعد سطر فارغ ، يقرأ السطر الأول: "مجموعات الاختبار: تم اجتياز 1 ، وإجمالي 1." تقرأ الثانية: "الاختبارات: 1 ناجح ، 1 المجموع". يقرأ الثالث: "لقطات: 0 المجموع." والرابع نصه: "الوقت: 0.959 ثانية". يقول السطر الأخير: "تم تشغيل جميع مجموعات الاختبار".

التعامل مع التحكم في المصدر باستخدام .gitignore

يجب علينا تكوين Git لاستبعاد ملفات معينة من التحكم بالمصادر. يمكننا إنشاء ملف .gitignore باستخدام gitignore.io لتخطي الملفات غير المطلوبة:

 # 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

جاهز ، جاهز ، أنشئ: مخططنا للنجاح

لدينا الآن مشروع Boilerplate AWS SAM كامل مع TypeScript. لقد ركزنا على الحصول على الأساسيات بشكل صحيح والحفاظ على جودة الأكواد العالية مع دعم ESLint و Prettier و Jest. يمكن أن يكون المثال من هذا البرنامج التعليمي AWS SAM بمثابة مخطط ، يضع مشروعك الكبير التالي على المسار الصحيح من البداية.

تعرب مدونة Toptal Engineering عن امتنانها لكريستيان لوف لمراجعة نماذج التعليمات البرمجية المقدمة في هذه المقالة.

شعار AWS مع كلمة "PARTNER" والنص "Advanced Tier Services" أدناه.
كشريك استشاري متقدم في شبكة شركاء أمازون (APN) ، تقدم Toptal للشركات إمكانية الوصول إلى خبراء معتمدين من AWS ، عند الطلب ، في أي مكان في العالم.