I'm trying to extend my browser-side library's runtime environment to node.js. I implemented the Universal Module Definition (UMD) pattern to do that. It works with AMD implementation and <script> but doesn't work in node.js since there is no window.
The dependencies in question are WebSocket, EventSource, XMLHttpRequest, document. The detail is described in here: https://github.com/flowersinthesand/portal/issues/115 So what is the best approach to resolve window dependency in node.js? I have though the following way to do that. Since I'm new to node.js, these are somewhat unfamiliar and I don't know which way is natural. Though I'm trying to support node.js, I don't want to change too much something.
The following code snippet comes from https://github.com/flowersinthesand/portal/blob/master/portal.js
Using jsdom
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(function() {
// Passes the window
return factory(root);
});
} else if (typeof exports === 'object') {
// Node
module.exports = factory(require('jsdom').something);
} else {
// Browser globals, Window
root.portal = factory(root);
}
}(this, function(window) {
If the jsdom supports the above dependencies well, this looks best. If it works, however, I wonder why client of sockjs, socket.io and engine.io don't use jsdom. Maybe, they are for node rather than browser? performance?
Making window as a plain object of dependency
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(function() {
// Passes the window
return factory(root);
});
} else if (typeof exports === 'object') {
// Node
module.exports = factory({
WebSocket: require('package name for WebSocket').something,
EventSource: require('package name for EventSource').something,
document: require('jsdom').hmm,
// ...
});
} else {
// Browser globals, Window
root.portal = factory(root);
}
}(this, function(window) {
It looks picky and somewhat uncomfortable to make a group of dependency, window, But, I want to preserve current code if possible. Also, jsdom may be required as well to resolve document used to do HTTP request by script tag when XMLHttpRequest can't work due to cross-domain or unloading event and this situation includes IE 6. It's possible to separate logic using document to use something supported in node and script tag in browser checking what the current runtime is, but it's big change for me.
(function(root, factory) {
if (typeof define === "function" && define.amd) {
// AMD
define(function() {
return factory(root);
});
} else if (typeof exports === "object") {
// Node
module.exports = factory(function() {
// Prepare the window powered by jsdom
var window = require("jsdom").jsdom().createWindow();
window.WebSocket = require("ws");
window.EventSource = require("eventsource");
return window;
}());
// node-XMLHttpRequest 1.x conforms XMLHttpRequest Level 1 but can perform a cross-domain request
module.exports.support.corsable = true;
} else {
// Browser globals, Window
root.portal = factory(root);
}
}(this, function(window) {
Related
We are trying to minify/obfuscate a js library, so that we can reuse it in other projects. Background info: I have done some js, but i'm new with AMD or requireJS.
Here's a reduced version of the library, retaining only the format:
(function (global, factory) {
if ( typeof exports === 'object' && typeof module !== 'undefined'){
factory(exports);
}
else if (typeof define === 'function' && define.amd){
define(['exports'], factory);
}
else{
global['SimpleMath'] = factory({});
}}(typeof global !== "undefined" ? global : self.window || self.global,
(function (exports) {
'use strict';
exports.square = function square(value){
return value * value;
}
exports['square'] = exports.square;
return exports;})));
Inside the main.html we have
requirejs(['./libs/SimpleMath'], function (smath){
console.log(smath);
console.log(require.s.contexts._.defined);
});
This works and we can see the loaded object print out.
Since we wish to optimize/obfuscate the code, we are trying requirejs' optimizer (single js file):
node ../../r.js -o name=SimpleMath out=SimpleMath.min.js baseUrl=.
And we do get something that looks like a minified file.
However, if now I try to load the minified version with the same method:
requirejs(['./libs/SimpleMath.min'], function (smath){
console.log(smath);
console.log(require.s.contexts._.defined);
});
we get 'undefined' for the print out instead.
I can't tell if the original code was wrong, or we are not using the optimizer correctly, or we totally got the concept wrong. Any help or pointers is much appreciated. Thanks in advance.
Answering myself in case someone might find it useful: The minified file was exporting a named module like this
define('SimpleMath',['exports'],t)..
we had to either
load it as a named module with require.config or,
make it anonymous, which seems to be the recommended option for our case.
When I go to the vast majority of websites, open up the console and paste the following code:
(function(global, appName, app) {
if (typeof define === "function" && typeof define.amd === "object") {
console.log("AMD/RequireJS");
define(app);
console.log("defined app");
} else if (typeof module !== "undefined") {
console.log("CommonJS");
module.exports = app(global);
console.log("defined with module.exports");
} else {
console.log("Browser");
global[appName] = app(global);
console.log("defined as global");
}
})(this, "App", function(global) {
"use strict";
console.log("defining getThis");
function getThis() {
console.log('i got it!');
}
return {
getThis: getThis
};
});
console.log("finished IIFE");
App.getThis();
I get the following output:
Browser
defining getThis
defined as global
finished IIFE
i got it!
If I paste that same code into the console of a site that uses RequireJS such as www.bestbuy.com or www.homedepot.com it fails with this output:
AMD/RequireJS
defined app
finished IIFE
Uncaught ReferenceError: App is not defined at <anonymous>:27:1
I've tried changing the line define(app); to define(['App'], app); with no effect.
I have been googling and puttering around with this for hours now and am at my wit's end. I thought the UMD pattern was UNIVERSAL hence its name. What gives?
Well, it all depends on how are you going to use your script. If you know you are going to use your script in a browser without RequireJS, then it is OK the way you are testing it.
App.getThis();
Because it is in the global object, in the case of a browser, window.
But if you know RequireJS is present, then you need to require your app.
require(["App"], function(App){
App.getThis();
});
And if NodeJS
const App = require('App');
But I've not tested this one.
Also, I recommend you to change define(app); by define(appName, app);
Hope it helps
I want to grab browser version and OS from useragent via js - this is a practicing example (cause yes - I know - feature detection is how you do it properly ;).
I stumbled across a little library that does so and now I'm trying to understand whats going on.
See this Codepen: http://codepen.io/anon/pen/gPWZGE?editors=001
obviously by running bowser.version - you get the version extracted from the browsers useragent. However the function bowser itself is an anonymous function - even though I can access elements within this function which has to do with this part of the code
!function (name, definition) {
var module;
var define;
if (typeof module != 'undefined' && module.exports) module.exports = definition();
else if (typeof define == 'function' && define.amd) define(definition);
else this[name] = definition();
}
To be honest I have hardly any idea whats going on there - can someone please explain what those lines are doing?
Thanks a lot
Ok, so step by step ...
first you have an IIFE
!function( name, definition ) {
// some code
}( 'bowser', function(){
// your module code
});
this calls the first function immediately with two parameters: the name of the module (here "bowser") and the actual definition function. In that definition function you create the module: You define any objects, properties and whatever else it needs to work. Important point is: You interface (the view the "outside" has on your module, has to be returned by that defintion function (in your codepen this is line 282: return bowser;).
The outer function then determines, how to publish your module:
First it looks for a CommonJS environment:
typeof module != 'undefined' && module.exports
This would be used, e.g., inside NodeJS. If it found the respective objects, it will export your module as a CommonJS module: module.exports = definition().
If this was not a CommonJS environment, the script looks, if it is an AMD (for example, requireJS):
typeof define == 'function' && define.amd
Again, if that matches, the module is exported as a AMD module: define(definition).
Finally, if this is neither CommonJS nor AMD, the script assumes it is a (vanilla) browser or something similar and just attaches the module to the global namespace:
this[name] = definition();
Note, that each time the definition function is called and the return value is exported as a module. This is why your definition function has to return the module interface.
I'm trying to use alertify with Emberjs. Thats how it is exported.
// AMD and window support
if (typeof define === "function") {
define([], function () { return new Alertify(); });
} else if (typeof global.alertify === "undefined") {
global.alertify = new Alertify();
}
I have put this line to Brocfile.js
app.import('./bower_components/alertify.js/lib/alertify.js');
But it thinks that it is AMD and to sum up I cant use it.
How should I handle it?
The reason is Ember does not work with unnamed AMD modules.
I am using the following closure pattern to modularise my code:
(function(root) {
// MODULE CODE HERE
if (typeof module !== 'undefined' && module.exports) { // CommonJS
/* var dependencies = require(...) */
module.exports = myModule;
} else if (typeof define !== 'undefined' && define.amd) { // AMD
/* var dependencies...; */
define([/* dependencies */], function(/* dependencies */) {
/* Assign closure level vars to respective arguments */
return myModule;
});
} else {
// Dependencies??
root.myModule = myModule;
}
})(this);
i.e., We use feature detection to support CommonJS modules (e.g., node.js), AMD or basic global namespace instantiation.
This works fine in node.js; I haven't tested the AMD pattern yet, as I'm still reading up on it (See Edit 2: AMD exhibits the exact same effect); but it fails in the browser if the module has any dependencies. That is, say, if myModule references something that is defined in a different module: For example, say if I had super.js and child.js with respective module definitions, as above, where super.js creates a function called root.super (root === window in the browser), if child.js tries to do super(), I will get something like super is not a function.
What's going on here?
To try to fix it, I changed the order in which super.js and child.js are loaded in <script> elements: No luck. Then I tried forcing child.js to load when the document is ready, using jQuery:
$(document).ready(function() {
$.getScript('child.js', function() {
// Do stuff with child, which calls super
});
});
...again, same problem. However, in both cases, if I enter the console, super is available and defined as I'm expecting.
Why is super in child.js presumably from a different (i.e., not the global) scope?
I should add, if I remove the dependency injection bit in the CommonJS export, it fails in node.js with the same error (if there are any dependants).
EDIT #Amberlamps' answer solved the problem, but it didn't answer the question as to why this occurs. My module pattern is now:
(function(root) {
// MODULE CODE HERE
if (typeof module !== 'undefined' && module.exports) { // CommonJS
/* var dependencies = require(...) */
module.exports = myModule;
} else if (typeof define !== 'undefined' && define.amd) { // AMD
/* var dependencies...; */
define([/* dependencies */], function(/* dependencies */) {
/* Assign closure level vars to respective arguments */
return myModule;
});
} else {
if (root.hasOwnProperty(/* dependencies */)) {
/* var dependencies = root... */
root.myModule = myModule;
}
}
})(this);
This keeps dependants with a common name, across environments. However, the question remains: Why is the global object not available within the closure's scope?
EDIT 2 I've been experimenting with RequireJS and AMD and have corrected my code, above, so that the AMDs work. Exactly the same thing happens in this case, too: You have to explicitly assign the global object to a variable within the closure for it to be available within said closure...
This pattern works just fine. If you actually tested it with a function called super and calling it via super(), you might ran into an error, because super is a reserved word. Following code works fine:
(function(root) {
root.super = function() {
console.log("hello");
};
}) (window);
(function(root) {
root.super();
}) (window);
You can call your function using window.super(). super() however will result in an error.