I use this code in grunt config:
const libDir = 'public/lib'
const cssDir = 'public/css'
// Project configuration.
grunt.initConfig({
watch: {
scripts: {
files: '**/*.js',
tasks: ['default'],
},
},
clean: [libDir],
bower_concat: {
all: {
dest: {
'js': libDir + '/vendor.js',
'css': libDir + '/vendor.css'
},
}
},
sass: {
options: {
sourceMap: true
},
dist: {
files: {
'output.css': 'input.scss'
}
}
}
});
How do I generate output.css dynamically by concatenating cssDir and some string ?
I tried using templating but it ignores cssDir.
Templates are not expanded for all config properties and especially not for config keys.
Since Grunt files are basically Javascript programs, you can construst the object programmatically:
const cssDir = 'public/css';
var sassFilesMap = {};
// build sass output file mapping programmatically
sassFilesMap[cssDir + "/output.scss"] = "input.scss";
// Project configuration.
grunt.initConfig({
// ...
sass: {
options: {
sourceMap: true
},
dist: {
files: sassFilesMap
}
}
});
If there are multiple .scss files to be converted (to be configured) you might with something like this:
const sassMappings = [["edit.scss", "edit.css"], ["public.scss", "public.css"]];
sassMappings.forEach(function(pair) {
// pair[0] is the .scss filename, pair[1] is the .css filename
sassFilesMap[cssDir + "/" + pair[0]] = pair[1];
});
I found out that ES6 supports dynamic property keys so basically this solves the problem as I can now compute both the key and value as dirVar + 'outputFile': inputDir + 'input.file'
Related
I am trying to minify an angularjs application using grunt and terser. I first used uglifiy-es but then read that it has some issues. So I tried terser. But the output does not give me minified files.
The gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
//grunt task configuration will go here
ngAnnotate: {
options: {
singleQuotes: true
},
app: {
files: {
'./public/min-safe/js/_config_min.js': ['./controllers/_config.js'],
'./public/min-safe/js/ctrl_accountingdashboard.js': ['./controllers/ctrl_accountingdashboard.js'],
}
}
},
concat: {
js: { //target
src: ['./public/min/app.js', './public/min-safe/js/*.js'],
dest: './public/min/app.js'
}
},
terser: {
options: {},
files: {
'./public/min/app.js': ['./public/min/app.js'],
},
}
});
//load grunt tasks
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-terser');
grunt.loadNpmTasks('grunt-ng-annotate');
//register grunt default task
grunt.registerTask('default', ['ngAnnotate', 'concat', 'terser']);
}
I had the same problem. According to the documentation, this should work but it didn't for me. Wrapping the "files" setting in a custom target works for me:
terser: {
options: {},
main: {
files: {
'./public/min/app.js': ['./public/min/app.js'],
}
}
}
To add to #Tim's great answer:
Here is an example that allows to run grunt-terser with path / file wildcard patterns (globbing) – which it does not support out of the box.
Please note the helper properties _src and _dest in the terser config which are not read by grunt-terser itself but by the task terser_all. This task expands the globbing pattern(s) in _src and builds the real config in the files property. When done it runs terser with that updated config.
module.exports = function (grunt) {
grunt.initConfig({
terser: {
dist: {
options: {
compress: {
drop_console: true // remove console.log, console.info, ...
}
},
files: {
// FILLED through terser_all task below!
// Examples config:
// "dist/example.js": [ "path/to/files/example.js" ]
// "dist/example_joined.js": [ "path/to/files/*.js" ]
},
// -----
// HELPER PROPERTIES to build the files prop (see above) in the terser_all task below.
_src: [
"path/to/files/*.js"
],
_dest: "dist/"
}
}
});
grunt.registerTask('terser_all', function () {
// work on this target in terser config
var terser_target_name = "dist";
// read the terser config
var terser_config = grunt.config.get('terser') || {};
var terser_target_config = terser_config[terser_target_name] || {};
// get the destination dir
var destDir = terser_target_config._dest;
// loop through all source files and create an entry in the terser config for each of it
var files = grunt.file.expand(terser_target_config._src);
for (const [i, file] of files.entries()) {
grunt.log.writeln(file);
// add this file to the terser config as: dest_file: src_file
terser_target_config.files[destDir + file] = file;
}
// show new config on CLI
grunt.log.writeflags(terser_target_config);
// write back config and run task
grunt.config.set('terser', terser_config);
grunt.task.run('terser');
});
grunt.loadNpmTasks('grunt-terser');
grunt.registerTask('build', ['terser_all']);
grunt.registerTask('default', ['build']);
};
Just a note:
If you try to "disable" some options by renaming this disables the whole process. At least this was my result with grunt-terser. I was left with the original js file.
{
mangleX: {
reserved: [/* ... */]
}
}
In my web project I have the following directory structure
|- target
|- foo
|- bar
|- baz
I'm trying to write a Grunt task that will copy all JSON files into the target directory from the directory whose name matches a parameter provided to the build
grunt.registerTask('flavor', function(srcDir) {
var from = './' + srcDir + '/*.json';
var dest = './target/';
grunt.file.expand(from).forEach(function(src) {
grunt.file.copy(src, dest);
});
});
But when I call this with
grunt flavor:foo
I get an error
Warning: Unable to write "./target/" file (Error code: EISDIR). Use --force to continue.
As #DanielApt mentioned, you should just use grunt-contrib-copy. To build on his answer regarding on your comment about build-parameters, you can get them to the task via grunt-option.
Way one: running different target
grunt.initConfig({
copy: {
foo: {
files: [
{
'expand': 'true',
'cwd': 'foo/',
'src': [**],
'dest': 'dist/en'
}
]
},
bar: {
files: [/**/]
},
baz: {
files: [/**/]
}
}
});
var target = grunt.option("target") || "foo";
grunt.registerTask("default", ["copy:" + target]);
// run with grunt --target=foo
Way 2: Arbituary folder with templating:
var target = grunt.option("target") || "foo";
grunt.initConfig({
target: target,
copy: {
default_target: {
files: [
{
'expand': 'true',
'cwd': '<%= target %>/',
'src': [**],
'dest': 'dist/en'
}
]
},
}
});
grunt.registerTask("default", ["copy"]);
// run with grunt --target=anyfolderhere
Do you need to write your own task? If you don't I achieved this with grunt-contrib-copy.
copy: {
default: {
files: [
{
expand: true,
src: ['foo/**', 'bar/**'],
dest: 'target/'
}
]
}
}
I'm trying to compile my handlebars template using Grunt as described below. It's okay but it compiles all the hbs files into one file which is not so good. I would like to have a separate file for Menu1 and Menu2 directories. I'd be really great if I could somehow make a separate task for each directory.
But as I'm still learning this stuff I couldn't find any better way to make it work..
Or perhaps if I could somehow make the Handlebars compile each file separately.
Project tructure:
/js/:
->/app/
app_init.js
->/templates/
-> /Menu1/
template1.hbs
template2.hbs
-> /Menu2/
template1.hbs
template2.hbs
Gruntfile.js
module.exports = function(grunt) {
var TEMPLATES_LOCATION = "./js/templates/",
TEMPLATES_EXTENSION = ".hbs",
TEMPLATES_OUTPUT_LOCATION = TEMPLATES_LOCATION,
TEMPLATES_OUTPUT_FILENAME = "compiled_templates.js";
grunt.initConfig({
watch: {
handlebars: {
files: [TEMPLATES_LOCATION + '**/*' + TEMPLATES_EXTENSION],
tasks: ['handlebars:compile']
}
},
handlebars: {
compile: {
src: TEMPLATES_LOCATION + '**/*' + TEMPLATES_EXTENSION,
dest: TEMPLATES_OUTPUT_LOCATION + TEMPLATES_OUTPUT_FILENAME,
options: {
amd: true,
namespace: "templates"
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-handlebars');
grunt.loadNpmTasks('grunt-contrib-watch');
}
Can anyone please advise?
I just came up with a slight workaround. Not sure if this is the best way to do this but it works..
handlebars: {
compile_Menu1: {
src: TEMPLATES_LOCATION + '/Menu1/*' + TEMPLATES_EXTENSION,
dest: TEMPLATES_OUTPUT_LOCATION+"Menu1"+TEMPLATES_OUTPUT_EXTENSION,
options: {
amd: true,
namespace: "M1_templates"
}
},
compile_Menu2: {
src: TEMPLATES_LOCATION + '/Menu2/*' + TEMPLATES_EXTENSION,
dest: TEMPLATES_OUTPUT_LOCATION+"Menu2"+TEMPLATES_OUTPUT_EXTENSION,
options: {
amd: true,
namespace: "M2_templates"
}
}
}
I have two different paths where I want to compile the mobile vs desktop code. I would like to alternate by passing a grunt parameter in the command line.
/**
* #module Build
* #class Build.Config
* #static
*/
module.exports = function(grunt) {
var config = {};
var NewPath;
var env = grunt.option('target') || "Mobile";
if (env == "Desktop") { // MAKE THIS DYNAMIC WITH COMMAND LINE ARGUMENT
newPath = "source/desktop/";
}
else {
newPath = "source/mobile/";
}
config.root = newPath;
config.stylesheets = config.root + '/stylesheets';
config.javascripts = config.root + '/javascripts';
config.images = config.root + '/images';
config.jsbin = config.javascripts + '/generated';
config.cssbin = config.stylesheets + '/generated';
config.docsbin = 'docs';
// Project configuration.
grunt.initConfig({
'beautifier': {
'options': {
'indentSize': 1,
'indentChar': '\t',
'spaceAfterAnonFunction': true
}
},
'beautify': {
'files': [ config.javascripts + '/app/**/*.js' ]
},
'requirejs': require('./build/config/requirejs.js')(config),
'watch': require('./build/config/watch.js')(config),
'stylus':require('./build/config/stylus.js')(config)
});
// Default task.
grunt.registerTask('default', ['stylus:compile','requirejs']);
grunt.registerTask('dev', ['stylus:dev']);
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.loadNpmTasks('grunt-contrib-stylus');
};
Turns out I was doing it right I just needed to pass in the variable for env correctly:
$ grunt --target="Desktop"
An alternate to --option is to pass it through the colon. for example passing it to jshint
grunt jshint:desktop
Then configure grunt to pick up that command line argument using process.argv and you can use it to configure your paths or whatever else might be needed:
module.exports = function(grunt) {
"use strict";
//dynamic config after the ':'. 'desktop' here
var env = process.argv[2].split(':')[1];
var config = {
pkg: grunt.file.readJSON('package.json'),
jshint: {
options: {
jshintrc: '.jshintrc',
"force": true
}
},
};
//...
config.jshint[env] = { // ex: $ grunt jshint:desktop
src: ['public/'+env+'/js/main.js']
};
//...
// Project configuration.
grunt.initConfig(config);
//...
};
One caveat on using process is that it won't work when you use a grunt task that respawns your process like the useful grunt-concurrent. In that case it's bettter to go with grunt.option as shown by #im_benton. passing grunt mytask --myvar=myval and picking it up in you Gruntfile.js as grunt.option('myvar') `
I have a directory with a bunch of jade templates, and a grunt task that compiles all of them to individual html files.
I'd like to have a watch task that recompiles a template when it changes, but right now my task recompiles every template when any of them change.
Here is a demo in a gist.
Is there a succinct way to write a task that recompiles a template when it changes, but not all of the other templates?
the solution is to add a filter function to the files list:
var fs = require('fs');
var join = require('path').join;
module.exports = function(grunt) {
grunt.initConfig({
jade: {
files: {
src: ['*.jade'],
dest: './',
expand: true,
ext: '.html',
filter: function(destDir, src) {
var dest = join(destDir, src.replace(/jade$/, 'html'));
var destMod;
try {
destMod = +fs.lstatSync(dest).mtime;
} catch (e) {
if (e.code === 'ENOENT') {
// there's no html file, so ensure that the
// jade file is compiled by returning true
return true;
}
throw e;
}
return fs.lstatSync(src).mtime > destMod;
}
}
},
watch: {
files: ['*.jade'],
tasks: ['jade']
}
});
grunt.loadNpmTasks('grunt-contrib-jade');
grunt.loadNpmTasks('grunt-contrib-watch');
};