Angular-cli: File replacements for multiple configs are not getting merged

Created on 17 Jan 2020  路  7Comments  路  Source: angular/angular-cli

馃悶 Bug report

Command (mark with an x)


  • [ ] new
  • [x ] build
  • [ ] serve
  • [ ] test
  • [ ] e2e
  • [ ] generate
  • [ ] add
  • [ ] update
  • [ ] lint
  • [ ] xi18n
  • [ ] run
  • [ ] config
  • [ ] help
  • [ ] version
  • [ ] doc

Is this a regression?

no

Description

With https://github.com/angular/angular-cli/pull/15819# the CLI allows to define multiple configurations that get overlayed. However the merge process of the configs only does a shallow merge, which e.g. for fileReplacements means that all replacements in a previous config get overriden.

I suggest to do a deep merge on the configurations, not a shallow merge.

馃敩 Minimal Reproduction

Example:

with this angular.json:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "proto-sites-next-app": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:component": {
          "style": "scss"
        }
      },
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/proto-sites-next-app",
            "index": "src/index.html",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "main": "src/main.ts",
            "aot": true,
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "./node_modules/bootstrap/dist/css/bootstrap.css",
              "./node_modules/sample-sites-next/data/assets/lightTheme/main.css",
              "src/styles.scss"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb",
                  "maximumError": "10kb"
                }
              ]
            },
            "edit": {
              "outputPath": "dist/proto-sites-next-app-edit",
              "fileReplacements": [
                {
                  "replace": "src/app/app.bootstrap.module.ts",
                  "with": "src/app/app.bootstrap.edit.module.ts"
                }
              ]
            },
            "view": {
              "outputPath": "dist/proto-sites-next-app-view",
              "fileReplacements": [
                {
                  "replace": "src/app/app.bootstrap.module.ts",
                  "with": "src/app/app.bootstrap.view.module.ts"
                }
              ]
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "proto-sites-next-app:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "proto-sites-next-app:build:production"
            },
            "edit": {
              "browserTarget": "proto-sites-next-app:build:edit-mode"
            },
            "view": {
              "browserTarget": "proto-sites-next-app:build:view-mode"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "proto-sites-next-app:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.scss"
            ],
            "scripts": []
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "tsconfig.app.json",
              "tsconfig.spec.json",
              "e2e/tsconfig.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        },
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "proto-sites-next-app:serve"
          },
          "configurations": {
            "production": {
              "devServerTarget": "proto-sites-next-app:serve:production"
            }
          }
        }
      }
    }},
  "defaultProject": "proto-sites-next-app"
}

I specify two new configs edit and view. These configs are logically orthogonal to production vs normal mode, so I would like to be able to build the application e.g. like this:

ng build --configuration=edit

and

ng build --configuration=production,edit

However the later does not work as expected because the file replacements for the environment as defined in the production config are overridden.

馃實 Your Environment


Angular CLI: 9.0.0-rc.9
Node: 10.17.0
OS: win32 x64

Angular: 9.0.0-rc.9
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Ivy Workspace: Yes

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.900.0-rc.9
@angular-devkit/build-angular     0.900.0-rc.9
@angular-devkit/build-optimizer   0.900.0-rc.9
@angular-devkit/build-webpack     0.900.0-rc.9
@angular-devkit/core              9.0.0-rc.9
@angular-devkit/schematics        9.0.0-rc.9
@ngtools/webpack                  9.0.0-rc.9
@schematics/angular               9.0.0-rc.9
@schematics/update                0.900.0-rc.9
rxjs                              6.5.4
typescript                        3.6.4
webpack                           4.41.2

Anything else relevant?

devkiarchitect devkibuild-angular investigation triage #1 feature

Most helpful comment

I'd love a feature where I'd be able to merge the fileReplacements-Array for example. If I have to copy/paste the build config permutations, the angular.json file gets quite big for me...

Best approach for me would be to merge the fileReplacements-Arrays together, like:
config a:
"fileReplacements": [ { "replace": "a", "with": "x" }, { "replace": "c", "with": "z_A" } ]
config b:
"fileReplacements": [ { "replace": "b", "with": "y" }, { "replace": "c", "with": "z_B" } ]

So when ng build --configuration a,b is run it uses:
configs merged:
"fileReplacements": [ { "replace": "a", "with": "x" }, { "replace": "b", "with": "y" }, { "replace": "c", "with": "z_B" } ]

Instead of only using fileReplacements for config b:
"fileReplacements": [ { "replace": "b", "with": "y" }, { "replace": "c", "with": "z_B" } ]

All 7 comments

It is indeed intentional that multiple configurations are not deeply merged. We opted for that approach for now because deep merging doesn't allow for wholly replacing objects easily, while shallow merging does.

So while there is repetition, the mental model is straightforward and isn't missing capabilities. We might provide more merge strategies in the future, but it just didn't make a lot of sense to start with the least flexible one.

@filipesilva thanks. How would you suggest to address the usecase I am depicting? Would I have to create the permutations "production-edit", "production-view", "normal-edit", "normal-view" to capture the correct replacements?

There's no great way of doing it that doesn't involve repetition at the moment. So something like that would be the best approach currently.

I'd love a feature where I'd be able to merge the fileReplacements-Array for example. If I have to copy/paste the build config permutations, the angular.json file gets quite big for me...

Best approach for me would be to merge the fileReplacements-Arrays together, like:
config a:
"fileReplacements": [ { "replace": "a", "with": "x" }, { "replace": "c", "with": "z_A" } ]
config b:
"fileReplacements": [ { "replace": "b", "with": "y" }, { "replace": "c", "with": "z_B" } ]

So when ng build --configuration a,b is run it uses:
configs merged:
"fileReplacements": [ { "replace": "a", "with": "x" }, { "replace": "b", "with": "y" }, { "replace": "c", "with": "z_B" } ]

Instead of only using fileReplacements for config b:
"fileReplacements": [ { "replace": "b", "with": "y" }, { "replace": "c", "with": "z_B" } ]

I am also interested in having a way to extend configurations (fileReplacements) instead of overriding it. will save us a lot of repetition in Angular.js file.

Hi,
"You can also pass in more than one configuration name as a comma-separated list. For example, to apply both stage and fr build configurations, use the command ng build --configuration stage,fr. In this case, the command parses the named configurations from left to right. If multiple configurations change the same setting, the last-set value is the final one."

I had read this feature enhancement as each subsequent configuration in the comma delimited list being merged into the target, thus overwriting any properties along the way. We're talking about shallow vs deep copies here, but I've got a set of shallow object graphs with simple properties that aren't being merged at all - just taking the last item and overwriting environment.ts wholesale. Looking at the PR that made the change it looks like we're just applying the last iteration of configurations instead of merging, right? I'm missing something I think.

What version of the CLI is in this in? I'm using 9; and it's in the docs there, but I don't see any tags or anything indicating where the change went upon merge to master.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

donaldallen picture donaldallen  路  3Comments

rwillmer picture rwillmer  路  3Comments

brtnshrdr picture brtnshrdr  路  3Comments

MateenKadwaikar picture MateenKadwaikar  路  3Comments

JanStureNielsen picture JanStureNielsen  路  3Comments