Edit

Share via


Pipelines for JavaScript apps

This article explains how Azure Pipelines works with JavaScript apps. Microsoft-hosted agents preinstall common JavaScript build, test, and deployment tools like npm, Node.js, Yarn, and Gulp without requiring you to set up any infrastructure. You can also configure self-hosted agents.

To quickly create a pipeline for JavaScript, see the JavaScript quickstart.

Node tool installers

To install Node.js and npm versions that aren't preinstalled, or to install the tools on self-hosted agents:

To install a specific Node.js version, add the following code to your azure-pipelines.yml file:

- task: UseNode@1
  inputs:
    version: '16.x' # replace with the version you need

Note

This task can require significant time updating to a newer minor version every time the pipeline runs. Microsoft-hosted agents are regularly updated, so use this task only to install specific Node versions that aren't preinstalled. To find out what Node.js and npm versions are preinstalled on Microsoft-hosted agents, see Software.

Use multiple node versions

You can use the Use Node.js ecosystem v1 task with a matrix strategy to build and test your app on multiple versions of Node.js. For more information, see Multi-job configuration.

pool:
  vmImage: 'ubuntu-latest'
strategy:
  matrix:
    node_16_x:
      node_version: 16.x
    node_13_x:
      node_version: 18.x

steps:
- task: UseNode@1
  inputs:
    version: $(node_version)

- script: npm install

Dependency tool installation

If you have development dependency tools in your project package.json or package-lock.json file, install the tools and dependencies through npm. The project file defines the exact version of the tools, independent of other versions that exist on the build agent.

To install these tools on your build agent, use a script, the npm task, or a command-line task in your pipeline.

To use a script:

- script: npm install --only=dev

To use the npm task:

- task: Npm@1
  inputs:
     command: 'install'

Tools you install this way use the npm npx package runner, which detects the tools in its PATH resolution. The following example calls the mocha test runner, and uses the development dependency version rather than the version installed globally through npm install -g.

- script: npx mocha

To install tools your project needs that aren't set as development dependencies in package.json, call npm install -g from a script in your pipeline. The following example installs the latest version of the Angular CLI by using npm. Other scripts in the pipeline can then use the Angular ng commands.

- script: npm install -g @angular/cli

Note

On Microsoft-hosted Linux agents, preface the command with sudo, like sudo npm install -g.

These tool installation tasks run every time the pipeline runs, so be mindful of their impact on build times. If the overhead seriously impacts build performance, consider using self-hosted agents preconfigured with the tool versions you need.

Note

These tool installation tasks run every time the pipeline runs, so be mindful of their impact on build times.

Dependency package downloads

You can use Yarn or Azure Artifacts to download packages from the public npm registry or a private npm registry that you specify in an *.npmrc file. To specify an npm registry, add its URL to the *.npmrc file in your code repository.

Use npm

You can use npm to download build packages in your pipeline in the following ways:

  • For the simplest way to download packages without authentication, directly run npm install.
  • To use an authenticated registry, add the npm task.
  • To run npm install from inside task runners Gulp, Grunt, or Maven, use the npm authenticate task.

Note

If your npm feed uses authentication, you must create an npm service connection on the Services tab in Azure DevOps Project settings to manage its credentials.

To install npm packages directly, use the following script in azure-pipelines.yml. If your build agent doesn't need development dependencies, you can speed up build times by adding the --only=prod option to npm install.

- script: npm install --only=prod

To use a private registry specified in your *.npmrc file, add the Npm@1 task to azure-pipelines.yml.

- task: Npm@1
  inputs:
    customEndpoint: <Name of npm service connection>

To pass registry credentials to npm commands via task runners such as Gulp, add the npmAuthenticate@0 task to azure-pipelines.yml before you call the task runner.

- task: npmAuthenticate@0
  inputs:
    customEndpoint: <Name of npm service connection>

Note

Microsoft-hosted agents use a new machine with every build. Restoring dependencies can take a significant amount of time. To mitigate the issue, you can use Azure Artifacts or a self-hosted agent with the package cache.

If your builds occasionally fail because of connection issues when you restore packages from the npm registry, you can use Azure Artifacts with upstream sources to cache the packages. Azure Artifacts automatically uses the credentials of the pipeline, which are ordinarily derived from the Project Collection Build Service account.

Note

Restoring dependencies can take a significant amount of time. To mitigate the issue, you can use Azure Artifacts or a self-hosted agent with the package cache.

If your builds occasionally fail because of connection issues when you restore packages from the npm registry, you can use Azure Artifacts with upstream sources to cache the packages. Azure Artifacts automatically uses the credentials of the pipeline, which are ordinarily derived from the Project Collection Build Service account.

Use Yarn

Use a script to install Yarn for restoring dependencies. Yarn is preinstalled on some Microsoft-hosted agents. You can install and configure Yarn on self-hosted agents like any other tool.

- script: yarn install

You can also use the CLI or Bash task in your pipeline to invoke Yarn.

JavaScript compilers

JavaScript apps use compilers such as Babel and the TypeScript tsc compiler to convert source code into versions usable by the Node.js runtime or in web browsers. If you have a script object set up in your project package.json file to run your compiler, you can invoke it in your pipeline.

- script: npm run compile

You can also call compilers directly from the pipeline by using a script. These commands run from the root of the cloned source code repository.

- script: tsc --target ES6 --strict true --project tsconfigs/production.json

You can use the npm task to build the code if your project package.json defines a compile script. If you don't define a compile script, you can use the Bash task to compile your code.

Unit testing

You can configure your pipelines to run your JavaScript tests so they produce results in the JUnit XML format. You can then publish the results using the Publish test results task.

If your test framework doesn't support JUnit output, add support through a partner reporting module such as mocha-junit-reporter. You can either update your test script to use the JUnit reporter, or pass those options into the task definition if the reporter supports command-line options.

The following table lists the most commonly used test runners and the reporters you can use to produce XML results:

Test runner Reporters for XML reports
Mocha mocha-junit-reporter
cypress-multi-reporters
Jasmine jasmine-reporters
Jest jest-junit
jest-junit-reporter
Karma karma-junit-reporter
Ava tap-xunit

The following example uses the mocha-junit-reporter and invokes mocha test directly by using a script. This script produces the JUnit XML output at the default ___location of ./test-results.xml.

- script: mocha test --reporter mocha-junit-reporter

If you defined a test script in your project package.json file, you can invoke it by using npm test.

- script: npm test

Publish test results

To publish test results, use the Publish test results task.

- task: PublishTestResults@2
  condition: succeededOrFailed()
  inputs:
    testRunner: JUnit
    testResultsFiles: '**/test-results.xml'

Publish code coverage results

If your test scripts run a code coverage tool such as Istanbul, add the Publish code coverage results task. You can then see coverage metrics in the build summary and download HTML reports for further analysis.

The task expects Cobertura or JaCoCo reporting output. Ensure that your code coverage tool runs with the necessary options to generate the right output, for example --report cobertura.

The following example uses the Istanbul command-line interface nyc along with mocha-junit-reporter, and invokes npm test.

- script: |
    nyc --reporter=cobertura --reporter=html \
    npm test -- --reporter mocha-junit-reporter --reporter-options mochaFile=./test-results.xml
  displayName: 'Build code coverage report'

- task: PublishCodeCoverageResults@2
  inputs: 
    summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/*coverage.xml'

End-to-end browser testing

Your pipeline can use tools like Protractor or Karma to run tests in headless browsers, and then publish test results. To configure browser testing and publish results, follow these steps:

  1. Install a headless browser testing driver such as headless Chrome or Firefox, or a browser-mocking tool such as PhantomJS, on the build agent.
  2. Configure your test framework to use your headless browser or driver option according to the tool's documentation.
  3. Configure your test framework to output JUnit-formatted test results, usually with a reporter plug-in or configuration.
  4. Add a script or CLI task to start the headless browser instances.
  5. Run the end-to-end tests in the pipeline stages along with your unit tests.
  6. Publish the results along with your unit tests by using the same Publish test results task.

Packaging and delivery

After you build and test your app, you can:

  • Upload the build output to Azure Pipelines.
  • Create and publish an npm or Maven package.
  • Package the build output into a ZIP archive for deployment to a web application.

Publish files to Azure Pipelines

To upload the entire working directory, add the Publish build artifacts task to your azure-pipelines.yml file.

- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: '$(System.DefaultWorkingDirectory)'

To upload a subset of files, first copy the necessary files from the working directory to a staging directory with the Copy files task, and then use the Publish build artifacts task.

- task: CopyFiles@2
  inputs:
    SourceFolder: '$(System.DefaultWorkingDirectory)'
    Contents: |
      **\*.js
      package.json
    TargetFolder: '$(Build.ArtifactStagingDirectory)'

- task: PublishBuildArtifacts@1

Publish a module to an npm registry

If your project's output is an npm module for other projects to use and isn't a web application, use the npm task to publish the module to a local registry or to the public npm registry. Provide a unique name/version combination each time you publish.

The following example uses the script to publish to the public npm registry. The example assumes that you manage version information such as npm version through a package.json file in version control.

- script: npm publish

The following example publishes to a custom registry defined in your repo's *.npmrc file. Set up an npm service connection to inject authentication credentials into the connection as the build runs.

- task: Npm@1
  inputs:
     command: publish
     publishRegistry: useExternalRegistry
     publishEndpoint: https://my.npmregistry.com

The following example publishes the module to an Azure DevOps Services package management feed.

- task: Npm@1
  inputs:
     command: publish
     publishRegistry: useFeed
     publishFeed: https://my.npmregistry.com

For more information about versioning and publishing npm packages, see Publish npm packages and How can I version my npm packages as part of the build process.

Package and deploy a web app

You can package applications to bundle all modules with intermediate outputs and dependencies into static assets ready for deployment. Add a pipeline stage after compilation and testing to run a tool like webpack or the Angular CLI ng build.

The following example calls webpack. For this process to work, make sure webpack is configured as a development dependency in your package.json project file. This script runs webpack with the default configuration, unless you have a webpack.config.js file in the root folder of your project.

- script: webpack

The following example uses npm run build to call the build script object defined in the project package.json file. Using the script object in your project moves the build logic into the source code and out of the pipeline.

- script: npm run build

You can also use the CLI or Bash task in your pipeline to invoke your packaging tool, such as webpack or Angular's ng build.

To create a *.zip file archive that's ready to publish to a web app, use the Archive files task.

- task: ArchiveFiles@2
  inputs:
    rootFolderOrFile: '$(System.DefaultWorkingDirectory)'
    includeRootFolder: false

To publish this archive to a web app, see Deploy to Azure App Service by using Azure Pipelines.

JavaScript frameworks

You can install packages in your pipeline to support various JavaScript frameworks.

Angular

For Angular apps, you can run Angular-specific commands such as ng test, ng build, and ng e2e. To use Angular CLI commands in your pipeline, install the angular/cli npm package on the build agent.

- script: |
    npm install -g @angular/cli
    npm install
    ng build --prod

Note

On Microsoft-hosted Linux agents, preface the command with sudo, like sudo npm install -g.

For tests in your pipeline that require a browser to run, such as running Karma with the ng test command, use a headless browser instead of a standard browser. In the Angular starter app:

  • Change the browsers entry in your karma.conf.js project file from browsers: ['Chrome'] to browsers: ['ChromeHeadless'].
  • Change the singleRun entry in your karma.conf.js project file from false to true. This change helps ensure that the Karma process stops after it runs.

React and Vue

All the dependencies for React and Vue apps are captured in your package.json file. Your azure-pipelines.yml file contains the standard npm scripts.

- script: |
    npm install
  displayName: 'npm install'

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

The build files are in a new folder, dist for Vue or build for React. The following example builds an artifact, www, that's ready for release. The pipeline uses the Use Node.js, Copy file, and Publish build artifacts tasks.

trigger:
- main

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: UseNode@1
  inputs:
    version: '16.x'
  displayName: 'Install Node.js'

- script: npm install
  displayName: 'npm install'

- script: npm run build
  displayName: 'npm build'

- task: CopyFiles@2
  inputs:
    Contents: 'build/**' # Pull the build directory (React)
    TargetFolder: '$(Build.ArtifactStagingDirectory)'

- task: PublishBuildArtifacts@1
  inputs: 
    PathtoPublish: $(Build.ArtifactStagingDirectory) # dist or build files
    ArtifactName: 'www' # output artifact named www

To release the app, point your release task to the dist or build artifact and use the Azure Web App task.

Webpack

You can use a webpack configuration file to specify a compiler such as Babel or TypeScript, to transpile JavaScript XML (JSX) or TypeScript to plain JavaScript, and to bundle your app.

- script: |
    npm install webpack webpack-cli --save-dev
    npx webpack --config webpack.config.js

Build task runners

It's common to use Gulp or Grunt as task runners to build and test JavaScript apps.

Gulp

Gulp is preinstalled on Microsoft-hosted agents.

You can run the gulp command in the YAML pipeline file.

- script: gulp # add any needed options

If the steps in your gulpfile.js file require authentication with an npm registry, add the npm authenticate task.

- task: npmAuthenticate@0
  inputs:
    customEndpoint: <Name of npm service connection>

- script: gulp

To publish JUnit or xUnit test results to the server, add the Publish test results task.

- task: PublishTestResults@2
  inputs:
    testResultsFiles: '**/TEST-RESULTS.xml'
    testRunTitle: 'Test results for JavaScript using gulp'

To publish code coverage results to the server, add the Publish code coverage results task. You can find coverage metrics in the build summary, and you can download HTML reports for further analysis.

- task: PublishCodeCoverageResults@1
  inputs: 
    codeCoverageTool: Cobertura
    summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/*coverage.xml'
    reportDirectory: '$(System.DefaultWorkingDirectory)/**/coverage'

Grunt

Grunt is preinstalled on Microsoft-hosted agents.

You can run the grunt command in the YAML file.

- script: grunt # add any needed options

If the steps in your Gruntfile.js file require authentication with an npm registry, add the npm authenticate task.

- task: npmAuthenticate@0
  inputs:
    customEndpoint: <Name of npm service connection>

- script: grunt

Troubleshooting

If you can build your project on your development machine but can't build it in Azure Pipelines, explore the following potential causes and corrective actions.

  • Check that the versions of Node.js and the task runner on your development machine match the ones on the agent.

    You can include command-line scripts such as node --version in your pipeline to check the versions installed on the agent. Either use the Use Node.js task to install the same version on the agent, or run npm install commands to update the tool versions.

  • If your builds fail intermittently while you restore packages, either the npm registry has issues, or there are networking problems between the Azure data center and the registry. Explore whether using Azure Artifacts with an npm registry as an upstream source improves the reliability of your builds.

  • If you're using nvm to manage different versions of Node.js, consider switching to the Use Node.js (UseNode@1) task instead. nvm is installed for historical reasons on the macOS image. nvm manages multiple Node.js versions by adding shell aliases and altering PATH, which interacts poorly with the way Azure Pipelines runs each task in a new process. For more information, see Pipeline runs.

    The Use Node.js task handles this model correctly. However, if your work requires using nvm, you can add the following script to the beginning of each pipeline:

    steps:
    - bash: |
        NODE_VERSION=16  # or your preferred version
        npm config delete prefix  # avoid a warning
        . ${NVM_DIR}/nvm.sh
        nvm use ${NODE_VERSION}
        nvm alias default ${NODE_VERSION}
        VERSION_PATH="$(nvm_version_path ${NODE_VERSION})"
        echo "##vso[task.prependPath]$VERSION_PATH"
    

    Then, node and other command-line tools work for the rest of the pipeline job. In each step where you use the nvm command, start the script with the following code:

    - bash: |
        . ${NVM_DIR}/nvm.sh
        nvm <command>
    

FAQ

How do I fix a pipeline failure with the message 'FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory'?

This failure type occurs when the Node.js package exceeds the memory usage limit. To resolve the issue, add a variable like NODE_OPTIONS and assign it a value of --max_old_space_size=16384.

How can I version my npm packages as part of the build process?

One option is to use a combination of version control and npm version. At the end of a pipeline run, you can update your repo with the new version. The following YAML pipeline has a GitHub repo, and the package deploys to npmjs. The build fails if there's a mismatch between the package version on npmjs and the package.json file.

variables:
    MAP_NPMTOKEN: $(NPMTOKEN) # Mapping secret var

trigger:
- none

pool:
  vmImage: 'ubuntu-latest'

steps: # Checking out connected repo
- checkout: self
  persistCredentials: true
  clean: true
    
- task: npmAuthenticate@0
  inputs:
    workingFile: .npmrc
    customEndpoint: 'my-npm-connection'
    
- task: UseNode@1
  inputs:
    version: '16.x'
  displayName: 'Install Node.js'

- script: |
    npm install
  displayName: 'npm install'

- script: |
    npm pack
  displayName: 'Package for release'

- bash: | # Grab the package version
    v=`node -p "const p = require('./package.json'); p.version;"`
    echo "##vso[task.setvariable variable=packageVersion]$v"

- task: CopyFiles@2
  inputs:
      contents: '*.tgz'
      targetFolder: $(Build.ArtifactStagingDirectory)/npm
  displayName: 'Copy archives to artifacts staging directory'

- task: CopyFiles@2
  inputs:
    sourceFolder: '$(Build.SourcesDirectory)'
    contents: 'package.json' 
    targetFolder: $(Build.ArtifactStagingDirectory)/npm
  displayName: 'Copy package.json'

- task: PublishBuildArtifacts@1 
  inputs:
    PathtoPublish: '$(Build.ArtifactStagingDirectory)/npm'
    artifactName: npm
  displayName: 'Publish npm artifact'

- script: |  # Config can be set in .npmrc
    npm config set //registry.npmjs.org/:_authToken=$(MAP_NPMTOKEN) 
    npm config set scope "@myscope"
    # npm config list
    # npm --version
    npm version patch --force
    npm publish --access public

- task: CmdLine@2 # Push changes to GitHub (substitute your repo)
  inputs:
    script: |
      git config --global user.email "username@contoso.com"
      git config --global user.name "Azure Pipeline"
      git add package.json
      git commit -a -m "Test Commit from Azure DevOps"
      git push -u origin HEAD:main