If I configure a specific context:
var myRequire = require.config({
context: 'foo',
paths: {
'jquery': 'jquery-1.11.2'
}
});
I can then immediately use myRequire to load a module:
myRequire(['require', 'jquery'], function (require, $) { /* ... */ });
But if I move the configuration to a separate file, how do I retrieve the proper context?
Based on this answer I found that requirejs.s.contexts['foo'].require returns the same function as myRequire. That seems hacky. Another option would be to define a module for the config and return myRequire from it. What is the approved method?
This is working:
require.config.js:
define(function () {
'use strict';
var myRequire = require.config({
context: 'foo',
paths: {
'jquery': 'jquery-1.11.2'
}
});
return {
myRequire: myRequire
};
});
main.js:
// does need path and .js extension
requirejs(['/scripts/require.config.js'], function (requireConfig) {
var myRequire = requireConfig.myRequire;
myRequire(['require', 'jquery'], function (require, $) { /* ... */ });
});
I'm still open to better ways, and I have not tested this with r.js.
Related
I have a web app using RequireJS. Here is my js/main.js file:
require.config({
baseUrl: 'js/',
paths: {
jquery: 'libs/jquery/jquery',
lodash: 'libs/lodash/lodash',
backbone: 'libs/backbone/backbone',
// [other dependencies...]
}
});
require(['views/AppView'], function (AppView) {
var app_view = new AppView;
});
here is my js/views/AppView.js file:
define([
'jquery',
'underscore',
'backbone',
'joint',
'views/ProjectView',
'models/Command',
'views/detailsview',
'views/newcellview'
], function ($, _, Backbone, joint, ProjectView, Command, DetailsView, NewCellView) {
var app_view = {stub: 'stub'};
return app_view;
});
and finally here is my AppViewTest.js file which I run with mocha js/test/AppViewTest.js:
var assert = require('assert');
var requirejs = require('requirejs');
describe('AppView', function() {
var app_view;
beforeEach(function (done) {
requirejs(['../views/AppView.js'], function (AppView) {
app_view = new AppView;
});
});
it('should be [...]', function() {
assert.equal(app_view, ...);
});
});
I get the following error:
1) AppView views "before each" hook:
Uncaught Tried loading "jquery" at /usr/local/lib/node_modules/mocha/bin/jquery.js then tried node's require("jquery") and it failed with error: Error: Cannot find module 'jquery'
You start your test with:
var assert = require('assert');
var requirejs = require('requirejs');
and then you start making calls to requirejs but there's nothing in here that configures RequireJS.
If you cut and paste the require.config call you have in your main.js into your test file, that would configure your test for loading the same files as you have in your regular application.
Another way to do it would be:
before(function (done) {
requirejs(['full-path-to/main'], function () {
requirejs(['../views/AppView'], function (AppView) {
app_view = new AppView;
done();
});
});
});
The point is to load your configuration before you load your AppView. Note also that you need to call done, and I've changed beforeEach to before because there's no need to reload it before each test. (It was a slip of the mind earlier when I induced you to use beforeEach. My bad.)
I've also removed the .js from your requirejs call because you should not use .js. Sometimes it is warranted but unless you can explain why you need it, you should not use it.
I was hoping that the following code would guarantee that parentClass is loaded before childClass, and that both would be loaded before startMyApp was called.
require([
"parentClass",
"childClass"
], function (parentClass, childClass){
Main.startMyApp();
});
If not, how can I guarantee that? Main, is an object. Child class is defined as such:
var childClass = function childClass() {
this.name = 'some name';
};
childClass.prototype = new parentClass();
childClass.prototype.constructor = childClass;
And here is parentClass:
var parentClass = function parentClass() {
};
parentClass.prototype.myFunction = function myFunction(){
//do something
}
I am trying to avoid adding define's to all of my classes, I've got dozens. Is that the only way to guarantee class are available when I need them? Thanks!
The call require(["parentClass", "childClass"], ... tells RequireJS to load both modules but this call does not coerce the order in which the modules are loaded. What does coerce the order of the modules are the dependencies you establish between the modules.
Since this is your own code, and you've decided to use RequireJS, then you should write proper AMD modules. To establish dependencies you list them as the first argument of your define calls (if needed). For your parent class:
define(function () {
var parentClass = function parentClass() {
};
parentClass.prototype.myFunction = function myFunction(){
//do something
}
return parentClass;
});
For your child class:
define(['parentClass'], function (parentClass) {
var childClass = function childClass() {
this.name = 'some name';
};
childClass.prototype = new parentClass();
childClass.prototype.constructor = childClass;
return childClass;
});
Then whatever other modules needs childClass just require your childClass module and parentClass is guaranteed to be loaded before childClass because parentClass is listed as a dependency in the define call.
You want to use the shim config, for example:
require.config({
paths: {
jquery: ['../bower_components/jquery/jquery.min'],
underscore: ['../bower_components/underscore/underscore-min']
app: 'app'
},
shim: {
underscore: {
deps: ['jquery'],
exports: '_'
},
waitforimages: {
deps: ['jquery']
},
cyclotron: {
deps: ['jquery']
},
placeholder: {
deps: ['jquery']
},
app: {
deps: ['jquery', 'underscore', 'fastclick', 'spinjs', 'waitforimages', 'backgroundCheck', 'raphael', 'swipe', 'history', 'cyclotron', 'placeholder']
}
}
});
require([
'app'
]);
this isn't the most optimized example, but basically, if you say that something is a dep of another script, it will make sure to load those files. So you can see in this example, I am telling require that these plugins require jquery, and my app requires jquery and these plugins.
This way everything gets loaded before my app.js
I'm using RequireJS (version 2.1.14) and would like to concatenate my JavaScript files into one single app-built.js.
I've created a little node module which reads my app.js, extracts the project paths and gets executed once I run node build in the js directory of my application.
The node module (build.js):
var fs = require('fs'),
path = require('path'),
directory = __dirname + path.sep,
requirejs = require(directory + 'vendor/r.js');
fs.readFile(directory + 'app.js', 'utf8', function(err, data) {
if (err) {
console.log('Error: ' + err);
return
} else {
data = data.replace(/'/g, '"').replace(/\s+/g, '');
var paths = data.substr(data.indexOf('{'), data.indexOf('}')),
paths = paths.substr(0, paths.indexOf('}') + 1),
paths = JSON.parse(paths);
createAppBuilt(paths);
}
});
function createAppBuilt(paths) {
var config = {
baseUrl: __dirname,
paths: paths,
name: 'app',
out: 'app-built.js',
preserveLicenseComments: false,
findNestedDependencies: true,
removeCombined: true
};
requirejs.optimize(config, function(buildResponse) {
var contents = fs.readFileSync(config.out, 'utf8');
console.log('Created app-built.js');
}, function(err) {
console.log('Error: ' + err);
return;
});
}
app.js:
var paths = {
'jquery': 'vendor/jquery-1.11.0.min',
// other paths
};
// Set language, necessary for validtaion plugin -> validation.js
if (Modernizr.localstorage) {
localStorage.getItem('language') || localStorage.setItem('language', navigator.language || navigator.userLanguage);
}
requirejs.config({
paths: paths,
shim: {
touchswipe: {
deps: ['jquery']
},
icheck: {
deps: ['jquery']
},
validate: {
deps: ['jquery']
},
mask: {
deps: ['jquery']
},
chosenImage: {
deps: ['jquery', 'chosen']
},
cookie: {
deps: ['jquery']
}
}
});
require(['globals', 'jquery', 'underscore'], function() {
var initial = ['main'];
if (!Modernizr.localstorage) {
initial.push('cookie');
}
require(initial, function(Main) {
$(function() {
if (!Modernizr.localstorage) {
$.cookie.json = true;
}
Main.init();
});
});
});
The app-built.js gets generated but when I include it in my index.php all the other modules get loaded as well. How can I prevent the loading of all modules and only load the app-built.js?
I recommend you look into http://webpack.github.io/
or http://browserify.org/ as these solve this problem for you.
They allow you to use require much as before, yet the code is compiled/concatenated into a single file.
Webpack allows for a bit more flexibility in loading different chunks of code for different parts of your site, but Browserify is the most well-known so far.
There may be a cost in switching over to these, as I don't think that they're 100% compatible requirejs, however they bring great advantages.
Here's someone's journey from RequireJS to Browserify with some Pros and Cons.
Separate modules into different files, e.g. app-built.js, user-built.js. Then load script when it's needed.
Here's a demo: http://plnkr.co/edit/s6hUOEHjRbDhtGxaagdR?p=preview .
When page loaded, requirejs only loads global.js. After clicking the Change Color button, requirejs starts to load colorfy.js and random-color.js, which required by colorfy.js.
I am not sure about the exact details, but, yet if you don't have an exports option, r.js doesn't define a named module for you, that causes to actually load the script.
I assume you have jquery plugins there so add this extra exports option:
shim: {
touchswipe: {
deps: ['jquery'],
exports: 'jQuery.fn.touchswipe'
},
This should force r.js to build a named module for touchswipe:
define("touchswipe", (function (global) {
return function () {
var ret, fn;
return ret || global.jQuery.fn.touchswipe;
};
}(this)));
Note that, exports option might not build this named module, in that case your best bet is to include this manually.
Again I am not sure about why and how this happens, It must be a bug in requirejs, it's unlikely there is a tweak for this.
Changing the r.js optimizer (to uglify2) solved the problem for me:
var config = {
baseUrl: __dirname,
paths: paths,
name: 'app',
out: 'app-built.js',
findNestedDependencies: true,
preserveLicenseComments: false,
removeCombined: true,
optimize: 'uglify2'
};
I need some help with the concept of only loading modules when they are needed using requireJS
this is my main.js
require(['jquery', 'path/somemodule'],
function($, somemodule) {
$(document).ready(function() {
somemodule.init()
})
})
and in the somemodule.js
define(['jquery', 'path/someothermodule'], function ($, someothermodule) {
"use strict";
var somemodule;
somemodule = {
init: function () {
someothermodule.init()
}
}
return somemodule;
)}
right now somemodule.js and someothermodule.js is loaded on all pages. How do I only load it when it's needed?
When you require a module2 from module1 using the standard define() syntax module1 will not load/run until module2 has been fully loaded. That looks like this:
// inside module1
define(['module2'], function(mod2) {
// we don't get here until AFTER module2 has already been loaded
});
An alternative to lazy-load module2 looks like this:
// inside module1
define([], function() {
require(['module2'], function(mod2) {
// we don't get here until AFTER module2 has already been loaded
});
// but we DO get here without having loaded module2
});
Now you have to program somewhat carefully to make sure you don't run into any issues with asynchronicity.
In your case you can modify your main.js file
require(['jquery'],
function($) {
// jquery is loaded, but somemodule has not
if(thisPageNeedsSomeModule) {
require(['path/somemodule'],
function(somemodule) {
// now somemodule has loaded
$(document).ready(function() {
somemodule.init()
})
});
}
})
Your main.js file will load any file paths provided to it, so long as other elements of your application specify them as dependencies. See my example main.js file:
require.config({
paths: {
'app': 'app',
'underscore':'bower_components/underscore/underscore-min',
'backbone':'bower_components/backbone/backbone-min',
'marionette':'bower_components/backbone.marionette/lib/backbone.marionette.min',
'jquery': 'bower_components/jquery/jquery.min',
'tpl':'bower_components/requirejs-tpl/tpl',
'bootstrap':'bower_components/bootstrap/dist/js/bootstrap.min',
'leaflet':'bower_components/leaflet/leaflet',
'leaflet.markercluster':'bower_components/leaflet/leaflet.markercluster',
},
shim: {
'underscore': {
exports: '_'
},
'leaflet': {
exports: 'L'
},
'leaflet.markercluster': {
deps: ['leaflet']
},
'backbone': {
deps: ['underscore']
},
'marionette': {
deps: ['backbone']
},
'jquery': {
exports: '$'
},
'bootstrap': {
deps: ['jquery']
},
'app': {
deps: ['jquery', 'leaflet','bootstrap', 'leaflet.markercluster', 'marionette', 'tpl']
},
'app.elem': {
deps:['app']
},
'app.api': {
deps:['app']
}
}
})
require(['app','app.api','app.elem'], function() {
App.start();
})
And my initial application file:
define(['router', 'collections/moments'], function(router, momentCollection) {
// Boot the app!
App = new Marionette.Application();
App.LocResolve = false; // Have we resolved the user's location?
App.Locating = true; // Are we actively tracking the user's location?
App.FileReader = window.FileReader ? new FileReader : null;
App.Position = null; // Instant access to Lat & Lng of user.
App.MomentsRaw = null; // Keep cached copy of returned data for comparison.
App.Moments = new momentCollection; // Current collection of moments.
App.Markers = new L.MarkerClusterGroup(); // Create Marker Cluster Group
App.View = null; // Current view.
// Marionette Regions
App.addRegions({
header: '#header',
map: '#map',
list: '#list',
modal: '#modal',
});
return App
})
I noticed that you aren't passing in a configuration object - is this intentional? If you use R.js, the build optimizer, it will automatically remove unused vendor files for you.
In short, sets paths to your vendor files in the require.js config, then call upon them via define() whenever you need a particular asset. This will ensure that only files you need are used. Hope this helps!
I have troubles with using oCanvas - it doesn't support AMD out of box so in RequireJS I define a shim:
configuration.js
require(
function () {
requirejs.config({
shim: {
'lib/ocanvas': {
exports: ['oCanvas']
}
}
});
}
);
The way I load shim-related configuration (I think it's enough to add it to require to the entry point file):
require(['configuration','main'],
function (configuration, main) {
main.startUniverse();
}
);
The problem is I cannot get the oCanvas object in my JS files:
define(['lib/ocanvas'],
function (oCanvas) {}
It appears undefined here. In oCanvas sources I see that there are 2 self-invoking functions and they put the oCanvas object into global state like that: window.oCanvas = oCanvas. Maybe this doesn't work for RequireJS?
require(
function () {
requirejs.config({
shim: {
'lib/ocanvas': {
exports: 'oCanvas'
}
}
});
}
);
Try passing it as a string not an array?
Instead of requirejs.config I now use require.config and pass an object there:
require.config({
shim: {
'lib/ocanvas': {
exports: 'oCanvas'
}
}
});
That worked for me.