Scott Lin
Scott Lin

Reputation: 1562

requirejs callback undefined

Project Structure

root
    wwwroot <-- files under this location are static files public to the site
        css
        lib
            bootstrap/js/bootstrap.js
            jquery/js/jquery.js
            knockout/knockout.js
            requires/require.js
        scripts
            modules               ┌───────────────┐
                global.js      <--│ Built modules │
                dropdown.js       └───────────────┘
    modules
        global.js                 ┌────────────────┐
        dropdown               <--│ Source modules │
            dropdown.js           └────────────────┘
    gruntfile.js

global.cs Contents (pre-built version at ~/modules/global.js)

require.config({
    baseUrl: "scripts/modules",
    paths: {
        jquery: "../../lib/jquery/js/jquery",
        bootstrap: "../../lib/bootstrap/js/bootstrap",
        knockout: "../../lib/knockout/knockout"
    },
    shims: {
        bootstrap: {
            deps: ['jquery']
        }
    },
});

define(function (require) {
    var $ = require('jquery');
    var ko = require('knockout');
    var bootstrap = require('bootstrap');
});

dropdown.js Contents (pre-built version at ~/modules/dropdown.js)

define(function () {
    console.log('dropdown initialized');
    return 'foo';
});

HTML Page

Contains this script tag in the <head> of the page for loading requires config:

<script src="~/lib/requirejs/require.js" data-main="scripts/modules/global"></script>

In the body of the HTML page, I have the following:

<script>
    require(['global'], function () {
        require(['dropdown'], function (dropdown) {
            console.log(dropdown);
        });
    });
</script>

Issue

The dropdown callback is undefined instead of the expected "foo" string that I'm returning from the defined module.

In fact, the console does not contain a log item for "dropdown initialized" either. This makes me believe the module is not being invoked somehow? However, it's strange the dropdown.js is present in F12 debugger as a script loaded into the page. Therefore, requires did make a call to load it, but did not run the contents of the define?

Noteworthy mentions

Edit #1

I have added the r.js build configuration used with grunt per commenter request. In conjunction, I updated the file structure to include the overall project structure, instead of just the runtime public wwwroot structure.

The r.js process will compile built forms of global.js + other modules in ~/wwwroot/scripts/modules from the source location ~/modules in summary.

function getRequireJsConfiguration() {
    var baseUrl = './';
    var paths = {
        jquery: "wwwroot/lib/jquery/js/jquery",
        bootstrap: "wwwroot/lib/bootstrap/js/bootstrap",
        knockout: "wwwroot/lib/knockout/knockout"
    };
    var shims = {
        bootstrap: {
            deps: ['jquery']
        }
    };
    var optimize = 'none';

    var configuration = {};

    var jsFilePaths = grunt.file.expand('modules/**/*.js');
    jsFilePaths.forEach(function (jsFilePath) {
        var fileName = jsFilePath.split('/').pop();

        if (configuration[fileName]) {
            throw 'Duplicate module name conflict: ' + fileName;
        }

        configuration[fileName] = {
            options: {
                baseUrl: './',
                name: jsFilePath,
                out: 'wwwroot/scripts/modules/' + fileName,
                paths: paths,
                shims: shims,
                optimize: optimize,
                exclude: ['jquery', 'knockout', 'bootstrap']
            }
        };
    });

    configuration['global'] = {
        options: {
            baseUrl: './',
            name: 'modules/global.js',
            out: 'wwwroot/scripts/modules/global.js',
            paths: paths,
            shims: shims,
            optimize: optimize,
        }
    };

    return configuration;
}

Edit #2

Thought it'd be a good idea to include the versions of requirejs packages I'm using:

requirejs: 2.1.15
grunt-contrib-requirejs: 0.4.4

Thanks.

Upvotes: 2

Views: 671

Answers (3)

Scott Lin
Scott Lin

Reputation: 1562

Although I have resolved my issue with the hints wyantb's answer provided, I've since changed my approach to a single file concat due to the simplicity it brings. I still wanted to post the specifics of how I solved this question's issue for anyone else to happens along it.

In the grunt build configuration options, I added the onBuildWrite field to transform the content, so my assigned module IDs lined up with how I was lazily loading them.

onBuildWrite: function (moduleName, path, contents) {
    return contents.replace(/modules\/global.js/, 'global');
}

This code is specifically for the global.js file. I implemented a similar onBuildWrite for the other module files (in the foreach loop). The transformation will essentially strip the path and extension from the module name that r.js assigns.

Here are some examples of before and after:

Before                                After
/modules/global.js                    global
/modules/dropdown/dropdown.js         dropdown
/modules/loginButton/loginButton.js   loginButton

Therefore, when I load the modules using the HTML script from my original question, requirejs resolves and finds a match.

Upvotes: 1

wyantb
wyantb

Reputation: 101

The name assigned to the dropdown module by r.js processing is "modules/dropdown/dropdown.js". I'm unsure if I should be using this somehow, or if I'm referring to the module correctly as just dropdown and relying on my baseUrl config having the correct path.

In a sense, yes, you should be using that full path. That's what Require refers to as the module id - "modules/dropdown/dropdown" (if the .js in the above output was real, I suggest stripping that extension in the "name" config. .js is assumed by RequireJS, you don't want that string in your module ids). The basePath is used, when given IDs, to transform some unknown ID to a file path (e.g. 'bootstrap' id -> (applying path config) -> '../../lib/bootstrap/js/bootstrap' -> (applying base URL) -> 'scripts/modules/../../lib/bootstrap/js/bootstrap').

Really, though, just allowing r.js to concatenate everything into one file is the preferred way to go. You could use the include option to include modules un-referenced by global.js in with the optimized bundle, too ( https://github.com/jrburke/r.js/blob/master/build/example.build.js#L438 )

As to your specific problem: your lazy require(['dropdown']) call is misleading you. By combining the requested module id with the basePath, RequireJS comes up with the URL you want - scripts/modules/dropdown - which defines a module with the module id scripts/module/dropdown - but since you requested the module id dropdown, you get nothing. (I would've guessed you'd get a RuntimeError instead of undefined, but I suppose that's how things go). One way or another you need to address the id/path mismatches.

Upvotes: 1

hjl
hjl

Reputation: 2802

Either require by path or define global and dropdown in global.cs

require(['./global'], function () {
    require(['./dropdown'], function (dropdown) {
        console.log(dropdown);
    });
});

Upvotes: -1

Related Questions