Search on Firebear Studio blog

Advanced Development with RequireJS (+ Magento 2 Tutorial)

Below, I shed light on such useful technology as RequireJS and compare it with the closest competitors. Since here on the Firebear blog we are passionate about Magento 2, I also explain how to configure and adjust RequireJS with the second version of the ecommerce platform. The article itself combines real world coding experience, notes from the official documentation, code snippets, and materials from reliable sources. You will find a complete resource list in the end of this blog post.

Being a JavaScript modular script loader, RequireJS is optimized for both in-browser use and JS environments, such as Node.js. The technology is used to improve the quality of your code and increase the speed of your applications. As for supported browsers, they are: Chrome 3+, Firefox 2+, IE 6+, Opera 10+, and Safari 3.2+.

Since JavaScript does not offer any ways to specify what code is required to execute a file, RequireJS and other module loaders plays a crucial role in modern JS development. They specify dependencies for JS files and load those files into a browser.

Such opportunities are vital if you come from Java, Python, or any other server side language. Thus, by using RequireJS, you get the ability to define dependencies for your JS files. Besides, the module loader checks if those dependencies are loaded in the correct order. As a result, the code gets all necessary variables.

Since the dependencies are specified via several module formats, JavaScript modules incorporate 3 primary standards: Asynchronous Module Definition (AMD), EcmaScript 6 Modules, and CommonJS. But why modules on the web are so important?

There are several reasons for using modules, and all of them are based on constantly rising requirements. Since websites are getting bigger, code complexity grows; at the same time, some of them are turning into web apps. Thus, assembly gets harder, and new standards appear. For instance, discrete JS files are required, and code should be optimized in just a few HTTP calls. As a result, front-end developers need a tool that offer the following features:

#include/import/require

support for loading nested dependencies

ease of use

optimization of work

availability of deployment

First thing you can think about is a script loading API, such as CommonJS: require(“some/module”). Since it is a synchronous call, there is a necessity for returning the module immediately, that leads to lots of issues in a browser.

Furthermore, if require() is async, your code will never work, but synchronous loading in the browser absolutely destroys performance. Thus, such approach is not a good idea.But how about using XMLHttpRequest (XHR) instead?

Unfortunately, XHR requires using eval(), utilizes script tags, and has problems with cross-domain requests. That’s why you should think about another method to load scripts.

If you are going to choose Web Workers, note that they do not offer strong cross browser support. Another problem is related to a message-passing API nature of this approach, while the scripts likely want to interact with the DOM.

In addition to aforementioned approaches to script loading, it is necessary to mention document.write(), as it provides the ability to load scripts from other domains. Besides, it allows easy debugging. Unfortunately, it is impossible to execute that script directly, since we should know its dependencies before the script is executed, and that is impossible.

Thus, creating scripts on demand can be a good idea. Unlike document.write(), such approach helps to avoid blocking page rendering. Moreover, it works after page load, but the problem of unknown dependencies is still unsolved.

That’s why the only available solution is to construct a module loading API with function wrappers – the approach utilized by RequireJS. This laconic syntax type allows the loader to use head.appendChild(script) loading. A function wrapping format used by RequireJS is Asynchronous Module Definition (AMD).

Browser-friendly shims of Node.js modules. this feature offer the ability to use Node event systems as well as path and URL parsing.

Easier debugging with source maps.

Closure wrapping for each module.

It is also necessary to mention that Browserify has the following problems: there is no dynamic loading and it does nothing client-side. Thus, I recommend you to pay attention to RequireJS.

RequireJS features

Supports all desktop browsers.

Optimized for nested dependencies.

AMD implementation with the ability to implement CJS

Offers all necessary documentation and is easy to use.

Does not require a server for getting started

Offers a RequireJS optimizer designed to minify built files improving the performance of your projects.

Since REquireJS has a wide community, all its problems are fixed rapidly.

At the same time, there are some disadvantages related to RequireJS. While it is awesome for working with dependencies in an async manner, RequireJS is not the best solution to create a library for both client and server. Some other problems are discussed below.

One of the most important problems of using RequireJS is related to the AMD standard, which no longer holds a significant technical advantage over alternatives. Moreover, it has a less clear syntax and experiences problems with network effects. AMD modules, used by RequireJS, have the following look:

JavaScript

1

2

3

4

5

define(['file1','file2'],function(Class1,Class2){

let obj=newClass1(),

obj2=newClass2();

returnobj.foo(obj2);

});

As you can see, we have to deal with a define function while defining a new module. There are 2 arguments. The first one is an array of dependencies. The second argument is related to a callback function. The function is passed in values exported by each dependency. It is run after the dependencies have been loaded.

Moreover, AMD provides a require function. Although the function takes the same arguments, it plays a role of the program’s initialization point, loading and executing all the dependencies when it’s run.

As for two other standards, CommonJS and the ES6 Modules syntax, they have the following structure:

JavaScript

1

2

3

4

5

6

7

//commonJS

let Class1=require('file1'),

Class2=require('file2'),

obj=newClass1(),

obj2=newClass2();

module.exports=obj.foo(obj2);

1

2

3

4

5

6

7

8

//ES6

import Class1 from'file1';

import Class2 from'file2';

let obj=newClass1(),

obj2=newClass2();

export defaultobj.foo(obj2);

Thus, CommonJS and ES6 standards offer some obvious advantages: they do not require an outer function for wrapping your code. Besides, CommonJS or ES6 syntaxes do not require two changes of code for every added or removed dependency. In case of AMD it is necessary to specify all dependencies as an array of strings. That result in parameters to a callback function, so it is necessary to change code in two places when you add or remove a dependency.

The asynchronous nature nature of AMD still remains an advantage, but other module loaders offer similar opportunities. For instance, Webpack provides configuration options which will help you lazy-load individual modules. As for Browserify, there is also a workaround to the problem. So, other module formats no longer offer less opportunities compared to AMD. There are still a lot of differences, but they are not crucial. Moreover, by using AMD modules you can reduce the range of tools possible in your development, especially compared to CommonJS.

As for the closest competitors of RequireJS, Browserify is based on the NPM ecosystem and Node.js modules; it utilizes CommonJS standard. In its turn Webpack supports all three module formats: AMD, CommonJS, and ES6. Moreover, it handles such assets as JavaScript and CSS, as well as provides preprocessors for them. As you can see, RequireJS suffers in comparison to its closest competitors in terms of workflow and features. If you also think so, chose another loader, which offers better opportunities. For instance, you can easily convert your existing codebase to Webpack within several day. This process is described here.

RequireJS and Magento 2 Tutorial

Magento is the most popular ecommerce platform and Magento 2 is its upcoming version. The solution is aimed at creating robust ecommerce stores with all necessary capabilities. With Magento 2 you will get tons of marketing features, user friendly interface, out-of-the-box SEO, and other optimizations required for a successful web store. Besides, the new version of the platform offers top notch performance, which can be significantly improved, for instance with the help of the RequireJS library. Let’s see, how to use RequireJS within Magento 2.

How to Configure the RequireJS library

To make RequireJS library available in Magento 2, specify it along with specific configurations in layout.xml. Use the following code:

How to Specify JS Resources Mapping

The next step requires specifying the JavaScript resources mapping. Utilize requires-config.js to generate the mapping.

For more precise configurations, identify mapping at several levels. Note that there is a certain order for collecting and executing configurations.

The first are library configurations.

Then, the priority goes to the module level (considering dependencies between both modules and themes).

As for configurations at the theme module level, the ancestor themes are involved first. Then comes a current theme.

The same is about configurations at the theme level: the priority goes to the ancestor themes and then to the current theme.

Besides standard to RequireJS aliases, Magento uses relative paths. Therefore, you should specify the relative paths to JS resources in RequireJS configurations. For the following path app/code/Magento/Catalog/view/frontend/requirejs-config.js, utilize configurations similar to oneslisted below: