Skip to content
This repository has been archived by the owner on May 1, 2020. It is now read-only.

Alias environment specific module (to change API keys, constants in c… #683

Closed
wants to merge 1 commit into from

Conversation

ehorodyski
Copy link

Short description of what this resolves:

Allows environment-based configuration at the code-level.

Changes proposed in this pull request:

Using a module alias in Webpack, allow users to utilize environment-based configuration in their app code.

I've seen a lot of requests and half-measures researching this today and thought I could contribute to the base idea of how this can be accomplished.

This is a quick proof of what can be done. I am willing to help contribute upon this concept and expand it to a form that works for the Ionic team.

@ehorodyski
Copy link
Author

I realize that this is a very simple and base solution -- I am hardcoding in the alias name and path, as it's what I've used in my current project. I do not expect this to be pulled, but rather conceptualized, and serve as a base of how to achieve a request many users are asking for.
Best,
Eric

Copy link

@biesbjerg biesbjerg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you pass a value to env.IONIC_ENV? Is it automatically dev/prod, depending on if --prod is passed as argument?

@ehorodyski
Copy link
Author

ehorodyski commented Jan 24, 2017

@biesbjerg -- Yup, it's either --dev or --prod. That portion is controlled outside of the webpack.config.js file. Specifically, it's in src\util\config.ts:

  setProcessEnvVar(Constants.ENV_VAR_IONIC_ENV, 
    (context.isProd ? Constants.ENV_VAR_PROD : Constants.ENV_VAR_DEV));

@biesbjerg
Copy link

Nice. Seems like a clean solution, and should be possible to use now by extending the webpack config to define the alias like in your PR until official support hopefully arrives. Thank you for sharing!

@ehorodyski
Copy link
Author

Anytime! Thanks for your consideration!

@biesbjerg
Copy link

One drawback using this method is if you are using an interface to describe your config object.

For example:

// src/config/config.dev.ts
import { ConfigInterface } from '../interfaces';

export const config: ConfigInterface = {
	enableDebug: true
}

Visual Studio Code does not seem to resolve it and autocomplete keys when typing config. in files importing config from @app/config.

@danbucholtz
Copy link
Contributor

I'm going to give this some thought over the next couple weeks. Thanks for contributing!

Thanks,
Dan

@fiznool
Copy link
Contributor

fiznool commented Feb 16, 2017

It would be great to have this feature baked into app scripts. There have been other solutions also debated in ionic-team/ionic-cli#1205 - anything that provides us with environment-specific config would be a big step forward.

One thing that was brought up in the other thread was the need for more environments than just dev/prod - for example, qa. In addition to dev and prod environment, perhaps a new flag --env qa could be introduced to support this too? For example, ionic build --env qa would load the qa.ts file, ionic build --prod would be equivalent to setting --env prod, etc.

@riteshwaghela
Copy link

riteshwaghela commented Feb 16, 2017

Where to use this module alias? Can we have an example? I tried using this but getting TS error can' find module @app/config. Any help? Thanks in advance.

@danbucholtz
Copy link
Contributor

Can someone explain this PR to me like I'm five. I'm not familiar with the alias option in webpack. We need to make sure we consider Rollup, too.

Thanks,
Dan

@ehorodyski
Copy link
Author

ehorodyski commented Feb 16, 2017

@danbucholtz - The alias option let's you alias a path, so if I alias src/feature/service.ts to @service, in the files where I need to import src/feature/service.ts I can use it's alias and do import @service, instead.

This becomes important when you have multiple environments. It's a better practice to create multiple files that export the same module, but the values differ based on what environment being run. By creating an alias, such as @config, we don't need to modify import 'src/config/dev' to import 'src/config/qa' in every file we are importing configuration values.

Webpack creates the alias for us, which unfortunately, @riteshwaghela, does not seem to work well in certain IDEs. It works in VS Code, but you don't get a typeahead, which I'd expect because we are setting an alias. However, I can live with the trade-off because I can easily switch configuration values when building.

@ehorodyski
Copy link
Author

@danbucholtz - I don't think this PR is a solid fix. I think it's a good baseline to start from, however, and it served as a good stopgap for people who had the same issues I did.

I think there would be a more elegant way to incorporate this into your build scripts; this was quick-and-dirty. But, the need for supporting multiple environment configurations is needed, I think it would be best to address it from the ionic-app-script level.

@danbucholtz
Copy link
Contributor

What do you all think about something like this?

#762

Let's continue the discussion there. Please provide feedback. In the meantime, I'm going to close this PR.

Thanks for contributing and spurring the discussion.

Thanks,
Dan

@riteshwaghela
Copy link

@ehorodyski I am using VS code, still this didn't work for me. I added the alias in the webpack.config.js file the same way you did. And then imported the module in my code using
import {Config} from '@app/config'
But it is not compiling and I am getting can not find module error. It seems it is not resolving the module..am I missing anything else? Thanks.

@ehorodyski
Copy link
Author

@riteshwaghela - Did you modify the package.json file to use the new webpack config?

@riteshwaghela
Copy link

@ehorodyski Thanks for your reply. Yes I have modified my project's package.json to new webpack config file.
"config": { "ionic_webpack": "./config/webpack.config.js" }

and in the new webpack.config.js, added the alias like this:
resolve: { extensions: ['.ts', '.js', '.json'], modules: [path.resolve('node_modules')], alias: { "@app/config": path.resolve('./src/config/config.' + process.env.IONIC_ENV + '.ts') } }

@ehorodyski
Copy link
Author

Hey @riteshwaghela -- try this:

package.json

"scripts": {
    "clean": "ionic-app-scripts clean",
    "build": "ionic-app-scripts build -w ./config/webpack.config.js",
    "ionic:build": "ionic-app-scripts build -w ./config/webpack.config.js",
    "ionic:serve": "ionic-app-scripts serve -w ./config/webpack.config.js"
},
"config": {
    "webpack": "./config/webpack.config.js"
},

I noticed, at least in RC6 when I wrote this stop-gap, you needed to modify the scripts portion of package.json to get it to work. Not sure why, but the config portion wasn't working.

Hope this helps!

@bnova-stefan
Copy link

bnova-stefan commented Mar 17, 2017

For me the following is working:

package.json

"config": {
    "ionic_webpack": "./src/config/webpack.config.js"
  }

/src/config/webpack.config.js

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

module.exports = function () {
    useDefaultConfig.resolve.alias = {
        "@app/config": path.resolve('./src/config/config.' + process.env.IONIC_ENV + '.ts')
    };

    return useDefaultConfig;
}

Then you can import the config with
import { ENV } from '@app/config'

Config file

export const ENV = {
    //0=debug, 1=info, 2=error
    LOG_LEVEL: 0,
    ...
}

@tmvnk
Copy link

tmvnk commented Apr 1, 2017

@bnova-stefan I get @app/config cannot be found. Can you share you code?

@bnova-stefan
Copy link

Hi tmvnk.
This is everything I did to get it working...
I updated my post, because I forgot to share the config file.

@georgefrick
Copy link

You can use @bnova-stefan 's solution, and if you want more than DEV/PROD, use TARGET.

replace:
"@app/config": path.resolve('./src/config/config.' + process.env.IONIC_ENV + '.ts')

with
"@app/config": path.resolve('./src/config/config.' + process.env.IONIC_TARGET+ '.ts')

and for your build, ex: ionic serve --target qa

If you get @app/config cannot be found, it's not finding your config file.

@pbowyer pbowyer mentioned this pull request Apr 6, 2017
@vkniazeu
Copy link

vkniazeu commented Apr 6, 2017

Just to expand @bnova-stefan 's excellent solution for folks new to this.

  1. Modify your package.json per Alias environment specific module (to change API keys, constants in c… #683 (comment)

  2. Create /src/config/webpack.config.js with content from Alias environment specific module (to change API keys, constants in c… #683 (comment)

  3. Create dev and prod config files under /src/config/ with names:

config.dev.ts
config.prod.ts

For example,

// config.dev.ts
export const ENV = {
  LOG_LEVEL: 4
  // other properties with values for the dev environment
}
// config.prod.ts
export const ENV = {
  LOG_LEVEL: 3
  // other properties with values for the prod environment
}
  1. In your app.module.ts and/or app.component.ts or anywhere else within your app where you need to rely on the environment based values:

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

console.log(ENV.LOG_LEVEL). // or any other property you've defined in the ENV const

  1. serve or build your app accordingly to invoke the right environment:
ionic serve // pulls in config.dev.ts values. --dev is default
ionic build // pulls in config.dev.ts values. --dev is default

ionic serve --prod // pulls in config.prod.ts values
ionic build --prod // pulls in config.prod.ts values

@vkniazeu
Copy link

vkniazeu commented Apr 6, 2017

Actually, I spoke a bit too soon as I'm getting an error when building for production:

ionic build browser --prod

[17:52:00]  ionic-app-scripts 1.3.0
[17:52:00]  build prod started ...
[17:52:01]  clean started ...
[17:52:01]  clean finished in 1 ms
[17:52:01]  copy started ...
[17:52:01]  ngc started ...
[17:52:03]  ionic-app-script task: "build"
[17:52:03]  Error: Error encountered resolving symbol values statically. Could not resolve @app/config relative to XXX/src/app/app.module.ts., resolving symbol AppModule in...

I assume this is due to the AoT Compiler.
Any pointers on how to overcome this?

@tmvnk
Copy link

tmvnk commented Apr 6, 2017

@vkniazeu yes idd, same result here

@fiznool
Copy link
Contributor

fiznool commented Apr 6, 2017

@vkniazeu it's probably complaining about the import inside your config files.

Does the error go away if you use a static value instead, e.g:

export const ENV = {
  LOG_LEVEL: 1
  // other properties with values for the prod environment
}

@vkniazeu
Copy link

vkniazeu commented Apr 6, 2017

@fiznool , unfortunately it doesn't. Removing the imports was the first thing I'd tried, but the error persisted regardless.

@vkniazeu
Copy link

vkniazeu commented Apr 6, 2017

@bnova-stefan , does your solution work for you with ionic build --prod, which enables AoT compilation?

@vkniazeu
Copy link

vkniazeu commented Apr 6, 2017

FWIW, the following changes make ionic build --prod run successfully with AoT Compiler, but they require a manual modification of a file. Perhaps, for production builds that don't happen that often it's a bit of a compromise, but I'd love to see the clean solution to this.

  1. Replace
    import { ENV } from '@app/config';
    with an import relative to the including file. E.g.:
    import { ENV } from '../config';

  2. Create index.ts in /src/config/

  3. Toggle its contents for dev/production according as both files export the same ENV object:

export * from './config.dev';
// export * from './config.prod';

This basically overwrites the "--prod" or "--dev" environment flags in serve/build commands, but at least the production build is error free and you have some flexibility with having dev/prod config variables in separate files.

@vkniazeu
Copy link

vkniazeu commented Apr 6, 2017

I suspect that the AoT Compiler is looking for a properly defined (installed?) module/package, when it encounters import { ENV } from '@app/config';
Not sure how to make sure the config directory is treated as a module.

@mediastuttgart
Copy link

mediastuttgart commented Apr 8, 2017

well toggling contents of an index.ts file makes the whole webpack stuff obsolete. then for example you could have a index.ts file for every git branch which you don't touch anymore and you switch env by branch. but I also would like to see a solution for the alias issue. this whole dev/prod stuff with ionic drives me crazy.

@mediastuttgart
Copy link

@vkniazeu you can also take a look at http://www.roblouie.com/article/296/ionic-2-environment-variables-the-best-way/ which i used to manage env variables in components. but I this approach doesn't seem to work inside app.module.ts when trying to setup firebase config inside imports.

@vkniazeu
Copy link

vkniazeu commented Apr 9, 2017

Thank you for the tips, @mediastuttgart!
Surely toggling index.ts isn't nice, it was just a quick compromise I found for my small project.
But it is akin to manually changing import statements and/or having separate ENV_DEV and ENV_PROD objects withing the same file.
I wish Ionic just followed the same env setup Angular already has, which would mean smaller learning curve around something that doesn't really need to be different in a platform that builds on top of the other.
I suspect the solution in the article you shared fails for reasons similar to this one.
I am too trying to separate my Firebase configs.
Perhaps, @bnova-stefan knows the answer.
I've also asked in #762, but no answer yet.

@alu
Copy link

alu commented Apr 14, 2017

I found fix to Could not resolve @app/config issue.

Create dummy config.

// src/config/config.ts
export const ENV = {
  LOG_LEVEL: undefined
}

Define alias @app/config to your tsconfig.json.

{
  "compilerOptions": {
...
    "baseUrl":  "./src",
    "paths": {
      "@app/config": ["config/config"]
    }
  }
...

Include configs.

import {Env} from '@app/config';

This trik deceives ts compiler by paths entry in tsconfig.json. Then its will over written to dev|prod config by webpack process.

@vkniazeu
Copy link

vkniazeu commented Apr 14, 2017

@alu , I spent quite some time yesterday trying to do something very similar although a slightly different way.
The main problem with this "hack" (I don't call it a fix on purpose) is that there's a reason why the AoT Compiler needs to resolve the values statically.

I'm realizing this environment config adjustment must occur before ngc and webpack steps.
There are some good examples at #762, although I wish they were simpler and lighter.

The reason I'm saying this is that I ran into a situation that seemed strange, but with exploring static resolution of values more, it makes sense.

The following would work fine with your script and ngc:

    // app.component.ts constructor
    if (ENV.LOG_LEVEL === 3) {
      // do something
    }

However, if you were to use the ENV value directly in creating another value, for example in your components, the value would be statically created at ngc compilation time, and NOT be replaced by webpack:

    // home.ts constructor
    this.logLevel = 'log level:  ' + ENV.LOG_LEVEL;

this.logLevel in this case will be undefined in your case as AoT needs to optimize the values statically and set them at compile-time, before webpack kicks in.

So, in summary, your workaround works, but for statements that do not get AoT-compiled into static values by ngc.

@bergergit
Copy link

I've spent a few hours trying almost all the solutions above, but none of them seem to work with AoT compiler, for setting up different Firebase environments, which occur in app.module.ts in the imports section (and I can't use Angular DI in this section). Not even using @alu fix.

Because of this I've decided to take a different approach: I've created a shell wrapper for Ionic CLI. I call it Bionic :)
It will simply copy my ./src/config/config.prod.ts over my config.ts if a --prod argument is found.
Not much a clean solution, but certainly works with AoT and doesn't require any webpack config changes. Certainly fits my needs.

Steps:

  1. Create a file named bionic in your app root:
#!/bin/bash
isProd=
for i in "$@" ; do
	if [[ $i == "--prod" ]] ; then
        isProd=1
        break
    fi
done
if [ "$isProd" = "1" ]; then
	cp ./src/config/config.prod.ts ./src/config/config.ts
else 
	cp ./src/config/config.dev.ts ./src/config/config.ts
fi

ionic $1 $2 $3 $4 $5

cp ./src/config/config.dev.ts ./src/config/config.ts
exit 0
chmod +x bionic 
  1. Create ./src/config folder and add your config files:
    files
    config.ts may be just a copy of config.dev.ts - the shell will overwrite it

config.dev.ts

export const ENV = {  
	environment: "DEV",
	firebaseConfig: {
		apiKey: "myDevKey",
		authDomain: "myDevApp.firebaseapp.com",
		databaseURL: "https://mydevapp.firebaseio.com",
...
	}
}
  1. Use your configuration anywhere you want (e.g: app.module.ts)
import { ENV } from '../config/config'
...
 imports: [
    ...
    AngularFireModule.initializeApp(ENV.firebaseConfig),
]
  1. From now on, simply bionic instead of ionic
./bionic serve // will serve app with your dev config.
./bionic build android --prod // will build for android using your prod config.

Tweak the shell however you want.
That's my 1st shell script and there is probably a LOT of room for improvement here (like, making it a global node script with more possibilites for example). But that's too much, and I need to go back doing what matters: coding my app :)
Hope this helps someone.

@jksdua
Copy link

jksdua commented Jul 18, 2017

A stopgap solution I am currently using which makes use of git:

branch = master

src/config.ts

export API_URL = 'dev.example.com';

branch = release/production

src/config.ts

export ENV = 'prod.example.com';

branch = qa

src/config.ts

export ENV = 'qa.example.com';

@souzace
Copy link

souzace commented Oct 19, 2017

thanks @mediastuttgart nice article

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.