Using Grunt to preprocess and replace environment variables - angularjs

I have an angular app setup that's using grunt, but I'd like to be able to use grunt as a preprocessor to replace variables and I haven't been able to find anything that matches my needs.
For instance, if I change the name of my main app module to "someAppName" in a config file, I'd like to just use something like "ENV.APP_NAME" in various html and js files and have that replaced by an appropriate value for that environment.
Ideally I'd like to have a config file somewhere along these lines, either as a .json file or using module.exports to expose an object, which specifies values for different environments:
{
APP_NAME:{
dev: "someAppDev",
prod: "someApp"
},
API_BASE:{
dev: "localhost:8000/",
prod: "https://www.some-site.com/api/"
}
}
and then I could create a grunt task and pass it either "dev" or "prod" to have it run the preprocessor and replace each instance with the corresponding value. I've found this https://github.com/STAH/grunt-preprocessor but the examples are confusing and I don't think it's quite what I'm looking for.
Is there anything like this that allows you to create preprocessed environment variables and read them from an external config file, or am I forced to build my own grunt plugin? Has anyone achieved something similar with grunt?
EDIT: I've begun building a grunt plugin for this specific task, once it's done and tested I'll post it up on npm

Use grunt-ng-constant.
Npm install this plugin:
npm install grunt-ng-constant --save-dev
Then in grunt write to config file:
ngconstant: {
// Options for all targets
options: {
space: ' ',
wrap: '"use strict";\n\n {%= __ngModule %}',
name: 'config',
},
// Environment targets
development: {
options: {
dest: '<%= yeoman.app %>/scripts/config.js'
},
constants: {
ENV: {
myvar1: 'Hello from devel',
myname: 'John Doe'
}
}
},
production: {
options: {
dest: '<%= yeoman.dist %>/scripts/config.js'
},
constants: {
ENV: {
myvar1: 'Hello',
myname: 'John Doe'
}
}
}
},
Then add to grunt task:
grunt.task.run([
'clean:server',
'ngconstant:development',
'connect:livereload',
'watch'
]);
Running task would generate config.js with constants defined in gruntfile.
Then add config.js to your index.html:
<script src="/scripts/config.js" />
Inject it to your app:
var app = angular.module('myApp', [ 'config' ]);
And inject into controller:
.controller('MainCtrl', function ($scope, $http, ENV) {
console.log(ENV.myvar1);
});
You can set different variables for production and different for development, by setting it in gruntfile and setting ng:production or ng:development.
Refer this article explaining the procedure for more information.

You could do something making use of grunt-string-replace while loading some config values into Grunt from a JSON file. Suppose you had this in myconfig.json:
{
"appName": "MyGrowth",
"version": "1.1.2",
}
Then perhaps load the config into Gruntfile.js from JSON with something like:
grunt.initConfig({
myConfig: grunt.file.readJSON('myconfig.json'),
// ... the rest of the stuff in your initConfig goes sure
}
Then you would have some sort of task like this maybe:
'string-replace': {
version: {
options: {
replacements: [
{
pattern: /MY_VERSION/ig,
replacement: '<%= myConfig.version %>'
}
]
},
files: [{
expand: true,
cwd: 'src/',
src: '**/*.js',
dest: 'dist/'
}]
}
}
That would make actual changes to the files if you had 'src' and 'dest' in the same folder, otherwise it would put the processed files in the dest folder.
I've not actually used this recipe for preprocessing, but I use it for other types of things like replacing tags in a framework with the app name as configured in package.json.
Hope it helps.

Alright I ended up building a grunt plugin just for this task because it's something that I really needed. It allows you to place environment variable definitions in a .json file like I described in my original question and replaces all instances in a specified file or list of files.
https://github.com/ejfrancis/grunt-envpreprocess
So if you have something like this
HTML
<head>
<title>ENV.APP_NAME</title>
</head>
<script src="ENV.API_BASE/user/create">
JS
var version = "ENV.APP_VERSION";
alert(version);
The task will replace it with this
HTML
<head>
<title>AppDev</title>
</head>
<script src="http://localhost:8000/user/create">
JS
var version = "0.1.0";
alert(version);

Related

Can I use an Angular es5 controller with a new es6 service using babel and grunt?

I want to start using ES6 (2015) in my Angular 1.3 + grunt stack without refactoring the entire existing ES5 code, (or switching grunt with other tool..)
But when trying to use a new ES6 service from an "old" controller I'm getting following error,
" Cannot read property '1' of null
at Function.annotate [as $$annotate] .... "
The babel configuration in grunt:
babel: {
options: {
sourceMap: true,
presets: ['es2015']
},
dist: {
files: [{
expand: true,
cwd: '<%= yeoman.app %>',
src: '**/*.es6.js',
dest: '.tmp/app',
ext: '.es5.js'
}]
},
test: {
files: [{
expand: true,
cwd: 'test/spec',
src: '{,*/}*.es6.js',
dest: '.tmp/spec',
ext: '.es5.js'
}]
}
},
The service code:
class InfoService {
constructor($http) {
this.$http = $http;
}
getInfo() {
console.log('getting');
return this.$http.get('/backend/something/readAll');
}
}
InfoService.$inject = ['$http'];
angular.module('app').service('Info', $http => InfoService($http));
The use in es5 controller:
angular.module('app').controller('SomeCtrl',
function ComposerCtrl(Info) {
Info.getInfo();
});
The transpiled ES5 InfoService was generated under .tmp/app (and I configured grunt watch to update changes while developing) so I wonder what am I doing wrong..
You have forgotten about new:
...
angular.module('app').service('Info', $http => new InfoService($http))
In this case, angular will not benefit from $inject property and you will need to ng-annotate your code, as it solves to:
angular.module('app').service('Info', function($http) { return new InfoService($http); });
The simpler solution it to replace service definition with:
angular.module('app').service('Info', InfoService);
Angular's DI will use $inject property and add new operator for you.
It is worth noting, that TypeScript users had the same problem:
How can I define an AngularJS service using a TypeScript class that doesn't pollute the global scope?
EDIT:
It is possible, that you are using wrong controller expression (for example unclosed ng-controller attribute:
<div .... ng-controller="SignupFormController as signupFormCtrl>
This messes up angular and leads to this error message on older versions of angular (1.3).
More info about issue:
https://github.com/angular/angular.js/issues/10875
So I've found a way to make it work but I don't like it so much..
I configured the babel to dist the files at the same place my *.es6.js files and updated the index.html so Angular will load the es5 transpiled file (InfoService.js), and when I debug I do it on the es6 file (I guess it relates to the sourceMap)
babel: {
options: {
sourceMap: true,
presets: ['es2015']
},
dist: {
files: [{
expand: true,
src: '**/*.es6.js',
ext: '.js'
}]
},

grunt-ng-annotate all angularjs files

I want to run ng-annotate via grunt on all my project's angular files.
Could not find this option.
I only have the option annotate by specify file name.
ngAnnotate: {
dist: {
files: {
'output_file_name.js': ['input_file_name'],
},
}
}
I need something more generic that would run recursively on a specific directory.
You can use the other configuration to go through folders and annotate each file which will be outputted in a *.annotated.js file.
ngAnnotate: {
dist: {
files: [{
expand: true,
src: ['**/*.js', '!**/*.annotated.js'],
ext: '.annotated.js',
extDot: 'last'
}],
}
}

AngularJS support multiple environments

What is the conventional way to support multiple environments(develop, staging, production, ci) in an AngularJS application?
For a more concrete example, how would one go about provisioning(probably requires an additional tool) a server endpoint in a config somewhere and then read that server endpoint in for use elsewhere, such as calls like $http.get(server_endpoint+'/rest/api/route'?
You can define an Angular constant, inject it into the required service/controller and refer to environment-specific values
angular.module('YourApp')
.constant('AppConstants', {
'SERVER_ENDPOINT': 'http://api.example.com',
'SHOW_DEBUG': true
});
You would use that like
$http.get(AppConstants.SERVER_ENDPOINT + '/rest/api/route')
If you use Grunt or Gulp, there's a nice task that allows you to create an Angular constant file based on the environment.
grunt-ng-constant
gulp-ng-constant
Grunt example:
ngconstant: {
options: {
name: 'config',
},
dev: {
options: {
dest: '<%= yeoman.app %>/scripts/config.js'
},
constants: {
ENV: {
FB_APP_ID: '123456'
}
}
},
prod: {
options: {
dest: '<%= yeoman.dist %>/scripts/config.js'
},
constants: {
ENV: {
FB_APP_ID: '98765'
}
}
}
}
The tasks that create the production and development version of the app call ngconstant:prod and ngconstant:dev respectively

Grunt + Yeoman - How to stop rewrite linked JS files in index.html?

I have a Angular project which is build with Yeoman and Grunt. Everytime if I run command:
sudo grunt serve
Is app normally launched but in index.html are deleted some linked libraries from bower_components directory.
FXP:
<script src="bower_components/spin.js/spin.js"></script>
So I must everytime revert changes in index.html file in git before I can continue in testing.
Is any possibility how can i solve this annoying problem?
Thanks for any help.
EDIT:
I tried following:
Installed:
npm install grunt-script-inject
GrunFile.js
// Automatically inject Bower components into the app
wiredep: {
app: {
src: ['<%= yeoman.app %>/index.html'],
ignorePath: new RegExp('^<%= yeoman.app %>/|../')
},
scriptinject: {
dev: {
srcs: ['<%= yeoman.client %>/bower_components/path/to/src/plugin.js',
'<%= yeoman.client %>/bower_components/path/to/src/plugin2.js'] ,//order is important if this sciprt will be concated and minified
html: '<%= yeoman.client %>/index.html', //file that as the block comment to look for a place to insert the script tags
without: '<%= yeoman.client %>/' //this script will be used to remove this block of string of script tag file location
}
}
},
And
grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
if (target === 'dist') {
return grunt.task.run(['build', 'connect:dist:keepalive']);
}
grunt.task.run([
'clean:server',
'wiredep',
'scripinject',
'concurrent:server',
'autoprefixer',
'connect:livereload',
'watch'
]);
});
But now, after the grunt serve i see following error:
Running "serve" task
Warning: Task "scripinject" not found. Use --force to continue.
Aborted due to warnings.
What is wrong?
I resolved using the --s in bower install command
So:
bower install --save angular-translate
Works fine.
Here is documentation:
http://bower.io/docs/api/#install
I had a similar problem.
In index.html I added the following code:
<script src="bower_components/highcharts-release/highcharts.js"></script>
<script src="bower_components/highcharts-release/modules/heatmap.js"></script>
and every time I ran grunt serve or grunt build commands, grunt rewrote it to:
<script src="bower_components/highcharts-release/highcharts.js"></script>
<script src="bower_components/highcharts-release/highcharts-more.js"></script>
<script src="bower_components/highcharts-release/modules/exporting.js"></script>
So I looked into bower_components/highcharts-release/bower.json file and saw:
{
"name": "highcharts",
"version": "v4.1.4",
"main": [
"highcharts.js",
"highcharts-more.js",
"modules/exporting.js"
]
}
I changed it to:
{
"name": "highcharts",
"version": "v4.1.4",
"main": [
"highcharts.js",
"modules/heatmap.js"
]
}
and everything is fine now. Hope it will help someone, even this is a quite late answer.

Is there a way to automatically generate karma.conf.js with a grunt task?

I've been using a modified version of the gruntfile that comes with the Yeoman.io base angularjs generator, and the grunt-bower-install command is kind of handy for keeping my base index.html file up-to-date with bower dependencies.
However, when I do a bower install (package) --save and then grunt bower-install, my index.html updates, but my karma.conf.js does not update, meaning I need to manually add the new file to the list of files to load when karma runs the test suite (otherwise the injector fails trying to inject a nonexistent package).
Is there any easy-peasy way to add this to my grunt workflow? It's not the end of the world, but it is one of those easy-to-forget things.
I actually came up with a solution for just this problem. Check out https://github.com/stephenplusplus/grunt-bower-install/issues/35#issuecomment-32084805
'bower-install': {
app: {
src: '<%= yeoman.app %>/index.html',
ignorePath: '<%= yeoman.app %>/'
},
test: {
src: 'karma.conf.js',
fileTypes: {
js: {
block: /(([\s\t]*)\/\/\s*bower:*(\S*))(\n|\r|.)*?(\/\/\s*endbower)/gi,
detect: {
js: /'.*\.js'/gi
},
replace: {
js: '\'{{filePath}}\','
}
}
}
}
}

Resources