Modules 1.1.1 Edge Cases

150 views
Skip to first unread message

Jasper St. Pierre

unread,
Jan 3, 2014, 12:45:53 PM1/3/14
to comm...@googlegroups.com
Hi. I'm implementing the Modules 1.1.1 spec in our own JavaScript environment (gjs). I have a few questions about edge cases of Modules behavior.

1. All Modules 1.1.1 requires is that there's a free variable called `require` that conforms to the definition above. Is it defined if `require` is observably global? For instance, if in module A, I do:

    // module A
    var B = require('B');
    require.paths.push('ThisIsAPath');

will I see this change to `require.paths` in B? Does the answer change if I instead do:

    // module A
    require.paths.push('ThisIsAPath');
    var B = require('B');

?

2. require(); takes a slash-delimited list of terms that describe a tree layout. It also allows relative lookup by starting with the terms .' and '..'.

Is relative lookup allowed anywhere, or only as the starting term? That is, are any of these supported?

    require('foo/./bar');
    require('foo/../bar');
    require('././bar');
    require('./../bar');
    require('../../bar');
    require('.././bar');

3. If my module is at the "toplevel" in the tree of modules, is it defined what happens if the user does a relative lookup outside of this using '..' terms? Will it search `require.paths`?

4. Are empty terms considered illegal or undefined, or should I ignore them? How should I behave with these calls?

    require('foo//bar');
    require('/foo/bar');
    require('foo/bar/');

--
  Jasper

Sam L'Ecuyer

unread,
Jan 3, 2014, 1:50:03 PM1/3/14
to comm...@googlegroups.com
In the spec, 

1)
Require 1.6.2:
  1. The "paths" attribute must be referentially identical in all modules.
I would read that to mean that if require.paths exists, all updates to the path list will be available to all modules in the runtime.  The implementations I've seen (and written myself) wind up wrapping the module body in a function declaration with a parameter called "require" and invoking it.

2)
Module Identifiers:
  1. A module identifier is a String of "terms" delimited by forward slashes.
  2. A term must be a camelCase identifier, ".", or "..".
I don't see why any of those would be illegal, albeit unnecessary.

3)
Yes, it's defined:
  1. Module identifiers may be "relative" or "top-level". A module identifier is "relative" if the first term is "." or "..".
  2. Top-level identifiers are resolved off the conceptual module name space root.
  3. Relative identifiers are resolved relative to the identifier of the module in which "require" is written and called.


--
You received this message because you are subscribed to the Google Groups "CommonJS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to commonjs+u...@googlegroups.com.
To post to this group, send email to comm...@googlegroups.com.
Visit this group at http://groups.google.com/group/commonjs.
For more options, visit https://groups.google.com/groups/opt_out.



--
Thanks,
Sam L'Ecuyer

Kris Kowal

unread,
Jan 3, 2014, 2:06:52 PM1/3/14
to CommonJS
Some implementations use a global "require", while others have distinct "require" functions. If you are writing portable code, you cannot guarantee that monkey patching a property on require will be reflected in other modules. If you are writing an implementation, you can encourage folks to write portable code by guaranteeing a unique "require" function in every module.

"require.paths" was always stop-gap today is an anti-pattern. Narwhal discouraged its use at run-time and would initialize it using a topological sort of the transitive dependencies. Node.js does not have it at all, and goes on to mandate that top-level identifiers must reach into dependencies, requiring relative identifiers for modules inside the same package.

If you do support "require.paths", I recommend copying it as a property of each module’s "require" function. That will satisfy all of the requirements of the spec. That is, replacing "require.paths" on one module’s "require" will have no effect, but changes to the content of any "require.paths" will, as in "require.paths.push(path)".

Kris Kowal

Jasper St. Pierre

unread,
Jan 3, 2014, 2:58:16 PM1/3/14
to comm...@googlegroups.com


On Friday, January 3, 2014 1:50:03 PM UTC-5, Sam L'ecuyer wrote:
In the spec, 

1)
Require 1.6.2:
  1. The "paths" attribute must be referentially identical in all modules.
I would read that to mean that if require.paths exists, all updates to the path list will be available to all modules in the runtime.  The implementations I've seen (and written myself) wind up wrapping the module body in a function declaration with a parameter called "require" and invoking it.

Aha, thanks. It seems I missed that.
 
2)
Module Identifiers:
  1. A module identifier is a String of "terms" delimited by forward slashes.
  2. A term must be a camelCase identifier, ".", or "..".
I don't see why any of those would be illegal, albeit unnecessary.

The requirement for a relative lookup is that the first term is "..". It does not say anything about what happens with subsequent terms. That's why I'm wondering if 'foo/../bar' is equivalent to 'foo/bar' or not.
 
3)
Yes, it's defined:
  1. Module identifiers may be "relative" or "top-level". A module identifier is "relative" if the first term is "." or "..".
  2. Top-level identifiers are resolved off the conceptual module name space root.
  3. Relative identifiers are resolved relative to the identifier of the module in which "require" is written and called.

Right, but if I'm at the root of the module tree, so to speak, then doing a relative lookup of '../' seems to be ill-defined. If my `require.paths` is ['/foo/bar/'], and the toplevel module calls require('../baz');, then I see three possible things happening:

* require('../baz'); throws an error.

* require('../baz'); is treated the same as require('./baz'); or require('baz');

* require('../baz'); loads the module with the path '/foo/baz'.

And no comments on 4) ?

 

Sam L'Ecuyer

unread,
Jan 3, 2014, 5:19:23 PM1/3/14
to comm...@googlegroups.com
Relative lookups would indicate that the path starts with a "." or ".." but is otherwise a valid module identifier.  "." and ".." are allowed in valid paths.  'foo/../bar' would indicate that 'bar' is not in 'foo', and would never be equivalent to 'foo/bar'.

Presumably, if you have [/libs/foo.js; /app/main.js;] and you're running main.js, require('../libs/foo') should work but your implementation may not allow this based on your system's security model.  Whatever your loader does, "require('../baz'); is treated the same as require('./baz');" should not be the case.


Require 1.6.5: 

If the "paths" attribute exists, it may not be an exhaustive list of search paths, as the loader may internally look in other locations before or after the mentioned paths.

As Kris mentioned, require.paths was an optional and discouraged stop-gap when it was included.  Some systems don't support it.  

4)
Require 1.6.6: 
If the "paths" attribute exists, it is the loader's prorogative to resolve, normalize, or canonicalize the paths provided.

I didn't write the language there, but I'd assume that it's generally the loader's prerogative to normalize paths.  That's probably up to you.

Reply all
Reply to author
Forward
0 new messages