Related
I've got several projects that each use an identical Gruntfile to run tasks and put the output in their own dist folder. Folder setup:
MyProjects
- Project1
- src
- dist
- Project2
- src
- dist
.....
I can't figure out how to run Grunt at the top level (MyProjects) and still have the output generated in the correct dist folder dynamically.
Is there a way I can have Grunt put the output in the correct dist folder without having to hard code it into the Gruntfile? Something like:
dist: {
files: {
// destination : source js
'<% ProjectName %>/dist/app.js': '<% ProjectName %>/src/app.js'
},
Thanks
module.exports = function (grunt) {
require('load-grunt-tasks')(grunt);
// Project configuration.
grunt.initConfig({
watch: {
scripts: {
files: ['src/**/*.js'],
tasks: ['browserify', 'file_append', 'concat'],
options: {
spawn: false
}
},
sass: {
files: "src/scss/*.scss",
tasks: ['sass', 'file_append', 'concat']
}
},
sass: {
dist: {
options: {
style: 'expanded'
},
files: {
// destination // source file
"format/css/styles.css": "src/scss/styles.scss"
}
},
options: {
sourcemap: "none",
style: "compact",
noCache: true
}
},
file_append: {
default_options: {
files: [
// Development build
{
append: "",
prepend: "",
input: "format/app.js",
output: "format/dev.app.js"
},
{
append: "</style>`)",
prepend: "document.body.insertAdjacentHTML('afterbegin', `\n<style>\n",
input: "format/css/styles.css",
output: "format/css/dev.styles.html"
},
// Production build
{
append: "</script>",
prepend: "<script>\n",
input: "format/app.js",
output: "format/prod.app.html"
},
{
append: "</style>",
prepend: "<style>\n",
input: "format/css/styles.css",
output: "format/css/prod.styles.html"
}
]
}
},
concat: {
options: {
seperator: '\n'
},
// Development build
dev: {
src: ['format/dev.app.js', 'format/css/dev.styles.html'],
dest: 'dev/dev.app.js'
},
// Production build
prod: {
src: ['format/prod.app.html', 'format/css/prod.styles.html'],
dest: 'dist/prod.app.html'
}
},
browserify: {
dist: {
files: {
// destination for transpiled js : source js
'format/app.js': 'src/app.js'
},
options: {
transform: [
[
'babelify', {
presets: "es2015",
comments: false,
plugins: "transform-object-rest-spread"
}
]
],
browserifyOptions: {
debug: false
}
}
}
}
});
grunt.registerTask('default', [
'sass',
'browserify:dist',
'file_append',
'concat',
'watch'
]);
};
There's a couple ways you can tackle this.
One option is to overload the arguments you pass to the task & include the folder name you wish to target.
grunt sass:dist:Project1
The additional argument is accessible via lodash templates which are a part of the GruntJS framework, and allows the configuration to be set at the time the task is ran:
sass: {
dist: {
options: {
style: 'expanded'
},
files: {
// destination // source file
"MyProjects/<%= grunt.task.current.args[0] %>/format/css/styles.css": "MyProjects/<%= grunt.task.current.args[0] %>/src/scss/styles.scss"
}
},
options: {
sourcemap: "none",
style: "compact",
noCache: true
}
}
This approach works in the context of the function that's executing, but it wouldn't continue to pass the args to the next task. To do that, we need to add a custom task which will set a configuration object before executing the task list:
grunt.registerTask("build", (project) => {
const buildConfig = { project };
grunt.config.set("build", buildConfig);
grunt.task.run([
'sass',
'browserify:dist',
'file_append',
'concat',
'watch'
]);
});
Now when we run grunt build:Project1, your custom task build will run and set the property we passed in the grunt config object. We can then reference that value in our other grunt config objects using lodash like we did for the first option. To access config values with lodash templates, we just have to provide the config pointer in json notation:
files: {
"MyProjects/<%= build.project %>/format/css/styles.css": "MyProjects/<%= build.project %>/src/scss/styles.scss"
}
Grunt compiles the configs required for a task at the time they're run & will process the lodash templates then, allowing you to inject your project name into a task. Since we stored the value in the config object, the value will persist through until grunt completes and exits.
Here is my problem.
All mentioned paths as per below gruntfile.js are watched fine (shown in grunt --verbose -v). Livereload fires whenever I change files (at least --verbose shows livereload fires). But the page is live reloaded ONLY in case I change my /development/index.html (in Sublime Text) or /less/mainpage.less (with contrib-less).
If I change development/img//* or anyting in /test//*, livereload FIRES but do not RELOAD my page.
I would really appreciate if someone could help.
Here is my folder structure:
source location root: /development/
destination location root: /test/
Here is my gruntfile.js:
module.exports = function(grunt) {
grunt.initConfig({
watch: {
livereload: {
files: ['development/*.html', "test/**/*", "development/img/**/*"],
options: {
livereload: true,
spawn: false
}
},
// watch tasks start here
scripts: {
files: ['development/js/**/*.js'],
tasks: ['concat']
},
html: {
files: ['development/*.html'],
tasks: ['copy:html']
},
less_compile: {
files: ['development/a_source/less/**/*.less'],
tasks: ['less', "stripCssComments"]
},
images: {
files: ['development/img/**/*'],
tasks: ['tinyimg']
}
},
// runs local server for livereload
connect: {
sever: {
options: {
hostname: 'localhost',
port: 3000,
base: 'test/',
livereload: true
}
}
},
// *** *.html, *.img copy task here
copy: {
html: {
expand: true,
cwd: 'development',
src: '*.html',
dest: 'test/',
}
},
// *** LESS tasks here
less: {
compile: {
options: {
paths: ["development/b_components/less/"]
},
files: {
"temp/css/style.css": "development/a_source/less/style.less"
}
}
}, // compiles less and put compiled version into /temp/css/style.test
stripCssComments: {
dist: {
files: {
'test/css/style.css': 'temp/css/style.css'
}
}
}, // strips comments from /temp/css/style.css and copies it to /test/
// minify images
tinyimg: {
dynamic: {
files: [{
expand: true, // Enable dynamic expansion
cwd: 'development/img/', // Src matches are relative to this path
src: ['**/*.{png,jpg,gif}'], // Actual patterns to match
dest: 'test/img/' // Destination path prefix
}]
}
}
}); //initConfig
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-strip-css-comments');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-tinyimg');
grunt.registerTask('default', ["connect", "watch"]);
}; //wrapper function
Try this
livereload:{
options:{
livereload:'<%= connect.options.livereload %>'
},
files:[
'app/{,*/}*.html',
'app/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
]
}
This should reload if you make changes in your images folder. (Customize the URL according to your structure.)
OK. I just wanted to wrap up after I solved all my problems.
I arrived at the following acceptably working setup:
- as per above: Grunt serves as only "files" worker doing all compile / concat / copy etc.
work.
- browser-sync is my local server (with bunch of other vital capabilities) and fast livereload tool, that additionally syncs
several open test windows (https://browsersync.io/).
- Divvy: great addition to my workflow capable of arranging windows on the desktop on the fly (http://mizage.com/divvy/).
Update:
Soon after I switched to Gulp + Browsersync. I could not be satisfied more. Gulp works roughly 10 times faster and Browsersync is smooth and convenient.
Here is my gulpfile.js example just to demonstrate the tasks this pair manages for me:
var gulp = require("gulp"),
gutil = require("gulp-util"),
less = require('gulp-less'),
concat = require('gulp-concat'),
browserify = require('gulp-browserify'),
browserSync = require('browser-sync').create(),
gulpcopy = require('gulp-copy'),
newer = require('gulp-newer'),
imagemin = require('gulp-imagemin'),
autoprefixer = require('gulp-autoprefixer'),
uglifyjs = require('gulp-uglify'),
cleanCSS = require('gulp-clean-css'),
uglifycss = require('gulp-uglifycss'),
htmlmin = require('gulp-htmlmin'),
htmlhint = require("gulp-htmlhint"),
htmlv = require('gulp-html-validator'),
validatecss = require('gulp-w3c-css'),
sourcemaps = require('gulp-sourcemaps');
var lessMain = ["src/styles/less/styles.less"],
lessSources = ["src/styles/less/*.less", "src/styles/**/*.css"],
jsSources = "src/js/*.js",
jsonSources = ["src/js/*.json"],
htmlSources = ["src/html/*.html"],
imgSources = ["z_design/images/processed/**/*.*"],
imgDest = ["public/img/**/*.*"],
cssTemp = ["src/temp/css/styles.css"],
srcjQuery = "node_modules/jquery/dist/jquery.min.js",
srcMustache = "node_modules/mustache/mustache.min.js";
gulp.task("message", function() {
gutil.log("============= Gulp script started ==============");
});
// compiling less
gulp.task("less-compile", function() {
gulp.src(lessMain)
// switches sourcemaps on/off
.pipe(sourcemaps.init())
.pipe(less()
.on("error", gutil.log))
// switches sourcemaps on/off
.pipe(sourcemaps.write())
// change .dest("folder") to "public/css"
// to make no-autoprefix
// or to "src/temp/css/" to switch autoprefix on
.pipe(gulp.dest("public/css"))
});
// prepare & copy js files
gulp.task("js", function() {
gulp.src([srcjQuery, srcMustache, jsSources])
.pipe(concat("script.js"))
.pipe(gulp.dest("public/js/"))
});
// .pipe(browserify())
// {bundleExternal: false}
// copy JSON files
gulp.task("copyjson", function() {
gulp.src(jsonSources)
.pipe(newer("public/js/"))
.pipe(gulpcopy("public/js/", {
prefix: 2
}))
});
// copy html files
gulp.task("copyhtml", function() {
gulp.src(htmlSources)
.pipe(newer("public/"))
.pipe(gulpcopy("public/", {
prefix: 2
}))
});
// --- minify & compress images: 2 tasks - auto and manual
// minify & copy images - manual task
gulp.task("img-ondemand", function() {
gulp.src("z_design/images/unprocessed/**/*.*")
.pipe(newer("public/img/"))
.pipe(imagemin({
progressive: true
}))
.pipe(gulp.dest('z_design/images/processed/'))
});
// minify & copy images - automatic task
gulp.task("processimages", function() {
gulp.src(imgSources)
.pipe(newer("public/img/"))
.pipe(imagemin({
progressive: true
}))
.pipe(gulp.dest('public/img/'))
});
// --- end
// forced reload
gulp.task("reload", function() {
browserSync.reload();
});
// autoprefixer
gulp.task("autoprefix", function() {
gulp.src(cssTemp)
.pipe(autoprefixer({
browsers: ['last 3 versions', 'safari 5', 'ie 8', 'ie 9', 'ie 10', "ie11", 'opera 12.1', 'ios 6', 'android 4'],
cascade: false
}))
.pipe(gulp.dest("public/css/"))
});
// watching for changes
gulp.task("watch", function() {
gulp.watch(lessSources, ["less-compile"])
gulp.watch(jsSources, ["js"])
gulp.watch(jsonSources, ["copyjson"])
gulp.watch(htmlSources, ["copyhtml"])
gulp.watch(imgSources, ["processimages"])
gulp.watch(imgDest, ["reload"])
gulp.watch("src/temp/css/styles.css", ["autoprefix"])
});
// serving localhost
gulp.task('browser-sync', function() {
browserSync.init({
server: ["public", "src"],
watchTask: true,
open: false,
files: ["public/*.html", "public/css/*.css", "public/js/*.*",
"public/img/**/*.*"]
});
});
// === production preparations: RUN SEPARATE TASKS ON DEMAND ===
// --- minify & compress HTML, CSS, JS
// uglify JS
gulp.task("compress-js", function() {
gulp.src("public/js/script.js")
.pipe(uglifyjs())
.pipe(gulp.dest('public/js/'))
});
// uglify CSS
gulp.task('uglify-css', function() {
gulp.src('public/css/styles.css')
.pipe(uglifycss({
"debug": true,
"uglyComments": true
}))
.pipe(gulp.dest('public/css/'));
});
// compress HTML
gulp.task('compress-html', function() {
return gulp.src('src/html/*.html')
.pipe(htmlmin({
collapseWhitespace: true,
removeComments: true
}))
.pipe(gulp.dest('public/'));
});
// --- lint HTML and validate CSS
// lint html
gulp.task('lint-html', function() {
gulp.src("public/*.html")
.pipe(htmlhint())
.pipe(htmlhint.reporter())
});
// validate html
// Option format set to html
gulp.task('validate-html', function() {
gulp.src('public/*.html')
.pipe(htmlv({
format: 'html'
}))
.pipe(gulp.dest('src/temp/validation/'));
});
// add css validation
gulp.task('validate-css', function() {
gulp.src('public/css/*.css')
.pipe(validatecss())
.pipe(gulp.dest('src/temp/validation/'));
});
gulp.task("validate", ["validate-html", "validate-css", "lint-html"]);
gulp.task("compress", ["compress-js", "uglify-css", "compress-html"]);
gulp.task("default", ["watch", "browser-sync"]);
// =======================
For some reason running grunt from the terminal doesn't work. When I run grunt dev and open http://localhost:8000/ it works, but when I just use grunt it says This site can’t be reached. localhost refused to connect.
Any ideas what I am missing?
'use strict';
var path = require('path');
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;
var folderMount = function folderMount(connect, point) {
return connect.static(path.resolve(point));
};
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
clean: {
build: {
src: [".sass-cache"]
}
}, // end clean
sass: {
dist: {
options: {
style: 'expanded',
noCache: true
},
files: {
'app/production/css/style.css': 'app/scss/style.scss'
}
}
}, // end sass
cssmin: {
target: {
files: [{
expand: true,
cwd: 'app/production/css',
src: ['*.css', '!*.min.css'],
dest: 'app/production/css',
ext: '.min.css'
}]
}
}, //end cssmin
connect: {
server: {
options: {
port: 8000
}
}
}, // end connect
uglify: {
options: {
mangle: false
},
my_target: {
files: {
'app/production/js/app.min.js': ['app/js/module.js', 'app/js/config.js', 'app/js/factory.js', 'app/js/filter.js', 'app/js/PotatoAppController.js']
}
}
}, // end js minify
watch: { // this is a watcher, to run this in terminal write: grunt watch
options: {
dateFormat: function(time) {
grunt.log.writeln('The watch finished in ' + time + 'ms at' + (new Date()).toString());
grunt.log.writeln('Waiting for new changes ...');
},
livereload: true
},
css: {
files: 'app/scss/style.scss',
tasks: ['sass', 'cssmin']
},
jsmin: {
files: 'app/js/*.js',
tasks: ['uglify']
},
html: {
files: ['app/views/**/*.html'],
options: {
livereload: true
}
}
} // end watch
});
grunt.loadNpmTasks('grunt-contrib-watch'); // Load the plugin that provides the "watch" task.
grunt.loadNpmTasks('grunt-contrib-cssmin'); // Load the plugin that provides the "cssmin" task.
grunt.loadNpmTasks('grunt-contrib-sass'); // Load the plugin that provides the "sass" task.
grunt.loadNpmTasks('grunt-contrib-uglify'); // Load the plugin that provides the "uglify" task.
grunt.loadNpmTasks('grunt-contrib-livereload'); // Load the plugin that provides the "livereload" task.
grunt.loadNpmTasks('grunt-contrib-connect'); // Load the plugin that provides the "connect" task.
grunt.loadNpmTasks('grunt-contrib-clean'); // Load the plugin that provides the "clean" task.
grunt.registerTask('default', ['watch']); // this is the default command, use in terminal 'grunt'
grunt.registerTask('dev', ['connect', 'sass', 'cssmin', 'uglify', 'clean', 'watch']); // use 'grunt dev' for development
};
Once you execute 'grunt', 'default' command will be executed.
In your case, only 'watch' task is executed.
grunt.registerTask('default', ['watch']);
If you want to reach 'localhost', you need to run 'connect' module.
'watch' task is just watching file changes. not launch web-server.
'connect' is for launching web-server.
Thanks.
Please help me.. I am new to grunt. I have to run grunt task one by one. When i execute the Grunt file i am trying to execute one by one ['clean', 'writefile','concat','requirejs'] since write file helps to create a dynamic json for requier.
When ever i execute first time grunt gives me error and at the second time it runs without error since the json file is created in the path. I tried grunt.task.run() but i couldn't get it
module.exports = function (grunt) {
'use strict';
grunt.initConfig({
// Before generating any new files, remove any previously-created files.
clean: {
tests: ['rjs/build.json','frontend-built']
},
writefile: {
json_value: {
options: {
data: 'frontend/config.json'
},
src: 'rjs/value.hbs',
dest: 'rjs/build.json'
}
},
requirejs: {
compile: {
options:grunt.file.readJSON('rjs/build.json')
}
},
concat: {
dist: {
files: {
'frontend/theme/css/theameA.css': ['frontend/theme/css/common/**/*.css','frontend/theme/css/lib/**/*.css','frontend/theme/css/theme_a/**/*.css'],
'frontend/theme/css/theameB.css': ['frontend/theme/css/common/**/*.css','frontend/theme/css/lib/**/*.css','frontend/theme/css/theme_b/**/*.css']
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-writefile');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.registerTask('default', ['clean', 'writefile','concat','requirejs']);
};
Ok the problem is that the config code is processed before the tasks run so even if it didn't error out, it wouldn't be the correct behavior.
Try this to set the requirejs config dynamically via another custom task:
module.exports = function (grunt) {
'use strict';
grunt.initConfig({
// Before generating any new files, remove any previously-created files.
clean: {
tests: ['rjs/build.json','frontend-built']
},
writefile: {
json_value: {
options: {
data: 'frontend/config.json'
},
src: 'rjs/value.hbs',
dest: 'rjs/build.json'
}
},
concat: {
dist: {
files: {
'frontend/theme/css/theameA.css': ['frontend/theme/css/common/**/*.css','frontend/theme/css/lib/**/*.css','frontend/theme/css/theme_a/**/*.css'],
'frontend/theme/css/theameB.css': ['frontend/theme/css/common/**/*.css','frontend/theme/css/lib/**/*.css','frontend/theme/css/theme_b/**/*.css']
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-writefile');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.registerTask('setRjsConfig', function() {
grunt.config('requirejs.options.compile', grunt.file.readJSON('rjs/build.json'));
});
grunt.registerTask('default', ['clean', 'writefile','concat', 'setRjsConfig', 'requirejs']);
};
Does anyone know the best solution for automatically restarting node and running tests after the restart every time a file changes?
I am currently using grunt-contrib-watch with grunt-develop. I am getting an ECONNREFUSED error on the some restarts. I think it is because my tests are running before the server is fully online.
Any ideas on how best to achieve what I want?
What I want: Restart node and then run all integration tests after each file change.
I am taking a BDD approach to testing (as opposed to regular unit tests) with cucumber.js. I wanted to make sure that each test run against the API I was building started on a fresh boot-up of the application.
I figured it out. Here is what I used:
grunt-contrib-watch to monitor for file changes.
It in turn calls
grunt-develop to restart the application
grunt-cucumberjs to run the cucumber tests
I then modified my index.js (starts the app) so that it doesn't start the app if the NODE_ENV is set to test. That way the cucumber tests actually start the server and can wait till the start process has finished before running the tests.
Here is the GruntFile and Index file:
Gruntfile.js
/*jslint node: true */
"use strict";
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
env: {
dev: {
APP_DIR_FOR_CODE_COVERAGE: 'coverage/instrument/',
NODE_ENV: 'dev',
PORT: 8080
},
test: {
APP_DIR_FOR_CODE_COVERAGE: 'coverage/instrument/',
NODE_ENV: 'test',
PORT: 8081
},
coverage: {
APP_DIR_FOR_CODE_COVERAGE: 'coverage/instrument/',
NODE_ENV: 'test',
PORT: 8081
}
},
watch: {
js: {
files: [
'index.js',
'features/**/*.js',
'server/**/*.js'
],
tasks: ['develop', 'cucumberjs', 'jshint'],
options: {
nospawn: true
}
}
},
jshint: {
all: ['Gruntfile.js', 'index.js', 'server/**/*.js', 'features/**/*.js']
},
nodemon: {
dev: {
script: 'index.js'
}
},
cucumberjs: {
src: './features',
},
develop: {
server: {
file: 'index.js'
}
},
instrument: {
files: ['index.js', 'server/**/*.*'],
options: {
lazy: true,
basePath: 'coverage/instrument/'
}
},
storeCoverage: {
options: {
dir: 'coverage'
}
},
makeReport: {
src: 'coverage/coverage.json',
options: {
type: 'lcov',
dir: 'coverage/reports',
print: 'detail'
}
},
coverage: {
options: {
thresholds: {
'statements': 90,
'branches': 90,
'lines': 90,
'functions': 90
},
dir: 'coverage',
root: ''
}
}
});
grunt.loadNpmTasks('grunt-env');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-develop');
grunt.loadNpmTasks('grunt-cucumber');
grunt.loadNpmTasks('grunt-istanbul');
grunt.loadNpmTasks('grunt-istanbul-coverage');
grunt.loadNpmTasks('grunt-nodemon');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.registerTask('default', ['env:dev', 'nodemon']);
grunt.registerTask('test', ['env:test', 'watch']);
grunt.registerTask('testcoverage', ['env:test', 'jshint', 'instrument', 'cucumberjs', 'storeCoverage', 'makeReport', 'coverage']);
};
Index.js
/*jslint node: true */
"use strict";
var Hapi = require('hapi');
var Good = require('good');
var server = {};
exports.server = {
start: function(callback) {
/* istanbul ignore next */
var port = process.env.PORT || 8080;
server = new Hapi.Server(port);
var routes = require('./server/routes');
routes.register(server);
var exceptionHandling = require('./server/exceptionHandling');
exceptionHandling.register(server);
server.pack.register(Good, function(err) {
/* istanbul ignore if */
if (err) {
throw err; // something bad happened loading the plugin
}
/* istanbul ignore next */
server.log('info', 'Server starting at ' + server.info.uri);
server.start(callback);
});
},
stop: function(callback) {
server.log('info', 'Server stopping.');
server.stop(null, callback);
},
rootUrl: function() { return server.info.uri; }
};
/* istanbul ignore if */
if (process.env.NODE_ENV != 'test') {
exports.server.start(function() {});
}