Comment gérez-vous les variables/constantes de configuration pour différents environnements?
Cela pourrait être un exemple:
Mon API de repos est accessible sur localhost:7080/myapi/
, mais mon ami qui fonctionne sur le même code sous le contrôle de version Git a l'API déployée sur son Tomcat sur localhost:8099/hisapi/
.
En supposant que nous ayons quelque chose comme ceci:
angular
.module('app', ['ngResource'])
.constant('API_END_POINT','<local_end_point>')
.factory('User', function($resource, API_END_POINT) {
return $resource(API_END_POINT + 'user');
});
Comment injecter dynamiquement la valeur correcte du point de terminaison de l'API, en fonction de l'environnement?
Dans PHP, je fais habituellement ce genre de choses avec un fichier config.username.xml
, en fusionnant le fichier de configuration de base (config.xml) avec le fichier de configuration de l'environnement local reconnu par le nom de l'utilisateur. Mais je ne sais pas comment gérer ce genre de chose en JavaScript?
Je suis un peu en retard sur le fil, mais si vous utilisez Grunt , j'ai eu beaucoup de succès avec grunt-ng-constant
.
La section de configuration pour ngconstant
dans mon _Gruntfile.js
_ ressemble à
_ngconstant: {
options: {
name: 'config',
wrap: '"use strict";\n\n{%= __ngModule %}',
space: ' '
},
development: {
options: {
dest: '<%= yeoman.app %>/scripts/config.js'
},
constants: {
ENV: 'development'
}
},
production: {
options: {
dest: '<%= yeoman.dist %>/scripts/config.js'
},
constants: {
ENV: 'production'
}
}
}
_
Les tâches qui utilisent ngconstant
ressemblent à
_grunt.registerTask('server', function (target) {
if (target === 'dist') {
return grunt.task.run([
'build',
'open',
'connect:dist:keepalive'
]);
}
grunt.task.run([
'clean:server',
'ngconstant:development',
'concurrent:server',
'connect:livereload',
'open',
'watch'
]);
});
grunt.registerTask('build', [
'clean:dist',
'ngconstant:production',
'useminPrepare',
'concurrent:dist',
'concat',
'copy',
'cdnify',
'ngmin',
'cssmin',
'uglify',
'rev',
'usemin'
]);
_
Donc, exécuter _grunt server
_ va générer un fichier _config.js
_ dans _app/scripts/
_ qui ressemble à
_"use strict";
angular.module("config", []).constant("ENV", "development");
_
Enfin, je déclare la dépendance sur les modules qui en ont besoin:
_// the 'config' dependency is generated via grunt
var app = angular.module('myApp', [ 'config' ]);
_
Maintenant, mes constantes peuvent être une dépendance injectée si nécessaire. Par exemple.,
_app.controller('MyController', ['ENV', function( ENV ) {
if( ENV === 'production' ) {
...
}
}]);
_
Une solution intéressante peut être de séparer toutes les valeurs spécifiques à l’environnement dans un module angular distinct, dont tous les autres modules dépendent:
angular.module('configuration', [])
.constant('API_END_POINT','123456')
.constant('Host','localhost');
Ensuite, vos modules qui ont besoin de ces entrées peuvent en déclarer une dépendance:
angular.module('services',['configuration'])
.factory('User',['$resource','API_END_POINT'],function($resource,API_END_POINT){
return $resource(API_END_POINT + 'user');
});
Maintenant, vous pouvez penser à d'autres choses intéressantes:
Le module, qui contient la configuration peut être séparé en configuration.js, qui sera inclus sur votre page.
Ce script peut être facilement édité par chacun de vous, à condition de ne pas archiver ce fichier séparé dans git. Mais il est plus facile de ne pas vérifier la configuration si elle se trouve dans un fichier séparé. En outre, vous pouvez le brancher localement.
Désormais, si vous avez un système de construction, tel que ANT ou Maven, vos étapes suivantes pourraient consister à implémenter des espaces réservés pour les valeurs API_END_POINT, qui seront remplacées pendant la construction par vos valeurs spécifiques.
Ou vous avez votre configuration_a.js
et configuration_b.js
et décidez à l’arrière-plan de l’inclure.
Pour les utilisateurs de Gulp , gulp-ng-constant est également utile en combinaison avec gulp-concat , event-stream et yargs .
var concat = require('gulp-concat'),
es = require('event-stream'),
gulp = require('gulp'),
ngConstant = require('gulp-ng-constant'),
argv = require('yargs').argv;
var enviroment = argv.env || 'development';
gulp.task('config', function () {
var config = gulp.src('config/' + enviroment + '.json')
.pipe(ngConstant({name: 'app.config'}));
var scripts = gulp.src('js/*');
return es.merge(config, scripts)
.pipe(concat('app.js'))
.pipe(gulp.dest('app/dist'))
.on('error', function() { });
});
Dans mon dossier de configuration, j'ai ces fichiers:
ls -l config
total 8
-rw-r--r--+ 1 .. ci.json
-rw-r--r--+ 1 .. development.json
-rw-r--r--+ 1 .. production.json
Ensuite, vous pouvez exécuter gulp config --env development
et cela créera quelque chose comme ceci:
angular.module("app.config", [])
.constant("foo", "bar")
.constant("ngConstant", true);
J'ai aussi cette spec:
beforeEach(module('app'));
it('loads the config', inject(function(config) {
expect(config).toBeTruthy();
}));
Pour y parvenir, je vous suggère d'utiliser AngularJS Environment Plugin: https://www.npmjs.com/package/angular-environment
Voici un exemple:
angular.module('yourApp', ['environment']).
config(function(envServiceProvider) {
// set the domains and variables for each environment
envServiceProvider.config({
domains: {
development: ['localhost', 'dev.local'],
production: ['acme.com', 'acme.net', 'acme.org']
// anotherStage: ['domain1', 'domain2'],
// anotherStage: ['domain1', 'domain2']
},
vars: {
development: {
apiUrl: '//localhost/api',
staticUrl: '//localhost/static'
// antoherCustomVar: 'lorem',
// antoherCustomVar: 'ipsum'
},
production: {
apiUrl: '//api.acme.com/v2',
staticUrl: '//static.acme.com'
// antoherCustomVar: 'lorem',
// antoherCustomVar: 'ipsum'
}
// anotherStage: {
// customVar: 'lorem',
// customVar: 'ipsum'
// }
}
});
// run the environment check, so the comprobation is made
// before controllers and services are built
envServiceProvider.check();
});
Et ensuite, vous pouvez appeler les variables de vos contrôleurs telles que:
envService.read('apiUrl');
J'espère que ça aide.
Vous pouvez utiliser lvh.me:9000
pour accéder à votre application AngularJS (lvh.me
pointe uniquement sur 127.0.0.1), puis spécifier un autre point de terminaison si lvh.me
est l'hôte:
app.service("Configuration", function() {
if (window.location.Host.match(/lvh\.me/)) {
return this.API = 'http://localhost\\:7080/myapi/';
} else {
return this.API = 'http://localhost\\:8099/hisapi/';
}
});
Ensuite, injectez le service de configuration et utilisez Configuration.API
chaque fois que vous devez accéder à l'API:
$resource(Configuration.API + '/endpoint/:id', {
id: '@id'
});
Un peu maladroit, mais fonctionne bien pour moi, bien que dans une situation légèrement différente (les points de terminaison API diffèrent en termes de production et de développement).
Nous pourrions aussi faire quelque chose comme ça.
(function(){
'use strict';
angular.module('app').service('env', function env() {
var _environments = {
local: {
Host: 'localhost:3000',
config: {
apiroot: 'http://localhost:3000'
}
},
dev: {
Host: 'dev.com',
config: {
apiroot: 'http://localhost:3000'
}
},
test: {
Host: 'test.com',
config: {
apiroot: 'http://localhost:3000'
}
},
stage: {
Host: 'stage.com',
config: {
apiroot: 'staging'
}
},
prod: {
Host: 'production.com',
config: {
apiroot: 'production'
}
}
},
_environment;
return {
getEnvironment: function(){
var Host = window.location.Host;
if(_environment){
return _environment;
}
for(var environment in _environments){
if(typeof _environments[environment].Host && _environments[environment].Host == Host){
_environment = environment;
return _environment;
}
}
return null;
},
get: function(property){
return _environments[this.getEnvironment()].config[property];
}
}
});
})();
Et dans votre controller/service
, nous pouvons injecter la dépendance et appeler la méthode get avec la propriété à laquelle accéder.
(function() {
'use strict';
angular.module('app').service('apiService', apiService);
apiService.$inject = ['configurations', '$q', '$http', 'env'];
function apiService(config, $q, $http, env) {
var service = {};
/* **********APIs **************** */
service.get = function() {
return $http.get(env.get('apiroot') + '/api/yourservice');
};
return service;
}
})();
$http.get(env.get('apiroot')
renverrait l'URL basée sur l'environnement de l'hôte.
Bonne question!
Une solution pourrait être de continuer à utiliser votre fichier config.xml et de fournir des informations de point de terminaison api à partir du backend au code HTML généré, comme ceci (exemple en php):
<script type="text/javascript">
angular.module('YourApp').constant('API_END_POINT', '<?php echo $apiEndPointFromBackend; ?>');
</script>
Peut-être pas une belle solution, mais cela fonctionnerait.
Une autre solution pourrait consister à conserver la valeur constante API_END_POINT
comme elle devrait l'être en production et à modifier uniquement votre fichier hôtes pour qu'il pointe vers cette API locale.
Ou peut-être une solution utilisant localStorage
pour les substitutions, comme ceci:
.factory('User',['$resource','API_END_POINT'],function($resource,API_END_POINT){
var myApi = localStorage.get('myLocalApiOverride');
return $resource((myApi || API_END_POINT) + 'user');
});
Très tard dans la discussion, mais une technique que j’ai utilisée, préangulaire, consiste à tirer parti de JSON et de la souplesse de JS pour référencer de manière dynamique les clés de collection et utiliser des faits inaliénables de l’environnement (nom du serveur hôte, langue actuelle du navigateur). , etc.) comme entrées pour discriminer/préférer sélectivement les noms de clé suffixés dans une structure de données JSON.
Cela fournit non seulement un contexte d'environnement de déploiement (par OP), mais tout contexte arbitraire (tel que le langage) permettant de fournir l'i18n ou tout autre écart requis simultanément et (idéalement) au sein d'un manifeste de configuration unique, sans duplication ni biais apparent.
DANS ENVIRON 10 LIGNES Vanilla JS
Exemple trop simpliste, mais classique: une URL de base du point de terminaison de l'API dans un fichier de propriétés au format JSON qui varie en fonction de l'environnement où (natch) le serveur hôte varie également:
...
'svcs': {
'VER': '2.3',
'API@localhost': 'http://localhost:9090/',
'[email protected]': 'https://www.uat.productionwebsite.com:9090/res/',
'[email protected]': 'https://www.productionwebsite.com:9090/api/res/'
},
...
Une clé de la fonction de discrimination est simplement le nom d'hôte du serveur dans la requête.
Ceci, naturellement, peut être combiné avec une clé supplémentaire basée sur les paramètres de langue de l'utilisateur:
...
'app': {
'NAME': 'Ferry Reservations',
'NAME@fr': 'Réservations de ferry',
'NAME@de': 'Fähren Reservierungen'
},
...
La portée de la discrimination/préférence peut être limitée à des clés individuelles (comme ci-dessus) où la clé "de base" n'est écrasée que s'il existe une clé correspondante + suffixe pour les entrées de la fonction - ou une structure entière, et cette structure elle-même récursivement analysé pour faire correspondre les suffixes de discrimination/préférence:
'help': {
'BLURB': 'This pre-production environment is not supported. Contact Development Team with questions.',
'PHONE': '808-867-5309',
'EMAIL': '[email protected]'
},
'[email protected]': {
'BLURB': 'Please contact Customer Service Center',
'BLURB@fr': 'S\'il vous plaît communiquer avec notre Centre de service à la clientèle',
'BLURB@de': 'Bitte kontaktieren Sie unseren Kundendienst!!1!',
'PHONE': '1-800-CUS-TOMR',
'EMAIL': '[email protected]'
},
Ainsi, si un utilisateur visitant le site Web de production possède un paramètre de préférence de langue allemand ( de ), la configuration ci-dessus deviendrait:
'help': {
'BLURB': 'Bitte kontaktieren Sie unseren Kundendienst!!1!',
'PHONE': '1-800-CUS-TOMR',
'EMAIL': '[email protected]'
},
À quoi ressemble une telle fonction de réécriture JSON des préférences/discriminations magiques? Pas tant:
// prefer(object,suffix|[suffixes]) by/par/durch storsoc
// prefer({ a: 'Apple', a@env: 'banana', b: 'carrot' },'env') -> { a: 'banana', b: 'carrot' }
function prefer(o,sufs) {
for (var key in o) {
if (!o.hasOwnProperty(key)) continue; // skip non-instance props
if(key.split('@')[1]) { // suffixed!
// replace root prop with the suffixed prop if among prefs
if(o[key] && sufs.indexOf(key.split('@')[1]) > -1) o[key.split('@')[0]] = JSON.parse(JSON.stringify(o[key]));
// and nuke the suffixed prop to tidy up
delete o[key];
// continue with root key ...
key = key.split('@')[0];
}
// ... in case it's a collection itself, recurse it!
if(o[key] && typeof o[key] === 'object') prefer(o[key],sufs);
};
};
Dans nos implémentations, qui incluent Angular et les sites Web pré-angulaires, nous simplement bootstrap _ la configuration bien avant les autres appels de ressources en plaçant le JSON dans une fermeture JS à exécution automatique, y compris le La fonction prefer () et les propriétés de base du nom d’hôte et du code de langue (et accepte tous les suffixes arbitraires supplémentaires dont vous pourriez avoir besoin):
(function(prefs){ var props = {
'svcs': {
'VER': '2.3',
'API@localhost': 'http://localhost:9090/',
'[email protected]': 'https://www.uat.productionwebsite.com:9090/res/',
'[email protected]': 'https://www.productionwebsite.com:9090/api/res/'
},
...
/* yadda yadda moar JSON und bisque */
function prefer(o,sufs) {
// body of prefer function, broken for e.g.
};
// convert string and comma-separated-string to array .. and process it
prefs = [].concat( ( prefs.split ? prefs.split(',') : prefs ) || []);
prefer(props,prefs);
window.app_props = JSON.parse(JSON.stringify(props));
})([location.hostname, ((window.navigator.userLanguage || window.navigator.language).split('-')[0]) ] );
Un site préangulaire aurait désormais une fenêtre (> @ suffixe suffixée) réduite à laquelle window.app_props se référer.
Un site Angular, en tant qu'étape bootstrap/init, copie simplement l'objet prop-drop-props dans $ rootScope et (éventuellement) le détruit à partir de la portée globale/window
app.constant('props',angular.copy(window.app_props || {})).run( function ($rootScope,props) { $rootScope.props = props; delete window.app_props;} );
à être ensuite injecté dans les contrôleurs:
app.controller('CtrlApp',function($log,props){ ... } );
ou référencé dans les liaisons dans les vues:
<span>{{ props.help.blurb }} {{ props.help.email }}</span>
Mises en garde? Le caractère @ n'est pas un nom de variable/clé JS/JSON valide, mais a été accepté jusqu'à présent. S'il s'agit d'un compromis, remplacez la convention de votre choix, telle que "__" (double trait de soulignement), à condition de vous y tenir.
La technique peut être appliquée côté serveur, portée sur Java ou C #, mais votre efficacité/compacité peut varier.
Sinon, la fonction/convention peut faire partie de votre script de compilation front-end, de sorte que le code JSON gore tout environnement/tout langage ne soit jamais transmis par câble.
UPDATE
Nous avons développé l'utilisation de cette technique pour autoriser plusieurs suffixes à une clé, pour éviter d'être obligé d'utiliser des collections (vous pouvez toujours, aussi profondément que vous le souhaitez), ainsi que pour respecter l'ordre des suffixes préférés.
Exemple (voir aussi travail jsFiddle ):
var o = { 'a':'Apple', 'a@dev':'Apple-dev', 'a@fr':'pomme',
'b':'banana', 'b@fr':'banane', 'b@dev&fr':'banane-dev',
'c':{ 'o':'c-dot-oh', 'o@fr':'c-point-oh' }, 'c@dev': { 'o':'c-dot-oh-dev', 'o@fr':'c-point-oh-dev' } };
/*1*/ prefer(o,'dev'); // { a:'Apple-dev', b:'banana', c:{o:'c-dot-oh-dev'} }
/*2*/ prefer(o,'fr'); // { a:'pomme', b:'banane', c:{o:'c-point-oh'} }
/*3*/ prefer(o,'dev,fr'); // { a:'Apple-dev', b:'banane-dev', c:{o:'c-point-oh-dev'} }
/*4*/ prefer(o,['fr','dev']); // { a:'pomme', b:'banane-dev', c:{o:'c-point-oh-dev'} }
/*5*/ prefer(o); // { a:'Apple', b:'banana', c:{o:'c-dot-oh'} }
1/2 (usage de base) préfère les clés '@dev', élimine toutes les autres clés suffixées
3 préfère '@dev' à '@fr', préfère '@ dev & fr' à tous les autres
4 (identique à 3 mais préfère "@fr" à "@dev")
5 aucun suffixe préféré, supprime TOUTES les propriétés suffixées
Pour ce faire, il marque chaque propriété suffixée et promeut la valeur d'une propriété suffixée en propriété non suffixée lors d'une itération sur les propriétés et de la recherche d'un suffixe mieux noté.
Certaines améliorations de cette version, notamment l’élimination de la dépendance à l’égard de JSON pour la copie en profondeur, et la récurrence uniquement dans des objets qui survivent à la profondeur du scoring:
function prefer(obj,suf) {
function pr(o,s) {
for (var p in o) {
if (!o.hasOwnProperty(p) || !p.split('@')[1] || p.split('@@')[1] ) continue; // ignore: proto-prop OR not-suffixed OR temp prop score
var b = p.split('@')[0]; // base prop name
if(!!!o['@@'+b]) o['@@'+b] = 0; // +score placeholder
var ps = p.split('@')[1].split('&'); // array of property suffixes
var sc = 0; var v = 0; // reset (running)score and value
while(ps.length) {
// suffix value: index(of found suffix in prefs)^10
v = Math.floor(Math.pow(10,s.indexOf(ps.pop())));
if(!v) { sc = 0; break; } // found suf NOT in prefs, zero score (delete later)
sc += v;
}
if(sc > o['@@'+b]) { o['@@'+b] = sc; o[b] = o[p]; } // hi-score! promote to base prop
delete o[p];
}
for (var p in o) if(p.split('@@')[1]) delete o[p]; // remove scores
for (var p in o) if(typeof o[p] === 'object') pr(o[p],s); // recurse surviving objs
}
if( typeof obj !== 'object' ) return; // validate
suf = ( (suf || suf === 0 ) && ( suf.length || suf === parseFloat(suf) ) ? suf.toString().split(',') : []); // array|string|number|comma-separated-string -> array-of-strings
pr(obj,suf.reverse());
}
Si vous utilisez Brunch , le plugin Constangular vous aide à gérer les variables pour différents environnements.