load module in requirejs only in browser, not in nodejs - javascript

I wrote modules for both browser and nodejs using requirejs.
Everything works fine, but I want to include a module just for the browser, not for node as I don't need it and it would not work in node. (It's just a fancy design library for the browser).
My code looks like this:
define([
'requirement',
'libs/fancy'
], function(Requirement, fancy) {
// do stuff
});
fancy is the lib that I don't want in node. So I could write a workaround like this:
if (typeof window !== 'undefined') { // cheap detection of browser/node
define([
'requirement',
'libs/fancy'
], start);
} else {
define([
'requirement'
], start);
}
function start(Requirement, Fancy) {
// do stuff
}
But obviously this is ugly. Does anyone know a better way to do it?
-- EDIT 1:
var requirements = ['requirement'];
if (typeof window !== 'undefined') {
requirement.push('libs/fancy');
}
define(requirements, function(Requirement, Fancy) {
// do stuff
}
Still not perfect

I've sometimes used the second method you show of creating an array of dependencies on which I push depending on what I need.
There's another method, however, that I've used when I don't want to modify the list of dependencies. Presumably the code inside your module will have to work with an undefined value for Fancy. So you could use something like what follows. The idea is to configure RequireJS to load a module that returns an undefined value when loaded. This way you don't need to modify your dependency list. It just needs to be able to handle a case where Fancy is undefined.
var requirejs = require("requirejs");
// Create a fake module that we name immediately as "undefined".
requirejs.define("undefined", [], function () { return undefined; });
var req = requirejs.config({
map: {
// Make it so that all requests for `foo` load `undefined` instead.
"*": {
foo: "undefined"
}
}
});
req(["foo"], function (foo) {
console.log(foo);
});
The example above maps foo to undefined so when console.log executes, the value on the console is undefined. In your own code you'd map libs/fancy to undefined.
A variation on this method would be to have the undefined module return an object which shows the same interface as the real library but does nothing. This would avoid having to test whether Fancy is defined or not inside your module. I'd call the fake module something else than undefined though. Maybe something like fake-fancy.

Related

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

Clean method of sharing an object (or state) between modules with AMD and Backbone

I've got a Backbone/AMD app and what I would like to do is have a model/object be fetched in the main module when the app loads, then be either accessible to or be able to be loaded into subsequent modules without the overhead of re-fetching it (it's a permissions model and thus is required pretty much everywhere).
Is there a clean way of doing this?
Simply make it its own dependency:
define(["backbone", "text!api/v1/user/permissions"],
function(Backbone, permissionJSON) {
return new Backbone.Model(JSON.parse(permissionJSON));
});
If you require a module for which you want to keep state you can do as follows
stateMod.js
require(['jquery', 'lib1','lib2'], function($, l1,l2) {
var thisVariableHoldsStateBetweenModules = "initValue"
var thisIsTheAPIOfYourModule = function(newValue) {
thisVariableHoldsStateBetweenModules = newValue
}
var getInternalState = function () {
return thisVariableHoldsStateBetweenModules
}
return {
set: thisIsTheAPIOfYourModule,
get: getInternalState
}
}
})
What you get back when you require this module is thisIsAPIOfYourModule, so you can just use it to change thisVariableHoldsStateBetweenModules:
otherMod.js
require(['stateMod'], function(stateMod) {
stateMod.set("Hello world")
})
and later on, in module nextMod.js:
require(['stateMod'], function(stateMod) {
stateMod.get() // outputs Hello World
})
requirejs saves the object returned from the first time it is required, and this why you don't re-load (network wiise) d3js and jQuery or other common libs every time you need them in a module, otherwise it would be pretty crap.
Give this a go and let me know how it worked for you.

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.

RequireJS dependency override for configurable dependency injection

I'm working with something that seems a perfect fit for DI but it's being added to an existing framework that did not have that in mind when it was written. The config that defines dependencies is coming from a back-end model. Really it's not a full config at this point it basically contains a key that can be used to determine a particular view should be available or not.
I'm using require so the dependency looks something like this
// Dependency
define(['./otherdependencies'], function(Others) {
return {
dep: "I'm a dependency"
};
});
And right now the injector looks something like this
// view/injector
define([
'./abackendmodel',
'./dependency'
], function(Model, Dependency) {
return {
show: function() {
if (model.showDepency) {
var dep = new Dependency();
this.$el.append(dep);
}
}
};
});
This is a far stretch from the actual code but the important part is how require works. Notice in the injector code the dependency is required and used in the show method but only if the model says it should be shown. The problem is that there maybe additional things required by the dependency that aren't available when it shouldn't be shown. So what I'd really like to do is not have to specify that dependency unless the model.showDependency is true. I've come up with a couple of ideas but nothing that I like.
Idea one
Have another async require call based on that model attribute. So the injector would look like this.
// Idea 2 view/injector
define([
'./abackendmodel'
], function(Model) {
var Dep1 = null;
if (model.showDepedency) {
require([
'./dependency'
], function(Dependency) {
Dep1 = Dependency;
});
}
return {
show: function() {
if (Dep1) {
var dep = new Dep1();
this.$el.append(dep);
}
}
};
});
Obviously this has issues. If show is called before the async require call is finished then Dep1 will still be null. So we don't really show the dependency which is a goal and obviously there's JS errors that will be thrown in this case. Also we're still using an if check on show which I don't like but the use case is that a dependency may or may not be present and we just don't want to require it if it's not needed so I might not be able to get around that. Also keep in mind that the model.showDependency is not actually a boolean value. It can be have multiple values which would call for different dependencies to be required. I'm just stripping it down here for simplicity of understanding the basic issue.
Idea two
This is less solidified i.e. I don't think this will even work but I've considered playing with the require config.path stuff. My idea was basically having two configs so that './dependency' pointed to different places. Problem with that is despite what the model.showDependency value is the config is the same require config so can't change that at run-time. Maybe there's some magic that could be done here like having separate view directory path defined and using a factory type object to return the one that we care about but since that would ultimately result in the same async behaviour in Idea one I don't think that buys me anything (it's basically the same).
Idea three
Have the dependency return null base on the model.showDependency attribute.
This might be the best solution right now. I'm still stuck with some ifs but I don't think that's going away. Also this prevent in initialization code from being called.
Any better ideas?
Why not try using a promise, for loading the dependency?
You have two choices, depending on how your code needs to work.
Option 1)
Return a promise for the result of the module 'view/injector', the result of this promise would be the current object result show above.
Option 2)
Use a promise to load the dependency, and then execute the logic once the promise has been resolved.
Below is an example of Option 2, using the jQuery style deferred. I typically prefer
when.js or Q. This example might fall apart if order of appending is important.
// Option 2
define([
'./abackendmodel'
], function(Model) {
var dep1Promise = null;
if (model.showDepedency) {
var dep1Deferred = $.Deferred();
dep1Promise = dep1Deferred.promise();
require([
'./dependency'
], function(Dependency) {
dep1Deferred.resolve(Dependency);
}, dep1Deferred.reject); // Optionally reject the promise if there is an error finding the dependency.
}
return {
show: function() {
if (dep1Promise) {
dep1Promise.then(function(Dep1) {
var dep = new Dep1();
this.$el.append(dep);
});
}
}
};
});

Categories

Resources