ionic-team/ionic-cli

Feature request: Ionic 2 (dev/prod) environment variables configuration

jgw96 opened this issue Β· 121 comments

jgw96 commented

From @amreladawy on July 24, 2016 6:58

Short description of the problem:

It is a feature request.
Just like Angular2, I hope that Ionic can provide 2 files (environment.dev.ts and environment.prod.ts) that would contain variables with different values corresponding to production and development environment.
During the build, the appropriate file to be copied and bundled within the app artifact.

Which Ionic Version? 2.x

https://forum.ionicframework.com/t/ionic-2-environment-variables-config-setup/58147/1

https://stackoverflow.com/questions/36004810/how-to-config-different-development-environment-in-angular-2-app

Copied from original issue: ionic-team/ionic-framework#7413

Can we raise the priority for this issue? This is actually very important. Our app has different configs for prod and devel. Without this, it requires ugly hand rolled variable updates... Just make the release really cumbersome.

dnmd commented

Currently our implementation uses the rollup-plugin-replace, and a slightly modified rollup config.

Basically what happens is that the import paths are replaced based on the process.env.IONIC_ENV variable during compile time. This will use the environment.dev.ts during development and environment.prod.ts when in production.

Implementation steps

  1. install the rollup-plugin-replace
    npm install --save-dev rollup-plugin-replace

  2. create two files; environment.dev.ts and environment.prod.ts within e.g. the src/config directory.

β”œβ”€β”€ src/config/environment.dev.ts

export const ENV = {  
  PRODUCTION : false,
  API_URL    : 'dev.local'
};
β”œβ”€β”€ src/config/environment.prod.ts

export const ENV = {  
  PRODUCTION : true,
  API_URL    : 'prod.local'
};
  1. import the environment-dev as the default within your pages / services etc.
// use the 'environment-dev' as the default import(!)
import { ENV } from '../../config/environment-dev';

@Component({
  templateUrl: 'hello-ionic.html'
})
export class HelloIonicPage {
   console.log( ENV.API_URL );
}
  1. update your /package.json
β”œβ”€β”€ /package.json

"config": {
    "ionic_rollup": "./config/rollup.config.js"
}
  1. create your rollup.config.js within the /config directory. Require the plugin, and add the replace snippet below to your plugins section. One can use node_modules\@ionic\app-scripts\config\rollup.config.js as a template.
β”œβ”€β”€ /config/rollup.config.js

var replace = require('rollup-plugin-replace');
var isProd  = (process.env.IONIC_ENV === 'prod');
...
plugins: [
    replace({
      exclude: 'node_modules/**',
      // use the /config/environment-dev as the default import(!), no stub needed.
      // note we only replace the "last" part of the import statement so relative paths are maintained
      '/config/environment-dev' : ( isProd ? '/config/environment-prod' : '/config/environment-dev'),
    })
   ...
]

Hope this helps someone! Of course in an ideal situation a file replacement would take place, similar to the Angular CLI

Is there any plans to actually implement this?

With "@ionic/app-scripts": "0.0.39" now defaulting to webpack for bundling, I am doing something like this.

Steps to implement:

  1. Make sure you have installed the latest app-scripts npm install @ionic/app-scripts@latest and have the latest tsconfig.json.

  2. Install dotenv.

  3. Create .env file with env variables defined in the root directory of your app. Recommended:
    add .env to .gitignore.

  4. Create and modify webpack.config.js from original found here:

β”œβ”€β”€ /config/webpack.config.js

require('dotenv').config();

...
function getPlugins() {
  var plugins = [
    new webpack.DefinePlugin({
      'API_URL': JSON.stringify(process.env.API_URL)
      // Add any more variables here that you wish to define
    })
  ];

  if (process.env.IONIC_ENV === 'prod') {
      // This helps ensure the builds are consistent if source hasn't changed:
    plugins.push(new webpack.optimize.OccurrenceOrderPlugin());
  }
  return plugins;
}
...
  1. Define custom webpack config file in package.json.
β”œβ”€β”€ /package.json

"config": {
    "ionic_webpack": "./config/webpack.config.js"
}
  1. Create custom typings file defining the constants that are in your .env file so that webpack can populate them and you are able to reference them in your application.
β”œβ”€β”€ /src/customTypings/customTypings.d.ts

declare var API_URL: string;
// Add any more variables here that you wish to define

You should now be able to reference the global variable(s) in your app.

Furthermore, you can create multiple .env files for different environments and require them in webpack.config.js like so:

require('dotenv').config({path: '/custom/path/to/your/env/vars'})

This solution is working, but I think, rewriting all webpack.config is not good, becase updates... Is there any idea from ionic team?

@jgw96 Can we get this added to a milestone? We should be able to specify different environment configuration e.g. different API endpoints for dev and production. As @slinto mentioned creating a modified webpack.config is not ideal as updates will cause issues :/

I'd really like to see this functionality supported. Is a custom webpack config the only way?

Okay... After a lot of coffee and head bumping, I've realized that the best way (at least for me) is to change the environments manually and not rely on ionic to change it. That was mainly because ionic-cli does not let you runionic serve with production environment.
I have set an environment.ts in the root folder of the app that contains all the variables.

environment.ts

export class ENV {

    public static currentEnvironment: string = "development";
    // public static currentEnvironment: string = "production";

    public static development: any = {
         key1: "value1",
         key2: "value2",
        ......
        ......
    };
    public static production: any = {
        key1: "value1",
        key2: "value2",
        ......
        ......
    };
}

Then I added an environment.service among my services.

environment.service.ts

import {Injectable} from '@angular/core';
import {ENV} from '../../environments/environment';

@Injectable()
export class EnvironmentService {


    public getKey1() : string{
        if(ENV.currentEnvironment === "development"){
            return ENV.development.key1;
        } else if(ENV.currentEnvironment === "production") {
            return ENV.production.key1;
        }
    }

    public getKey2() : string{
        if(ENV.currentEnvironment === "development"){
            return ENV.development.key2;
        } else if(ENV.currentEnvironment === "production") {
            return ENV.production.key2;
        }
    }

     ......
     ......

}

Then I added the service to the providers of the app.module and used it like any other service.
E.g. in any component you can access the values like this:

any.component.ts

...
...
...

import {EnvironmentService} from '../../services/environment.service';

...
...
...

constructor(
                      ...
                      ...
                      ...
                      private environmentService: EnvironmentService
                      ...
                      ...
                      ...) {
    }

...
...
...

let baseUrl = this.environmentService.getKey1();

...
...
...

And finally, in order to change the environment, you have to toggle (comment and uncomment) the first two line of environment.ts.

This is a good workaround for now, if you need different values (URLs, API keys, etc.) to be used in development and production. But, I think eventually, ionic and ionic-cli teams will have to work together to integrate something like this internally, that lets us use different sets of environment variables based on a flag that we pass to the command that is running the app.
E.g. :
ionic (serve/run) (--prod/--dev)

hmm, the manual comment/uncomment of right ENV is dangerous, because is there a big risk with publishing application with development environment to store, especially if your team is about 1+ people :) I think about right way as:

ionic serve / DEVELOPMENT ENV (DEFAULT):
$ ionic serve

ionic serve / PRODUCTION or CUSTOM ENV:
$ ionic serve --env=prod,
$ ionic serve --env=test,
$ ionic serve --prod

ionic build / PRODUCTION ENV (DEFAULT):
$ ionic build

ionic build / DEVELOPMENT or CUSTOM ENV:
$ ionic build --dev
$ ionic build --env=test
$ ionic build --env=dev

eg. http://jonnyreeves.co.uk/2016/simple-webpack-prod-and-dev-config/

@slinto You are absolutely correct, there is the possibility of publishing the app with dev variables. But, it is only as dangerous as any other solution. You may build the prod app with the wrong command as well.
I'm actually using this in a corporate project with a big number of people on the team. I think nowadays every team uses version control systems like git or svn. So, this would be just a matter of team functionality and team members being coordinated with each other. And furthermore, there is usually only one person in team who is in charge of deploying the app.
But, again, you're right. With manual toggle of environments, there is a slightly higher chance of making the mistake. So, as I said, this is only a temporary fix. Ideally, this should be internal to ionic, using the commands you mentioned.

+1

Hey, guys, any progress of official implements?

@dnmd can you confirm that your solution works with a production build?

with 0.0.46 adding anything at config in package.json trigger rollup warnings (and the script freeze after that)

[18:58:40]  rollup: Conflicting namespaces: /space/www/applications/wav/node_modules/@angular/compiler/index.js
            re-exports 'NgContentAst' from both
            /space/www/applications/wav/node_modules/@angular/compiler/src/template_parser/template_ast.js (will be
            ignored) and /space/www/applications/wav/node_modules/@angular/compiler/src/template_parser/template_ast.js.
[18:58:40]  rollup: Conflicting namespaces: /space/www/applications/wav/node_modules/@angular/compiler/index.js
            re-exports 'PropertyBindingType' from both
            /space/www/applications/wav/node_modules/@angular/compiler/src/template_parser/template_ast.js (will be
            ignored) and /space/www/applications/wav/node_modules/@angular/compiler/src/template_parser/template_ast.js.
[18:58:40]  rollup: Conflicting namespaces: /space/www/applications/wav/node_modules/@angular/compiler/index.js
            re-exports 'templateVisitAll' from both
            /space/www/applications/wav/node_modules/@angular/compiler/src/template_parser/template_ast.js (will be
            ignored) and /space/www/applications/wav/node_modules/@angular/compiler/src/template_parser/template_ast.js.

By the way, isn't it better to listen for the argument --release ?
Another thing, why with 0.0.46 it's rollup and not webpack by default?

I like the idea of @slinto
In our case, we have actually 5 environments...

  • dev
  • test (unit test)
  • iat (human first line test => developer tester)
  • uat (human second line test => business client tester)
  • prod

And we have to copy config file at build time to choose the environment.
To chose the environment at build time, we run it like :
TARGET=xxx ionic run yyy for mac or set TARGET=xxx&& ionic run yyy for windows developer.

This is managed by a hook based on this post : http://www.kdmooreconsulting.com/blogs/build-a-cordova-hook-to-setup-environment-specific-constants/

But since we do that, we lost the live reload feature! (ionic-app-script watch doesn't copy our right config.js file and we don't know how to do that).

If we can set a parameter like @slinto told -- to choose the environment with something like "--env=xxx", and make it available for live reload ... -- it will be really cool!

Definitely a must. So +1

So how would this tie in with the Live Deploy to channel feature currently available https://docs.ionic.io/services/deploy/ ?

OK, so I needed this kind of feature to be able to have different parameters (api endpoints, google analytics ID etc...) with different environments (dev, preprod, prod...).

So I made my first module (feedbacks more than welcome, and please be gentle :p) to be able to do that: gl-ionic2-env-configuration

Basically, it loads a env-configuration.json located in the www folder before anything else (so that your api url will be set even in your apiService constructor, for example).

In the package documentation, I linked a simple node executable to easily copy the configuration file for a specific environment.

Advantages

  • You don't need to override the Ionic webpack.config (which could break things when Ionic updates).
  • You can have as much environments as you want and name it as you like.
  • You can dynamically change the configuration file without having to recompile the app (which will be very useful for continous integration systems).

Hope this will help.

Any updates to this Ionic team? I would love to see this baked into the ionic-app-scripts repo.

Inspired by the solution from @tabirkeland I thought I'd post my own version of this. The differences with the script below vs the original solution:

  • The Ionic webpack.config script is built upon, not replaced, meaning that any updates to @ionic/app-scripts should be merged in.
  • The script is aware of Ionic's --prod flag, allowing you to provide separate configuration for dev and prod builds.
  • There is no dependency on dotenv.

Here's the steps to get it all working:

  1. Create a file at config/webpack.config.js and paste the following content:
// Set the `ENV` global variable to be used in the app.
var path = require('path');
var webpack = require('webpack');

var projectRootDir = process.env.IONIC_ROOT_DIR;
var appScriptsDir = process.env.IONIC_APP_SCRIPTS_DIR;

var config = require(path.join(appScriptsDir, 'config', 'webpack.config.js'));

var env = process.env.IONIC_ENV || 'dev';
var envVars;
try {
  envVars = require(path.join(projectRootDir, 'env', env + '.json'));
} catch(e) {
  envVars = {};
}

config.plugins = config.plugins || [];
config.plugins.push(
  new webpack.DefinePlugin({
    ENV: Object.assign(envVars, {
      environment: JSON.stringify(env)
    })
  })
);

if(env === 'prod') {
  // This helps ensure the builds are consistent if source hasn't changed:
  config.plugins.push(new webpack.optimize.OccurrenceOrderPlugin());
}

module.exports = config;
  1. Add the following entry to your package.json:
  "config": {
    "ionic_webpack": "./config/webpack.config.js"
  }
  1. If you need additional configuration, create two files env/dev.json and env/prod.json. In here, put any configuration you need for that environment. Your json should be an object of key-value pairs, which will be made available for use in your application.
{
  "enableLogging": true,
  "apiServerUrl": "\"http://example.com/api\""
}

Now, you can use the ENV global constant anywhere in your .ts files:

declare const ENV;

if(ENV.environment === 'dev') {
  // Run without the `--prod` flag.
  // Any keys defined in `dev.json` will be available on the `ENV` object.
} else if (ENV.environment === 'prod') {
  // Run with the `--prod` flag.
  // Any keys defined in `prod.json` will be available on the `ENV` object.
}

The script creates an ENV object with a single key, environment, which is set to prod if you run your ionic command with the --prod flag. Otherwise, this will be dev.

The env/dev.json and env/prod.json files are optional. If present, the script will merge the appropriate object into ENV. The script above uses webpack's DefinePlugin, so remember to 'stringify' any string values, otherwise webpack will assume you want to insert a code fragment. For example:

{
  "apiServerUrl": "\"http://example.com/api\""
}

It would be great to see this baked into the core webpack.config.js file in the app-scripts repo.

I implemented the solution from @fiznool the webpack script does not look for env/dev.json but env/dev and when it does have .json it auto converts to an object so no need for the JSON.parse. I changed envVars = JSON.parse(require(path.join(projectRootDir, 'env', env))); to envVars = require(path.join(projectRootDir, 'env', env + '.json'));.

My very limited knowlege of webpack stumped me for a bit and I had to add extra quotes in my json so I ended up with {"foo": "\"bar\""}. There is probably a better way to deal with this though.

Since Ionic 2.0 final is out, I'm guessing there will be many more production level apps starting out.

If we can get an update from the Ionic team about this feature, we can at least know if we need to implement a temporary solution or go all in using one of the various suggestions from the comments.

so fyi i was able to implement @fiznool suggestion along with @NightOnFire changes and it worked for me. That said, pay attention to @NightOnFire comments on how to set up entries in your json file.

{"foo": "bar"} WILL NOT WORK and will generate exceptions.
you HAVE to format your json entries the same way @NightOnFire did his.

Hope this helps.

@NightOnFire @coldAlphaMan thanks for your comments, I've updated my comment to reflect these needed changes. πŸ‘

You cannot useENV in app.module.ts in production using @fiznool method. Any solution for this?

@geraneum Use Rollup with rollup-plugin-replace and replace strings instead of variables:

replace({
    values: {
        '{{IONIC_ENV}}': process.env.IONIC_ENV,
    },
    exclude: 'node_modules/**'
}),

And somewhere in your app:

const env = '{{IONIC_ENV}}';

@geraneum could you share a code snippet that shows this issue? It might be possible to split the references to ENV out into separate exported functions or constants to allow for aot builds.

@fiznool

I have put declare const ENV; in /src/declarations.d.ts

Then in /env/prod.json I have something like this:

{
  "ionicCloudAppId": "'<APP ID>'",
}

in /src/app/app.module.ts I have:

const cloudSettings: CloudSettings = {
  'core': {
    'app_id': ENV.ionicCloudAppId
  }
}

Everything else is the same as what you've suggested.
I have other variables declared in prod.json and they work everywhere except app.module.ts.
Also using ENV.ionicCloudAppId in other places works.
This only happens with --prod flag.

AOT can be fiddly. What exactly is the error message?

I've previously had luck exporting constants so that AOT works correctly, you could try this.

export const cloudSettings: CloudSettings = {
  'core': {
    'app_id': ENV.ionicCloudAppId
  }
}

@fiznool added export still getting the same error:
Cannot read property 'ionicCloudAppId' of undefined

Also how can I use this feature with ionic package build --release? when I do that, dev settings gets picked up in the generated APK, I also provided release.json, but still no luck.

@geraneum I just created a sample repo and building for production works as expected. In app.module.ts, there is a console log which prints the variable ENV.api_server to the console. This is defined differently in dev.json and prod.json.

When running ionic run android --prod I see the following in logcat:

02-11 14:16:26.883 16383 16383 I chromium: [INFO:CONSOLE(32)] "http://prod.example.com", source: file:///android_asset/www/build/main.js (32)

which is what I would expect.

Perhaps you can use this repo and see what the differences are in your own app? Here is my ionic info:

Cordova CLI: 6.5.0 
Ionic Framework Version: 2.0.1
Ionic CLI Version: 2.2.1
Ionic App Lib Version: 2.2.0
Ionic App Scripts Version: 1.0.0
ios-deploy version: Not installed
ios-sim version: Not installed
OS: Linux 4.8
Node Version: v6.9.4
Xcode version: Not installed

Regarding the release build, you still need the --prod flag, AFAIK --release is purely concerned with generating release keys to sign your app with.

ionic package build --prod --release

@fiznool Thank you very much for your help.
I got the correct output from console log... but for test I changed the app.module.ts like this:

imports: [
    IonicModule.forRoot(MyApp, {
      tabsHideOnSubPages: ENV.tabsHideOnSubPages
    })
  ],

and prod.json to this:

{
  "api_server": "'http://prod.example.com'",
  "tabsHideOnSubPages": true
}

And as soon as I did it, I got the following error on app launch:

Uncaught TypeError: Cannot read property 'tabsHideOnSubPages' of undefined
    at e.createInternal (main.js:26)
    at e.create (main.js:11)
    at t.create (main.js:11)
    at main.js:7
    at t.invoke (polyfills.js:3)
    at Object.onInvoke (main.js:4)
    at t.invoke (polyfills.js:3)
    at e.run (polyfills.js:3)
    at t.run (main.js:4)
    at e._bootstrapModuleFactoryWithZone (main.js:7)

Yes, I see now. Seeing the same issue. Maybe someone with more aot knowledge can help as I'm a bit stumped too πŸ˜„

I was seeing the problem of undefined things after creating config files like rollup.config.js and updating package.json. It is important to copy the files from app-scripts on git hub and edit them rather than create new files because you are overriding the defaults, which breaks if things are missing.

For the initial problem, I wanted to contribute my problem and solution:

I think the support is already there. I wanted to avoid storing the api keys and other identities from source control. I did the following:

  1. Create a "config" folder and an "environment" folder under the app root.

  2. Copy the default ionic app-script for the copy config from https://github.com/driftyco/ionic-app-scripts/blob/master/config/copy.config.js to /config

  3. Edit the copy.config.js (for me it was a firebase config, which I placed before polyfills):
    ... copyFonts: { src: ['{{ROOT}}/node_modules/ionicons/dist/fonts/**/*', '{{ROOT}}/node_modules/ionic-angular/fonts/**/*'], dest: '{{WWW}}/assets/fonts' }, copyFirebaseConfig: { src: ['{{ROOT}}/environment/firebase-config.js'], dest: '{{BUILD}}' } , copyPolyfills: { src: ['{{ROOT}}/node_modules/ionic-angular/polyfills/polyfills.js'], dest: '{{BUILD}}' }, ...

  4. Add the config to package.json so that ionic build knows that the default is overridden:
    ... "config": { "ionic_copy:": "config/copy.config.js" }, ...

  5. Add a file which exports the desired config as a const at the location specified in the modified copy.config.js

  6. Import the config where it is needed (for me it's a single import in app.module.ts):
    import {FirebaseConfig} from '../../environment/firebase-config';

  7. Tell git to ignore everything under "environment"

  8. Tell your build server to add the config file to its working dir under subdir "environment" (I haven't done this yet)

Now only your build server and dev environment know what your api keys are. I hope this helps somebody else.

@domlogic But doing so I can not get a configuration file for environments, right? Example, firebase.dev.ts, firebase.prod.ts etc ...

@jhonathas you don't need to manage that in the app code, just one environment for dev which you can use locally. Because it's not checked into source control, you can have a build server provide the prod config in the same location with the same name (but in its own workspace) and that one will be used instead. Also, other developers can provide their own config and work isolated from you if they provision their own resources for development (eg- a different firebase). I feel like these configurations are a dev ops concern rather than app dev.

Inspired by the solution of @dnmd I updated the rollup config a little bit in order to make it work with multiple ts-config files. It's supporting live reload, build and run commands of Ionic. I just copied most of the description from @dnmd, thanks mate. ;-)

Implementation steps

install the rollup-plugin-replace
npm install --save-dev rollup-plugin-replace

Create as many config files you want; environment.dev.ts, environment.qa.ts, environment.production.ts within e.g. the src/config directory.

β”œβ”€β”€ src/config/environment.dev.ts

export const ENV = {  
  PRODUCTION : false,
  API_URL    : 'dev.local'
};
β”œβ”€β”€ src/config/environment.qa.ts

export const ENV = {  
  PRODUCTION : false,
  API_URL    : 'qa.local'
};
β”œβ”€β”€ src/config/environment.production.ts

export const ENV = {  
  PRODUCTION : true,
  API_URL    : 'prod.local'
};

import the environment-dev as the default within your pages / services etc., as the rollup config will look for the environment.dev to be replace with the targeted environment.

// use the 'environment.dev' as the default import(!)
import { ENV } from '../../config/environment.dev';

@Component({
  templateUrl: 'hello-ionic.html'
})
export class HelloIonicPage {
   console.log( ENV.API_URL );
}

Update your /package.json

β”œβ”€β”€ /package.json

"config": {
    "ionic_rollup": "./config/rollup.config.js"
}

create your rollup.config.js within the /config directory. Require the plugin, and add the replace snippet below to your plugins section. One can use node_modules@ionic\app-scripts\config\rollup.config.js as a template.

β”œβ”€β”€ /config/rollup.config.js

var replace = require('rollup-plugin-replace');

// Read environment from passed parameter.
var env = 'config/environment.dev';
env = (argv.dev !== undefined) ? 'config/environment.dev' : env;
env = (argv.production !== undefined) ? 'config/environment.production' : env;
env = (argv.qa !== undefined) ? 'config/environment.qa' : env;
...
plugins: [
    replace({
      exclude: 'node_modules/**',
      'config/environment.poc': env
    }),
   ...
]

We still got the risk of outdated rollup configuration whenever Ionic releases an update. But for now it's working fine for us.

I tried the solution of @dnmd.. I am getting rollup failed: Cannot read property 'file' of undefined.
Any help? Thanks.

Another smart solution here -> ionic-team/ionic-app-scripts#683 (using webpack alias) πŸ˜‰

I am going to move this thread to driftyco/ionic-app-scripts because any updates to accomplish this would be in that codebase.

Currently using the implementation here and so far so good, works like a charm. Sadly I could not extend it because IONIC_ENV only supports dev and prod and I could not for the love of me find a way to get webpack to work with IONIC_ENV and NODE_ENV together even using webpacks EnvironmentPlugin did not seem to work so I am stuck with just dev and prod for now and its not that bad really.

We have 5 environments and urgently need this. If there is no solution I am willing to code one. Can anybody give me pointers where to hook into?

genu commented

@nottinhill Since you have 5 environments and there isn't an official way to do this, there are several working solutions in the comments above that you can use until there official support.

We are currently using a merge of gits .gitattributes use own feature and some of the examples stated above to manage multiple environments. You could do that too.

@dani-inside how would you pass the nodejs argument in this case? I tried "npm run ionic:serve --prod" but that doesn't seem to work. And what about "ionic serve"?

@marcorinck That's how we use the nodejs arguments:

  • ionic serve --qa
  • ionic build android --qa
  • ionic serve --production
  • ionic build android --production

Please note that the "--prod" argument is reserved by Ionic, that's why we used "production" for the custom environment. According to the Ionic documentation the "--prod" flag will minify your app’s code as Ionic’s source and also remove any debugging capabilities from the APK. This is generally used when deploying an app to the Google Play Store or Apple App Store.

@dani-inside thanks for the hint, yes, now the argument is being passed through.

Problem is, when the rollup replace plugin is doing an actual replacement the build doesn't seem to work anymore. No error is logged into the console, but the dev server doesn't serve any app files, I get HTTP 404 even for main.css and main.js

I use ionic 3.4.0 and have used the included rollup.config.js and just added the replace plugin. I had to add "var argv = require('yargs').argv;" though to parse the arguments.

I'd prefer to use your environment solution because I need environment specific variables before bootstrapping the angular app which doesn't work with many other proposed solutions.

@marcorinck I was having the same issue as you when using rollup replace plugin. Somewhere along the way, main.js and main.css are not completed.

But using @fiznool webpack implementation did the trick just fine. Try that instead!

You can use webpack implementation but you should use webpack-merge which merges the webpack config from app-scripts and yours. This approach will not touch the default webpack config then we can keep it up to date.

Source: http://disq.us/p/1i75llk

@nottinhill A bit late for you but I had the same need for multiple environments.

I solved it using webpack-merge, webpack's NormalModuleReplacementPlugin and deepmerge.

Full installation instructions at https://github.com/pbowyer/ionic-cascading-config/blob/master/INSTALL.md and a working project repository at https://github.com/pbowyer/ionic-cascading-config.

I could not get command line flags to work with the latest Ionic like @dani-inside uses - they worked for ionic serve but not for ionic cordova build. As a result I've had to define my own npm scripts, one for each environment. If anyone can tell me how I'd like to learn.

For anyone needing a cross-platform solution for windows too - one small change to the solution from @pbowyer

install cross-env:
npm install -S cross-env

and then change the scripts config in package.json to

"cordova:build:dev": "cross-env NODE_ENV=dev ionic cordova build android",
        "cordova:build:stage": "cross-env NODE_ENV=stage ionic cordova build android",
        "cordova:build:prod": "cross-env NODE_ENV=prod ionic cordova build android --prod",
        "ionic:serve:dev": "cross-env NODE_ENV=dev npm run ionic:serve",
        "ionic:serve:stage": "cross-env NODE_ENV=stage npm run ionic:serve",
        "ionic:serve:prod": "cross-env NODE_ENV=prod npm run ionic:serve"

Here is my easy to use solution

  • Easy to import: import { ENV } from '@app/env'
  • Can be used anywhere, even in your app.module.ts

Thanks, @gshigeto, your solution worked! It was surprising that it worked just like that, out of the box, without installing any plugins.

Because several environment variables are the same for all my environments, I used class inheritance to make it work easily. For example:

environment.model.ts:

export interface Environment {
	readonly BASE_PATH: string;
	readonly DEFAULT_BROWSER_PARAMS: ThemeableBrowserOptions;
	readonly HEADER_BG_COLOR: string;
	readonly PATH_01: string;
	readonly PATH_02: string;
	readonly PATH_03: string;
	readonly SOCKET_URI: string;
	readonly STATUS_BAR_BG_COLOR: string;
	readonly TOOLBAR_BG_COLOR: string;
	readonly TOOLBAR_COLOR: string;
}

environment.base.ts:

export abstract class EnvironmentBase implements Environment {
	// public abstract to be defined in subclass
	public abstract readonly BASE_PATH: string;

	// protected abstract to be defined in subclass and used only here
	// (it isn't in the interface)
	protected abstract readonly SOCKET_PORT: number;

	// shared environment variables
	public readonly STATUS_BAR_BG_COLOR = '#4a8bfc';
	public readonly TOOLBAR_BG_COLOR = '#189be3';
	public readonly TOOLBAR_COLOR = '#ffffff';

	// shared computed environment variables
	// (BE CAREFUL: use "this" in a get function, 
	// otherwise you can get unexpected undefined values
	// if the value is defined in subclass:
	//
	// public readonly HEADER_BG_COLOR = this.STATUS_BAR_BG_COLOR; // this works
	// public readonly PATH_01 = this.BASE_PATH + '/my/path/02/'; // this doesn't work
	//
	// To avoid these kind of problems, its better to use "this" inside getters always)
	public get HEADER_BG_COLOR() { return this.STATUS_BAR_BG_COLOR };
	public get PATH_01() { return this.BASE_PATH + '/my/path/01/' };
	public get PATH_02() { return this.BASE_PATH + '/my/path/02/' };
	public get PATH_03() { return this.BASE_PATH + '/my/path/03/' };
	public get SOCKET_URI() { return `${this.BASE_PATH}:${this.SOCKET_PORT}` };

	// the constant can be an object with other constants too
	public get DEFAULT_BROWSER_PARAMS() {
		let params: ThemeableBrowserOptions = {
			statusbar: {
				color: this.STATUS_BAR_BG_COLOR,
			},
			toolbar: {
				height: 44,
				color: this.TOOLBAR_BG_COLOR,
			},
			title: {
				color: this.TOOLBAR_COLOR,
				showPageTitle: true
			},
			backButton: {
				image: 'ic_arrow_back',
				imagePressed: 'ic_arrow_back_pressed',
				align: 'left',
			},
			forwardButton: {
				image: 'ic_arrow_forward',
				imagePressed: 'ic_arrow_forward_pressed',
				align: 'left',
			},
			closeButton: {
				image: 'ic_close',
				imagePressed: 'ic_close_pressed',
				align: 'left',
			},
			backButtonCanClose: true,
		};
		return params;
	};
}

environment.ts:

class EnvironmentImpl extends EnvironmentBase {
	public readonly BASE_PATH = 'https://localhost';
	public readonly SOCKET_PORT = 3000;
}

export const ENV: Environment = new EnvironmentImpl();

environment.stage.ts:

class EnvironmentImpl extends EnvironmentBase {
	public readonly BASE_PATH = 'https://my-fake-site.com';
	public readonly SOCKET_PORT = 3000;
}

export const ENV: Environment = new EnvironmentImpl();

environment.prod.ts:

class EnvironmentImpl extends EnvironmentBase {
	public readonly BASE_PATH = 'https://my-site.com';
	public readonly SOCKET_PORT = 3000;
}

export const ENV: Environment = new EnvironmentImpl();

Then I just import the ENV constant and use it like:

console.log('path01: ' + ENV.PATH_01);
console.log('socket uri: ' + ENV.SOCKET_URI);

Thanks, @gshigeto great solution. I have one question in your documentation you mentioned

For any other configuration, just add another file src/environments/environment.*.ts which will then be used with build flags. It is that easy!

I have added an extra configuration enviroment file environment.acc.ts but I'm unable to use it, how do I set a build flag.

I have tried these command lines already:

  • npm run ionic:serve --env=acc
  • npm run ionic:serve --acc
  • npm run ionic:serve:acc (and in the package.json added a script like "ionic:serve": "IONIC_ENV=acc ionic-app-scripts serve")
  • export IONIC_ENV=acc && npm run ionic:serve

But IONIC_ENV is always set to dev except when running as:

  • npm run ionic:serve --prod

@gshigeto having the same issue as @Heerschop

Any suggestions? Thank yoU!

@Heerschop @jamesdixon I made it work using NODE_ENV instead of IONIC_ENV in the files /config/optimization.config.js and /config/webpack.config.js:

"@app/env": path.resolve('./src/environments/environment' + (process.env.NODE_ENV === 'dev' ? '' : '.' + process.env.NODE_ENV) + '.ts')

This also allows me to use ionic prod flag (for optimization) with other environments other than production (like stage).

In your package.json scripts you can add something like:

"ionic:serve:dev": "cross-env NODE_ENV=dev npm run ionic:serve",
"ionic:serve:stage": "cross-env NODE_ENV=stage npm run ionic:serve",
"ionic:serve:prod": "cross-env NODE_ENV=prod npm run ionic:serve",
"ionic:build:dev": "cross-env NODE_ENV=dev ionic cordova build",
"ionic:build:stage": "cross-env NODE_ENV=stage ionic cordova build --prod",
"ionic:build:prod": "cross-env NODE_ENV=prod ionic cordova build --prod",
"ionic:run:dev": "cross-env NODE_ENV=dev ionic cordova run",
"ionic:run:stage": "cross-env NODE_ENV=stage ionic cordova run --prod",
"ionic:run:prod": "cross-env NODE_ENV=prod ionic cordova run --prod",

(I've used cross-env because I'm on Windows: npm install --save-dev cross-env) (following the posts from @pbowyer and @wearetelescopic)

Then you can just execute commands like:

npm run ionic:serve:dev
npm run ionic:build:stage android
npm run ionic:run:prod android

and so on...

@jamesdixon thanks for the response i have solved it in a similar way by using APP_ENV. We also had a problem on a windows machine. Now I know how to fix that :-)

@gshigeto I'm still curious if we are missing something.

@lucasbasquerotto nice solution! Thank you πŸ‘

@Heerschop the Ionic CLI currently only supports the use of dev and prod. I was in the process of making a pull request to support other environments by changing the IONIC_ENV but got pretty busy. I got ahead of myself and put that in the README

Using @lucasbasquerotto solution of using NODE_ENV or even creating your own argument such as ENV will work as long as you change it in your webpack.config.js and optimization.config.js to use that variable. This is a great solution for the time being!

Thanks, everyone!

Hey all, I'm going to be re-opening this issue to attempt to solve this in the Ionic CLI. I think this issue alone shows how important this is πŸ‘

When built with --prod option, enableProdMode() is called. There is no "Angular is running in the development mode...."

So doesn't it mean that the mechanism is there already? Can anyone please explain how Ionic does the magic, detecting whether --prod is set?

@shawnlan , if I don't make mistake,

  • --prod is an ionic command parameter to optimize the build process (uglify, angular aot, sourcemap, ...)
  • enableProdMode() is an angular method and must be called by yourself in the code (inside a if(isProdEnv()) to enable optimization inside angular process.
  • --release is a cordova parameter, a bit like --prod and will enable some optimisation in cordova native part, make signatures, ...

But finally, the --prod parameter will not allow you to defined multiple environment if you have test, qa, dev, prod, pre-prod, pilot, or others... For example path to server with db can be different on each environment. In our compagny, we have 5 differents environments and each have different server address, different log level, but actually we can not choose easilly between them.

So the initial question was,
how can we configure variables depending on your environment (dev machine 1, dev machine 2, QA, pre-prod, prod...) and keeping functionnality of enableProdMode(), --prod and --release ?
The angular-cli propose to use file called environment.xxx.ts that you can choose with --env=xxx. And one variable inside this file will enable the enableProdMode() call. But this file choose independent of --prod and --release.

@movrack I understand. To me I just need to distinguish production and development.

The thing is, when you use --prod with ionic cli, enableProdMode() is called somehow, not by myself. That's why I wonder, maybe, there is a way to detect whether --prod is set from JS, and the Ionic team is using it. It's just not documented. Again, I don't know.

Not sure if Cordova has a similar mechanism so that you can detect --release from JS.

@jgw96
Could yo give us a status/feedback on this problematic from ionic team point of view?

@gshigeto @lucasbasquerotto updated to "@ionic/app-scripts": "^3.0.0-201710101530"
This breaks the solution offered using 'app/env'. Does anyone have an example to start using the new exports??

module.exports = {
  dev: devConfig,
  prod: prodConfig
}

Here is the error

nahollmo37714n:drinkpublic.com gap4$ npm run ionic:build:dev

> drinkpublic@0.0.1 ionic:build:dev /Users/gap4/Documents/WEB/drinkpublic.com
> cross-env NODE_ENV=dev ionic build

[INFO] Running app-scripts build:

[23:31:33]  build dev started ...
[23:31:33]  clean started ...
[23:31:33]  clean finished in 10 ms
[23:31:33]  copy started ...
[23:31:33]  deeplinks started ...
[23:31:33]  deeplinks finished in 117 ms
[23:31:33]  transpile started ...
There was an error in config file "/Users/gap4/Documents/WEB/drinkpublic.com/config/webpack.config.js". Using defaults instead.
TypeError: Cannot set property 'alias' of undefined
    at module.exports (/Users/gap4/Documents/WEB/drinkpublic.com/config/webpack.config.js:5:34)
    at Object.fillConfigDefaults (/Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/util/config.js:322:30)
    at Object.getWebpackConfig (/Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/webpack.js:192:44)
    at Object.buildJsSourceMaps (/Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/bundle.js:26:35)
    at /Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/transpile.js:117:51
    at transpileWorker (/Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/transpile.js:107:12)
    at Object.transpile (/Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/transpile.js:64:12)
    at /Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/build.js:106:82
[23:31:36]  transpile finished in 2.99 s
[23:31:36]  preprocess started ...
[23:31:36]  preprocess finished in 1 ms
[23:31:36]  webpack started ...
There was an error in config file "/Users/gap4/Documents/WEB/drinkpublic.com/config/webpack.config.js". Using defaults instead.
TypeError: Cannot set property 'alias' of undefined
    at module.exports (/Users/gap4/Documents/WEB/drinkpublic.com/config/webpack.config.js:5:34)
    at Object.fillConfigDefaults (/Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/util/config.js:322:30)
    at getWebpackConfig (/Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/webpack.js:192:44)
    at webpackWorker (/Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/webpack.js:64:25)
    at Object.webpack (/Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/webpack.js:29:12)
    at bundleWorker (/Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/bundle.js:13:22)
    at Object.bundle (/Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/bundle.js:6:12)
    at /Users/gap4/Documents/WEB/drinkpublic.com/node_modules/@ionic/app-scripts/dist/build.js:113:25
[23:31:36]  copy finished in 3.28 s
Error: ./src/app/app.module.ts
Module not found: Error: Can't resolve '@app/env' in '/Users/gap4/Documents/WEB/drinkpublic.com/src/app'

@Ajonp With newer versions of app-scripts it seems they don't have the optimization.config.ts anymore (that I think was used with IONIC_ENV=prod) and both dev and prod are in the webpack.config.js. I changed it to the following:

var path = require('path');
var useDefaultConfig = require('@ionic/app-scripts/config/webpack.config.js');

module.exports = function () {
	var env = process.env.NODE_ENV || 'dev';
	var envFile = './src/environments/environment.' + env + '.ts';
	var alias = { "@app/env": path.resolve(envFile) };

	useDefaultConfig.dev.resolve.alias = alias;
	useDefaultConfig.prod.resolve.alias = alias;

	return useDefaultConfig;
};

PS: I use the dev environment file with the name environment.dev.ts, if yours is environment.ts you can change the above accordingly, but the main change to be done is setting both the alias above in the same file.

@Ajonp I updated my repo a couple of days ago to reflect the recent changes in ionic app scripts.

@gshigeto @lucasbasquerotto
No matter what when I try to npm run cross-env DP_ENV=dev ionic-app-scripts build with --prod flag it still takes the environment for prod even though I am saying copy from environment.dev.ts. I really need a compiled prod build, but using the dev env variables.

var path = require('path');
var useDefaultConfig = require('@ionic/app-scripts/config/webpack.config.js');

module.exports = function () {
	var env = process.env.DP_ENV || 'dev';
	console.log(`using DP_ENV:${env}`)
	var envFile = './src/environments/environment' + (env === 'prod' ? '' : '.' + env) + '.ts';
	var alias = { "@app/env": path.resolve(envFile) };

	useDefaultConfig.dev.resolve.alias = alias;
	useDefaultConfig.prod.resolve.alias = alias;

	return useDefaultConfig;
};

package.json

    "scripts": {
        "clean": "ionic-app-scripts clean",
        "build": "ionic-app-scripts build",
        "lint": "ionic-app-scripts lint",
        "ionic:build": "ionic-app-scripts build",
        "ionic:serve": "ionic-app-scripts serve",
        "ionic:build:dev": "cross-env DP_ENV=dev ionic-app-scripts build",
        "ionic:build:stage": "cross-env DP_ENV=stage ionic-app-scripts build",
        "ionic:build:prod": "cross-env DP_ENV=prod ionic-app-scripts build"
    },
    "config": {
        "ionic_webpack": "./config/webpack.config.js"
    },

Ionic

nahollmo37714n:drinkpublic.com gap4$ ionic info

cli packages: (/Users/gap4/Documents/WEB/drinkpublic.com/node_modules)

    @ionic/cli-utils  : 1.13.0
    ionic (Ionic CLI) : 3.13.0

global packages:

    cordova (Cordova CLI) : 7.0.1

local packages:

    @ionic/app-scripts : 3.0.0
    Cordova Platforms  : none
    Ionic Framework    : ionic-angular 3.7.1-201710060319

System:

    Android SDK Tools : 26.0.2
    ios-deploy        : 1.9.1
    Node              : v6.11.4
    npm               : 5.4.2
    OS                : macOS Sierra
    Xcode             : Xcode 9.0 Build version 9A235

Misc:

    backend : pro

I see it getting picked up while running the webpack

[16:01:41]  ionic-app-scripts 3.0.0
[16:01:41]  build prod started ...
[16:01:41]  clean started ...
[16:01:41]  clean finished in 5 ms
[16:01:41]  copy started ...
[16:01:41]  deeplinks started ...
[16:01:41]  deeplinks finished in 125 ms
[16:01:41]  ngc started ...
Warning: Can't resolve all parameters for AngularFirestore in /Users/gap4/Documents/WEB/drinkpublic.com/node_modules/angularfire2/firestore/index.d.ts: ([object Object], ?). This will become an error in Angular v5.x
Warning: Can't resolve all parameters for AngularFirestore in /Users/gap4/Documents/WEB/drinkpublic.com/node_modules/angularfire2/firestore/index.d.ts: ([object Object], ?). This will become an error in Angular v5.x
[16:01:50]  ngc finished in 9.18 s
[16:01:50]  preprocess started ...
[16:01:50]  copy finished in 9.38 s
[16:01:50]  preprocess finished in 83 ms
[16:01:50]  webpack started ...
using DP_ENV:dev

@Ajonp I think it is weird such a behaviour as you mentioned, if I understood correctly. Try these 2 cases and see which environment they are using:

In scripts inside package.json:

"ionic:build:dev": "cross-env DP_ENV=dev ionic-app-scripts build",
"ionic:build:dev:prod": "cross-env DP_ENV=dev ionic-app-scripts build --prod",

And run it like:

npm run ionic:build:dev android (or ios) for dev environment without ionic prod flag
npm run ionic:build:dev:prod android (or ios) for dev environment with ionic prod flag

And see if both have the same environment (both should have dev environment).

Call console.log('ENV', ENV) in one of your ts files to see the environment.

This way will be easier to know what is wrong.

@lucasbasquerotto thank you for reminding me to output using console.log('ENV', ENV)

It showed I was using the dev file, it is something with firebase wanting to hold on for some reason.

Thanks again! I would upvote you and @gshigeto if we were on SO. However, I am already probably misusing github for this issue πŸ’―

Well @lucasbasquerotto the saga continues. When I console.log(ENV) I see my dev environment. However when I run AngularFireModule.initializeApp(ENV.firebase), it still seems to use the production variables.

@Ajonp I don't know why that would happen. Do you use lazy load modules? (although even in this case it should work)

Try to put this code in your app.module.ts:

import { ENV } from '@app/env';

console.log('ENV', ENV, ENV.firebase);

If ENV.firebase is an object (rather than a primitive value), be careful not to be using the wrong reference. The problem might be there. You can call console.log('ENV', ENV, ENV.firebase); in your environment.dev.ts and environment.ts (and even call console.log(ENV.firebase.someprop) to see the values inside ENV.firebase, because they could change in runtime).

Or you can try to use only primitive values in your ENV object. I do that but more because I store them in a json file because I use a hook to set config.xml variables (like FACEBOOK_APP_ID and FCM_SENDER_ID) dynamically, according to my environment variables, but there is also the benefit to avoid pitfalls due to using references inside the ENV object.

So try some logs and see how you create the ENV.firebase value because the problem might be there.

Thanks again @lucasbasquerotto this solution is now working. @gshigeto solution works great except with the AOT compiler. The repo attached makes it much simpler to work anywhere. Maybe @danbucholtz or @mhartington could take a look and see if it would fit for the overall project needs?

https://github.com/ajonp/angularfire2-env-issue/blob/master/README.md

is there an official way of configuring this? I could not find it in the official document so all above is just a work around?

@sandangel I don't think there is an official way, but the above works pretty much like I want it to, defining the environment variables based on a flag passed when the app is build. Aside from the initial setup (that is done only once), when I add new variables I only have to define it in the interface and in the environment files. Even adding a new environment is very easy, just adding a file for it and defining the values of the variables.

I had end up with integrating ionic with angular cli project. So I can use environment variables and also use angular 5, @nrwl workspace without losing cordova support. You can take a look at https://github.com/ngx-rocket, it’s great. Everything work fine in both angular v4 and v5 except some warning message. Since angular cli support schematics from v1.5, I think ionic team should consider using angular/cli too to take advantages of super fast angular bazel build tool. but i guess I should open another feature request about this.

Hi @lucasbasquerotto @gshigeto @Ajonp @sandangel I'm coming to this looking for the best way of building a three way environment approach (prod, dev and staging) into an app using the latest version of Ionic (3.8.0). I'm a bit confused as to what the best approach is now because it seems all three of you do it slightly differently?

@richardshergold I think integrate with angular cli is the best way because with angular 5, you can now do server side rendering with ionnic.

@richardshergold Because there isn't an official approach, my advice is to use the one that you think is the best for you, and the simplest to maintain. In my case I've used @gshigeto approach, it worked very well for almost all cases, just changed it a bit because it didn't worked with AOT when you pass environment variables inside a NgModule. So I made a few changes and you can see this demo that @Ajonp created to understand the approach:

https://github.com/ajonp/angularfire2-env-issue

The key files are listed in the README of the repository above, it uses cross-env to define the environment (npm install --save-dev cross-env). Other than the files listed there, give a look to tsconfig.json and you can download the sources and execute it locally to understand how it works. You can add to package.json, inside scripts:

"ionic:run:dev": "cross-env MY_ENV=dev ionic cordova run"

And just execute in browser for simplicity:

npm run ionic:run:dev browser

The environment files are json files inside the env directory in the project root folder (you can see them in the demo linked above).

If you want to create a staging environment, just create a file env/environment.staging.json (just like the other json files), add the folowing to the scripts in package.json:

"ionic:run:staging": "cross-env MY_ENV=staging ionic cordova run",
"ionic:run:staging:prod": "cross-env MY_ENV=staging ionic cordova run --prod",

And execute with:

npm run ionic:run:staging browser (without optimizations)
npm run ionic:run:staging:prod browser (with optimizations)

You can add any number of environments that you want, just like with staging.

Yet another webpack plugin to address this problem.

const EnvironmentSuffixPlugin = require('webpack-environment-suffix-plugin');
//...
new EnvironmentSuffixPlugin(
    ext: 'ts'
    suffix: process.env.NODE_ENV
);

@gshigeto @lucasbasquerotto
I got an error Module not found: Error: Can't resolve '@app/env', When set MY_ENV=prod

from https://github.com/gshigeto/ionic-environment-variables

@ThunderBirdsX3 Make sure you have this in your tsconfig.json:

"baseUrl": "./src",
"paths": {
  "@app/env": [
     "environments/environment"
  ]
}

Does the error happen with dev or just prod?

If your dev environment file is not environment.ts, but environment.dev.ts, then use

"baseUrl": "./src",
"@app/env": [
   "environments/environment.dev"
]

I don't know hou you are using the environment files, in my case I have the variables in json files and only 1 environment.ts, with the correct json file transferred, like I said above in the comments, and you can see it here.

@lucasbasquerotto I made everything from https://github.com/gshigeto/ionic-environment-variables

And I got error when try MY_ENV=prod, But dev or test was fine.

@ThunderBirdsX3 I'm using the environment like in this repository

https://github.com/ajonp/angularfire2-env-issue

and not from the one you linked above, so I don't know exactly what you are having. Are you receiving the error in the build step or at runtime? I think it is in the build step, if so it is in the ngc step or webpack (or some other step)?

How are you environment files named? Make sure your webpack.config.ts generate the correct name (you may log it to see in your console). There is this line in the instructions from the link you showed:

var filePath = './src/environments/environment' + (env === 'prod' ? '' : '.' + env) + '.ts';

This means that if your env is prod the file is environment.ts and not environment.prod.ts. The error may be in the file names.

If this doesn't solve your problem I don't know what could be the problem. If you can create a demo repository on github and post the link I could try to reproduce in my machine.

with latest ionic scripts, I can not get it to work with --prod. Checked out fresh from git and running npm run ionic:serve --prod
gives the error, can not find module " @ app/env"

only thing i did is npm i -g ionic@latest

Ionic only exposes two environments, dev and prod.
so while using my own env variable to facilitate stage, test, etc, i had to add the alias to both dev and prod. everything works then. I can be more precise by finding out the ionic_env and adding my aliases to just that, but then.....

so new webpack.config.js is

var chalk = require("chalk");
var fs = require('fs');
var path = require('path');
var useDefaultConfig = require('@ionic/app-scripts/config/webpack.config.js');

var env = process.env.APP_ENV;
useDefaultConfig["dev"].resolve.alias = {
    "@myenv": path.resolve(environmentPath())
}
useDefaultConfig["prod"].resolve.alias = {
    "@myenv": path.resolve(environmentPath())
}

function environmentPath() {
    var filePath = './src/environments/environment.' + env + '.ts';
    if (!fs.existsSync(filePath)) {
        console.log(chalk.red('\n' + filePath + ' does not exist!'));
        return './src/environments/environment.dev.ts'
    } else {
        return filePath;
    }
}

module.exports = function () {
    return useDefaultConfig;
};

@jhavinay that is correct. There is an open issue and update for that in my repo right now. I am sorry that I haven't gotten around to updating my repo, but that is what needs to happen with the latest ionic scripts!

Still can't figure out how to get a QA env to get passed down.
I am trying to use https://github.com/gshigeto/ionic-environment-variables

I have three environment.ts (prod, qa, dev).

I tried to update the actually app-scripts/webpack.config.js and added a new qa in that file (I'm thinking that is useless).
Then in the local /config/webpack.config.js

can't figure out how to change the env.
var env = process.env.IONIC_ENV;

I tried this. (using a new variable)

if(process.env.MY_ENV)
{
  env = process.env.MY_ENV;
}

then I would just run

MY_ENV=accept ionic cordova build android

It still building dev seems like. Even if its outputting the passed in var.

~/dev/new-mobile/gui $ MY_ENV=qa ionic cordova build android
Running app-scripts build: --platform android --target cordova
[11:39:22] build dev started ...
[11:39:22] clean started ...
[11:39:22] clean finished in 4 ms
[11:39:22] copy started ...
[11:39:22] deeplinks started ...
[11:39:22] deeplinks finished in 272 ms
[11:39:22] transpile started ...


I'm still missing something else.
Why is there no Ionic QA variable? Or is this an Angular thing.

in a nutshell, IONIC_ENV can only be dev or prod, and it depends on whether you are using --prod or --release options.
You can define any number of MY_ENV, as explained by the posts above. And use the relevant files.

In your case, by using
MY_ENV=accept ionic cordova build android
your environment is "accept", not "qa". while IONIC_ENV is dev.

so if you followed the same principles it will be looking for environment.accept.ts file

in case you have something like
MY_ENV=accept ionic cordova build android --prod
MY_ENV is accept while IONIC_ENV Is prod.

I ended up using config like
ionic:build:{{MY_ENV}}:{{IONIC_ENV}} in my package.json

Yeah once I realized everything has to be dev or prod, I did a mix of both.

But in the webpack.config.js I added this. (updating what https://github.com/gshigeto/ionic-environment-variables had)

var env = process.env.IONIC_ENV;

if(process.env.MY_ENV)
{
console.log(process.env.MY_ENV)
env = process.env.MY_ENV;
}
...

useDefaultConfig.dev.resolve.alias = {
"@app/env": path.resolve(environmentPath(env))
};

To build for dev

ionic cordova build browser --dev
or
MY_ENV=dev ionic cordova build browser
(dev is default for ionic)

Acceptance/QA
has to be
MY_ENV=accept ionic cordova build browser

prod is the same as before
ionic cordova build browser --prod

The only problem I had, is if I ran "ionic serve" to run things local using the proxy setting we have problems with cookies getting stored on the remote box and local host not having them. (We can't log in).

I fixed that by just adding in another environment called "no". environment.no.ts
This just sets up the values as empty no no environment.

Change scripts to
"ionic:serve": "MY_ENV=no ionic-app-scripts serve"

Then just run
npm run ionic:serve

Does the same as ionic serve but still uses our proxy value for backend.

As a 15-year veteran Java/Spring developer/architect, the complexity of this boggles my mind. I don't understand how something so inherently simple as environment variables could be made so complex.

i am struggling to do the same. for the time being im using the scripts object in package.json to configure for different environments like below.

"scripts": {
    "clean": "ionic-app-scripts clean",
    "build-dev": "cp ./env/config/environment-dev.ts ./src/config/environment.ts ; ionic-app-scripts build",
    "build-qa": "cp ./env/config/environment-qa.ts ./src/config/environment.ts && ionic-app-scripts build ",
    "build-prod": "cp ./env/config/environment-prod.ts ./src/config/environment.ts ;ionic-app-scripts build --prod",
}

Now i execute below for qa build.
npm run build-qa

** I have all three env configurations ina folder called env in the project root. the cp command simply copies the necessary file into a config folder in src with the right name.

@yerboogieman It is silly, but the complexity stems from lack of environment variables in the browser. Environment variables are the best thing ever. They're so good, webpack and similar tools do a sed-style replace of process.env.MY_VAR within code for the browser. But there's no standard way of having environmental config in the browser, so it's convoluted.

Anyway, for those following this issue, Ionic Angular v4 will support environmental config through the Angular CLI. Once your project is upgraded, the benefits of the Angular CLI come naturally. Therefore, I can finally put a milestone on this issue.

For Ionic Angular v2/v3, the tooling won't make use of the Angular CLI. It will continue using our own @ionic/app-scripts. I don't know if we'll revisit environmental config within app-scripts, but I do know that you can accomplish this today with the various methods listed in this issue and you can also use a custom webpack config entirely: https://github.com/ionic-team/ionic-app-scripts#overriding-config-files, which unlocks a bunch of features, but becomes much more difficult to maintain and configure.

Well, fair enough @dwieeb and thank you very much for the detailed explanation!

+1 (what's the correct way to vote on a feature request?)

@zuperm4n Use the thumbs up emoji on the first post

@dwieeb can we get an ETA for Ionic Angular 4 please? Weeks, months?
Be nice to get a ballpark so we can figure out whether to implement one of the many workarounds or hold out.

+1 for this feature request