πŸ“œ ⬆️ ⬇️

How does Grunt work: see the source



Grunt.js has long gained the popularity of a great tool for streamlining and automating workflow. Recognized as the best open source project in the field of web technologies for 2014. It is easy to use, effective in work, used in many projects. We will not talk about what you can do with it, it is implied that there is no need for this. Better try to see what's inside.


Getting Started


The source code for Grunt is organized as an organization on Github. In which there are about the following projects:

Gruntfile

Configuration file This can be either Gruntfile.js, Gruntfile.coffee, which is needed for loading and configuring plugins, creating tasks, etc. All our communication with Grunt happens through him. It is a wrapper function into which the grunt object is passed:
module.exports = function(grunt) { // Do grunt-related things in here }; 

Developers seem to like CoffeeScript, by default it can be used for everything (configuring Gruntfile.coffee , writing plugins, ...)
')

Command Line Interface


On the official website we are recommended to install the command line interface for Grunt.
 npm install -g grunt-cli 

Grunt-cli structure:
 β”œβ”€β”€ AUTHORS β”œβ”€β”€ Gruntfile.js # jshint β”œβ”€β”€ LICENSE-MIT β”œβ”€β”€ README.md β”œβ”€β”€ bin β”‚  └── grunt #  β”œβ”€β”€ completion #  β”‚  β”œβ”€β”€ bash β”‚  └── zsh β”œβ”€β”€ lib β”‚  β”œβ”€β”€ cli.js #  cli β”‚  β”œβ”€β”€ completion.js #   β”‚  └── info.js #      └── package.json 3 directories, 11 files 


Its only purpose is to locate the locally installed Grunt in the current directory and run:
 var resolve = require('resolve').sync; try { gruntpath = resolve('grunt', {basedir: basedir}); } catch (ex) { gruntpath = findup('lib/grunt.js'); // No grunt install found! if (!gruntpath) { if (options.version) { process.exit(); } if (options.help) { info.help(); } info.fatal('Unable to find local grunt.', 99); } } /** * gruntpath -> {HOME_DIR}/node_modules/grunt/lib/grunt.js *  .cli()   grunt */ require(gruntpath).cli(); 


Grunt core


The main project, grunt , here the tasks and run. Designed as an npm module.
File structure:
 β”œβ”€β”€ AUTHORS β”œβ”€β”€ CHANGELOG β”œβ”€β”€ CONTRIBUTING.md β”œβ”€β”€ Gruntfile.js # jshint, test, watch β”œβ”€β”€ LICENSE-MIT β”œβ”€β”€ README.md β”œβ”€β”€ appveyor.yml β”œβ”€β”€ docs β”‚  └── README.md β”œβ”€β”€ internal-tasks #   β”‚  β”œβ”€β”€ bump.js β”‚  └── subgrunt.js β”œβ”€β”€ lib β”‚  β”œβ”€β”€ grunt #   β”‚  β”‚  β”œβ”€β”€ cli.js β”‚  β”‚  β”œβ”€β”€ config.js β”‚  β”‚  β”œβ”€β”€ event.js β”‚  β”‚  β”œβ”€β”€ fail.js β”‚  β”‚  β”œβ”€β”€ file.js β”‚  β”‚  β”œβ”€β”€ help.js β”‚  β”‚  β”œβ”€β”€ option.js β”‚  β”‚  β”œβ”€β”€ task.js β”‚  β”‚  └── template.js β”‚  β”œβ”€β”€ grunt.js #  β”‚  └── util β”‚  └── task.js #   β”œβ”€β”€ package.json └── test └── **/*.js #  22 directories, 71 files 


Code Style

Understandable. There are a lot of comments and explanations in the code, practically in every method or block, and they are written somehow β€œhumanly”, for example Yes, this is a total hack... They urge you to do more, but easier than shorter, but β€œsly ".
Examples:
 // bad var foo = 'bar' , bizz = 'bazz'; // better var foo = 'bar'; var bizz = 'bazz'; // bad isOk && doGrunt(); // better if (isOk) { doGrunt(); } // bad isOk ? doGrunt() : doMake(); // better if (isOk) { doGrunt(); } else { doMake(); } 


Like almost any Grunt npm-module has its own dependencies , it is important to pay attention to them for a better understanding of what is happening.
dependencies:
 "async": "~0.1.22", "coffee-script": "~1.3.3", "colors": "~0.6.2", "dateformat": "1.0.2-1.2.3", "eventemitter2": "~0.4.13", "findup-sync": "~0.1.2", "glob": "~3.1.21", "hooker": "~0.2.3", "iconv-lite": "~0.2.11", "minimatch": "~0.2.12", "nopt": "~1.0.10", "rimraf": "~2.2.8", "lodash": "~0.9.2", "underscore.string": "~2.2.1", "which": "~1.0.5", "js-yaml": "~2.0.5", "exit": "~0.1.1", "getobject": "~0.1.0", "grunt-legacy-util": "~0.2.0", "grunt-legacy-log": "~0.1.0" 


Task runner

The main file of the β€œfactory” is located in /lib/grunt.js . It "builds" and exports the grunt object, connects the internal project libraries from /lib/grunt/*.js :
 ... // Grunt -> export var grunt = module.exports = {}; ... //       grunt function gRequire(name) { return grunt[name] = require('./grunt/' + name); } //   gRequire('template'); ... //    (alias)      grunt function gExpose(obj, methodName, newMethodName) { grunt[newMethodName || methodName] = obj[methodName].bind(obj); } //    registerTask  task   grunt gExpose(task, 'registerTask'); ... 

The tasks method is added to the object, which is subsequently called from the CLI:
 grunt.tasks = function(tasks, options, done) { ... if (option('version')) { // show version return; } ... if(option('help')) { // show help return; } ... //      ,  'default' //  task -> /lib/grunt/task.js var tasksSpecified = tasks && tasks.length > 0; tasks = task.parseArgs([tasksSpecified ? tasks : 'default']); ... //       // ,    `grunt {TASK_1} {TASK_2}` tasks.forEach(function(name) { task.run(name); }); //  task.start({asyncDone:true}); }; 

CLI

As we have already noticed, from the command line we call the grunt.cli() method, this method goes in the set of internal libraries /lib/grunt/*.js , its task is to parry the transferred tasks and options and run grunt.tasks() :
 ... var nopt = require('nopt'); ... //  cli() var cli = module.exports = function(options, done) { if (options) { //   ... } //  grunt.tasks(cli.tasks, cli.options, done); }; ... //    var optlist = cli.optlist = { help: { short: 'h', info: 'Display this help text.', type: Boolean } ... }; ... //       var parsed = nopt(known, aliases, process.argv, 2); cli.tasks = parsed.argv.remain; cli.options = parsed; 


Tasks


Starting and automating tasks is the main function of Grunt . The task for him is just a function that needs to be called at the right moment.
Example registerTask:
 grunt.registerTask('foo', function(a, b) { console.log(a, b); }); // > grunt foo // > undefined undefined // > grunt foo:bar:baz // > bar baz //  : /lib/util.task.js -> task.init() -> task._taskPlusArgs() -> task.splitArgs() 


To work with Grunt tasks, the internal library /lib/grunt/task.js and the utilities library /lib/util/task.js are used , look at the registerTask method:
 ... // ""  "task" util lib. var parent = grunt.util.task.create(); //          var task = module.exports = Object.create(parent); ... task.registerTask = function(name) { ... //  .registerTask()  //       {name: name, info: info, fn: fn} //      task._tasks parent.registerTask.apply(task, arguments); //       var thisTask = task._tasks[name]; //     var _fn = thisTask.fn; thisTask.fn = function(arg) { ... //   ,   ... //    return _fn.apply(this, arguments); }; return task; }; 

Grunt allows us to declare tasks using the registerMultiTask method. Its only difference from registerTask is that Grunt will search for a property with the same name as the task name in the grunt.initConfig({}) config, which specifies various configurations (targets) and data to be executed:
Example registerMultiTask:
 // Gruntfile.js grunt.initConfig({ log: { bar: [1, 2, 3], baz: 'hello world' } }); grunt.registerMultiTask('foo', function() { grunt.log.writeln(this.target + ': ' + this.data); }); // > grunt foo // > Running "foo:bar" (foo) task // > bar: 1,2,3 // > Running "foo:baz" (foo) task // > baz: hello world // > grunt foo:bar // > Running "foo:bar" (foo) task // > bar: 1,2,3 


 task.registerMultiTask = function(name, info, fn) { ... var thisTask; task.registerTask(name, info, function(target) { //     ()     if (!target || target === '*') { return task.runAllTargets(name, this.args); } else if (!isValidMultiTaskTarget(target)) { throw new Error('Invalid target "' + target + '" specified.'); } //        `name` this.requiresConfig([name, target]); //     {name: target} //  /lib/grunt/config.js -> .get() -> .getRow() this.data = grunt.config([name, target]); //  return fn.apply(this, this.args); }); thisTask = task._tasks[name]; thisTask.multi = true; }; 

Conclusion


It cannot be said that Grunt one of a kind and there are no alternatives to it. But it is safe to say that he won popularity and authority. It is easy to install, quickly configured, has an attractive declarative style of configuring tasks, a rather β€œmodular” structure of the project.

I would also like to attach a link to the slides of one of the authors of the project The State of Grunt , which tells about what Grunt is now and what it is going to be. I hope the article turned out to be at least a little interesting and helped to look deeper at the tool that we use so often.

Source: https://habr.com/ru/post/230753/


All Articles