I've tackled this problem by only importing the views when the route is first hit:
define(['backbone'], function(Backbone) {
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home',
'users': 'users'
},
home: function() {
requirejs(["views/home/mainview"], function(HomeView) {
//..initialize and render view
});
},
users: function() {
requirejs(["views/users/mainview"], function(UsersView) {
//..initialize and render view
});
}
});
return AppRouter;
});
It doesn't solve the issue of having to eventually import all the views to the router, but the lazy requirejs
calls don't force loading and evaluating all scripts and templates up front.
Fact of the matter is that someone, somewhere, must import the modules. The router is a sensible location, because typically it's the first piece of code that's hit when user navigates to a certain page (View). If you feel like one router is responsible for too much, you should consider splitting your router into multiple routers, each responsible for different "section" of your application. For a good analogy think of the Controller in a typical MVC scenario.
Example of multiple routers
userrouter.js handles all User-related views (routes under 'users/'):
define(['backbone'], function(Backbone) {
var UserRouter = Backbone.Router.extend({
routes: {
'users', 'allUsers',
'users/:id', 'userById'
},
allUsers: function() {
requirejs(["views/users/listview"], function(UserListView) {
//..initialize and render view
});
},
userById: function(id) {
requirejs(["views/users/detailview"], function(UserDetailView) {
//..initialize and render view
});
}
});
return UserRouter;
});
postrouter.js handles all Post-related views (routes under 'posts/'):
define(['backbone'], function(Backbone) {
var PostRouter = Backbone.Router.extend({
routes: {
'posts', 'allPosts',
'posts/:id', 'postById'
},
allPosts: function() {
requirejs(["views/posts/listview"], function(PostListView) {
//..initialize and render view
});
},
postById: function(id) {
requirejs(["views/posts/detailview"], function(PostDetailView) {
//..initialize and render view
});
}
});
return PostRouter;
});
approuter.js is the main router, which is started up on application start and initializes all other routes.
define(['backbone', 'routers/userrouter', 'routers/postrouter'],
function(Backbone, UserRouter, PostRouter) {
var AppRouter = Backbone.Router.extend({
routes: {
'', 'home',
},
initialize: function() {
//create all other routers
this._subRouters = {
'users' : new UserRouter(),
'posts' : new PostRouter()
};
},
start: function() {
Backbone.history.start();
},
home: function() {
requirejs(["views/home/mainview"], function(HomeView) {
//..initialize and render view
});
}
});
return UserRouter;
});
And finally, your application's main.js, which starts the app router:
new AppRouter().start();
This way you can keep each individual router lean, and avoid having to resolve dependency trees before you actually need to.
Sidenote: If you use nested requirejs
calls and you're doing a build with r.js
, remember to set the build option findNestedDependencies:true
, so the lazily loaded modules get included in the build.
Edit: Here's a gist that explains lazy vs. immediate module loading in RequireJS.