Skip to content

Debugging Typescript Azure Functions with VS Code

James Fox edited this page Dec 12, 2018 · 3 revisions

The Azure Functions Core Tools CLI and VS Code extension makes it really easy to build, debug and deploy JavaScript functions. It creates a project structure like this:

FunctionsProject
 | - MyFirstFunction
 | | - index.js
 | | - function.json
 | - MySecondFunction
 | | - index.js
 | | - function.json
 | - SharedCode
 | | - myFirstHelperFunction.js
 | | - mySecondHelperFunction.js
 | - node_modules
 | - host.json
 | - package.json
 | - extensions.csproj
 | - bin

And we can F5 straight into our JavaScript code:

js debugging

However, I wanted to write a function in Typescript and whilst there are many resources showing how to write a function entry point that compiles (transpiles) to the correct JavaScript I didn't find anything to show how to create a Typescript function project that looked like the kind of Typescript projects we're used to working with. Something with a structure like:

FunctionsProject
 | - __tests__
 | | - index-spec.ts
 | - dist
 | - src
 | | - MyFirstFunction
 | | | - index.ts
 | | | - function.json
 | - node_modules
 | - host.json
 | - package.json
 | - extensions.csproj
 | - bin

This article describes how to create this Typescript project so that local debugging still functions correctly and it becomes as easy as possible to package the function for deployment from the dist folder. The code in this repo is the resulting project.

NOTE: Taking this approach breaks deploying directly to Azure from the VS Code extension. It was never my intention to use this feature and CI/CD Azure Pipelines take care of deployment in my case.

Having reorganized the project into the above structure I initialized Typescript and used the following tsconfig.json:

{
  "compilerOptions": {
    "target": "es6",
    "lib": [
      "es6",
      "dom"
    ],
    "types": [
      "reflect-metadata",
      "node"
    ],
    "sourceMap": true,
    "declaration": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "noImplicitAny": true,
    "outDir": "./dist",
    "rootDir": "./",
    "preserveConstEnums": true,
    "removeComments": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  },
  "include": [
    "src/**/*",
    "__tests__/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

With this in place (and my tests and code written), building the project creates a dist folder with the structure I was looking for:

dist folder

The problem now is that even though we have created the correct JS files, when we try to debug the Azure Function Core Tools CLI doesn't know where to find them and so it reports 'No job functions found'

no functions found

To fix this we need to edit the tasks.json file created by the VS Code extension when it created the function project from this:

 "tasks": [
    {
      "label": "runFunctionsHost",
      "type": "shell",
      "command": "func host start",
      "isBackground": true,
      "problemMatcher": "$func-watch",
      "options": {
        "env": {
          "languageWorkers__node__arguments": "--inspect=5858"
        }
      },
      "dependsOn": "installExtensions"
    },
    {
      "label": "installExtensions",
      "command": "func extensions install",
      "type": "shell",
      "dependsOn": "build"
    }

To something like this:

  "tasks": [
    {
      "label": "runFunctionsHost",
      "type": "shell",
      "command": "func",
      "args": ["host", "start", "--script-root", "dist/src"],
      "isBackground": true,
      "problemMatcher": "$func-watch",
      "options": {
        "env": {
          "languageWorkers__node__arguments": "--inspect=5858"
        }
      },
      "dependsOn": "installExtensions"
    },
    {
      "label": "installExtensions",
      "command": "func extensions install",
      "type": "shell",
      "dependsOn": "build"
    }

The key point is that we now pass the --script-root dist/src argument to the functions CLI to tell it where to find the compiled JS files. With this in place when we F5 the project the functions CLI correctly finds our function.

functions found

Moreover, thanks to the magic of source mapping (and VS Code's native Typescript support) we automatically hit any breakpoints in our Typescript files and can debug as we would any other Typescript project.

typescript debugging

The last stage was to make sure several supporting files also get copied to our dist directory. These are

  • local.settings.json if you have settings that you want to read whilst running your function locally, this needs to be copied
  • host.json - not required for local running, but needs to be present if we're going to use dist to create our deployment package
  • package.json - again, only required to give us a correct deployment package.

These files get copied in a postbuild script task.

Deployment

I use an Azure Pipelines YAML pipeline for my CI build. This compiles the code, runs the tests and creates a ZIP file that can be used by an Azure Pipelines Release Pipeline to push the function to Azure.

name: 1.0$(rev:.r)

pool:
  vmImage: 'Ubuntu 16.04'

steps:
- task: NodeTool@0
  inputs:
    versionSpec: '10.x'
  displayName: 'Install Node.js'

- script: |
    npm install
    npm run build
  displayName: 'npm install and build'

- script: |
    npm run test-ci
  displayName: 'npm test'

- task: PublishTestResults@1
  inputs:
    testResultsFiles: 'dist/__tests__/test-results.xml'

- task: CopyFiles@2
  displayName: 'Copy dist files'
  inputs:
    SourceFolder: 'dist/src'
    Contents: |
     **/*.js
     **/*.json
     !**/local.settings.json
    TargetFolder: '$(Build.ArtifactStagingDirectory)'

- task: ArchiveFiles@2
  displayName: 'Archive files'
  inputs:
    rootFolderOrFile: '$(Build.ArtifactStagingDirectory)'
    includeRootFolder: false
    archiveFile: '$(Build.ArtifactStagingDirectory)/insult-function_$(Build.BuildNumber).zip'

- task: PublishBuildArtifacts@1
  displayName: 'Publish artifacts: drop'
  inputs:
    PathtoPublish: '$(Build.ArtifactStagingDirectory)/insult-function_$(Build.BuildNumber).zip'

The task CopyFiles@2 extracts the correct files from the dist directory for packaging. In this case it is simply all of the .js files and all of the .json files. The folder structure within dist is preserved. In the name of neatness local.settings.json is omitted as this is not used by the Azure function host and is ignored if present.

If you'd like to test this for yourself feel free to clone this repo, open the project in VS Code (with the Azure Functions extension installed) and hit F5.

Clone this wiki locally