📜 ⬆️ ⬇️

Who could tell me how to require to connect

One sunny spring morning, I had a great idea - to study the popular library RequireJS. I have long read a lot of good things about ease of use and the advantages that it has when used in projects. Therefore, I could not have imagined that connecting RequireJS to a modular project on Backbone can cause so many problems. I spent two days on something that should take no more than an hour. And if the developer does not have these two days? So I decided to share my experience with others in order to save time and nerves.

A bit of theory (instead of the preface)

The RequireJS library is really very simple. It is available, as well as documentation, on the official website of the developer. The principle of the library is to break Java script code into "modules" and their further use. Modules are described using the define () directive, and used - using the require () directive. What is the problem then? And the problem arose while trying to connect other libraries to the project. All the documentation I could find on the Internet, described only the simplest cases - one small script that uses only the jQuery library, and all the files are just in one folder. So I had to break my head to make RequireJS, jQuery and Backbone together. And now enough theory go to practice.

Modules, modules, modules

Sooner or later, each developer comes to understand the need to break a large project into small parts (modules). Let's do it and we. Let's create a simple one-page application, for visual demonstration. In order not to reinvent the wheel, we use the ready-made Backbone library, which allows you to implement an MVC pattern for code in a Java script. What does it mean? This means that all our code will be broken into such modules:


In addition, we will have an Application object, which is responsible for the operation of the application as a whole and the module for initializing RequireJS (also known as the entry point). To work we need the following libraries:
')
  1. RequireJS
  2. Backbone
  3. underscore (Backbone doesn't work without it)
  4. jQuery
  5. jStorage (just a plugin to demonstrate how dependencies work in RequireJS)
  6. json2 (needed for jStorage)

We place everything in our folders and get the following structure:

image

In each file we have one object. This requirement is RequireJS. If you want to place several objects in one file, you will need to use the optimizer. Let's turn these objects into modules, i.e. connect to RequireJS.

define(['backbone'], function(Backbone){ var Controller = Backbone.Router.extend({ initialize: function (options) { this.appModel = options.model; }, routes: { "": "showMainPage", "result": "showResultPage", "page/:page": "showPage" }, showMainPage: function () { this.appModel.set({ type: "mainpage", page: 0 }); }, showResultPage: function () { this.appModel.set({ type: "resultpage", page: 0 }); }, showPage: function (pageNum) { this.appModel.set({ type: "page", page: pageNum }); }, }) return Controller; }); 


This block describes using the define controller module. Define takes three parameters:


As you can see, our module depends on the Backbone library, and the variable is transferred to the function Backbone - a variable that the library exports to the global scope of the application (like $ for jQuery). The function must return an object that will be available for future use. Do the same for other modules.

 define(['appModels/appModel', 'appViews/appView', 'appControllers/appController', 'jquery', 'backbone'], function(baseModel, View, Controller, $, Backbone){ var Application = (function() { var appView; var appTemplates = { "mainpage": _.template($('#main-page').html()), "page": _.template($('#page').html()), }; var appController; var appModel; var self = null; var module = function() { self = this; }; module.prototype = { constructor: module, init: function() { self.initModel(); self.initView(); self.initRouter(); }, initRouter: function() { appController = new Controller({ model: appModel}); Backbone.history.start(); }, initView: function() { appView = new View({ model: appModel, templates: appTemplates, el: $("#main-content")}); appModel.trigger("change"); }, initModel: function() { appModel = new baseModel(); }, }; return module; })(); return Application; }); 


The Application object is described in the same way as a controller. Of course there are more dependencies. And this is normal, because Application integrates the controller, model, views into one whole (Views) and gives us a ready object that is able to independently monitor changes in the application and respond effectively (redraw elements on the page). And in the function, we are just passing all the components of our application (objects that are returned by the function in the define block), plus global objects for the Backbone and jQuery libraries. Everything is simple, and one thing is not clear - what are these file paths such 'appModels /', 'appViews /', 'appControllers /', why do we just turn to libraries 'jquery' or 'backbone' and how does Backbone work without underscore? Finding the answer to this question took me two days, and was very simple. Not enough configuration.

Configuration

Let's try to run our application. To do this, add a line to the html header of the page.

 <script data-main="js/init" src="js/library/require.js"> < / script > 

This is how RequireJS connects to the project. The src attribute indicates the location of the library file, and data-main indicates the location of the file that is the “entry point”, that is, the script will be started from it. Here it will look like this:

 require(["jquery", "../application"], function ($, Application) { $(document).ready(function() { var myApplication = new Application(); myApplication.init(); }); }); 

As you can see, we do not describe anything (do not use define), but simply use ready-made modules described in other files using the require () keyword. The first argument is the dependencies here (in our case it is the jQuery library and the Application object), and the second is the function that will be executed when all the dependencies are loaded. Unlike define (), this function returns nothing. Everything is ready and you can check the application in the browser. Open and see what? That's right, nothing. It would seem that everything described in the documentation was done. What is the problem? And the problem is in the proper connection of libraries. Their files are called not just jquery.js or backbone.js (as in the documentation) and are located in the library folder, and not next to other modules. Therefore, for the application to work properly, we must somehow specify all this, that is, create a configuration for RequireJS. Add it before calling the require statement in the init.js file

 requirejs.config({ baseUrl: "js/library", paths: { jquery: 'jquery.min', backbone: 'backbone.min', underscore: 'underscore.min', storage: 'jstorage', json: 'json2', appControllers: '../Controllers', appModels: '../Models', appViews: '../Views' }, shim: { 'underscore': { exports: '_' }, 'backbone': { deps: ['underscore', 'jquery'], exports: 'Backbone' }, 'json': { exports: 'JSON' }, 'storage': { deps: ['json', 'jquery'], } } }); 


Consider the configuration details


To describe the library in the shim block, just add a type entry there

  'backbone': { deps: ['underscore', 'jquery'], exports: 'Backbone' } 

Here 'backbone' is a synonym for the module that we are connecting, deps - dependencies, exports is a global variable that the library exports to the scope. That is, this example connects the Backbone library (its alias is described in the path) and indicates that it depends on the underscore and jQuery libraries, as well as further access to the functions of this library can be done through the Backbone variable. Here is another example of connecting the jQuery plugin

  'storage': { deps: ['json', 'jquery'], } 

As you can see, the storage plugin depends on jQuery and JSON2, does not export anything, because the plugin is used through the $ jQuery library ID.

Hurray, success! Or not at all?

We did a good job and our application worked. But why was so much torment? Isn’t it easier to just connect all these libraries in an html file and not even think about who depends on whom?

To be honest, for our small example is easier. I myself thought, what benefit does RequireJS give us if eventually all the libraries and all additional files (controller, model, view, Application object) were loaded?

But let's imagine that we have, for example, 20 models and say 40 views (Views). Each module in a separate file. In addition, for each model there is a rather large (requires a long load time) auxiliary library of functions. If we include all this in the html file, we get:

  1. A huge list of tags in the document header makes it difficult to control dependencies (as in such a list, check if one of the plugins is loaded before the library itself)
  2. Since individual scripts can have a large load time, the total load time of the application will be very large (we load all models with supporting libraries, although only a few of them can be used during the execution of the application)
  3. Usually the work of the designer and the programmer is divided, so the designer needs to know in advance what tools the programmer will use to include them in the page code. With further support of such a project (technology change, refactoring), changes will need to be carried out both in the script code and in the code of the page itself.

Only these three points are already enough to reflect on the use of RequireJS. After all this:

  1. Single entry point for scripts (the designer does not think about what the programmer will use)
  2. Asynchronous script downloads (they are loaded as needed, so the total application load time is extremely short)
  3. Simplifies the control of dependencies between modules.

I hope that my short article will be a good start in studying RequireJS. The source code of the sample can be downloaded here .

Source: https://habr.com/ru/post/227449/


All Articles