I cannot figure out why uglify does not want concat string as input or output ...
This works :
uglify: {
dev_uglify_js: {
files: {
'my_file.min.js': ['my_file.js']
}
}
}
For example, this does not works :
uglify: {
dev_uglify_js: {
files: {
'my'+'_file.min.js': ['my_file.js']
}
}
}
Do you have any idea why ?
The output error is "SyntaxError: Unexpected token".
The real insterest here is to concatenate a timestamp to the file name.
But just with 2 strings it does not work so ...
Thanks for your help !
In JavaScript, an object key cannot be declared dynamically. This is not a problem with grunt or uglify - it's a language constraint.
myObject = { 'a' + 'b' : 'b' } // NOPE!
However, any object property can be accessed via square brackets. For example:
myObject = { 'banana': 'boat' }
myObject.banana // boat
myObject['banana'] // boat!
Therefore, you can add properties after the object is already created, using the square brackets syntax.
myObject = {}
myObject[ 'a' + 'b' ] = 'b' // Yes
myObject.ab // b
The Gruntfile example
In your Gruntfile, you're bound to, at some point, call something like grunt.config.init or grunt.initConfig. This is usually done inline:
grunt.initConfig({
uglify: {} // properties ...
});
However, initConfig simply receives an object. You can define it and manipulate it as much as you need before calling this function. So, for example:
var config = { uglify: {} };
config.uglify['such'+'dynamic'+'very'+'smarts'] = {};
grunt.initConfig(config);
Similar questions:
How do I create a dynamic key to be added to a JavaScript object variable
How do I add a property to a JavaScript object using a variable as the name?
Related
I'm trying to create a simple Ractive adaptor to parse a value from the Color Thief (http://lokeshdhakar.com/projects/color-thief/) into a template with a defined mustache. (I know there may be better ways to achieve this, but there is a reason for why I'm using the adaptor route!)
I've set up a demo of what I have so far here - this the Ractive code part:
var colorThief = new ColorThief();
var img2 = document.getElementById('ctimage');
var imgColor;
Ractive.adapt.CTImg = {
filter: function ( object ) {
return object instanceof img2;
},
wrap: function ( ractive, img2, keypath, prefixer ) {
// Setup
return {
teardown: function(){
colorThief.destroy();
},
get: function(){
imgColor = colorThief.getColor(img);
},
set: function(property, value){
ractive.set('mainColor', imgColor);
},
reset: function(value){
}
}
}
};
var ractive = new Ractive({
target: '#container',
template: '#template',
adapt: [ 'CTImg' ],
data: {
mainColor: "rgb(97, 79, 112)" // this is what should be returned
}
});
My aim is to get the prominent color from the image given in the Codepen (above), pass it into Ractive (and to Color Thief by the adaptor), then output the resulting color on screen in the relevant mustache.
I can display a hard coded color OK in the template, so I know that the data keypath / reference is OK. However, my issue is getting the color back from Color Thief via the adaptor - the error I'm getting is Uncaught "TypeError: Cannot set property 'CTImg' of undefined".
I've checked through SO and the Ractive Github site to see if I can figure out what is going wrong, but my head is starting to spin!
Can anyone please help me to at least get the color to come back from Color Thief via the adaptor?
So adapt and adaptors are two different config objects. adaptors is a registry of adaptor definitions and adapt tells the component/instance what adaptors to use. There's no global adapt property.
For global registration of an adaptor, you need Ractive.adaptors.
Ractive.adaptors.CTImg = {...}
The next problem is actually how you use the adaptor. Adaptors require you to put the non-POJO data into the instance. The filter is run on the data and determines if the data needs to be adapted, and if so, does the setup. Then, it's the usual adaptor setup. get returns the value to Ractive, set sets the value to your custom object, etc.
Here's an updated example:
Ractive.adaptors.CTImg = {
filter: function ( object ) {
// Detect if the data is an image element
return object instanceof HTMLImageElement;
},
wrap: function ( ractive, object, keypath, prefixer ) {
// Set up color thief for this piece of data because it's an image
var colorThief = new ColorThief();
return {
teardown: function(){
colorThief.destroy();
},
get: function(){
// Return the replacement data
return colorThief.getColor(object);
},
set: function(property, value){
// We're not setting to color thief, leave empty
},
reset: function(value){
// Always replace the data when the data is changed
return false;
}
}
}
};
var ractive = new Ractive({
target: '#container',
template: '#template',
adapt: [ 'CTImg' ],
data: {
dominant: null
},
onrender: function(){
// set image on data. adaptor will capture it.
this.set('dominant', this.find('#ctimage'))
}
});
Consider the following code in a javascript library;
document.registerElement('my-component', { prototype: { foo: true }});
It seems registerElement returns a function which can be used as a constructor.
How can I get a reference to this function later ?
var tempDom = document.createElement('my-component')
console.log(tempDom.__proto__)
Seems working but it requires creating an instance first.
I think you just need to save the return from registerElement() in a variable, and then use that variable later. If you do not save the return then I believe it is lost.
// Save the return in a variable
var mycomp = document.registerElement('my-component');
// Use the var to create the element
document.body.appendChild(new mycomp());
// Then you can do things with the new tag
var mytag = document.getElementsByTagName("my-component")[0];
mytag.textContent = "I am a my-component element.";
prototype method will give you an expected result.
var mc = document.registerElement(
'my-component', { prototype: { foo: true }}
);
console.log(mc.prototype);
//⇒ my-component {foo: true}
Hope it helps.
I'm trying to set the files option in grunt config dynamically.
This works:
grunt.initConfig({
mocha_casperjs: {
options: options,
files: {
src: ['test.js']
}
}
});
And I'm trying to reproduce it like so:
grunt.registerTask('run_tests', function() {
grunt.config('options', options);
grunt.config('files', { src: ['test.js'] });
grunt.task.run('mocha_casperjs');
});
But the files attribute is not set. Am I doing something wrong here?
Please, read carefully about grunt.config API.
grunt.config.get(taskName) returns config object for specified task. It performs just key's lookup in the root config object.
grunt.config.set(taskName, newValue) works the same way.
So, to set new files for mocha_casperjs you need to do it like this:
var mochaCasperjsConfig = grunt.config.get('mocha_casperjs');
mochaCasperjsConfig.files = { src: ['test.js'] };
grunt.config.set('mocha_casperjs', mochaCasperjsConfig);
I have a Grunt task that requires the src property to be built by calling a function that returns an array. However, the return value is ignored when more than one item is returned from the array.
I've boiled the issue down to its most simple form. Lets say I have a function, getItems that simply returns an array of two strings (files in the project).
getItems: function() {
return ['build/file1.js', 'build/file2.js'];
}
Lets say we want to call this function in the src property of the clean task.
clean: {
items: ['<%= getItems() %>', 'build/file3.js']
}
Calling clean:items does not remove build/file1.js or build/file2.js (but does remove build/file3.js) from the project. Why not?
It's worth noting that this behavior exists in any task, not just clean. I simply used clean for demonstration purposes.
However, if I return just one item from the getItems function, the clean task will remove it.
// the following removes build/file1.js and build/file3.js from the project
getItems: function() {
return ['build/file1.js'];
}
clean: {
items: ['<%= getItems() %>', 'build/file3.js']
}
It's also worth noting that using only the getItems function does not work either when it returns more than one item.
// the following does not remove build/file1.js or build/file2.js
getItems: function() {
return ['build/file1.js', 'build/file2.js'];
}
clean: {
items: '<%= getItems() %>'
}
Why can't I call a function that returns an array with more than one item from within the src property of any arbitrary task?
I tried for awhile to get this to work when the function is defined in grunt.initConfig and had no luck.
Is there anything stopping you from just defining it before your initConfig though?
You could do this:
module.exports = function(grunt) {
// define the function outside of the config
var getItems = function() {
return ['build/file1.js', 'build/file2.js'];
};
grunt.initConfig({
clean: {
items: getItems()
}
});
grunt.loadNpmTasks('grunt-contrib-clean');
};
Then if you wanted file3.js on there as well just concat:
clean: {
items: getItems().concat(['build/file3.js'])
}
jshanley's answer is exactly what I was going to suggest to solve your problem.
The reason getItems function works when it is returning a single item in an array is because whatever is returned by the function is returned as a string, since you are invoking the function through template string.
To see this, you can try:
grunt.registerTask('debug', function () {
console.log(grunt.template.process('<%= getItems() %>'));
});
When getItems function returns one item in an array:
getItems: function () {
return ['test3'];
}
Running grunt debug, returns
Running "debug" task
test3
Done, without errors.
When getItems function return multiple items in an array:
getItems: function () {
return ['test3', 'test4'];
}
Running grunt debug, returns
Running "debug" task
test3,test4
Done, without errors.
Therefore when your config looked like:
clean: {
items: ['<%= getItems() %>', 'build/file3.js']
}
It was consumed by grunt-contrib-clean plugin as:
clean: {
items: ['build/file1.js,build/file2.js', 'build/file3.js']
}
Which is not the desired behaviour.
I'm running a grunt concat task on one of my projects and it looks something like this:
/**
* Concatenate | Dependencies Scripts
*/
concat: {
dependencies: {
files: {
"./Ditcoop/js/plugins.min.js": ["./Ditcoop/js/vendor/**/*.min.js", "!./Ditcoop/js/vendor/modernizr/*.js", "!./Ditcoop/js/vendor/jquery/*.js"],
"./Global/js/plugins.min.js": ["./Global/js/vendor/**/*.min.js", "!./Global/js/vendor/modernizr/*.js", "!./Global/js/vendor/jquery/*.js"],
"./Webshop/js/plugins.min.js": ["./Webshop/js/vendor/**/*.min.js", "!./Webshop/js/vendor/modernizr/*.js", "!./Webshop/js/vendor/jquery/*.js"]
}
}
}
My question would be if I could somehow make that more dynamic without having to specify each root folder. I was thinking of something like this:
concat: {
dependencies: {
files: {
"./*/js/plugins.min.js": ["./*/js/vendor/**/*.min.js", "!./*/js/vendor/modernizr/*.js", "!./*/js/vendor/jquery/*.js"],
}
}
}
I'm pretty sure I cannot do it this way, but I could use the expand option, I'm just not sure how I could use it so I can do that under the right root folder, so I won't create the same destination file as many times I run the concat.
Always remember Gruntfiles are javascript :)
grunt.initConfig({
concat: {
dependencies: {
files: (function() {
var files = Object.create(null);
grunt.file.expand({filter: 'isDirectory'}, '*').forEach(function(dir) {
files[dir + '/js/plugins.min.js'] = [
dir + '/js/vendor/**/*.min.js',
'!' + dir + '/js/vendor/modernizr/*.js',
'!' + dir + '/js/vendor/jquery/*.js'
];
});
return files;
}()),
},
},
});
But if your dependency handling logic is this complex you may want to consider using a module loader such as browserify or requirejs. The concat task is really just for joining simple files together.