Until now we have learned about what Browserify is and how to use it. Now we will apply that background to our contacts app to load all the code as Node modules.
Before we continue, ensure that you have installed all the required dependencies for the project:
{ "name": "mastering-backbone-04", // ... "dependencies": { "backbone": "^1.2.3", "backbone-validation": "^0.11.5", "body-parser": "^1.14.1", "bootstrap": "^3.3.5", "browser-sync": "^2.9.11", "crispy-string": "0.0.2", "express": "^4.13.3", "http-proxy": "^1.11.3", "jquery": "^2.1.4", "lodash": "^3.10.1", "morgan": "^1.6.1", "sweetalert": "^1.1.3", "underscore": "^1.8.3" } }
The easiest modules to convert are Models and Collections because they don't have huge dependencies.
// apps/contacts/models/contact.js 'use strict' var Backbone = require('backbone'); class Contact extends Backbone.Model { // ... } module.exports = Contact;
As you can see, the module remains almost the same. We have just added the require()
calls and the export statement at the end of the file:
// apps/contacts/contactCollection.js 'use strict'; var Backbone = require('backbone'); var Contact = require('../models/contact'); class ContactCollection extends Backbone.Collection { // ... get model() { return Contact; } } module.exports = ContactCollection;
Views are easy to convert, too. We just have to add the require()
calls as we did with the Contact
and ContactCollection
modules. Before we continue, we need an extra step with the views; currently all the views for a given controller are contained in a single script; contactEditor.js
for example contains ContactForm
, ContactPreview
, and PhoneList
, and so on.
As we are modularizing the project, it's a good idea to put each view in its own file and require it when we need it. The following shows this idea. You have many good reasons to do this: to isolate your components for testing, to keep your files small, to improve maintenance, and to get interchangeable modules.
'use strict'; var Layout = require('../../common').Layout; class ContactFormLayout extends Layout { // ... } module.exports = ContactFormLayout;
As you can see, the conversion of plain JavaScript files to Node modules is very easy to do. The code of the business logic is exactly the same. The subapplication controller depends on the views that we have converted on the previous step.
'use strict'; var _ = require('underscore'); var Backbone = require('backbone'); var App = require('../../app'); var ContactFormLayout = require('./views/contactFormLayout'); var ContactPreview = require('./views/contactPreview'); var PhoneListView = require('./views/phoneListView'); var EmailListView = require('./views/emailListView'); var ContactForm = require('./views/contactForm'); var PhoneCollection = require('./collections/phoneCollection'); var EmailCollection = require('./collections/emailCollection'); class ContactEditor { // ... } module.exports = ContactEditor;
The application façade depends on many subapplication controllers, the models, and the collections:
'use strict'; var App = require('../../app'); var ContactList = require('./contactList'); var ContactViewer = require('./contactViewer'); var ContactEditor = require('./contactEditor'); var Contact = require('./models/contact'); var ContactCollection = require('./collections/contactCollection'); function ContactsApp(options) { // ... } // ... module.exports = ContactsApp;
The routers depend on the subapplication façade and the application infrastructure:
'use strict'; var Backbone = require('backbone'); var App = require('../../app'); var ContactsApp = require('./app'); var ContactsRouter = Backbone.Router.extend({ // ... }); module.exports = new ContactsRouter();
The App object is responsible for loading all the subapplication routers and then starting the history module:
'use strict'; var _ = require('underscore'); var Backbone = require('backbone'); var BackboneValidation = require('backbone-validation'); var swal = require('sweetalert'); var noty = require('noty'); var Region = require('./common').Region; // Initialize all available routes require('./apps/contacts/router'); var App = { start() { // The common place where sub-applications will be showed App.mainRegion = new Region({el: '#main'}); // Create a global router to enable sub-applications to // redirect to other URLs App.router = new DefaultRouter(); Backbone.history.start(); }, // ... }; // ... module.exports = App;
The next step is to start the application by calling the start()
method on the App object; this is done from the index.html
file:
<script type="text/javascript">App.start();</script>
As we are re-packing the application with Browserify, it's better to create a new file to the main entry point:
// main.js var App = require('./app'); App.start();
Once our application is written as Node modules, we can use Browserify to bundle the code in a single script:
$ mkdir –p .tmp/js $ cd app/js $ browserify main.js -o ../../.tmp/js/app.js
This will create a bundled file with all the dependencies on it. To use the bundled version of the code, we have to change the index.htm
file to load it instead of loading all the individual files:
<html> <head> // ... </head> <body> // ... <script src="js/app.js"></script> </body> </html>
That should be enough; however, the application won't start because we have a cyclic dependency issue.
Having two modules that depend on each other is called cyclic dependency. In our Contacts application, the infrastructure application depends on the subapplication routers, and the routers depend on the application infrastructure to load the subapplication controllers and facades. Figure 4.4 shows how this looks.
It is not possible to run the application properly because of the cyclic dependency. Here is what happens in detail.
ContactsRouter
:var ContactsRouter = require('./apps/contacts/router');
ContactsRouter
requires the App module but the App module is not exported yet:var App = require('../../app'); // returns undefined
ContactsRouter
receives an undefined
value for the App variablevar App = { // ... }; module.exports = App;
ContactsRouter
matches a route, but as the App value is undefined it triggers an error:startApp() { // App = undefined return App.startSubApplication(ContactsApp); }
We should break the cycle in some way. An easy approach to do it is to require the App module after it is exported. Instead of requiring the App
module from ContactsRouter
on top of the file, we can do it only when it's necessary:
// apps/contacts/router.js class ContactsRouter extends Backbone.Router { // ... startApp() { var App = require('../../app'); var ContactsApp = require('./app'); return App.startSubApplication(ContactsApp); } }
This is a simple but effective way to break a cyclic dependency. Now you can re-bundle the application and run the application again. It should work:
$ mkdir –p .tmp/js $ cd app/js $ browserify main.js -o ../../.tmp/js/app.js
3.144.110.155