Reputation: 865
I have a project, with javascript files structured something like this:
My goal: I would like to minify/uglify these javascript files in the build output, such as there is 1 minified file per each directory. So for example, if I'm storing the js in my build output in bld/js, then in bld/js, I would end up with files: dir1.min.js (result of concatenating and minifying fileA.js, fileB.js, and all the other js files in /src/js/dir1/), dir2.min.js (result of concatenating and minifying fileG.js and fileN.js, and all the other js files in /src/dir2/), and so on.
Now - I am using the grunt framework to generate the build, and must do this within a grunt file. I am new to grunt, node.js, and javascript. I wanted to explain my thought process towards solving this problem, and am wondering if someone more familiar with these frameworks can tell me if my approach/thinking towards this is what is wrong.
So as I understand, the main way to do things in grunt, is to use plugins. And so my first thought was to use a plugin to accomplish this task. There is a plugin called 'npm-contrib-uglify', which is used specifically for minification. https://github.com/gruntjs/grunt-contrib-uglify The problem is - my understanding is that to do what I'm hoping to do here (1 min.js file per each directory), I would need to create specific tasks for each directory. so the init looking something like this:
grunt.initConfig({
uglify: {
dir1: {
files: {
'dest/js/dir1.min.js': ['src/js/dir1/**.js']
}
},
dir2: {
files: {
'dest/js/dir2.min.js': ['src/js/dir2/**.js']
}
},
...
dirN: {
files: {
'dest/js/dirN.min.js': ['src/js/dirN/**.js']
}
},
}
});
If I only had one or two dirs, and the project format was going to stay relatively the same - I guess I could live with this. But there are many dirs in this project, and more importantly, the project structure will be changing; I'd like to be able to add dirs to my project, and not have to update the grunt file each time as a result by adding a new target. I hoped for a way to parameterize the task - meaning, have a single generic target that says 'for some dir, minify all the .js files in that dir to some .min.js file' and then I could invoke the target programmatically for each dir - but this doesn't seem possible.
So I decided to avoid the plugin, and just rely on node.js. There is an npm module called 'npm-uglify. https://www.npmjs.com/package/uglify-js . I thought I can make my own custom grunt task, which loops through the source directory, and calls uglify-js on each nested dir. Here is an example (note I am using pseudocode in a couple places, particularly, for getting the dir listing, as I do not have access to a machine, and do not have the syntax committed to memory. But this should give the idea of what I mean to do:)
var fs = require('fs');
var uglifyJs = require('uglify-js');
...
grunt.registerTask('minifyMyJs', function(myJsSrcDir) { // myJsSrcDir is an abs. path to the src dir
for (dir in myJsSrcDir): // go through all the dirs.. realize I should use fs.readdir, but just use pseudocode here to give the idea
var minifiedOutputFileName = fs.basename(dir); // to get dir1 from /src/js/dir1
var minified = uglifyJs.Minify(["**.js"]);
fs.writeFile(minifiedOutputFileNam, result.code, function(err)) {
if(err) {
... error checking...
}
}
}
}
Now - I'm not asking for you to solve this problem for me, and I know the above would not compile. Rather my question is - am I just thinking about this in the completely wrong way? Is the whole point of grunt to avoid coding things like this in node.js (and so I'm defeating the purpose by doing this?) Is it that you really can parameterize the targets of the grunt plugins such as grunt-contrib-uglify? I am running in to this same issue with many plugins, where I have some task I need to repeat over and over across many different dirs/files but in the exact same way, and so I'd like to be able to configure a general target I can call programmatically for different values (rather than hardcoding tons of targets for specific directory or filenames which could change often). But this doesn't seem possible, and so I keep just implementing in node.js instead. Is there something fundamentally flawed with the way I am approaching grunt? And if so - can you offer some alternate perspective?
Upvotes: 1
Views: 329
Reputation: 24952
am I just thinking about this in the completely wrong way?
No, I don't think you are thinking about this in the wrong way. It's always a good thing to question the why/how for any development [period]. This is what makes us better developers long-term.
Whichever solution/tools you choose to utilize for your build process, whether that be a dedicated task runner such as grunt or gulp, or a higher level solution such as nodejs perhaps combined with npm-scripts, you still have to craft some code. Of course, the commonality between all those approaches mentioned is that you have to code using JavaScript.
Is the whole point of grunt to avoid coding things like this in node.js (and so I'm defeating the purpose by doing this?
It's not for me to say which solution/tool is better to use than another (that would be too opinionated) and personally I have had experience developing build processes using all those tools mentioned. Typically though I often find my development time for handling builds is reduced when using a dedicated task runner (grunt or gulp) than when using a more custom approach such as npm-scripts
combined with nodejs
.
Often, I too find a really great plug-in for grunt (such as grunt-contrib-uglify
) to then discover after some tinkering that it meets 90% of my requirements, yet fails miserably for the remaining 10%. However, that's often the case when utilizing/integrating any open sourced package/module... You still have to fill the gaps so to speak!
Before ditching gruntjs
and going into nodejs
only development mode just yet, (and again to reiterate I have no bias towards any of the approaches), I recommend that you check-out some of grunts features such as:
There has been may scenarios when using grunt that I've found utlizing the features listed above has helped me to address that remaining 10% of the requirement that I mentioned previously. The custom-tasks feature in particular has proved very useful many times.
It's worth noting that grunt-contrib-uglify is a multi-task plugin. I.e. It allows you to configure multiple Targets as you have demonstrated in the your uglify
configuration in your question. You have included three Targets named dir1
, dir2
, and dirN
.
With the fact that grunt-contrib-uglify
is multi-task and keeping in mind the grunt features I listed previously... One approach to meet your requirement using grunt is to:
grunt.file.expand
to get the paths to the parent folder of each .js file.files
configuration for each Target of the uglify
task.uglify
task with multiple Targets using grunt.config
and then run the task using grunt-task.run
.Grunfile.js
module.exports = function (grunt) {
'use strict';
grunt.initConfig({
uglify: {
// <-- Intentionally blank, will be dynamically generated.
}
});
/**
* 1. Helper custom Task to dynamically configure and run the uglify task.
*/
grunt.registerTask('myUglify', 'Configures uglify task', function () {
var srcDirGlob = 'src/js/**/*.js';
var destDir = 'dist/js/';
var config = {};
// 2. Get the paths to the parent diretory of each .js file.
var paths = grunt.file.expand({ filter: 'isFile' }, srcDirGlob)
.map(function (_path) {
return _path.substring(0, _path.lastIndexOf("/"));
});
// 3. Filter paths Array to only unique directory paths.
paths.filter(function(_path, pos){
return paths.indexOf(_path) === pos;
})
// 4. Dynamically create the `files` configuration for each Target.
.forEach(function(_path, index) {
var dirName = _path.substring(_path.lastIndexOf('/') + 1);
var destPath = destDir + dirName + '.min.js';
var srcGlob = _path + '/*.js';
config[index] = {
files: {}
}
config[index].files[destPath] = srcGlob;
});
// NOTE: The dynamically created `config` object is now something like this:
//
// config: {
// 0: {
// files: {
// 'dist/js/dir1.min.js': 'src/js/dir1/**.js'
// }
// },
// 1: {
// files: {
// 'dist/js/dir2.min.js': 'src/js/dir2/**.js'
// }
// },
// ...
// }
// 5. Configure the uglify task with multiple Targets
// (i.e. the `config` object) and then run the Task.
grunt.config('uglify', config);
grunt.task.run(['uglify']);
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('default', ["myUglify"]);
};
Results
Running $ grunt
via your CLI using the Gruntfile.js
above with a src
directory as follows:
.
└── src
└── js
├── dir1
│ ├── fileA.js
│ └── fileB.js
├── dir2
│ ├── fileG.js
│ └── fileN.js
└── dir3
├── fileQ.js
└── fileR.js`
..will concatenate and uglify
the .js
files producing a directory structure as follows:
.
└── dist
└── js
├── dir1.min.js
├── dir2.min.js
└── dir3.min.js`
Note: The parent folder name of the source .js
file is used for the name of the resultant concatenated/uglified .js
file.
Gruntfile.js
(above) can be modified as necessary for many of the the plug-ins available.I hope this helps you.
Upvotes: 1