MDL-49817 grunt: Refactor the uglify task
[moodle.git] / Gruntfile.js
CommitLineData
adeb96d2
DW
1// This file is part of Moodle - http://moodle.org/
2//
3// Moodle is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// Moodle is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
15
16/**
17 * @copyright 2014 Andrew Nicols
18 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
19 */
20
21/**
22 * Grunt configuration
23 */
24
25module.exports = function(grunt) {
26 var path = require('path'),
8f76bfb6 27 fs = require('fs'),
e67585f8 28 tasks = {},
c9b6feea
JLB
29 cwd = process.env.PWD || process.cwd(),
30 inAMD = path.basename(cwd) == 'amd';
adeb96d2 31
5cc5f311
DP
32 // Globbing pattern for matching all AMD JS source files.
33 var amdSrc = [inAMD ? cwd + '/src/*.js' : '**/amd/src/*.js'];
34
35 /**
36 * Function to generate the destination for the uglify task
37 * (e.g. build/file.min.js). This function will be passed to
38 * the rename property of files array when building dynamically:
39 * http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
40 *
41 * @param {String} destPath the current destination
42 * @param {String} srcPath the matched src path
43 * @return {String} The rewritten destination path.
44 */
45 var uglify_rename = function (destPath, srcPath) {
46 destPath = srcPath.replace('src', 'build');
47 destPath = destPath.replace('.js', '.min.js');
48 destPath = path.resolve(cwd, destPath);
49 return destPath;
50 };
51
adeb96d2
DW
52 // Project configuration.
53 grunt.initConfig({
54 jshint: {
55 options: {jshintrc: '.jshintrc'},
5cc5f311 56 amd: { src: amdSrc }
adeb96d2
DW
57 },
58 uglify: {
5cc5f311
DP
59 amd: {
60 files: [{
61 expand: true,
62 src: amdSrc,
63 rename: uglify_rename
64 }]
adeb96d2 65 }
a4a52e56
DP
66 },
67 less: {
68 bootstrapbase: {
69 files: {
70 "theme/bootstrapbase/style/moodle.css": "theme/bootstrapbase/less/moodle.less",
71 "theme/bootstrapbase/style/editor.css": "theme/bootstrapbase/less/editor.less",
72 },
73 options: {
74 compress: true
75 }
76 }
adeb96d2
DW
77 }
78 });
79
80 tasks.shifter = function() {
81 var exec = require('child_process').spawn,
82 done = this.async(),
83 args = [],
84 options = {
85 recursive: true,
86 watch: false,
87 walk: false,
88 module: false
89 },
90 shifter;
91
e67585f8
DW
92 args.push( path.normalize(__dirname + '/node_modules/shifter/bin/shifter'));
93
adeb96d2 94 // Determine the most appropriate options to run with based upon the current location.
e67585f8 95 if (path.basename(cwd) === 'src') {
adeb96d2
DW
96 // Detect whether we're in a src directory.
97 grunt.log.debug('In a src directory');
98 args.push('--walk');
99 options.walk = true;
e67585f8 100 } else if (path.basename(path.dirname(cwd)) === 'src') {
adeb96d2
DW
101 // Detect whether we're in a module directory.
102 grunt.log.debug('In a module directory');
103 options.module = true;
104 }
105
106 if (grunt.option('watch')) {
107 if (!options.walk && !options.module) {
108 grunt.fail.fatal('Unable to watch unless in a src or module directory');
109 }
110
111 // It is not advisable to run with recursivity and watch - this
112 // leads to building the build directory in a race-like fashion.
113 grunt.log.debug('Detected a watch - disabling recursivity');
114 options.recursive = false;
115 args.push('--watch');
116 }
117
118 if (options.recursive) {
119 args.push('--recursive');
120 }
121
122 // Always ignore the node_modules directory.
123 args.push('--excludes', 'node_modules');
124
125 // Add the stderr option if appropriate
126 if (grunt.option('verbose')) {
127 args.push('--lint-stderr');
128 }
129
a07afffc
DP
130 if (grunt.option('no-color')) {
131 args.push('--color=false');
132 }
133
8f76bfb6
DM
134 var execShifter = function() {
135
136 shifter = exec("node", args, {
137 cwd: cwd,
138 stdio: 'inherit',
139 env: process.env
140 });
141
142 // Tidy up after exec.
143 shifter.on('exit', function (code) {
144 if (code) {
145 grunt.fail.fatal('Shifter failed with code: ' + code);
146 } else {
147 grunt.log.ok('Shifter build complete.');
148 done();
149 }
150 });
151 };
152
adeb96d2 153 // Actually run shifter.
8f76bfb6
DM
154 if (!options.recursive) {
155 execShifter();
156 } else {
157 // Check that there are yui modules otherwise shifter ends with exit code 1.
158 var found = false;
159 var hasYuiModules = function(directory, callback) {
160 fs.readdir(directory, function(err, files) {
161 if (err) {
162 return callback(err, null);
163 }
164
165 // If we already found a match there is no need to continue scanning.
166 if (found === true) {
167 return;
168 }
169
170 // We need to track the number of files to know when we return a result.
171 var pending = files.length;
172
173 // We first check files, so if there is a match we don't need further
174 // async calls and we just return a true.
175 for (var i = 0; i < files.length; i++) {
176 if (files[i] === 'yui') {
177 return callback(null, true);
178 }
179 }
180
181 // Iterate through subdirs if there were no matches.
182 files.forEach(function (file) {
183
184 var p = path.join(directory, file);
185 stat = fs.statSync(p);
186 if (!stat.isDirectory()) {
187 pending--;
188 } else {
189
190 // We defer the pending-1 until we scan the whole dir and subdirs.
191 hasYuiModules(p, function(err, result) {
192 if (err) {
193 return callback(err);
194 }
195
196 if (result === true) {
197 // Once we get a true we notify the caller.
198 found = true;
199 return callback(null, true);
200 }
201
202 pending--;
203 if (pending === 0) {
204 // Notify the caller that the whole dir has been scaned and there are no matches.
205 return callback(null, false);
206 }
207 });
208 }
209
210 // No subdirs here, otherwise the return would be deferred until all subdirs are scanned.
211 if (pending === 0) {
212 return callback(null, false);
213 }
214 });
215 });
216 };
217
218 hasYuiModules(cwd, function(err, result) {
219 if (err) {
220 grunt.fail.fatal(err.message);
221 }
222
223 if (result === true) {
224 execShifter();
225 } else {
226 grunt.log.ok('No YUI modules to build.');
227 done();
228 }
229 });
230 }
adeb96d2
DW
231 };
232
233 tasks.startup = function() {
234 // Are we in a YUI directory?
e67585f8 235 if (path.basename(path.resolve(cwd, '../../')) == 'yui') {
adeb96d2
DW
236 grunt.task.run('shifter');
237 // Are we in an AMD directory?
c9b6feea 238 } else if (inAMD) {
65d070ae 239 grunt.task.run('amd');
adeb96d2
DW
240 } else {
241 // Run them all!.
65d070ae
DP
242 grunt.task.run('css');
243 grunt.task.run('js');
adeb96d2
DW
244 }
245 };
246
247
248 // Register NPM tasks.
249 grunt.loadNpmTasks('grunt-contrib-uglify');
250 grunt.loadNpmTasks('grunt-contrib-jshint');
a4a52e56 251 grunt.loadNpmTasks('grunt-contrib-less');
adeb96d2 252
65d070ae 253 // Register JS tasks.
adeb96d2 254 grunt.registerTask('shifter', 'Run Shifter against the current directory', tasks.shifter);
65d070ae
DP
255 grunt.registerTask('amd', ['jshint', 'uglify']);
256 grunt.registerTask('js', ['amd', 'shifter']);
257
258 // Register CSS taks.
259 grunt.registerTask('css', ['less:bootstrapbase']);
adeb96d2
DW
260
261 // Register the startup task.
262 grunt.registerTask('startup', 'Run the correct tasks for the current directory', tasks.startup);
263
264 // Register the default task.
265 grunt.registerTask('default', ['startup']);
266};