You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何配置VSTS托管代理实现单仓多应用共享node_modules构建?

解决VSTS托管代理上Monorepo依赖复用与按需构建发布的方案

嘿,针对你遇到的monorepo里三个应用重复下载依赖、构建耗时久的问题,我整理了一套在Azure DevOps(原VSTS)上的完整配置方案,完美匹配你的需求:只装一次node_modules,三个应用按需构建,完成后自动发布对应产物。

核心思路拆解

我们把整个流水线分成三个核心环节,既保证依赖复用,又能实现按需构建发布:

  • 依赖一次性安装+缓存:只执行一次依赖安装(或依赖文件变更时重新安装),用缓存机制让后续构建直接复用
  • 分应用条件触发构建:三个独立的构建作业,各自对应一个应用,只有当对应应用的代码路径发生变更时才触发构建
  • 灵活发布策略:支持两种模式——单个应用构建完成后立即发布,或等所有构建完成后批量发布有更新的应用

具体YAML配置示例

第一步:全局配置与依赖缓存作业

先定义流水线的触发规则和缓存变量,确保只有相关文件变更时才触发流水线,同时基于依赖文件的哈希值生成缓存密钥,保证依赖变更时才重新缓存。

trigger:
  branches:
    include:
      - main # 替换成你的主分支名称
  paths:
    include:
      - app1/**
      - app2/**
      - app3/**
      - package.json
      - yarn.lock

variables:
  # 基于yarn.lock和package.json的哈希值生成缓存密钥,依赖变更时自动更新缓存
  cacheKey: 'yarn-$(Agent.OS)-$(hashFiles(''**/yarn.lock'', ''**/package.json''))'
  # 指定node_modules的缓存路径
  cachePath: $(System.DefaultWorkingDirectory)/node_modules

然后添加依赖安装与缓存的作业:

jobs:
  # 作业1:安装并缓存Node依赖
  - job: InstallDependencies
    displayName: 'Install & Cache Node Dependencies'
    pool:
      vmImage: 'ubuntu-latest' # 可根据需求换成windows-latest或macOS-latest
    steps:
      - task: Cache@2
        displayName: 'Restore Node Modules Cache'
        inputs:
          key: $(cacheKey)
          path: $(cachePath)
          restoreKeys: |
            yarn-$(Agent.OS)-

      - task: NodeTool@0
        displayName: 'Install Node.js'
        inputs:
          versionSpec: '18.x' # 替换成你的项目要求的Node版本

      - script: yarn install --frozen-lockfile
        displayName: 'Install Dependencies'
        # 只有缓存未命中时才执行安装,节省时间
        condition: ne(variables['Cache.RestoreCache'], 'true')

第二步:分应用条件构建作业

添加三个独立的构建作业,每个作业都依赖上面的依赖安装作业,并且仅当对应应用的路径发生变更时触发。每个作业都会复用缓存的node_modules,无需重复安装。

# 作业2:构建App1
  - job: BuildApp1
    displayName: 'Build Application 1'
    dependsOn: InstallDependencies
    pool:
      vmImage: 'ubuntu-latest'
    # 仅当App1路径有变更、手动触发流水线,或提交信息包含app1时触发
    condition: contains(variables['Build.SourceVersionMessage'], 'app1') or eq(variables['Build.Reason'], 'Manual') or exists('app1/**')
    steps:
      - task: Cache@2
        displayName: 'Restore Node Modules Cache'
        inputs:
          key: $(cacheKey)
          path: $(cachePath)
          restoreKeys: |
            yarn-$(Agent.OS)-

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

      - script: yarn build:app1 # 替换成你的App1构建命令
        displayName: 'Build App1'

      # 把构建产物发布为流水线工件,供后续发布使用
      - task: PublishPipelineArtifact@1
        displayName: 'Publish App1 Artifacts'
        inputs:
          targetPath: '$(System.DefaultWorkingDirectory)/app1/dist' # 替换成你的App1产物路径
          artifact: 'App1Build'
          publishLocation: 'Container'

  # 作业3:构建App2(结构和App1一致,替换对应路径和命令即可)
  - job: BuildApp2
    displayName: 'Build Application 2'
    dependsOn: InstallDependencies
    pool:
      vmImage: 'ubuntu-latest'
    condition: contains(variables['Build.SourceVersionMessage'], 'app2') or eq(variables['Build.Reason'], 'Manual') or exists('app2/**')
    steps:
      - task: Cache@2
        displayName: 'Restore Node Modules Cache'
        inputs:
          key: $(cacheKey)
          path: $(cachePath)
          restoreKeys: |
            yarn-$(Agent.OS)-

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

      - script: yarn build:app2
        displayName: 'Build App2'

      - task: PublishPipelineArtifact@1
        displayName: 'Publish App2 Artifacts'
        inputs:
          targetPath: '$(System.DefaultWorkingDirectory)/app2/dist'
          artifact: 'App2Build'
          publishLocation: 'Container'

  # 作业4:构建App3(结构和App1一致)
  - job: BuildApp3
    displayName: 'Build Application 3'
    dependsOn: InstallDependencies
    pool:
      vmImage: 'ubuntu-latest'
    condition: contains(variables['Build.SourceVersionMessage'], 'app3') or eq(variables['Build.Reason'], 'Manual') or exists('app3/**')
    steps:
      - task: Cache@2
        displayName: 'Restore Node Modules Cache'
        inputs:
          key: $(cacheKey)
          path: $(cachePath)
          restoreKeys: |
            yarn-$(Agent.OS)-

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

      - script: yarn build:app3
        displayName: 'Build App3'

      - task: PublishPipelineArtifact@1
        displayName: 'Publish App3 Artifacts'
        inputs:
          targetPath: '$(System.DefaultWorkingDirectory)/app3/dist'
          artifact: 'App3Build'
          publishLocation: 'Container'

第三步:按需发布配置

这里提供两种发布方案,你可以根据需求选择:

方案A:单个应用构建完成后立即发布

在每个构建作业的末尾添加发布步骤(比如部署到Azure App Service、FTP等),只要该应用构建完成就自动发布。以App1为例,在PublishPipelineArtifact步骤后添加:

# 示例:部署到Azure App Service(替换成你的发布目标)
      - task: AzureWebApp@1
        displayName: 'Deploy App1 to Azure'
        inputs:
          azureSubscription: '你的Azure订阅连接名称'
          appName: '你的App1应用名称'
          package: '$(System.DefaultWorkingDirectory)/app1/dist'

方案B:等待所有构建完成后批量发布

添加一个单独的发布作业,依赖所有构建作业,然后根据哪些构建作业成功执行,发布对应产物:

# 作业5:批量发布所有更新的应用
  - job: PublishApplications
    displayName: 'Publish All Updated Applications'
    dependsOn: [BuildApp1, BuildApp2, BuildApp3]
    pool:
      vmImage: 'ubuntu-latest'
    steps:
      # 下载App1产物(仅当BuildApp1成功时执行)
      - download: current
        artifact: App1Build
        condition: succeeded('BuildApp1')
        displayName: 'Download App1 Artifacts'

      # 发布App1
      - task: AzureWebApp@1
        displayName: 'Deploy App1'
        condition: succeeded('BuildApp1')
        inputs:
          azureSubscription: '你的Azure订阅连接名称'
          appName: '你的App1应用名称'
          package: '$(Pipeline.Workspace)/App1Build'

      # 下载并发布App2
      - download: current
        artifact: App2Build
        condition: succeeded('BuildApp2')
        displayName: 'Download App2 Artifacts'

      - task: AzureWebApp@1
        displayName: 'Deploy App2'
        condition: succeeded('BuildApp2')
        inputs:
          azureSubscription: '你的Azure订阅连接名称'
          appName: '你的App2应用名称'
          package: '$(Pipeline.Workspace)/App2Build'

      # 下载并发布App3
      - download: current
        artifact: App3Build
        condition: succeeded('BuildApp3')
        displayName: 'Download App3 Artifacts'

      - task: AzureWebApp@1
        displayName: 'Deploy App3'
        condition: succeeded('BuildApp3')
        inputs:
          azureSubscription: '你的Azure订阅连接名称'
          appName: '你的App3应用名称'
          package: '$(Pipeline.Workspace)/App3Build'

关键优化点说明

  • 缓存效率:通过Cache@2任务,只有当yarn.lock或package.json变更时才重新安装依赖,否则直接复用缓存的node_modules,大幅节省时间
  • 按需构建:每个应用的构建作业仅在对应路径变更时触发,避免无效构建
  • 依赖复用:所有构建作业共享同一个依赖缓存,无需重复执行yarn install
  • 灵活发布:两种发布模式满足“单个完成即发布”或“全部完成后批量发布”的需求

内容的提问来源于stack exchange,提问作者AbdulKareem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:16:38