Understanding gruntjs registerTask colon - javascript

I'm currently trying to learn gruntjs for dev and production build.
I want to assign a global config variable to determine stuff.
I have a simple initConfig :
grunt.initConfig({
foo: {
bar: {GLOBAL: true},
baz: {GLOBAL: false}
}
});
grunt.registerTask('one', ['foo:bar']);
grunt.registerTask('two', ['foo:baz']);
My question is:
What exactly is the colon in my tasks doing? (foo:bar or foo:baz)
And what is the difference between a colon and a simple dot?
My Goal is to have a global variable set either to true or false for further processing:
grunt.initConfig({
foo: {
bar: {GLOBAL: true},
baz: {GLOBAL: false}
},
awesomestuff: {
smth: GLOBAL ? 'yes' : 'no',
another: !Global ? 'DoDebug' : 'MakeRelease'
}
});
grunt.registerTask('one', ['foo:bar', 'awesomestuff']);
grunt.registerTask('two', ['foo:baz', 'awesomestuff']);
How would I achieve this?
Update
I got the global variable working somehow. By registering a new new task called init with an argument I can call it in an other task.
grunt.registerTask('init', 'Init', function(param) {
grunt.config('GLOBAL', param)
});
grunt.registerTask('one', ['init:true', 'foo:bar', 'awesomestuff']);
In this case the init task will be called with the vairable param set to true.
But the question is still:
Why would I use a colon insted of a dot to reference an object?

Why would I use a colon instead of a dot to reference an object?
To understand why, you firstly need to understand grunt task configurations and targets.
Single Target
To help you further understand this concept and terminology, take a look at this example configuration for a grunt plugin called grunt-contrib-copy. It's a plugin that copies files. Below is a snippet of that code:
grunt.initConfig({
copy: { // <-- Task
main: { // <-- Target
// ... <-- other configurations go here.
}
}
});
In this example above the Task is named copy and it includes a single Target named main.
To register this Task you would do so as follows:
grunt.registerTask('copyFiles', ['copy:main']);
and you would enter the following via your command line to run it:
$ grunt copyFiles
Multiple Targets
Grunt Tasks can also include more than one Target. Consider this example code below:
grunt.initConfig({
copy: {
js: {
// ... <-- Additional configurations for this Target go here.
},
css: {
// ... <-- Additional configurations for this Target go here.
}
}
});
You could register the example above as follows:
grunt.registerTask('copyJavaScriptFiles', ['copy:js']);
grunt.registerTask('copyCssFiles', ['copy:css']);
So, via the command line:
Running $ grunt copyJavaScriptFiles will copy all the JS files according to the configurations specified.
Running $ grunt copyCssFiles will copy all the CSS files according to the configurations specified.
If you wanted to copy both the JS and CSS files you could register a task as follows:
grunt.registerTask('copyAll', ['copy']);
And you would run it by entering $ grunt copyAll in your command line.
Notice in the last example it does not include any colon :. Grunt this time will run all the Targets in the copy Task, namely the js one and the css one.
And what is the difference between a colon and a simple dot?
Colon
Hopefully by now you can see what the colon : does. It is used to reference a particular Target within a Task and is typically only used when a Task has multiple Targets and you want to specifically reference one of them.
Simple dot
The simple dot is JavaScript's standard notation to access properties of an object. Google "JavaScript notation" to find out more about Dot Notation and Square Bracket Notation.
Within the context of your Gruntfile.js the dot notation is typically used to call the functions/methods/properties of the grunt object. For example:
grunt.initConfig({...});
grunt.loadNpmTasks(...);
grunt.registerTask(...);
EDIT 1 Updated the answer after the original post/question was updated.

Related

How to make JS function global in webpacker Rails 6

I try to organise my frontend as I used to do in Rails 5. I had some js file with functions and used this functions in different places of code up to my needs. But in Rails 6 work with js is quite different. Anyway, I think I got the main idea about packs and webpacker. But how to use custom js functions? Write it in one file and use in another? There is should be the way to do it.
For example, I have some custom js pack:
app/javascript/packs/custom_pack_with_functions.coffee:
console.log 'hey'
#hi = () ->
console.log 'HI'
And I expect that hi function will be available in my view.
some_view.html.slim:
= javascript_pack_tag 'custom_pack_with_functions'
javascript:
hi()
But when I come to appropriate page, I see in console only following messages:
hey
ReferenceError: hi is not defined
​
How to define hi function to use it from anywhere?
Webpack does not make modules available to the global scope by default. Here are a few ways you can do it:
Assign the function to the global window object, i.e., window.hi = function() { ... }. I don't like side effects like this in a lot of places so it's my least favorite option but perhaps the easiest to understand.
You could look at using expose-loader. This would mean customizing your webpack config to "expose" selected functions from selected modules to the global scope. It could work well for a handful of cases but would get tedious for many use cases.
Export selected functions from your entrypoint(s) and configure webpack to package your bundle as a library. This is my favorite approach if you prefer to call global functions from the view. I've written about this approach specifically for Webpacker on my blog.
// app/javascript/packs/application.js
export * from '../myGlobalFunctions'
// config/webpack/environment.js
environment.config.merge({
output: {
// Makes exports from entry packs available to global scope, e.g.
// Packs.application.myFunction
library: ['Packs', '[name]'],
libraryTarget: 'var'
},
})
:javascript
Packs.application.hi()
Don't use global functions at all; use a different mechanism to trigger the function from within your webpack JS, such from within an event listener for the given page or in the presence of a given element.
// app/javascript/initializer.js
import hi from '../hi';
document.addEventListener('DOMContentLoaded', () => {
if ( /* some logic for my page is true */ ) {
hi()
}
});

How to pass arguments/parameters to mocha tests invoked via Grunt

I have a Gruntfile.js through which i'm invoking mochaTest using grunt-mocha-test module. I can pass an argument/parameter to the gruntTask from command line but i'm struggling to pass the same parameter into the spec file running via the above module. The code looks like below,
mochaTest: {
test: {
options: {
reporter: 'spec',
quiet: false,
clearRequireCache: false,
clearCacheFilter: (key) => true,
noFail: false
},
src: [
'test/createSpec.js'
]
}
}
Task is registered like below,
grunt.registerTask('e2etest', function(scope) {
console.log(scope); // logs user/session based on the parameter passed
grunt.task.run('mochaTest');
});
// Above task is invoked like,
grunt e2etest:user
(or)
grunt e2etest:session
I need to pass this value (user/session) into mochaTest so it can be accessed inside the spec file. Fundamentally the aim is to run the createSpec.js file both for user and session, this values is parametrized inside the spec file and based on the value passed the suite would run.
Is there a possibility to do this? Please advise.
Please refer to this issue for detail, and I think the solution you need here is:
node <node flags here> node_modules/mocha/bin/_mocha <mocha arguments here>
You can utilize nodes process.argv to read the argument (i.e. user or session) from within the file named createSpec.js.
To better understand how, follow these steps:
At the top of createSpec.js add the following line of code:
console.log(process.argv);
Then run grunt e2etest:user via your CLI and you should see the following logged to your console:
[ 'node', '/usr/local/bin/grunt', 'e2etest:user' ]
Note: the information you want is positioned at index two of the array.
Now, delete the line we just added which reads console.log(process.argv); from createSpec.js.
createSpec.js
So, the steps above (1-3) illustrated that the arguments (user or session) can be accessed in createSpec.js utilizing process.argv. In which case you could do something like the following inside createSpec.js.
const argument = process.argv[2].split(':').pop();
if (argument === 'user') {
// Run `user` specific tests here...
} else if (argument === 'session') {
// Run `session` specific tests here...
}
Note, we're using process.argv[2].split(/:/).pop(); to extract either user or session from the array item positioned at index two, whose initial value will be either e2etest:user or e2etest:session respectively.
Gruntfile
Your createSpec.js file is now somewhat dependent on the grunt task named e2etest being invoked correctly. For example, if a user were to run grunt e2etest without providing the arguments then createSpec.js is not going to do much.
To enforce the correct usage of the e2etest task (i.e. it must be run using either grunt e2etest:user or grunt e2etest:session), you could change your task in your Gruntfile as follows:
grunt.registerTask('e2etest', function(scope) {
if (!scope || !(scope === 'user' || scope === 'session')) {
grunt.warn(`Must be invoked with: ${this.name}:user or ${this.name}:session`);
}
grunt.task.run('mochaTest');
});
The gist above initially checks that an argument has been provided and is either user or session. If the argument is incorrect or missing then grunt.warn is utilized to warn the user.
If your version of nodejs does not support ES6 Template literals then use grunt.warn as follows instead:
grunt.warn('Must be invoked with: ' + this.name + ':user or ' + this.name + ':session');
Additional comment
The code/gist shown in the createSpec.js section above will work if your use-case is exactly as you mention in your question. I.e. you invoke via the commandline using grunt e2etest:user or grunt e2etest:session. However, if that changes and you cannot guarantee that e2etest:user or e2etest:session will be exactly positioned at index two of the process.argv array, then you may need to do the following at the top of createSpec.js instead:
// Return the value in the array which starts with
// `e2etest` then extract the string after the colon `:`
const argument = process.argv.filter(arg => {
return arg.match(/^e2etest:.*/);
}).toString().split(':').pop();
if (argument === 'user') {
// Run `user` specific tests here...
} else if (argument === 'session') {
// Run `session` specific tests here...
}

Replacing requirejs with systemjs -- variables not visible in local scope

I'm trying to convert our requirejs calls to use SystemJS, but I'm not exactly sure what I'm doing wrong.
Our original calls look like this:
return function(callback) {
requirejs(["/app/shared.js"], function(result){
callbackFunction = callback;
callback(dashboard);
main();
})
}
And what I'm trying instead is:
return function(callback) {
console.log(callback.toString())
SystemJS.import('app/shared.js').then(function(result){
callbackFunction = callback;
callback(dashboard);
main();
});
}
I've had to remove some leading / to get things to load properly, which is fine, but I've now ran into an issue where variables that were defined at the top of shared.js aren't visible in my local main.js file. In my browser console I get:
Potentially unhandled rejection [1] ReferenceError: dashboard is not defined
shared.js defines dashboard:
var dashboard = { rows: [], }
// Other definitions...
define(["/app/custom-config.js", /* etc */]);
I guess I have two questions:
is this the correct way to replace requirejs calls?
if so, why aren't my variables from shared.js accessible?
For a fuller picture, main() just sets up the dashboard object, and then calls callbackFunction(dashboard) on it.
Your problem can be reduced to the following case where you have two AMD modules, with one that leaks into the global space, and the 2nd one that tries to use what the first one leaked. Like the two following modules.
src/a.js requires the module that leaks and depends on what that module leaks:
define(["./b"], function () {
console.log("a loaded");
callback();
});
src/b.js leaks into the global space:
// This leaks `callback` into the global space.
var callback = function () {
console.log("callback called");
}
define(["./b"], function () {
console.log("b loaded");
});
With RequireJS, the code above will work. Oh, it is badly designed because b.js should not leak into the global space, but it will work. You'll see callback called on the console.
With SystemJS, the code above won't work. Why? RequireJS loads modules by adding a script element to the header and lets script execute the module's code so callback does end up in the global space in exactly the same way it would if you had written your own script element with an src attribute that points to your script. (You'd get an "Mismatched anonymous define" error, but that's a separate issue that need not detain us here.) SystemJS, by default, uses eval rather than create script elements, and this changes how the code is evaluated. Usually, it does not matter, but sometimes it does. In the case at hand here callback does not end up in the global space, and module a fails.
Ultimately, your AMD modules should be written so that they don't use the global space to pass information from one another.
However, there is another solution which may be useful as a stepping-stone towards a final solution. You can use scriptLoad: true to tell SystemJS to use script elements like RequirejS does. (See the documentation on meta for details and caveats.) Here is a configuration that does that:
System.config({
baseURL: "src",
meta: {
"*": {
scriptLoad: true, // This is what fixes the issue.
}
},
packages: {
// Yes, this empty package does something. It makes `.js` the
// default extension for modules.
"": {}
},
});
// We have to put `define` in the global space to
// so that our modules can find it.
window.define = System.amdDefine;
If I run the example code I've given here without scriptLoad: true, then module a cannot call the callback. With scriptLoad: true, it can call the callback and I get on the console:
b loaded
a loaded
callback called

Get a Grunt plugin's target

I'm building a Grunt plugin, and I need to select one of the targets in the Gruntfile.js.
Gruntfile.js:
grunt.initConfig({
plugin: {
options: {},
target: {} // <= This is what I want to select
}
});
Inside of tasks/plugin.js I can use options = this.options() to get the options object, but I can't just use this.target. Any thoughts?
Grunt only exposes this.target for multi-tasks. To access the same value for regular tasks (bar in grunt foo:bar), access the args array instead: this.args[0]
Update:
Previous answer was written according to the official terminology by Grunt. Re-reading your question, you'll access the property within the config object using the grunt.config() method, drilling down to the specific properties you're trying to access within the grunt config object. Based on your grunt.initConfig above, you'd access the contents of target with grunt.config('plugin.target');

GruntJS Configurable First Time Run

I am working on a Angular Demo Application and I want to automatize a lot of things.
It's some sort of a boilerplate, albeit a more complex one, and I want to make a config file in which we'll put API Keys and other stuff, and I want that file to be populated by Grunt with user interaction when the project is started for the first time.
Something like:
grunt build - it should ask the user directly in the console for the API keys, that will be inserted in the config file where I am defining some global constants for the entire App.
Is there such an example of functionality with Grunt ?
You can handle the questioning by using:
https://github.com/dylang/grunt-prompt
It is a nice little plugin that do one job and do it well. It put whatever value you have entered in the command line into variables: (example)
prompt: {
target: {
options: {
questions: [
{
config: 'key', // arbitrary name or config for any other grunt task
type: 'input', // list, checkbox, confirm, input, password
message: 'What is your API key?',
default: '', // default value if nothing is entered
when: function(answers) { return !grunt.file.exists('config.yml'); } // only ask this question when this function returns true
}
]
}
}
}
Then you can use the Grunt.file functions to write those values into files:
http://gruntjs.com/api/grunt.file#grunt.file.write
To orchestrate it, you will need to create a custom task: (example)
grunt.registerTask("my_config_task", function (arg) {
var key = arg || grunt.config('key');
grunt.file.write("config.yml", key);
});
grunt.registerTask('build', ['prompt', 'my_config_task']);
The writing will likely need refinement as you will, I guess, need to replace values and organise as a yml file or json object, etc...
Found one of the possible solutions while looking at the sources of grunt-bump. What are they doing is parsing the config file as a JSON object:
https://github.com/darsain/grunt-bumpup/blob/master/tasks/bumpup.js#L128
Replacing whatever values they need (as JSON) and overwrite the file with the object stringified:
https://github.com/darsain/grunt-bumpup/blob/master/tasks/bumpup.js#153
Seems to work well.

Categories

Resources