requireJS optional dependency - javascript

I'm adding AMD support to a javascript library I develop.
This library may use jquery but it will still work if jquery isn't loaded.
When defining the module dependency there's a way to set a dependency as 'optional' so that if that library is missing the module will still work?

I've had exactly the same problem recently, and here's how I fixed it. I defined a RequireJS plugin called optional which ignores modules that fail to load by explicitly defining them as an empty object (but I suppose you could also define it as null or anything else if you wanted).
Here is the code (tested with RequireJS 2.1.15):
define("optional", [], {
load : function (moduleName, parentRequire, onload, config){
var onLoadSuccess = function(moduleInstance){
// Module successfully loaded, call the onload callback so that
// requirejs can work its internal magic.
onload(moduleInstance);
}
var onLoadFailure = function(err){
// optional module failed to load.
var failedId = err.requireModules && err.requireModules[0];
console.warn("Could not load optional module: " + failedId);
// Undefine the module to cleanup internal stuff in requireJS
requirejs.undef(failedId);
// Now define the module instance as a simple empty object
// (NOTE: you can return any other value you want here)
define(failedId, [], function(){return {};});
// Now require the module make sure that requireJS thinks
// that is it loaded. Since we've just defined it, requirejs
// will not attempt to download any more script files and
// will just call the onLoadSuccess handler immediately
parentRequire([failedId], onLoadSuccess);
}
parentRequire([moduleName], onLoadSuccess, onLoadFailure);
}
});
You can then require a module optionally using simply
require(['optional!jquery'], function(jquery){...});
knowing that if the jquery module could not be loaded, the parameter passed to your callback function will be an empty object.

You cannot really set it optional, but you can catch the error and unload the module using undef:
require(['jquery'], function ($) {
//Do something with $ here
}, function (err) {
//The errback, error callback
//The error has a list of modules that failed
var failedId = err.requireModules && err.requireModules[0];
if (failedId === 'jquery') {
//undef is function only on the global requirejs object.
//Use it to clear internal knowledge of jQuery. Any modules
//that were dependent on jQuery and in the middle of loading
//will not be loaded yet, they will wait until a valid jQuery
//does load.
requirejs.undef(failedId);
...
}
});
Full example here.

A plugin is not needed for this.
This can be done without plugins for RequireJS. In addition, you can do this for UMD modules as well.
Using jQuery only when it's already loaded
It's actually pretty simple, using require.defined, which lets you test whether a module has already been loaded or not. If so, you require jQuery and use it, otherwise you just skip the optional part:
define(['require'], function(require){
if (require.defined('jquery') {
var $ = require('jquery');
$.fn.something = function(){};
}
});
Notice how we add 'require' as a dependency, so we get a local require function that has the defined method on it.
Also note that this code will only find jQuery if it has been loaded prior to this module. If some module loads jQuery after this module has already loaded then it will not pickup jQuery afterwards.
This will not attempt to load jQuery and hence will not cause an error message in the logs.
Bonus: UMD support
If you want your library to support AMD loaders (RequireJS), CommonJS (Node) and regular script tags, and have an optional dependency on jQuery, here is what you can do:
(function(u, m, d) {
if ((typeof define == 'object') && (define.amd)) {
// handle AMD loaders such as RequireJS
define(m, ['require'], d);
}
else if (typeof exports === 'object') {
// handle CommonJS here... Does not really make sense for jQuery but
// generally, you can check whether a dependency is already loaded
// just like in RequireJS:
var $ = null;
try {
require.resolve('jquery'));
// the dependency is already loaded, so we can safely require it
$ = require('jquery');
} catch(noJquery) {}
module.exports = d(null, $);
}
else {
// regular script tags. $ will be available globally if it's loaded
var $ = typeof jQuery == 'function' ? jQuery : null;
u[m] = d(null, $);
}
})(this, 'mymodule', function(require, $) {
// if `$` is set, we have jQuery... if not, but `require` is set, we may
// still get it here for AMD loaders
if (!$ && require && require.defined && require.defined('jquery')) {
$ = require('jquery');
}
// At this point, `$` either points to jQuery, or is null.
});
Further reading
Node.js - check if module is installed without actually requiring it
https://github.com/jrburke/requirejs/issues/856

Related

Using functions from external JavaScript libraries in Moodle plugins

I am making a Moodle plugin and wanted to use bowser to detect the user's web browser. I referenced the file by putting
$PAGE->requires->js( new moodle_url($CFG->wwwroot.MOODLE_TINYMCE_RECORDRTC_ROOT.'tinymce/js/bowser.js') );
in the plugin's plugintype_pluginname.php file (placeholders of course), and I call the bowser function from the plugin's module.js file.
When I load the plugin (it appears as a button in TinyMCE), the console throws ReferenceError: bowser not defined, so I'm assuming this means Moodle doesn't make the functions in Bowser globally available.
I read many in many places that I need to wrap my code in an AMD, or something to that effect, but after lots of reading it still goes over my head. Is there any way to make bowser's functions available to the main plugin module?
Note: This works for me in Moodle 3.3.2, ymmv.
Put bowser.js into my_plugin_folder/amd/src/.
When using the original bowser.js I got Uncaught TypeError: bowser._detect is not a function. I don't exactly understand why I get this error but here's one way to fix it: Replace the top code block in bowser.js with this one from umdjs/umd.
Your file should now look like this:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.returnExports = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
// module definition here
return bowser
}));
Moodle bundles all JavaScript modules together so that clients don't need to perform a separate HTTP request to get each one. This bundle is called first.js. It contains all modules that aren't lazy-loaded. If you load a Moodle page now it should contain the contents of bowser.js with some values replaced by Moodle.
If you don't want bowser to be loaded on every page, you can just rename it to bowser-lazy.js. Then it should only be loaded when you use it.
You can test if it worked by calling:
require(['plugintype_pluginname/bowser'], function(bowser) {
var ua = bowser._detect(navigator.userAgent);
console.log(ua);
});
Seems like you need to change the require call to use bowser-lazy instead of bowser when you want to use lazy-loading.

How to circumvent RequireJS to load module with global?

I'm trying to load a JS file from a bookmarklet. The JS file has this JS that wraps the module:
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
// Node/CommonJS
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(factory);
} else {
// Browser globals
root.moduleGlobal = factory();
}
}(this, function factory() {
// module script is in here
return moduleGlobal;
}));
Because of this, if the webpage uses RequireJS, the script will not export a global when it loads. To get around this I temporarily set define to null, load the script, then reset define to its original value:
function loadScript(url, cb) {
var s = document.createElement('script');
s.src = url;
s.defer = true;
var avoidRequireJS = typeof define === 'function' && define.amd;
if (avoidRequireJS) {
var defineTmp = define;
define = null;
}
s.onload = function() {
if (avoidRequireJS) define = defineTmp;
cb();
};
document.body.appendChild(s);
}
This works, but it seems to me like it could be problematic to change a global variable when other parts of the application could depend on it. Is there a better way to go about this?
You may fetch the script using XMLHttpRequest, jQuery.ajax or the new Fetch API.
This will allow you to manipulate the script and reassign define before executing it. Two options:
Have the module export a global by wrapping the script with:
(function(define){ ... })(null);
Handle the module exports yourself by wrapping the script with:
(function(define, module){ ... })((function() {
function define(factory) {
var exports = factory();
}
define.amd = true;
return define;
})());
You can then load it using a new <script> element or eval 😲.
Note that when using XHR, you may have to address CORS issues.
If you can use the AJAX method above, that will be best. But as stated, you will need to deal with CORS issues, which is not always trivial - even impossible if you do not control the origin server.
Here is a technique which uses an iframe to load the script in an isolated context, allowing the script to export its global object. We then grab the global object and copy it to the parent. This technique does not suffer from CORS restrictions.
(fiddle: https://jsfiddle.net/qu0pxesd/)
function loadScript (url, exportName) {
var iframe = document.createElement('iframe');
Object.assign(iframe.style, {
position: 'fixed',
top: '-9999em',
width: '0px'
});
var script = document.createElement('script');
script.onload = function () {
window[exportName] = iframe.contentWindow[exportName];
document.body.removeChild(iframe);
}
script.src = url;
document.body.appendChild(iframe);
iframe.contentWindow.document.open();
iframe.contentWindow.document.appendChild(script);
iframe.contentWindow.document.close();
}
loadScript('https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js', 'jQuery');
I ran a quick test to see if a memory leak would happen from deleting the iframe, and it appears to be memory safe. Here's the snapshot of loading a script 100 times, resulting in 100 different iframes and 100 different instances of jQuery loading.
The parent window's jQuery variable is continuously overwritten, meaning only the last one prevails and all previous references are cleaned up. This is not entirely scientific and you will need to do your own testing, but this should be safe enough to get you started.
Update: The above code requires that you know the name of the exported object, which is not always known. Some modules may export multiple variables too. For example, jQuery exports both $ and jQuery. The following fiddle illustrates a technique for solving this issue by copying any global objects which did not exist before the script was loaded:
https://jsfiddle.net/qu0pxesd/3/
Which approach would work best really depends on the specific needs of the project. Context would determine which one I'd use.
Undefining define Temporarily
I'm mentioning it because you tried it.
DON'T DO THIS!
The approach of undefining define before you load your script and restoring it after is not safe. In the general case, it is possible for other code on the page to perform a require call that will resolve after you've undefined define and before you've defined it again. After you do document.body.appendChild(s); you're handing back control to the JavaScript engine, which is free to immediately execute scripts that were required earlier. If the scripts are AMD module, they'll either bomb or install themselves incorrectly.
Wrapping the Script
As Dheeraj V.S. suggests, you can wrap the script to make define locally undefined:
(function(define) { /* original module code */ }())
can work for trivial cases like the one you show in your question. However, cases where the script you try to load actually has dependencies on other libraries can cause issues when it comes to dealing with the dependencies. Some examples:
The page loads jQuery 2.x but the script you are trying to load depends on a feature added in jQuery 3.x. Or the page loads Lodash 2 but the script needs Lodash 4, or vice-versa. (There are huge differences between Lodash 2 and 4.)
The script needs a library that is not otherwise loaded by something else. So now you are responsible for producing the machinery that will load the library.
Using RequireJS Contexts
RequireJS is capable of isolating multiple configurations from one another by defining a new context. Your bookmarklet could define a new context that configures enough paths for the script you are trying to load to load itself and its dependencies:
var myRequire = require.config({
// Within the scope of the page, the context name must be unique to
// your bookmarklet.
context: "Web Designer's Awesome Bookmarklet",
paths: {
myScript: "https://...",
jquery: "https://code.jquery.com/jquery-3.2.1.min.js",
},
map: {...},
// Whatever else you may want.
});
myRequire(["myScript"]);
When you use contexts like this, you want to save the return value of require.config because it is a require call that uses your context.
Creating a Bundle with Webpack
(Or you could use Browserify or some other bundler. I'm more familiar with Webpack.)
You could use Webpack to consume all the AMD modules necessary for the script you are trying to load to produce a bundle that exports its "module" as a global. At a minimum, you'll need something like this in your configuration:
// Tell Webpack what module constitutes the entry into the bundle.
entry: "./MyScript.js",
output: {
// This is the name under which it will be available.
library: "MyLibrary",
// Tell Webpack to make it globally available.
libraryTarget: "global",
// The final bundle will be ./some_directory/MyLibrary.js
path: "./some_directory/",
filename: "MyLibrary.js",
}
Once this is done, the bookmarklet only needs to insert a new script element that points to the produced bundle and no longer has to worry about wrapping anything or dealing with dependencies.
If it were me, I would have the url provide the hint as to how to load the module. Instead of having just a "scripts/" directory -> I would make "scripts/amd/", "scripts/require/", etc. Then query the url for "amd", "require", etc. within your loadScript method... using, e.g.,
if (url.includes('amd')) {
// do something
} else if (url.includes('require')) {
// do something different
}
That should let you avoid the global var entirely. It might also provide a better structure for your app in general.
You could also return an object with a script property and loadType property that specifies amd, require, etc... but imho the first option would be the quickest and save you some additional typing.
Cheers

Call of anonymous function (vanilla js, pure js, no jquery)

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.

convert a javascript library to AMD

I'm trying to use a library -- Google's libphonenumber -- in my require application that is not AMD. What is the best way to consume this? I know I can create a module like this:
define(['module'], function (module) {
// insert and return library code here.
});
But that doesn't seem great. It seems like I would have to refactor some of their code to get that working (e.g., turn it all into an object and return that object). I see a lot of libraries using a different pattern where they use an immediately invoked function that defines the module on the window object and returns it.
(function() {
var phoneformat = {};
window.phoneformat = phoneformat;
if (typeof window.define === "function" && window.define.amd) {
window.define("phoneformat", [], function() {
return window.phoneformat;
});
}
})();
** UPDATE **
Is there any reason not to just do this?
define(['lib/phoneformatter'], function(phoneformatter) {
});
I get access to all of my methods but now it seems they are global because I did not wrap the library in a define...
Use RequireJS's shim. It'll look something like this
requirejs.config({
shim: {
'libphonenumber': {
exports: 'libphonenumber' // Might not apply for this library
}
}
});
This will load libphonenumber and put its variables in the global scope
This ended up working for me:
define(['module'], function (module) {
// insert and return library code here.
});
I am not entirely sure why 'module' was necessary. But it doesn't work without it. Also, I just returned an object and attached functions to it like so:
return {
countryForE164Number: countryForE164Number,
nextFunction: nextFunction,
// more functions as needed.
}
There is not much in the way of documentation for using 'module' but from what I can ascertain: Module is a special dependency that is processed by requireJS core. It gives you information about the module ID and location of the current module. So it is entirely possible that I messed up the paths in config.

Strange behavior with RequireJS using CommonJS sintax

I'm a strange behavior with RequireJS using the CommonJS syntax. I'll try to explain as better as possible the context I'm working on.
I have a JS file, called Controller.js, that registers for input events (a click) and uses a series of if statement to perform the correct action. A typical if statement block can be the following.
if(something) {
// RequireJS syntax here
} else if(other) { // ...
To implement the RequireJS syntax I tried two different patterns. The first one is the following. This is the standard way to load modules.
if(something) {
require(['CompositeView'], function(CompositeView) {
// using CompositeView here...
});
} else if(other) { // ...
The second, instead, uses the CommonJS syntax like
if(something) {
var CompositeView = require('CompositeView');
// using CompositeView here...
} else if(other) { // ...
Both pattern works as expected but I've noticed a strange behavior through Firebug (the same happens with Chrome tool). In particular, using the second one, the CompositeView file is already downloaded even if I haven't follow the branch that manages the specific action in response to something condition. On the contrary, with the first solution the file is downloaded when requested.
Am I missing something? Is it due to variable hoisting?
This is a limitation of the support for CommonJS-style require. The documentation explains that something like this:
define(function (require) {
var dependency1 = require('dependency1'),
dependency2 = require('dependency2');
return function () {};
});
is translated by RequireJS to:
define(['require', 'dependency1', 'dependency2'], function (require) {
var dependency1 = require('dependency1'),
dependency2 = require('dependency2');
return function () {};
});
Note how the arguments to the 2 require calls become part of the array passed to define.
What you say you observed is consistent with RequireJS reaching inside the if and pulling the required module up to the define so that it is always loaded even if the branch is not taken. The only way to prevents RequireJS from always loading your module is what you've already discovered: you have to use require with a callback.

Categories

Resources