Using: Grunt 1.01, load-grunt-config, jit-grunt.
I'm looking to put a simple means of setting a development/production flag in place for use in certain Grunt tasks.
The first task that needs the flag is webpack.js, in order to switch between the development and production builds of React. Here's that file:
module.exports = function( grunt ) {
// Get the task that was invoked from the command line.
var theTask = process.argv[2];
// Check to see if it's a production task. If so, change the
// `env` variable accordingly.
if ( theTask !== undefined && theTask.indexOf('prod') === 0 ) {
grunt.config.set('env', 'production');
}
return {
app: {
entry: './<%= siteInfo.build_dir %>/<%= siteInfo.temp_dir %>/<%= siteInfo.app_dir %>/<%= siteInfo.app_file %>.js',
output: {
path: '<%= siteInfo.build_dir %>/<%= siteInfo.temp_dir %>',
filename: '<%= siteInfo.bundle_file %>.tmp.js'
},
stats: false,
failOnError: true,
progress: false,
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(grunt.config.data.env)
}),
]
}
}
};
In the context of this task, it works perfectly. If I run grunt prod then the correct version of React is being included in the bundled JS.
However, I was under the (most certainly) mistaken impression that by setting the env variable using grunt.config.set this update to Grunt's config object would then be available to subsequent tasks.
As I've found out, env is undefined if I console.log(grunt.config.data.env) in another task.
Any pointers or suggestions of alternative approaches appreciated!
Instead of returning the config, try this:
Create a pre-build task which sets any config/options prior to running your tasks. You can check if the task is prod or not by checking this.name, and then since it's a simple value, set it using grunt.option:
function preBuild() {
grunt.option('env', this.name === 'prod' ? 'production' : 'development');
grunt.task.run(['task1', 'task2', task3']);
}
grunt.registerTask('prod', preBuild);
grunt.registerTask('dev', preBuild);
Using a lo-dash template, you can pass the option value to your config. Grunt compiles the lo-dash template strings when the task accessing it is run:
grunt.initConfig({
app: {
entry: './<%= siteInfo.build_dir %>/<%= siteInfo.temp_dir %>/<%= siteInfo.app_dir %>/<%= siteInfo.app_file %>.js',
output: {
path: '<%= siteInfo.build_dir %>/<%= siteInfo.temp_dir %>',
filename: '<%= siteInfo.bundle_file %>.tmp.js'
},
stats: false,
failOnError: true,
progress: false,
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': "<%= grunt.option('env') %>"
}),
]
}
});
Related
Trying to use a predefined array inside of a grunt file, thought using this.js_paths would work, but doesn't seem to work as I'm getting the error, "Cannot read property IndexOf of undefined" when it comes to trying to uglify the scripts. How can I link the js_paths variable to the files src property properly instead of copying the array into the files. Would like to define it separately at the top. Is this possible?
module.exports = function(grunt) {
// loadNpmTasks from package.json file for all devDependencies that start with grunt-
require("matchdep").filterDev("grunt-*", './package.json').forEach(grunt.loadNpmTasks);
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
js_paths: [
'inc/header1/js/*.js',
'!inc/header1/js/*.min.js',
'inc/header2/js/*.js',
'inc/header2/js/*.js',
'!inc/header2/js/*.min.js',
'js/*.js',
'!js/*.min.js'
],
uglify: {
options: {
mangle: true
},
build: {
files: [{
expand: true,
src: this.js_paths,
rename: function(dst, src) {
return src.replace('.js', '.min.js');
}
}]
}
},
watch: {
scripts: {
files: ['inc/header1/js/*.js', 'inc/header2/js/*.js', 'js/*.js'],
tasks: ['uglify'],
options: {
spawn: false,
}
}
}
});
grunt.registerTask('default', ['uglify', 'watch']);
};
Preferrably would like to use the same array js_paths in the watch files (since it's required there), if that makes sense? Still kinda new to using gruntfile.js
Utilize the Templates syntax. It's described in the docs as following:
Templates
Templates specified using <% %> delimiters will be automatically expanded when tasks read them from the config. Templates are expanded recursively until no more remain.
Essentially, change this.js_paths to '<%= js_paths %>' in your uglify task.
For instance:
// ...
uglify: {
options: {
mangle: true
},
build: {
files: [{
expand: true,
src: '<%= js_paths %>', // <-----
rename: function(dst, src) {
return src.replace('.js', '.min.js');
}
}]
}
},
// ...
Likewise for your watch task too.
For instance:
watch: {
scripts: {
files: '<%= js_paths %>', // <-----
tasks: ['uglify'],
options: {
spawn: false,
}
}
}
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.
I use a grunt package called grunt-preprocess, Obviously, it doesn't support multi-tasks.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
preprocess: {
options: {
context: {
ENV: grunt.option('env') || 'prod'
},
},
all_from_dir: {
src: '*.*',
cwd: 'src/',
dest: 'src',
expand: true
}
},
})
Now I want to execute preprocess twice, once from the src directory, and once from the dist directory. How should I configure this package to achieve that?
I have tried this configuration;
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
preprocess: {
first: {
options: {
context: {
ENV: grunt.option('env') || 'prod'
},
},
all_from_dir: {
src: '*.*',
cwd: 'src/',
dest: 'src',
expand: true
}
},
second: {
options: {
context: {
ENV: grunt.option('env') || 'prod'
},
},
all_from_dir: {
src: '*.*',
cwd: 'dist/',
dest: 'dist',
expand: true
}
}
}
})
and then execute grunt preprocess:first. However it does not work:
PS D:\workspace\environment-compile> grunt preprocess:first
Running "preprocess:first" (preprocess) task
Done.
Yes, you're right preprocess is single Task only, therefore doesn't allow multiple targets to be defined.
You'll need to create another custom task that dynamically configures the preprocess Task and then runs it.
For example:
Gruntfile.js
module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-preprocess');
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
preprocess: {
options: {
context: {
ENV: grunt.option('env') || 'prod'
},
},
all_from_dir: {
src: '*.*',
cwd: 'src/',
dest: 'src',
expand: true
}
},
// ...
});
// Custom task dynamically configures the `preprocess` Task and runs it.
grunt.registerTask('preprocess_dist', function() {
grunt.config.set('preprocess.all_from_dir.cwd', 'dist/');
grunt.config.set('preprocess.all_from_dir.dest', 'dist');
grunt.task.run(['preprocess']);
});
grunt.registerTask('preprocessBoth', [ 'preprocess', 'preprocess_dist' ]);
};
Running:
Via your CLI run the following single command to execute the preprocess task twice, once from the src directory, once from the dist directory:
grunt preprocessBoth
Explanation:
The custom Task named preprocess_dist dynamically configures the values for the cwd and dest properties, setting them to 'dist/' and 'dist' respectively. This is done via the grunt.config.set method
Then the task is run via the grunt.task.run method.
The last line of code that reads:
grunt.registerTask('preprocessBoth', [ 'preprocess', 'preprocess_dist' ]);
creates a task named preprocessBoth and adds the following two tasks to to the taskList:
preprocess
preprocess_dist
Essentially what happens when you run grunt preprocessBoth is:
The preprocess task runs using files from the src directory.
Then the custom preprocess_dist task runs using files from the dist directory.
If you prefer, you can also run each task independently via CLI, i.e:
grunt preprocess
and
grunt preprocess_dist
I am using grunt-contrib-watch and this sublime package sublime-grunt.
I am using the sublime-grunt package to start a watch session and livereload with the Chrome extension. Everything works great however once a 'watch' session is started what is the command to stop/kill/cancel a watch. I tried using a command in the sublime-grunt package called 'Grunt Kill Running Processes' and it tells me something has been canceled but if I change my style.scss file and save it, it compiles and updates are made to my html page, so 'watch' is still in effect. Right now I have to close down Sublime Text to kill the 'watch' session.
I am trying to use a variable for a path to my theme root directory but when I try to concat the variable with a string I received this error. What type of syntax should I use, do I need to create a variable in the package.json file?
Code
var theme_path = 'wp-content/themes/twentyfifteen/';
// Sass plugin
sass: {
dist: {
files: {
theme_path + 'style.css': theme_path + 'sass/style.scss',
}
}
},
// Error
Loading "Gruntfile.js" tasks...ERROR
>> SyntaxError: C:\wamp\www\grunt\Gruntfile.js:31
>> theme_path + 'style.css': 'wp-content/th
emes/twentyfifteen/sass/style.scs
>> ^
>> Unexpected token +
Warning: Task "default" not found. Use --force to continue.
Here is my Gruntfile.js:
module.exports = function(grunt) {
// Theme Directory Path
var theme_path = 'wp-content/themes/twentyfifteen/';
// Configure main project settings
grunt.initConfig({
// Basic settings and info about our plugins
pkg: grunt.file.readJSON('package.json'),
// Watch Plugin
watch: {
sass: {
files: 'wp-content/themes/twentyfifteen/sass/*.scss',
tasks: ['sass','postcss','uglify'],
options: {
livereload: true,
},
},
},
// Sass plugin
sass: {
dist: {
files: {
'wp-content/themes/twentyfifteen/style.css': 'wp-content/themes/twentyfifteen/sass/style.scss',
}
}
},
// Postcss plugin
postcss: {
options: {
map: true, // inline sourcemaps
// or
map: {
inline: false, // save all sourcemaps as separate files...
},
processors: [
require('pixrem')(), // add fallbacks for rem units
require('autoprefixer')({browsers: 'last 2 versions'}), // add vendor prefixes
require('cssnano')() // minify the result
]
},
dist: {
src: 'wp-content/themes/twentyfifteen/style.css'
}
},
// Minify JS Uglify
uglify: {
dist: {
files: {
'wp-content/themes/twentyfifteen/js/functions.min.js': ['wp-content/themes/twentyfifteen/js/functions.js']
}
}
}
});
// Load the plugin
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-sass');
grunt.loadNpmTasks('grunt-postcss');
grunt.loadNpmTasks('grunt-contrib-uglify');
// Run plugin
grunt.registerTask('default', ['watch'] );
}
I think that tasks options can't have variable assigned, i don't have any source at the moment but i can suggest another way to do it:
you can use task params:
watch: {
sass: {
files: '<%= grunt.task.current.args[0] %>/sass/*.scss',
tasks: ['sass:<%= grunt.task.current.args[0] %>','postcss','uglify'],
options: {
livereload: true,
},
},
},
sass: {
dist: {
files: {
'<%= grunt.task.current.args[0] %>/style.css': '<%= grunt.task.current.args[0] %>/sass/style.scss',
}
}
},
and than
grunt.registerTask('default', ['watch:'+theme_path] );
This is a problem that i have already meet to make dynamic watch tasks
I'm running a Grunt task that uses Concurrent to run both Nodemon and Watch/Livereload. On default load, I lint and launch Concurrent. I would also like to set up a Watch to lint individual files on change. Currently, all files are linted when any one file is changed.
I have examined a similar question on StackOverflow and decided to go with grunt-newer as a potential solution. In my implementation below, however, the 'newer' prefix doesn't seem to do anything. How can I fix this so that only changed files are linted?
module.exports = function(grunt) {
//load all dependencies
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
concurrent: {
dev: {
options: {
logConcurrentOutput: true
},
tasks: ['watch', 'nodemon']
}
},
jshint: {
files: ['Gruntfile.js', 'client/src/*.js', 'server/**/*.js'],
options: {
'-W030': true,
'-W083': true,
globals: {
console: true,
module: true,
document: true
}
}
},
watch: {
all: {
files: ['<%= jshint.files %>'],
tasks: ['newer:jshint']
},
frontend: {
files: ['client/**/*.{css,js,html}'],
options: {
livereload: true
}
}
},
nodemon: {
dev: {
options: {
file: 'server/server.js',
watchedFolders: ['server']
}
}
}
});
grunt.registerTask('test', ['jshint']);
grunt.registerTask('default', ['jshint', 'concurrent']);
};
I was having the same problem and finally figured it out. The solution is hidden deep in the documentation and very misleading with the spawn option in the code sample: https://github.com/gruntjs/grunt-contrib-watch#compiling-files-as-needed
Your config file should remain the same as you have it in your question, but you need to add a listener to the watch event. I recommend the 'robust' option they provide (modified for you specific task config). Place this code just above the call to grunt.initConfig and after you require calls.
var changedFiles = Object.create(null);
var onChange = grunt.util._.debounce(function() {
// Modified to point to jshint.files as per the task example in the question.
grunt.config('jshint.files', Object.keys(changedFiles));
changedFiles = Object.create(null);
}, 200);
grunt.event.on('watch', function(action, filepath) {
changedFiles[filepath] = action;
onChange();
});
Add the nospawn option to the all watch task. This is what is misleading in in the documentation. It mentions it should be disabled if you want dynamically modify your config but basically prevents it from working with newer unless it's set to true:
watch: {
all: {
files: ['<%= jshint.files %>'],
tasks: ['newer:jshint'],
options: {
nospawn: true,
}
},
...
NOTE: If you modify your grunt file while it's running then it will lint all files, not sure why it does this but then it gets stuck and will just keep linting everything for all changes you make. I just took out the 'gruntfile.js' from the list of files that should be linted to avoid it.