ค้นหาเว็บไซต์

aws lambda - การดีบัก TypeScript ของแอปเซิร์ฟเลส AWS SAM ใน VS Code


ฉันมีฟังก์ชันแบบไม่ใช้เซิร์ฟเวอร์ที่ฉันกำลังสร้างโดยใช้ AWS SAM ใน Visual Studio Code เวลารันไทม์ที่ฉันใช้คือ nodejs12.x แต่ฉันเขียนทุกอย่างใน TypeScript แล้วคอมไพล์เป็น JS ลงในไดเรกทอรี /dist นั่นคือไดเรกทอรีที่ฉันชี้ไปยังเท็มเพลต CloudFormation ของฉันเพื่อค้นหาฮันเดลเลอร์ ตัวอย่างเช่น ด้านขวาเป็น TS และด้านซ้ายเป็น JS ที่ฉันคอมไพล์แล้ว

ในแถบด้านข้างคุณสามารถเห็นไดเรกทอรี /dist ซึ่งไฟล์ JS ของฉันวางอยู่หลังจากที่ฉันรัน tsc ส่วนด้านล่างลงไปเล็กน้อยคือเทมเพลตและซอร์ส TypeScript ของฉัน

เทมเพลตของฉันจึงมีลักษณะดังนี้:

  LibraryAddMangaFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: dist/src/handlers.LibraryAddMangaHandler
      FunctionName: Foobar
      Runtime: nodejs12.x
      MemorySize: 256
      Timeout: 300

ฉันได้ทำตามเอกสารของ Amazon สำหรับการดีบักในเครื่องภายใน VS Code และสามารถทำงานได้ สำหรับไฟล์ JS ฉันสามารถตั้งจุดหยุด (break-points) ได้โดยไม่มีปัญหาและสามารถก้าวผ่านโค้ด JS ที่คอมไพล์แล้วได้

สิ่งที่ฉันสงสัยก็คือมันเป็นไปได้หรือไม่ที่จะดีบักขณะก้าวผ่านโค้ด TypeScript คล้ายกับที่ฉันทำใน Chrome กับแอปฝั่งไคลเอนต์ใน Angular/React ฉันรู้ว่ากรณีเหล่านั้นเฟรมเวิร์กรับผิดชอบการแมป JS กับ TS เป็นส่วนหนึ่งของกระบวนการสร้างเพื่อรองรับความต้องการของเครื่องมือดีบักของ Chrome ฉันไม่แน่ใจว่าสิ่งที่ฉันต้องการทำจะเป็นไปได้หรือไม่โดยไม่มีการสนับสนุนจาก SAM/AWS

นอกจากนี้ เมื่อคำขอ HTTP ของฉันเสร็จสิ้น เซสชั่นดีบักจะหยุดทำงานแทนที่จะดำเนินการต่อและฟังคำขอต่อไป - คล้ายกับ sam local start-api มีวิธีใดบ้างที่สามารถตั้งค่า debug runner ให้แอปยังคงทำงานอยู่? ฉันเคยเห็นบางคนแนะนำให้ใช้ Thundra แต่สิ่งนั้นต้องให้ Lambda ถูกปรับใช้ไปยัง AWS และฉันชอบวงจรการดีบักในเครื่องที่รวดเร็ว เช่น ดีบัก ปรับแต่ง ดีบัก จริงๆ ฉันไม่อยากทำ sam deploy ทุก 15 วินาทีและรอให้สแตกปรับใช้งาน

ขอบคุณ!

คำตอบ #1

ใช่ มันสามารถทำได้: สิ่งที่คุณต้องมีคือ sourcemaps

ในกรณีของฉัน ฉันมี Lambda ของฉันที่ถูกคอมไพล์และรวมเป็นไฟล์ index.js เดียวภายใต้โฟลเดอร์ dist ฉันจัดส่งสิ่งนี้พร้อมกับไฟล์ .map เดียวโดยใช้ webpack เป็นตัวรวมไฟล์

const path = require('path');
const glob = require('glob');
const webpack = require('webpack');
const CreateFileWebpack = require('create-file-webpack');
const nodeExternals = require('webpack-node-externals');

// Credits: https://hackernoon.com/webpack-creating-dynamically-named-outputs-for-wildcarded-entry-files-9241f596b065
const entryArray = glob.sync('./src/lambda/**/handler.ts');

const entryObject = entryArray.reduce((acc, item) => {
  let name = path.dirname(item.replace('./src/lambda', ''));
  // conforms with Webpack entry API
  // Example: { ingest: './src/ingest/index.ts' }
  acc[name] = item;
  return acc;
}, {});

/** @type {import('webpack').Configuration} */
module.exports = {
  cache: {
    type: 'memory',
  },
  entry: entryObject,
  devtool: false,
  target: 'node',
  // externals: [nodeExternals()],  // use if dependencies should not be bundled (like when using layers)
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
        options: {
          transpileOnly: true,
        },
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  plugins: [
    new webpack.IgnorePlugin({ resourceRegExp: /^pg-native$/ }),
    new webpack.IgnorePlugin({ resourceRegExp: /^hiredis$/ }),
    ...Object.keys(entryObject).map(lambda => {
      return new CreateFileWebpack({
        path: path.resolve(__dirname, 'build'),
        fileName: `${lambda}/package.json`,
        content: JSON.stringify({
          name: 'dummy_dependencies',
          dependencies: {},
          version: '1.0.0',
        }),
      });
    }),
    new webpack.SourceMapDevToolPlugin({
      columns: false,
      module: true,
      filename: '[file].map',
    }),
  ],
  // Output directive will generate build/<function-name>/index.js
  output: {
    filename: '[name]/index.js',
    path: path.resolve(__dirname, 'build'),
    devtoolModuleFilenameTemplate: '[absolute-resource-path]',
    // Credit to Richard Buggy!!
    libraryTarget: 'commonjs2',
  },
};

ฉันไม่รู้ว่ามันจะเป็นอย่างไรในกรณีของคุณที่มีหลายไฟล์ที่คอมไพล์แล้ว แต่ฉันคิดว่ามันคงเป็นสถานการณ์ที่คล้ายกันกับ webpack หรือเครื่องมือ "คอมไพล์" อื่น ๆ (ฉันเพิ่งไปดู esbuild เมื่อเร็ว ๆ นี้)

แม้แต่ tsc ธรรมดาที่เปิดใช้งาน sourcemaps ก็ควรเพียงพอ

หลังจากทำเช่นนั้นแล้ว คุณควรใช้การกำหนดค่า .vscode launch ที่ถูกต้องด้วย เพื่อให้ VS Code สามารถแมปไฟล์ระยะไกลของคอนเทนเนอร์กับไฟล์ในเครื่องได้ พร้อมทั้งเชื่อมต่อไฟล์ TypeScript ที่สอดคล้องกันผ่าน sourcemaps ด้วย

    {
      "name": "[Serverless] <lambda-name> attach",
      "type": "node",
      "request": "attach",
      "address": "localhost",
      "port": 5678,
      "localRoot": "${workspaceRoot}/.serverless/build/<lambda-folder>",
      "remoteRoot": "/var/task",
      "protocol": "inspector",
      "stopOnEntry": false,
      "outFiles": ["${workspaceRoot}/.serverless/build/<lambda-folder>"],
      "sourceMaps": true
    }

มีวิธีใดในการตั้งค่าโปรแกรมรันเดบั๊กให้แอปยังคงทำงานอยู่หรือไม่?

ถ้าผมเข้าใจถูก สิ่งที่คุณกำลังมองหาคือ การให้ดีบักเกอร์เชื่อมต่ออยู่ระหว่างคำร้องขอ เพื่อที่คุณจะได้ไม่ต้องเชื่อมต่อกับมันด้วยตนเองทุกครั้งที่คุณเรียกใช้ Lambda บนเครื่องท้องถิ่น

ถ้าเป็นกรณีนั้น คุณสามารถทำได้โดยการรัน sam local start-lambda โดยใช้แฟลก --shutdown และ --warm-containers LAZY (อ้างอิง):

sam local start-lambda -d 5678 --host 0.0.0.0 --shutdown --debug --warm-containers LAZY

นี่จะสร้าง API ที่คล้ายกับที่ AWS ใช้บนคลาวด์ ดังนั้นคุณสามารถเข้าถึงมันได้โดยใช้ทั้ง aws-sdk หรือ aws cli

aws lambda invoke --function-name \"<sam-template-lambda-id>\" --payload <stringified-json-input> --endpoint-url \"http://127.0.0.1:3001\" lambda_invoke_output.log

aws sam จะเก็บคอนเทนเนอร์ให้อุ่นจนกว่าโค้ด lambda จะเปลี่ยนแปลง และตัวดีบักเกอร์จะยังคงเปิดใช้งานอยู่ตลอดทุกเหตุการณ์ INVOKE ที่คุณกระตุ้นต่อมัน โดยจะหยุดที่เบรกพอยต์ของ typescript ที่คุณตั้งไว้ทุกครั้งที่คุณเรียกใช้ lambda

สรุปย่อ

ใช้ sourcemaps และค้นหาทั่วทั้งอินเทอร์เน็ตเกี่ยวกับวิธีการรวมทุกสิ่งเข้าด้วยกัน


บันทึกส่วนตัว: ครั้งแรกที่ฉันต้องจัดการกับเรื่องนี้ ฉันต้องล่องเรือผ่านอินเทอร์เน็ตอย่างยาวนานและเจ็บปวดกว่าจะทำมันเสร็จ

ชีวิตไม่ควรเป็นแบบนั้น :')

คำตอบ #2

ถ้าคุณต้องการทำสิ่งนี้โดยไม่ใช้ webpack ตอนนี้มีวิธีที่ง่ายกว่ามาก

ก่อนอื่นตรวจสอบให้แน่ใจว่าคุณกำลังสร้าง sourcemaps ใน tsconfig.js ของคุณ (มีหลายวิธีในการทำสิ่งนี้ ฉันชอบ sourcemaps แบบฝังในบรรทัด)

{
  "compilerOptions": {
    ...,
    "inlineSourceMap": true,
    ...
  },
  ...
}

ติดตั้งส่วนขยาย AWS Toolkit สำหรับ VSCode ตอนนี้ ในไฟล์ template.yaml ของคุณ คุณจะมีปุ่มวิเศษที่สร้างการตั้งค่า debug =]

บทความที่เกี่ยวข้อง