Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RequireJS doesn't work on a multipage project with CDN hosted libraries (jQuery)

I'm using RequireJS on a multipage project, with a Javascript folder structure that looks kinda like this (how do you make those fancy dir trees again in Markdown?):

common.js
lib/
-- jquery-1.9.1.min.js
-- modernizr-2.6.2.min.js
-- underscore-amd.min.js
page/
-- index.js
-- start.js
-- checkout.js

Anyway, common.js is my main script file that is where I set up the configuration parameters. This is what that looks like:

common.js file

// Configure RequireJS
requirejs.config({
    baseUrl: "assets/js/lib",
    paths: {
        page: '../page',
        jquery: [
            '//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min',
            //If the CDN location fails, load from this location
            'jquery-1.9.1.min'
        ],
        modernizr: [
            '//cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min',
            //If the CDN location fails, load from this location
            'modernizr-2.6.2.min'
        ],
        underscore: [
            'underscore-amd.min'
        ]
    }
});

All the page calls work as expected (with the CDN location being loaded), but I can't get my head around the minification part. The r.js optimizer simply refuses to cooperate, throwing an Error: paths fallback not supported in optimizer. Please provide a build config path override for underscore even though Underscore doesn't have a CDN specified.

build.js file

{
    appDir: '../www',
    baseUrl: 'js/lib',
    dir: '../www-built',
    mainConfigFile: '../www/assets/js/common.js',
    modules: [
        //First set up the common build layer.
        {
            //module names are relative to baseUrl
            name: '../common',
            include: ['jquery',
                      'modernizr',
                      'underscore'
            ]
        },
        // Now the page scripts
        {
            //module names are relative to baseUrl/paths config
            name: '../page-index',
            include: ['page/index'],
            exclude: ['../common']
        },
    ],
    paths: {
        jquery: "empty",
        modernizr: "empty"
    },
    optimize: "uglify2",
    removeCombined: true
}

Please help me figure out how to build this project to create the common.js script along with the individual page scripts.

(Note: I based the structure of my build.js file on this example

Updated!

I've updated the question to include the correct syntax for empty: (thanks Travis!), and now the build runs without error. However, my JS files are not concatenated or uglified. The CSS files are, at the import points, so something's happening. Complete build.js file below (forgive me, but she's a tall one):

{
    appDir: '../www',
    baseUrl: 'assets/js', // relative to appDir
    dir: '../www-built',
    mainConfigFile: '../www/assets/js/common.js',
    modules: [
        //First set up the common build layer.
        {
            //module names are relative to baseUrl
            name: 'common',
            //List common dependencies here. Only need to list
            //top level dependencies, "include" will find
            //nested dependencies.
            include: ['jquery',
                      'modernizr',
                      'underscore',
                      'bootstrap'
            ]
        },

        //Now set up a build layer for each page, but exclude
        //the common one. "exclude" will exclude nested
        //the nested, built dependencies from "common". Any
        //"exclude" that includes built modules should be
        //listed before the build layer that wants to exclude it.
        //"include" the appropriate "app/main*" module since by default
        //it will not get added to the build since it is loaded by a nested
        //require in the page*.js files.
        {
            //module names are relative to baseUrl/paths config
            name: 'pages/home',
            include: ['pages/home'],
            exclude: ['common']
        },
        {
            //module names are relative to baseUrl/paths config
            name: 'pages/start',
            include: ['pages/start'],
            exclude: ['common']
        },
        {
            //module names are relative to baseUrl/paths config
            name: 'pages/checkout',
            include: ['pages/checkout'],
            exclude: ['common']
        },
    ],
    paths: {
        jquery: "empty:",
        modernizr: "empty:"
//        underscore: "empty:"
    },
    optimize: "uglify2",
    optimizeCss: "standard",
    removeCombined: true,
    preserveLicenseComments: false
}

Final Update (Yaay! It's working)

Thanks to Travis below, everything is working great! (the files are getting minified and concatenated). Since his solution is hosted on Dropbox, and could be lost in the future (who knows amirite?), I'll just sum up the fixes he made:

1. Don't mix define and require calls in a single file.

I had a couple of files where I'd mixed them like so:

page/start.js

 define(['jquery','underscore'],function($,_) {
      ...
 });

 require(['jquery','page/start'], function($, Y) { // BAD!
      ...
 });

and the fix was to do this:

page/start.js

 require(['jquery','app/start_helper'], function($, Y) {
      ...
 });

app/start_helper.js

 define(['jquery','underscore'],function($,_) {
      ...
 });

2. It's "empty:" not "empty"

That's a tricky one, though the RequireJS docs mention them.

3. Because

what kind of list has just 2 bullet points?


Awesomelicious - now it works like a charm :)

like image 689
FloatingRock Avatar asked Aug 09 '13 21:08

FloatingRock


1 Answers

Looks like requireJs thinks that you're trying to provide a paths fallback for underscore, since you're specifying its path value as an array. Try just doing it as a string instead.

paths: {
    underscore: 'underscore-amd.min'
}

Also note that the correct symbol for empty paths is empty: (with a ':') not empty.

Finally, as an unrelated side note you could always look at lodash which is IMHO a more customizable, cross-browser compliant, faster underscore replacement with an identical API and it's hosted on cdnjs and is AMD-compliant

UPDATE

Following up on the conversation within the comments, here's my updated version of your project: https://dl.dropboxusercontent.com/u/21823728/so_rjs.tar.gz

As mentioned in the comments, your problem seemed to be that you were defineing modules within the same file that you were requiring those modules in, and that I believe was causing r.js to believe that these modules had no main entry point. The convention is to separate out module definitions from files that require those modules.

Notice there's an app folder now under assets/js where each module that used to be defined within your page modules is now stored. Your page modules now require these modules in. When I re-ran r.js (using uglify2 as the optimizer) everything seemed to work as expected.

Furthermore, I removed some redundancy from your build.js file (you only have to specify baseUrl in your mainConfigFile if that's the one you're using for your build configuration as well). Finally, you may want to look into using the shim config if you want to attach jQuery and Bootstrap globally to every page. Otherwise you should just explicitly list them as dependencies when you need them.

Finally as one last piece of advice you may want to look at cutting down on the number of require within each file. For most of those pages you could wrap everything in one require call and then just separate out different logic into functions to save some space on the event queue and also some cycles of having to redundantly call the require function.

like image 59
Travis Kaufman Avatar answered Sep 20 '22 21:09

Travis Kaufman