-
Notifications
You must be signed in to change notification settings - Fork 307
Environment proposal #762
Comments
Looks interesting! I have a few questions.
Could you expand on this? I didn't quite understand what you mean. How would the app use these variables, what would the code look like? One of the comments in another thread mentioned keeping sensitive values out of source control. Could there be a way of reading in a value from a |
@danbucholtz this looks great. Angular CLI has a similar implementation. They have a I'd disagree with the "ionic_env": {
"prod": "config/env.prod.js",
"staging": "config/env.staging.js",
"dev": "config/env.dev.js"
} And each file would export the following: const somePackage = require('somePackage');
module.exports = {
SOME_VAR: somePackage.getSomeValueSync(),
SOME_ASYNC_VAL: async function getSomeValueAsync() => {
await asyncValue = somePackage.computeSomeValueAsync();
return asyncValue;
},
VALUE_FROM_PROCESS: process.env.SOME_ENV_VAR_VAL
}; Then
Perhaps there could be a better approach, but I thing this would work in most scenarios. |
Perhaps I'm missing something, but what sort of use cases would benefit from async computation of values? |
Reading a file for instance (that's also possible to do sync). Or perhaps you store some keys in an AWS bucket and you'd like to pull those keys when you compile the app. I could think of plenty of use cases. |
Right, I'm with you now. 👍 One approach I've seen in other places is to allow exporting a function as well as an object inside the configuration file. This could simplify the logic from the app-scripts end and allow you to build the config object dynamically, and only return once you are ready. Something along these lines: // sync example
const somePackage = require('somePackage');
module.exports = {
SOME_VAR: somePackage.getSomeValueSync();
VALUE_FROM_PROCESS: process.env.SOME_ENV_VAR_VAL;
}; // async example
const somePackage = require('somePackage');
module.exports = function() {
const config = {
SOME_VAR: somePackage.getSomeValueSync();
VALUE_FROM_PROCESS: process.env.SOME_ENV_VAR_VAL;
};
return somePackage.computeSomeValueAsync().then(val => {
config.SOME_ASYNC_VAL = val;
return config;
});
}; app-scripts would then perform the following
This has the benefit that app-scripts doesn't need to enumerate all the keys in the config object (what about nested keys?) and provides the most flexibility in your config script (what if the fetching of a second async value depends upon another one being resolved first?) |
@fiznool true, that'd be much simpler and it would help if a value depends on another one. But the app scripts would still need to enum keys in order to replace, but that's just implementation detail. |
Though I'd actually argue that if we export an object, app scripts could potentially run async tasks in parallel so if we'd have more than one property that evaluates to a promise/observable, it could run all of them at once and complete when all are done (something like a Yet, the user could do this as well, but if app scripts would do it, it would spare the user of adding extra implementation. I guess there are many ways this can be tackled, but the simpler the better. |
The difficulty there though, is that if one async value depended on
another, you wouldn't be able to resolve the configuration correctly if
app-scripts ran everything in parallel.
Better to put the logic in the hands of the user, IMHO.
|
I like where @rolandjitsu is going, but instead of tying it down to a specific implementation, is there a way you can take the environment path being used from I don't know how the Angular library does aliasing so you can do Give the users the ultimate control of building what they want to export from their environment/config files, but provide a way for those paths to be swapped out during building, and I think properly creating a module alias, which I don't know how to do, is the best approach. |
If we were to expose a hook and allow the user to load in async values, perhaps we could do something like this:
Those modules could then export a function that returns a It could work like this:
Something like this would work in the vast majority of use cases. In an application's code, let's say you need to hit a different HTTP service for development and production. You could write a service like this:
Your implementation to replace it could look like this:
|
@danbucholtz - I think that is really over-complicated. Take a look at Webpack's resolve aliasing. This could be done dynamically when starting
The only implication to the user is that they'd need to store their config files outside the folder with their source code, as to not include all the files defined. Or maybe not, you can run a glob that excludes the other environment files not being used. Plus, there's no resolution during application running. |
For us this isn't necessarily just about env=dev/staging/prod. For some context, we're coming to ionic(v2) from a vanilla cordova app and we use the same codebase to generate multiple different apps primarily distinguished by an 'org_id'. (The app passes this value in server calls to receive different content, amongst a bunch of other differences). We also currently inject the internal and external version numbers since they vary (inconsistently) by org_id. So, in a nutshell, just passing in "environment" wouldn't solve this problem for us. Why not make this feature align better with unix best practices and simply make all of the current env available inside the application somehow? for example: ORG_ID=123 ENVIRONMENT=staging ionic serve For what it's worth, cordova (or node?) is doing this already automatically. That is, you can access 'process' inside a cordova hook without having to require anything. From that, it's just process.env.ORG_ID == '123' |
@danbucholtz I agree, that'd be a reasonable solution. And I'm sure that it would cover most use cases. @ehorodyski I think it's already possible to use aliasing if you use your own webpack config file (I cannot confirm it, never tried it). But I do see what you mean, though that would not work if you have async operations that need to run in order to compute the env values you want to have available in the app. It would also not work for the use case @josh-m-sharpe describes where you need some env vars that are not exposed by app scripts. @josh-m-sharpe using the solution proposed would also solve what you're referring to. You'd just need to expose from module.exports = function() {
// process.env is an object and has all the env vars
return Promise.resolve(process.env);
} I think it's essential to have a more generic and universal solution, such as the one proposed by @danbucholtz in #762 (comment), that can cover most of the uses cases since the dev needs vary as we can see from the posts on this topic. |
Am I the only one that really wants hinting of config keys, e.g. by defining a ConfigInterface? I have not seen it mentioned and I'm not sure how to do it elegantly, though... |
@biesbjerg - Right, this is what I'd like accomplished as well. I need to brush up on how to do Typescript module aliasing, so that the paths can be dynamically generated when you run If |
@danbucholtz all of the other stuff people are mentioning are great nice to haves. That said, i think your first comment is awesome enough and will suit most needs. |
export class MyService {
construtor(public http: Http){
}
makeServiceCall() {
return this.http.makeRequest('$BACKEND_SERVICE_URL');
}
}
module.exports = function() {
return Promise.resolve({
'$BACKEND_SERVICE_URL' : 'localhost:8080/myService'
});
} Are you suggesting that If so, I think this might be a bit confusing and difficult to maintain. As others have suggested, an ideal solution would be something along the lines of the following, which could be achieved in webpack using resolve aliasing: import { BACKEND_SERVICE_URL } from 'config';
export class MyService {
construtor(public http: Http){
}
makeServiceCall() {
return this.http.makeRequest(BACKEND_SERVICE_URL);
}
} I'm not sure how the above could be achieved with rollup. If this is a concern, a slightly lesser (but still very workable) version would be to add all config parameters to a global variable, e.g. declare const ENV;
export class MyService {
construtor(public http: Http){
}
makeServiceCall() {
return this.http.makeRequest(ENV.BACKEND_SERVICE_URL);
}
} For those wishing for TypeScript autocomplete, declare namespace ENV {
const BACKEND_SERVER_URL: string;
// Any other properties go here
} |
The resolve aliasing is interesting and wouldn't really require Webpack or Rollup to do it. From our perspective, a bundler is just a bundler, not a build tool. We don't dive into the world of plugins unless absolutely necessary, because they tighten our coupling to the tool. In our ideal world, we're going to swap out Webpack for a better bundler someday without 90% of our developers even noticing. I think the aliasing leaves many use-cases unfulfilled where you need to load asynchronous data prior to a build. I guess you could do it ahead of time in a separate workflow/process and put it in an environment variable? I'm not sure, this seems a bit complicated to me. I'll chat with some other team members this week and we'll figure out what we want to do. Any of these solutions will cover the vast majority of use cases. Thanks, |
+1 |
Could you provide some use-cases for where you'd load async data prior to a build -- that would be environmental configurations? Maybe there's a disconnect in what certain people are talking about. In my view, and many others, this is analogous to having |
Just my perspective as a user, but I pretty much use environment variables for any configuration changes required among different environments or deploys. This means I can change a configuration value for prod, staging, dev, individual branch builds ... any I'll post my specific project use cases that we have currently:
In the individual threads I see this asked more than once for environment variable support. I could generate a property file I guess at buildtime, but that would basically be me duplicating what I am today in a different form. Below are some references to what I would consider are analogous solutions for the same problem. references:
Anyway just some thoughts. I would really love environment variable support :). |
Angular-cli has implemented and its easy to configure it in angular-cli file why not to do the same? |
We want this too! |
We're currently using a script that runs before
I found some cons with this
|
Hey guys, I have been working all day on a temporary solution to this thread that should work for most of the use cases I have read in this and a few other threads of the same topic. The main concerns I noticed form suggestions include:
My project should solve all of those issues. If there are any others I am happy to work on it. I have it fairly well spelled out in the README.md. |
#762 (comment) (@mlegenhausen ) is AOT-compatible, which is a crucial feature for me. I am including environment-specific variables in the main
This can only be done by including a non-NgModule component - |
Are we going to see this feature any time soon? |
Angular CLI has parfect solution, which best fits my needs, see here. It would be very nice to have same mechanism in ionic projects. Thanks ;) |
We put this feature on hold because we are moving to the Angular CLI 🎉 . Thanks, |
@danbucholtz Any idea as to when the ionic-cli will be replaced by the Angular-CLI? |
As soon as possible. No date yet but it will coincide with Ionic 4. Check out the Thanks, |
Thanks. That helps. I will need to implement something prior to that. |
// Extends ./webpack.default.config.js
// which is a direct copy of node_modules/@ionic/app-scripts/config/webpack.config.js
//
// We force devs to copy/paste to webpack.default.config.js so that upgrading
// @ionic/app-scripts is explicit, intentional, and reviewed.
//
// Added minimist, customEnvPlugin
var defaultConfig = require('./webpack.default.config.js');
var webpack = require('webpack');
var path = require('path');
var fs = require('fs');
var chalk = require('chalk');
var argv = require('minimist')(process.argv.slice(2));
// Build environment vars
var customEnvPlugin;
if(!!argv.env){
fileName = './src/environments/environment.'+argv.env+'.ts';
var fileExists = fs.existsSync(fileName);
if(fileExists){
console.log(chalk.bgGreen('CONFIG LOADED: ', fileName));
customEnvPlugin = new webpack.NormalModuleReplacementPlugin(
/src\/environments\/environment\.ts/,
path.resolve('./src/environments/environment.'+argv.env+'.ts')
);
} else {
console.log(chalk.bgRed('CONFIG SPECIFIED BUT MISSING: ', fileName));
process.exit();
}
} else {
console.log('CONFIG DEFAULT: ', './src/environments/environment.ts');
customEnvPlugin = new webpack.NormalModuleReplacementPlugin(
/src\/environments\/environment\.ts/,
path.resolve('./src/environments/environment.ts')
);
}
defaultConfig.prod.plugins.push(customEnvPlugin);
defaultConfig.dev.plugins.push(customEnvPlugin);
module.exports = defaultConfig;
// Dev profile: animations & devtools
export const environment = {
production: false,
enableAnimation: true,
enableDevTools: true,
settings: {}
}; 2.5 Clone
...
import { environment } from '../environments/environment';
...
@NgModule({
imports: [
CommonModule,
BrowserModule,
HttpModule,
PagesModule,
ComponentsModule,
...
IonicModule.forRoot(AppComponent,
// Disable page transitions for screenshot task
{animate: environment.enableAnimation}
...
Credits: this thread and https://www.williamghelfi.com/blog/2017/06/22/ionic-environments-webpack/ |
I have to step back from my solution all these webpack solutions fail when you run the production build ( My recommendation is to use the angular cli with ionic when you need environment file support! |
@mlegenhausen make the prod config the default environment file? Then when not using To be clear, I have 3 environment files:
The first 2 files are identical - I include the |
This does only work when you have only one production version. I need to configuer differntly for android ios and web. |
@danbucholtz can apply a solution similar to https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-custom-environment-variables, following https://12factor.net/config see too: https://www.npmjs.com/package/dotenv. |
@GFoley83 Yes, AOT ( The downside of the provided example is that "production" builds end up with double flags, but that's not the end of the world. Here's a sample of my
The Oh, again as a disclaimer, that is totally not "my" solution, it came from William Ghelfi. |
I am just awaiting ionic 4, which will enable angular CLI support, then
finally there will be an OTB solution.
…On Thu, Feb 22, 2018 at 6:12 PM, Eric McNiece ***@***.***> wrote:
@GFoley83 <https://github.com/gfoley83> Yes, AOT (ngc) compilation works
well with this pattern.
The downside of the provided example is that "production" builds end up
with double flags, but that's not the end of the world. Here's a sample of
my package.json scripts:
"build-aot": "ionic-app-scripts build --release --prod --env=prod",
"production-ios": "ionic cordova build ios --buildConfig ./config/build-ios.json --release --prod --env=prod",
"production-android": "ionic cordova build android --release --prod --env=prod",
"production-browser": "ionic cordova build browser --release --prod --env=prod",
The --prod flag is for Ionic or Cordova, not totally sure which at this
moment. The --env=prod is the custom flag for the provided example
Webpack config modification. Using --prod compiles the code with ngc.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#762 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AGP5QYbYkYF0Hrf2AJ4d1yudLIjHITatks5tXSF4gaJpZM4MD5cy>
.
|
I ended up using a combo of @emcniece and @gshigeto solutions, so massive thanks to them. I wanted to have environment variables work in Ionic exactly like how they do in Angular CLI (especially given Ionic 4 is somewhere on the horizon), whilst also supporting Karma and Protractor tests. To use the environment variables, just add This solution also supports using I've managed all this with the following setup: /src/app/environments folder contains
/tsconfig.json {
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"baseUrl": "./src",
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"paths": {
"@env/*": [
"environments/*"
]
},
"lib": [
"dom",
"es2016"
],
"module": "es2015",
"moduleResolution": "node",
"sourceMap": true,
"target": "es5"
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"src/**/*.spec.ts"
],
"typeRoots": [
"node_modules/@types"
],
"compileOnSave": false,
"atom": {
"rewriteTsconfig": false
}
} /config/webpack.config.js var chalk = require("chalk");
var defaultConfig = require('@ionic/app-scripts/config/webpack.config.js');
var ionic_env = process.env.IONIC_ENV;
var fs = require('fs');
var path = require('path');
var env = require('minimist')(process.argv.slice(2)).env || process.env.IONIC_ENV || 'dev';
var webpack = require('webpack');
defaultConfig.dev.resolve.alias = {
"@env/environment": path.resolve(environmentPath('dev'))
};
defaultConfig.prod.resolve.alias = {
"@env/environment": path.resolve(environmentPath('prod'))
};
console.log(chalk.yellow.bgBlack('\nUsing ' + env + ' environment variables.\n'));
if (!!env) {
var pathToCustomEnvFile = path.resolve(environmentPath(env));
defaultConfig[env] = defaultConfig[ionic_env];
defaultConfig[env].resolve.alias = {
"@env/environment": pathToCustomEnvFile
};
var customEnvPlugin = new webpack.NormalModuleReplacementPlugin(
/src\/environments\/environment\.ts/, pathToCustomEnvFile
);
defaultConfig.prod.plugins.push(customEnvPlugin);
defaultConfig.dev.plugins.push(customEnvPlugin);
}
module.exports = function() {
return defaultConfig;
};
function environmentPath(env) {
var filePath = './src/environments/environment' + (env === '' ? '' : '.' + env) + '.ts';
if (fs.existsSync(filePath)) {
return filePath;
}
console.log(chalk.red.bgWhite('\n' + filePath + ' does not exist!\n'));
process.exit();
} /test-config/webpack.test.js var webpack = require('webpack');
var path = require('path');
var fs = require('fs');
var chalk = require('chalk');
module.exports = {
devtool: 'inline-source-map',
resolve: {
alias: {
'@env/environment': path.resolve(environmentPath('dev')),
},
extensions: ['.ts', '.js']
},
module: {
rules: [{
test: /\.ts$/,
loaders: [{
loader: 'ts-loader'
}, 'angular2-template-loader']
},
{
test: /\.html$/,
loader: 'html-loader?attrs=false'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'null-loader'
}
]
},
plugins: [
new webpack.ContextReplacementPlugin(
// The (\\|\/) piece accounts for path separators in *nix and Windows
/(ionic-angular)|(angular(\\|\/)core(\\|\/)@angular)/,
root('./src'), // location of your src
{} // a map of your routes
)
]
};
function environmentPath(env) {
var filePath = './src/environments/environment' + (env === '' ? '' : '.' + env) + '.ts';
if (fs.existsSync(filePath)) {
return filePath;
}
console.log(chalk.red('\n' + filePath + ' does not exist!\n'));
process.exit();
}
function root(localPath) {
return path.resolve(__dirname, localPath);
} /package.json ....
"ionic": "ionic",
"build": "ionic build",
"build:test": "ionic build --env=test",
"build:prod": "ionic build --prod",
"build:android": "ionic cordova build android --dev",
"build:android:test": "ionic cordova build android --prod --env=test",
"build:android:prod": "ionic cordova build android --prod",
.... |
@keithdmoore Thanks for your idea to fix this solution. I have followed your solution and able to load files as per what's been set as fileName. But with this solution is it possible to access process.argv.env in TS files anywhere in the application? I need to access them in my app to load some Mock services for different environments. Earlier(Angular 4 and ionic app scripts 1.x) i was achieving this by setting process.argv.NODE_ENV. I was using Opaque token for the same. Now with Angular 5 and ionic app scripts 3.1.x i am using Injection token and trying to access process.argv.env together with your solution but it always comes as undefined in TS files. Any idea on this? I had created an issue earlier in Ionic repo, probably should have created here: Here is the link with all info: [(https://github.com/ionic-team/ionic-framework/issues/14150)] |
I’m sure you could. I followed someone else’s suggestion above. I have a name attribute in my environment model that is the same as what is in the file name. I can access the name in my ts files if I need to. |
All, with Ionic 4 release approaching, I know that ENV vars will be handled. If you are using Ionic 3.9.2, here is what I have been using for months to handle ENV vars. It's been pieced together from this and a few other threads. https://gist.github.com/tabirkeland/a17c67b2f1ea3331d94db34ed7191c34 |
made a starter project with detail explanations. https://github.com/writer0713/ionic-environment-setting/blob/master/README.md |
@writer0713 nice one. So I assume my gist helped? |
@tabirkeland |
@writer0713 your readme was a life saver. Worked like a charm |
@writer0713 @tabirkeland thanks!! It works so well! |
this is the only efficient solution that worked for me, thanks! |
@GFoley83 I've tested your solution but with AOT in specific cases it doesn't work, I ignore the reason.
It's annoying if you use firebase and need to initiate it with env vars on your app.module ( |
I am thinking we could do this:
ionic serve --env qa
orionic run android --env prod
My first thought was that it's value would default to
dev
for non-prod builds, andprod
for prod builds. Developer's can pass in whatever they want, though.config
section of the package.json is read. It could look something like this:If the
ionic_env
data is not there, we would just move on in the build process. If it is present, we would then perform the text replacement.npm script
section, or we could provide a hook into thepostprocess
step. I prefer the latter as it's easier to document and and we can probably make it a 1/2 second faster or so if we do it in app-scripts.Feedback is appreciated.
Thanks,
Dan
The text was updated successfully, but these errors were encountered: