Thursday, November 1, 2018

javascript - Very unusual scope behaviour when I call modernizr.build in Node JS




I'm trying to build several jsons using Modernizr at once, but it appears to break the scope of my function.
It's very hard to explain so have a look at this example, give it a go if you don't believe me:



[1,2,3,4,5].forEach(function(i){
require("modernizr").build({}, function (result) {
console.log(i);
});
})



outputs:



5
5
5
5
5


Instead of the expected 1, 2, 3, 4, 5, as would any similar function.




I have not come across this behaviour before in all my years of coding in ECMAScript like languages, and have built my project (and previous projects) around the idea that you cannot break a function's scope like that.



It breaks any system based on promises or even just simple callbacks.
It's baffled me all day, and I can't find an appropriate fix for it.
I'm have a very hard time even conceptualizing what it is that's causing this to happen.
Please help.



EDIT:




OK, it appears you're all hung up on the forEach...
Here's another example that will make it a little clearer:



function asd(i){
require("modernizr").build({}, function (result) {
console.log(i);
});
}

asd(1);

asd(2);
asd(3);
asd(4);


outputs



4
4
4

4


What on earth is happening?


Answer



The issue specific to Modernizr had to to with a global variable being clobbered.



the build command is basically a large requirejs configuration function, all powered by a large config object. There is some basic things that are true, always, that are established at the top of the function



{

optimize: 'none',
generateSourceMaps: false,
optimizeCss: 'none',
useStrict: true,
include: ['modernizr-init'],
fileExclusionRegExp: /^(.git|node_modules|modulizr|media|test)$/,
wrap: {
start: '\n;(function(window, document, undefined){',
end: '})(window, document);'
}

}


Then, since Modernizr works in both the browser and in node without changes, there needs to be a way for it to know if it should be loading its dependencies via the filesystem or via http. So we add some more options like basePath inside of a environment check



if (inBrowser) {
baseRequireConfig.baseUrl = '/i/js/modernizr-git/src';
} else {
baseRequireConfig.baseUrl = __dirname + '/../src';
}



At this point, the config object gets passed into requirejs.config, which wires up require and allows us to start calling build.



Finally, after all of that has been created, we have a build function that also ends up modifying the config object yet again for build specific settings (the actual detects in your build, regex to strip out some AMD crud, etc).



So here is a super simplified pseudocode version of what is ended up happening



var config = {
name: 'modernizr'

}

if (inBrowser) {
config.env = 'browser';
} else {
config.env = 'node';
}

requirejs.config(config);


module.exports = function(config, callback) {
config.out = function (output) {
//code to strip out AMD ceremony, add classPrefix, version, etc

callback(output)
}

requirejs.optimize(config)
}



spot the problem?



Since we are touching the .out method of the config object (whose scope is the entire module, and therefore its context is saved between build() calls) right before we run the asynchronous require.optimize function, the callback you were passing was rewriting the .out method every time build is called.



This should be fixed in a couple hours in Modernizr


No comments:

Post a Comment

plot explanation - Why did Peaches' mom hang on the tree? - Movies & TV

In the middle of the movie Ice Age: Continental Drift Peaches' mom asked Peaches to go to sleep. Then, she hung on the tree. This parti...