Skip to content

Instantly share code, notes, and snippets.

@aaronj1335
Created May 11, 2012 15:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aaronj1335/2660344 to your computer and use it in GitHub Desktop.
Save aaronj1335/2660344 to your computer and use it in GitHub Desktop.
relative paths in AMD

Given a project with the files below and a directory structure as follows:

$ tree --charset ascii
.
|-- index.html
|-- js
|   |-- components
|   |   `-- vendor
|   |       |-- jquery-1.7.2.js
|   |       |-- jquery.js
|   |       `-- test.js
|   |-- order.js
|   `-- require.js
`-- nested
    `-- path
        `-- index.html -> ../../index.html

5 directories, 7 files

Note that the file names are below, but the directory structure is not preserved since github gist's don't allow subduers. The directory is served statically (using something like python -m SimpleHTTPServer), with the require.js config set with baseUrl: '/js' and the following URLs are visited:

http://127.0.0.1:8000/
http://127.0.0.1:8000/nested/path/

Here the page load works, but there are two gotchas:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>RequireJS relative path</title>
</head>
<body>
<script src="/js/require.js"></script>
<script>
require.config({baseUrl: '/js'});
</script>
<script>
require(['components/vendor/test'], function() { });
</script>
</body>
</html>
<!-- same as nested/path/index.html -->
define([
'order!./jquery-1.7.2'
], function() {
return window.jQuery;
});
// note the 'define' call instead of 'require'
define([
'./jquery'
], function($) {
if (! $) {
throw Error('jQuery is not defined');
}
});
@jrburke
Copy link

jrburke commented May 11, 2012

This example does not explicitly set a baseUrl to the js directory (by either using data-main or calling requirejs.config()) so the baseUrl is set to the document root. You can change this by doing the following:

<!-- No need for a separate tag for main.js -->
<script data-main="/js/main.js" src="/js/require.js"></script>

Or in main.js doing this before the require call:

requirejs.config({
baseUrl: '/js'
});

I thought the relative path thing on twitter was about doing a './id' dependency reference in a define()d module. Those are supported too, by doing:

define(['./dep'], function (dep) {});

//or use local require

define(function (require) {
    var dep = require('./dep');
});

@aaronj1335
Copy link
Author

This only seems to work if the entry point is at the base of the baseUrl directory, however if the entry point is in a subdirectory, /js/components/vendor/test.js, as in the revision above, it seems work differently:

  • "./" is resolved relative to the baseUrl for the entry-point file, test.js
  • "./" is resolved relative to the require'd module in the case of jquery.js

Is this because test.js is using require() and not define()? Is there a clean way to always resolve "./" relative to the directory of the file specifying the dependency?

@jrburke
Copy link

jrburke commented May 11, 2012

Yes, define() is required for resolution relative to the module needing it. In the above case, the require() is the global require, not a local one that knows its relative position, but define() allows for that.

Also note that module IDs are resolved relative to other module IDs, not relative to paths. So for instance if you have this structure:

  • a.js
  • foo/
    • b.js
    • c.js

and do this kind of config:

requirejs.config({
    paths: {
        'c': 'foo/c'
    }
});

and a.js looks like:

define(function (require) {
    var c = require('c');
});

Then foo/c.js looks like:

define(function (require) {
    var b = require('./b');
});

Since the module ID for foo/c.js is 'c', then its require call is resolved as looking like 'c/./b', which is 'b', which then just gets mapped to baseUrl + 'b.js'.

This is not normally a problem though, only shows up when doing a paths map for a higher level ID to a lower lever/multiple directory argument.

@justinbmeyer
Copy link

Shows up when on the same level too right? Not just lower level?

@aaronj1335
Copy link
Author

it's all starting to come together...

so as long as a module calls define() it can use paths relative to its id, which in typical cases is the same as its path on the server.
the gotcha's are:

  • your entry point script must use define(), even though it typically won't be require'd by anything
  • your entry point must be included with a require() call, if it is included as a <script> tag, require.js throws an error:

Mismatched anonymous define() module

  • the require() call that includes your entry point must be a relative path that's relative to the baseUrl parameter. if it's absolute the "./" paths in the entry point will be from the server root as well.

the updates to the gist allow this to work wether the entry point is at the baseUrl or not, and whether the page is at the server root or not.

so i stand corrected, @justinbmeyer! guess i won't need this path plugin...

@justinbmeyer
Copy link

justinbmeyer commented May 11, 2012 via email

@aaronj1335
Copy link
Author

by "entry point" I mean the first JS that's app-specific, i.e. main.js or index.js. in this example it's test.js.

it's the script you would include in line 15 of index.html, or possibly your data-main attribute (though i don't know if the data-main approach wold actually work in this example).

@justinbmeyer
Copy link

Since the module ID for foo/c.js is 'c', then its require call is resolved as looking like 'c/./b', which is 'b', which then just gets mapped to baseUrl + 'b.js'.

Does this mean it will load incorrectly (baseUrl/b.js is wrong)? My guess is that you meant there's a double mapping, first from b to c, then from c to the base url.

James, if I'm correct in this, why can't the global require still load relative paths? For steal, we assume paths without any prefix [/, ./, etc] are relative to the base url. Isn't requirejs the same way? For example, no matter where I am:

steal('jquery.js')

always does baseUrl/jquery.js. require(['jquery']) will always look in the baseUrl?

Thanks!

@jrburke
Copy link

jrburke commented May 11, 2012

@aaronj1335: yes sounds like you have it worked out now. Typically the entry point JS file is effectively "global" in that it is in the baseUrl, so relative paths are not needed for it, and using require() in there is fine instead of define. If you use data-main="" then the entry point script can use define().

@justinbmeyer: I should have kept going in that example, there is no double mapping. It will want to load it from baseUrl/b.js, since it resolved the relative ID relative to another ID, not a path. This is done because it would not make sense to encode path names in a built file -- those are also meant to be combined with other modules, and they expect to find modules at ID values, not path values.

Your example is correct: saying require(['jquery']) will load from baseUrl/jquery.js (unless of course there is a paths config that says otherwise). So, a global require can use './jquery' relative path, it is just relative to baseUrl.

@justinbmeyer
Copy link

justinbmeyer commented May 12, 2012 via email

@jrburke
Copy link

jrburke commented May 12, 2012

@justinbmeyer: correct. That pattern is what I am using now for this project template:
https://github.com/volojs/create-responsive-template/blob/master/www/js/app.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment