Considering an Yeoman Ember app.
I've looked different tools like:
https://github.com/logankoester/grunt-environment
https://github.com/jsoverson/grunt-env
https://github.com/outaTiME/grunt-config
But I don't quite see how can you set/get for instance different adapter url in your router.js depending on some grunt/node environment.
One approach would be having for example two files: development.properties and production.properties, and using grunt you can copy them to a current.properties file and then you can read the current.properties file always, using for example nconf from any place of your application.
You just need to use the grunt-contrib-copy tasks in your grunt file.
Summary: create a gruntfile that read the environment from the command line and copy the corresponding file property to current.properties, and then in your route.js you just include nconf and you just use it.
Also as an alternative nconf can read parameters from the command line given to node when you run it, so you can avoid the grunt file and run it like node app.js --property=value and take the value of the property with nconf from your route.js
So I ended up figuring out a way that works well but only with two env:
I made a pull-request for the ember-generator to start using that pattern:
https://github.com/borisrorsvort/generator-ember/commit/66f26e9e5a7599da53cb99b85e8ef5864648349b
Here is an implementation that works with yeoman ember generator:
Your replace task should look something like this:
replace: {
app: {
options: {
variables: {
ember: 'bower_components/ember/ember.js',
ember_data: 'bower_components/ember-data-shim/ember-data.prod.js',
app_config: 'scripts/app_config/development.js'
}
},
files: [{
src: '<%= yeoman.app %>/index.html',
dest: '.tmp/index.html'
}]
},
dist: {
options: {
variables: {
ember: 'bower_components/ember/ember.prod.js',
ember_data: 'bower_components/ember-data-shim/ember-data.prod.js',
app_config: 'scripts/app_config/production.js'
}
},
files: [{
src: '<%= yeoman.app %>/index.html',
dest: '.tmp/index.html'
}]
}
}
======
Then add '##app_config' the index.html under ##ember and ##ember_data so that it get replaced by the correct string
======
Then create two files:
app/templates/scripts/app_config/development.js
app/templates/scripts/app_config/production.js
These contain an AppConfig onject that you can use in your Ember App
var AppConfig = {};
======
Now Imagine you want to change your adapter when you're building the app:
Just use:
MyApp.ApplicationAdapter = DS.RESTAdapter.extend({
host: AppConfig.apiAdapterUrl
});
Related
I have created a multiple pages web applications, including:
- a single requirejs configuration file, requestjsConfig.js
- some libraries, like jquery.js, etc...
- pages js, like homePage.js
- the form event binding js, like pageHeader.js
- common logical handling, like shoppingCart.js
I tried to use r.js to uglify my scripts, and merged into 1 js files. In general, it works, but with a small issues. After compiled, the browser still loads the form binding js files, event they are still merged into the page JS file.
requireJSConfig.js
requirejs.config({
baseUrl: 'frontjs/lib',
paths: {
eventHandler: '../page/eventHandler',
feature: '../feature'
}
});
homePage.js
requirejs(['../page/requestjsConfig'], function (requestjsConfig) {
//requirejs(['feature/page-header', 'feature/common', 'feature/index', 'feature/transport', 'feature/utils']);
requirejs(['eventHandler/pageHeader']);
});
pageHeader.js
define(function (require) {
var $ = require('jquery');
require('jquery-cookie');
require('jquery-storageapi');
var commonString = require('feature/commonString');
var shoppingCart = require('feature/shoppingCart');
...
}
the build.js for r.js to optimize
appDir: '../ecomm',
mainConfigFile: '../ecomm/frontjs/page/requestjsConfig.js',
dir: '../ecomm-built',
modules: [
//First set up the common build layer.
{
//module names are relative to baseUrl
name: '../page/requestjsConfig',
//List common dependencies here. Only need to list
//top level dependencies, "include" will find
//nested dependencies.
include: ['jquery',
'jquery.md5'
]
},
{
//module names are relative to baseUrl/paths config
name: '../page/homePage',
include: ['../page/eventHandler/pageHeader'],
exclude: ['../page/requestjsConfig']
},
{
//module names are relative to baseUrl/paths config
name: '../page/subCategory',
include: ['../page/eventHandler/pageHeader'],
exclude: ['../page/requestjsConfig']
}
]
}
After run node tools/r.js -o tools/build.js,
and when access home page, the pageHeader.js is still required.
I made it work, thanks everyone, the root cause is path.
In homePage.js, the module is defined as
requirejs(['eventHandler/pageHeader']);
In build.js,the module is defined as
include: ['../page/eventHandler/pageHeader'],
I'm having issue with minifying multiple CSS files using grunt cssmin I'm not looking to minify all files into single file. I would like to have the files having same name with min.css extension.
My gruntfile.js is as follows.
module.exports = function (grunt) {
grunt.initConfig({
cssmin: {
target: {
files: [{
src: ['assets/css/*.css', '!assets/css/*.min.css'], // source files mask
dest: 'assets/css/', // destination folder
expand: true, // allow dynamic building
flatten: true, // remove all unnecessary nesting
ext: '.min.css' // replace .css to .min.css
}],
/* BELOW IS ONLY A TRICK USED TO MINIFY THE FILES SKIPPED BY THE ABOVE */
/*files: [{
src: ['assets/css/home.css', 'assets/css/institutions.css', 'assets/css/form-elements.css'], // source files mask
dest: 'assets/css/', // destination folder
expand: true, // allow dynamic building
flatten: true, // remove all unnecessary nesting
ext: '.min.css' // replace .css to .min.css
}],*/
}
}
});
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.registerTask('default', [ 'cssmin' ]);
};
As per the code (except the commented out part), it is suppose to minify all the css files. But for some reason it skips few css files from the directory. To minify those, I have to comment out the first files[{}] and uncomment the later.
It doesn't work when both files[{}] are uncommented. I'm clueless on why its happening.
My Nodejs version is: v0.10.25
Thanks in advance.
Note: I used to get warnings & task aborts on grunt cssmin due to node version issue and got it fixed by downgrading nvm version to nvm v0.10.39. Thanks to blindMoe for pointing the solution
We have a MEAN (nodes.js back-end + angular front-end), where we use the Amazon S3 sdk to upload files to the storage service. We have our AWS credentials and we need to use them in the code.
We would like to publish our project in a public repository, but we don't want to share our credentials (by the way, Amazon monitors GitHub and notifies developers who disclose their credentials). How should we adapt our development workflow to have something secure (no disclosure) and convenient?
The solution consists in tuning the Gruntfile.js file, which defines what needs to be done during the build process. Depending on the one that you use as a starting point, things will be a bit different (and various use cases might need to be considered).
In our case, we use the angular fullstack yeoman generator (https://github.com/DaftMonk/generator-angular-fullstack) to generate the project skeleton. The Gruntfile.js that is produced by the generator is fairly sophisticated, so we had to edit a couple of sections. Here are the key points:
1. Passing arguments to Grunt
The first thing that we need, is to be able to invoke the build process and to pass arguments. The arguments should not end up in the repository, so developers should create a script outside the codebase to launch the build process (or type the arguments manually each time...). Grunt gives the ability to access command line arguments with the grunt.option() function. So, the first modification to the Gruntfile.js file is to add the following two lines right at the beginning:
module.exports = function (grunt) {
var amznUserId = grunt.option('AMZN_USER_ID');
var amznUserPassword = grunt.option('AMZN_USER_PASSWORD');
...
}
If you do that and then invoke grunt by typing:
grunt build --AMZN_USER_ID=myUserId --AMZN_USER_PASSWORD=aSecret
grunt serve --AMZN_USER_ID=myUserId --AMZN_USER_PASSWORD=aSecret
Then you will have the credential values available in the script variables.
2. Adding placeholders in the source files
The AMZN credentials are used in an Angular.JS component, typically in a controller. What you need to do is to replace the hard-coded values with variable placeholders, like this:
angular.module('demoApp')
.controller('MainCtrl', function ($scope, $http, socket) {
$scope.amzn_user_id = '##AMZN_USER_ID';
$scope.amzn_user_password = '##AMZN_USER_PASSWORD';
3. Injecting command line arguments into the placeholders
The last (and most tricky) part of the solution consists of injecting the 2 values passed to grunt into the placeholders. There are different Grunt plugins that can be used for this purpose. We have used the grunt-replace plugin with success. Here is what we had to do:
3.1 Install the plugin
npm install grunt-replace --save
3.2 Configure the plugin (specify how the injection should happen, more on this at the end)
grunt.initConfig({
replace: {
dist: {
options: {
patterns: [
{
match: 'AMZN_USER_ID',
replacement: amznUserId
},
{
match: 'AMZN_USER_PASSWORD',
replacement: amznUserPassword
}
]
},
files: [
{
expand: true,
flatten: false,
src: ['.tmp/concat/app/app.js'],
dest: '.'
},
{
expand: false,
flatten: false,
src: ['client/app/main/main.controller.js'],
dest: '.tmp/app/main/main.controller.js'
}
]
}
},
3.3. Invoke the plugin in the grunt build and grunt serve workflows
Please note that the order is important and that the replace lines must be exactly in that position.
grunt.registerTask('serve', function (target) {
if (target === 'dist') {
return grunt.task.run(['build', 'env:all', 'env:prod', 'express:prod', 'wait', 'open', 'express-keepalive']);
}
if (target === 'debug') {
return grunt.task.run([
'clean:server',
'env:all',
'injector:stylus',
'concurrent:server',
'injector',
'wiredep',
'autoprefixer',
'concurrent:debug'
]);
}
grunt.task.run([
'clean:server',
'env:all',
'injector:stylus',
'concurrent:server',
'injector',
'wiredep',
'replace',
'autoprefixer',
'express:dev',
'wait',
'open',
'watch'
]);
});
and
grunt.registerTask('build', [
'clean:dist',
'injector:stylus',
'concurrent:dist',
'injector',
'wiredep',
'useminPrepare',
'autoprefixer',
'ngtemplates',
'concat',
'replace',
'ngAnnotate',
'copy:dist',
'cdnify',
'cssmin',
'uglify',
'rev',
'usemin'
]);
What happens behind the scenes
The key point is to realize that the angular fullstack generator defines a build workflow, where a special directory named .tmp is used. The content of this directory is not always the same (grunt build and grunt serve use it in slightly different ways). That is the reason why we had to define two replacement rules.
I'm working on a project using grunt, I haven't worked with grunt before and currently this is setup as to watch files and when a file has been changed recompile all the files (multiple subdirectories containing hundreds of files) using handlebars into html which is quite slow. I want to improve this to a faster process by only compiling what is needed.
Watching the files with grunt newer doesn't really work because there are dependencies within the directory and thus only recompiling the changed files will not result in a valid page.
I would basically need to recompile the whole parent directory of the file that has changed, but I'm not quite sure on how I would configure something like that.
Any hints where I should look at?
The assemble itself is configured like this:
var _ = require('lodash');
var path = require('path');
// expand the data files and loop over each filepath
var pages = _.flatten(_.map(grunt.file.expand('./src/**/*.json'), function(filepath) {
// read in the data file
var data = grunt.file.readJSON(filepath);
var dest=path.dirname(filepath)+ '/' +path.basename(filepath, path.extname(filepath));
dest=dest.replace("src/","");
var hbs;
if (data.hbs){
hbs=grunt.file.read(path.dirname(filepath)+ '/' + data.hbs)
}
// create a 'page' object to add to the 'pages' collection
return {
// the filename will determine how the page is named later
filename: dest,
// the data from the json file
data: data,
// add the recipe template as the page content
content:hbs
};
}));
return {
options: {
/*postprocess: require('pretty'),*/
marked: {sanitize: false},
data: '<%= options.src %>/**/*.json',
helpers: '<%= options.src %>/helpers/helper-*.js',
layoutdir: '<%= options.src %>/templates',
partials: ['<%= options.src %>/components/**/*.hbs']
},
build: {
options: {
layout: 'base.hbs',
assets: '<%= options.build %>',
pages: pages
},
files: [
{
cwd: '<%= options.src %>',
dest: '<%= options.build %>',
src: '!*'
}
]
},
}
So every time this loads all the pages get scanned down like /src/sites/abc/xyz/foo.json and get compiled, but I only want to have changed files. Watch does detect changed files, but all the files get compiled again and I'm not sure how I could get the changed files that watch has recognized in the config to only process part of the files.
I think what you need is already there in watch.
Check the Using the watch event in grunt doc.
Copying down the content here to satisfy the SO MODS/GODS.
This task will emit a watch event when watched files are modified. This is useful if you would like a simple notification when files are edited or if you're using this task in tandem with another task. Here is a simple example using the watch event:
grunt.initConfig({
watch: {
scripts: {
files: ['lib/*.js'],
},
},
});
grunt.event.on('watch', function(action, filepath, target) {
grunt.log.writeln(target + ': ' + filepath + ' has ' + action);
});
The watch event is not intended for replacing the standard Grunt API for configuring and running tasks. If you're trying to run tasks from within the watch event you're more than likely doing it wrong. Please read configuring tasks.
Compiling Files As Needed
A very common request is to only compile files as needed. Here is an example that will only lint changed files with the jshint task:
grunt.initConfig({
watch: {
scripts: {
files: ['lib/*.js'],
tasks: ['jshint'],
options: {
spawn: false,
},
},
},
jshint: {
all: {
src: ['lib/*.js'],
},
},
});
// on watch events configure jshint:all to only run on changed file
grunt.event.on('watch', function(action, filepath) {
grunt.config('jshint.all.src', filepath);
});
If you need to dynamically modify your config, the spawn option must be disabled to keep the watch running under the same context.
If you save multiple files simultaneously you may opt for a more robust method:
var changedFiles = Object.create(null);
var onChange = grunt.util._.debounce(function() {
grunt.config('jshint.all.src', Object.keys(changedFiles));
changedFiles = Object.create(null);
}, 200);
grunt.event.on('watch', function(action, filepath) {
changedFiles[filepath] = action;
onChange();
});
I have around 50 JS files and I have to optimize it using r.js and node...
I dont want to specify all the JS files, instead specify the top level folder and somehow let r.js to get all the required js files....
Is there a way to achieve this? Currently I am specifying all the 50 js files in a common js files and referring it in my build.js...I have more files in the coming weeks, and so maintaining a common js file will be a pain.
Please suggest some steps.
here is my build file
({
baseUrl: ".",
mainConfigFile: "../App/main.js",
//modules: [
// //{ name: "../App/Crosspoint/Address/AddressList" }
// { name: "../App/Crosspoint/Office/OfficeDetails" }
//],
//paths: {
// app: '../App',
// jquery: 'jquery'
//},
name: "../App/Crosspoint/Office/OfficeDetails",
deps: ["../App"],
out: "main-built123.js",
rawText: {
'some/id': 'define(["another/id"], function () {});'
},
// dir: "app",
})
Optimizer should include all the dependencies required by main config file. Also if you have nested dependencies then specify:
findNestedDependencies: true