Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use RequireJS build profile + r.js in a multi-page project

I am currently learning RequireJS fundamentals and have some questions regarding a build profile, main files, and use of RequireJS with multi-page projects.

My project's directory structure is as follows:

 httpdocs_siteroot/     app/         php files...     media/         css/             css files...         js/             libs/                 jquery.js                 require.js                 mustache.js             mains/                 main.page1.js                 main.page2.js                 main.page3.js             plugins/                 jquery.plugin1.js                 jquery.plugin2.js                 jquery.plugin3.js             utils/                 util1.js                 util2.js         images/ 

Since this project is not a single-page app, I have a separate main file for each page (although some pages use the same main file).

My questions are:

  1. Is RequireJS even practical for projects that are not single-page?

  2. Without using the optimizer, each of my main files start with essentially the same config options:

    requirejs.config({   paths: {     'jquery': 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min'   },   baseUrl: '/media/js/',   // etc... }); require(['deps'], function() { /* main code */ }); 

    Is there a way to avoid this? Like having each main file include the same build profile without having to actually build it?

  3. Should r.js go in httpdocs_siteroot's parent directory?

  4. Is there something glaringly wrong with my app dir structure or my use of RequireJS?

like image 711
AndyPerlitch Avatar asked Jul 26 '12 17:07

AndyPerlitch


People also ask

What is the main purpose of the RequireJS framework?

RequireJS is a JavaScript file and module loader. It improves perceived page load times because it allows JavaScript to load in the background. In particular, it enables asynchronous JavaScript loading.

How do I use NPM with RequireJS?

How can you use a RequireJS installed through Node in the browser? You can just install it with npm install requirejs , and then you have your HTML file have a script element that points to node_modules/requirejs/require. js . Exactly as you show in your code snippet.

How do I run RequireJS?

RequireJS can be initialized by passing the main configuration in the HTML template through the data-main attribute. It is used by RequireJS to know which module to load in your application. To include the Require. js file, you need to add the script tag in the html file.

What is Shim RequireJS?

As per RequireJS API documentation, shim lets you. Configure the dependencies, exports, and custom initialization for older, traditional "browser globals" scripts that do not use define() to declare the dependencies and set a module value. - Configuring dependencies.


2 Answers

First of all, this is not a question with a unique solution. I'll explain the way I use RequireJS that works for me, and may work for you :)

Second, English is not my mother language. Corrections and tips about the language will be very appreciated. Feel free, guys :)

1) Is require js even practical for projects that are not single-page?

It depends. If your project does not have shared code between pages for example, RequireJS help will be modest. The main idea of RequireJS is modularize the application into chunks of reusable code. If your application uses only page-specific code, then using RequireJS may not be a good idea.

2) Without using the optimizer, each of my main files start with essentially the same config options. Is there a way to avoid this? Like having each main file include the same build profile without having to actually build it?

The only way I see is making the configuration on the main file, or create a module that will configure RequireJS and then use that module as the first dependency on main.js. But this can be tricky. I do not use many main.js files in my applications; I use only one that acts as a loader (see below).

3) Should r.js go in httpdocs_siteroot's parent directory?

Not necessarily. You can put it inside the /media directory, since all your client stuff is there.

4) Is there something glaringly wrong with my app dir structure or my use of requirejs?

I would not say that. On the other hand, the structure is perhaps a bit too fragmented. For example, you can put all '3rd party stuff' inside a /vendor directory. But this is just sugar; your structure will work well and seems right. I think the major problem is the requirejs.config() call in multiple main files.

I had the same problems you are having now and I ended up with the following solution:

1) Do not wrap the non-AMD-compliant files with a define. Although it works, you can achieve the same results using the "shim" property in requirejs.config (see below).

2) In a multi-page application, the solution for me is not to require the page-specific modules from the optimized main.js file. Instead, I require all the shared code (3rd party and my own) from the main file, leaving the page-specific code to load on each page. The main file ends up only being a loader that starts the page-specific code after loading all shared/lib files.

This is the boilerplate I use to build a multi-page application with requirejs

Directory structure:

/src - I put all the client stuff inside a src directory, so I can run the optimizer inside this directory (this is your media directory).

/src/vendor - Here I place all 3rd party files and plugins, including require.js.

/src/lib - Here I place all my own code that is shared by the entire application or by some pages. In other words, modules that are not page-specific.

/src/page-module-xx - And then, I create one directory for each page that I have. This is not a strict rule.

/src/main.js: This is the only main file for the entire application. It will:

  • configure RequireJS, including shims
  • load shared libraries/modules
  • load the page-specific main module

This is an example of a requirejs.config call:

requirejs.config({         baseUrl: ".",         paths: {             // libraries path             "json": "vendor/json2",             "jquery": "vendor/jquery",             "somejqueryplugion": "vendor/jquery.somejqueryplufin",             "hogan": "vendor/hogan",              // require plugins             "templ": "vendor/require.hogan",             "text": "vendor/require.text"         },         // The shim section allows you to specify          // dependencies between non AMD compliant files.         // For example, "somejqueryplugin" must be loaded after "jquery".         // The 'exports' attribute tells RequireJS what global variable         // it must assign as the module value for each shim.         // For example: By using the configutation below for jquery,          // when you request the "jquery" module, RequireJS will          // give the value of global "$" (this value will be cached, so it is         // ok to modify/delete the global '$' after all plugins are loaded.         shim: {             "jquery": { exports: "$" },             "util": { exports: "_" },             "json": { exports: "JSON" },             "somejqueryplugin": { exports: "$", deps: ["jquery"] }         }     }); 

And then, after configuration we can make the first require() request for all those libraries and after that do the request for our "page main" module.

//libs require([     "templ",     //require plugins     "text",     "json",      //3rd libraries     "jquery",     "hogan",      "lib/util"  // app lib modules  ],     function () {         var $ = require("jquery"),             // the start module is defined on the same script tag of data-main.             // example: <script data-main="main.js" data-start="pagemodule/main" src="vendor/require.js"/>             startModuleName = $("script[data-main][data-start]").attr("data-start");          if (startModuleName) {             require([startModuleName], function (startModule) {                 $(function(){                     var fn = $.isFunction(startModule) ? startModule : startModule.init;                     if (fn) { fn(); }                 });             });         }     }); 

As you can see in the body of the require() above, we're expecting another attribute on the require.js script tag. The data-start attribute will hold the name of the module for the current page.

Thus, on the HTML page we must add this extra attribute:

<script data-main="main" data-start="pagemodule/main" src="vendor/require.js"></script> 

By doing this, we will end up with an optimized main.js that contains all the files in "/vendor" and "/lib" directories (the shared resources), but not the page-specific scripts/modules, as they are not hard-coded in the main.js as dependencies. The page-specific modules will be loaded separately on each page of the application.

The "page main" module should return a function() that will be executed by the "app main" above.

define(function(require, exports, module) {     var util = require("lib/util");      return function() {         console.log("initializing page xyz module");     }; }); 

EDIT

Here is example of how you can use build profile to optimize the page-specific modules that have more than one file.

For example, let's say we have the following page module:

/page1/main.js

/page1/dep1.js

/page1/dep2.js

If we do not optimize this module, then the browser will make 3 requests, one for each script. We can avoid this by instructing r.js to create a package and include these 3 files.

On the "modules" attribute of the build profile:

... "modules": [    {        name: "main"  // this is our main file    },    {         // create a module for page1/main and include in it         // all its dependencies (dep1, dep2...)         name: "page1/main",         // excluding any dependency that is already included on main module         // i.e. all our shared stuff, like jquery and plugins should not         // be included in this module again.         exclude: ["main"]    } ] 

By doing this, we create another per-page main file with all its dependencies. But, since we already have a main file that will load all our shared stuff, we don't need to include them again in page1/main module. The config is a little verbose since you have to do this for each page module where you have more than one script file.

I uploaded the code of the boilerplate in GitHub: https://github.com/mdezem/MultiPageAppBoilerplate. It is a working boilerplate, just install node and r.js module for node and execute build.cmd (inside the /build directory, otherwise it will fail because it uses relative paths)

I hope I have been clear. Let me know if something sounds strange ;)

Regards!

like image 122
Marcelo De Zen Avatar answered Oct 09 '22 04:10

Marcelo De Zen


<script data-main="js/main" src="js/lib/require.js"></script>   // file: js/main  require(['./global.config'], function(){     require(['./view/home'], function() {         // do something     }); }); 

This is what I used in my project.

like image 44
John Xiao Avatar answered Oct 09 '22 02:10

John Xiao