如何配置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
相关产品推荐
相关产品推荐

