8

Currently I have three different environments in Angular4:

  • Environment
  • Environment.Debug
  • Environment.Release

enter image description here

Now, in vsts build pipeline I have set up multi-configuration where one definition prepares artifacts for debug and release:
enter image description here

I am using "Replace tokens" task to replace variables per debug and release environment, before I run npm install and npm run {either debug or release) which then runs webpack to pack files for debug or release environment.

I saw there is an option to replace variables in Release where you can replace variables in your .json file(like appsettings.json): enter image description here

But problem is when webpack packs source code into one bundle.js I think I cannot use release to replace environment variables? Can I?

So what I want to achieve is decouple debug and release builds. This is simple I just recreate separate definitions for debug and release, where I am only replacing variables per environment. Second stage is to actually remove Replace tokens task from build pipeline and use Release variables section to replace variables per environment set up in Release. How is that possible for Angular after webpack builds bundle in js?

riQQ
  • 9,878
  • 7
  • 49
  • 66
sensei
  • 7,044
  • 10
  • 57
  • 125
  • Get the config out of environment files for all settings that are not specific to the build. Have a look here https://stackoverflow.com/a/49559443/1160794 – David Apr 11 '18 at 09:18
  • Thanks that's what I needed, is the structure of .json file the same as appsettings.json to work with Release variables? – sensei Apr 11 '18 at 09:21
  • You can use any format you need in the json file. After, it's up to you to parse it angular-side, depending on the format you chose (in the example provided, it was the same format as the environment.xx.ts files) – David Apr 11 '18 at 09:23
  • I was thinking if json file has to be same format so Release stage in vsts can replace variables? For example for standard appsettings.json we replace variables like this in release: https://i.imgur.com/U9zeoJ4.png for structure like "ApplicationInsights": { "InstrumentationKey": "xxx" }, – sensei Apr 11 '18 at 09:25
  • @AmelSalibasic if you are using angular-cli then this is done automatically by `ng build --env="envname"` – Niladri Apr 11 '18 at 09:28
  • @Niladri I don't want angular to replace variables, I want vsts Release to replace them so I don't have to use replace token and I remove variables from Build variable section. That's why I am asking where David properly guided me. I just now need to know the structure of .json and what model for config looks like so I can map json to ts class model – sensei Apr 11 '18 at 09:32
  • I'll write an answer to make it clearer – David Apr 11 '18 at 09:33

2 Answers2

6

The simplest and most efficient approach is to create tokens in your Angular Environment file and use a Tokenizer in your Release to replace those tokens which got compiled in to your Main Bundle. With this approach none of your existing code has to change.

This means you will be managing your environment variables in CI/CD where they belong, instead of your project. (Your project will still need a default environment file for running locally and maybe others for local testing)

To do this, first create an Angular Environment for deployment, such as environment.deploy.ts. This is what your build will use (only one build, multiple releases).

Here is an example environment.deploy.ts:

export const environment = {
  displayLeftPanel: "__env.displayLeftPanel__".toLowerCase() === "true",
  siteName: "__env.siteName__",
  apiUrl: "__env.apiUrl__",
};

(I add env. in front of the names to ensure token names do not clash with existing names in the bundle)

In your Release Variables configure these variable for each environment:

env.displayLeftPanel
env.siteName
env.apiUrl

For your release deployment you will want task such as the below (The below is for IIS, but you can use this as a road map for anything else)

The below tasks address the following issues:

  • The build artifact is typically zipped up and we cannot tokenize inside a zip file, so we have to first unzip it.
  • The WebPack bundle names have random IDs in them (example: main.20f8aa2b341c1c2f6442.bundle.js). We need to get exact file name to pass to our Tokenizer.
  • Then we just use a Tokenizer such as the "Tokenize with XPath/Regular expressions"

Here are the tasks:

  • IIS Web App Manage
  • Extract Files: *.zip to a folder like unzipped
  • PowerShell: Get Main Bundle Name. Something like: (you may need to adjust the path)

    $MainBundleFileName = (get-item $(System.DefaultWorkingDirectory)/main.*.bundle.js).FullName; Write-Host "##vso[task.setvariable variable=MainBundleFileName;]$MainBundleFileName"

  • Tokenizer: Perform variable substitutions on the Main Bundle > $(MainBundleFileName)

  • IIS Web App Deploy: The unzipped folder
Tolga
  • 2,643
  • 1
  • 27
  • 18
  • 1
    For replacing tokens in zip files I found this task: https://marketplace.visualstudio.com/items?itemName=solidify-labs.vsts-task-tokenize-in-archive – veuncent Nov 19 '19 at 13:45
4

You can get your config values out of the environment.xx.ts files and put them into json config files that you'll retrieve at runtime when angular bootstraps.

When releasing, use the token replace task you mentionned to replace the tokens in the json files.

The structure of the json file does not really matter, as long as the structure is the same for the config object client side (to make it easier to use). If the structure is not the same, you just need to transform retrieved data to assign it to your config object.

config.json

{
  "envName": "@@envName@@",
  "ApplicationInsights": { "InstrumentationKey": "@@xxx@@" }
}

Then you have a matching class in your angular app

export class MyConfig
{
  readonly envName: string;
  readonly ApplicationInsights:
  {
      readonly InstrumentationKey: string
  }

}

Once you've retrieved the json data angular side (called jsonData), assign it to a config object

config-service.ts

export let CONFIG: MyConfig;

//Modify jsonData if needed
let t = new MyConfig();
CONFIG = Object.assign(t, jsonData);

component.ts

import {CONFIG} from '../config-service.ts';
//...
//use it
let url = CONFIG.ApplicationInsights.InstrumentationKey;

Full implementation

https://stackoverflow.com/a/49559443/1160794

David
  • 33,444
  • 11
  • 80
  • 118