Skip to content

File replacements for multiple configs are not getting merged #16688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
14 tasks
CarstenLeue opened this issue Jan 17, 2020 · 8 comments
Closed
14 tasks

File replacements for multiple configs are not getting merged #16688

CarstenLeue opened this issue Jan 17, 2020 · 8 comments
Labels
area: @angular-devkit/architect area: @angular-devkit/build-angular feature Issue that requests a new feature needs: investigation Requires some digging to determine if action is needed
Milestone

Comments

@CarstenLeue
Copy link

🐞 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 #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?

@filipesilva
Copy link
Contributor

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.

@CarstenLeue
Copy link
Author

@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?

@filipesilva
Copy link
Contributor

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.

@antikalk
Copy link

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" } ]

@kyliau kyliau added needs: investigation Requires some digging to determine if action is needed triage #1 labels May 29, 2020
@NicholaAlkhouri
Copy link

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.

@thegr8snarfle
Copy link

thegr8snarfle commented Oct 23, 2020

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.

@alan-agius4
Copy link
Collaborator

Duplicate of #11149

@alan-agius4 alan-agius4 marked this as a duplicate of #11149 Dec 1, 2020
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Jan 1, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: @angular-devkit/architect area: @angular-devkit/build-angular feature Issue that requests a new feature needs: investigation Requires some digging to determine if action is needed
Projects
None yet
Development

No branches or pull requests

7 participants