How to use relative paths in JS's new `require()`?

Tom Swirly's icon

Hello again.

I'm trying to convert my repository over to use Max 7's require(). It works fine if I provide the absolute path of my Javascript files, but I haven't figured out to get relative paths to work.

You can see test code here!

I have a lot of files in hierarchical subdirectories, some of which depend on each other. For my application at least, I'd prefer that all includes be relative to the original "top-level" .js file, wherever that may be.

Jan M's icon

Not in front of Max right now, but you could open the toplevel js (needs to be inside the search payh) with the file object, read its parent folder and store it as a base path for the includes/requires.

Tom Swirly's icon

That seems suboptimal (no insult intended at what seems to be a possible "good hack"). ;-)

For one thing, aren't we getting away from the search path with Max 7?

Much worse is that I'd have to copy all that code into each file that included another .js - which is at this moment is 85 .js files (though not all of these are current). That's an awful lot of duplicated code...

Relative paths are required by the CommonJS spec, so it's quite likely that this is an oversight that will be fixed in an upcoming release.

(EDIT: I also realized that the CommonJS spec indicates that you can and should omit the .js in the filename, and that doesn't seem to work either. These things happen - it's early days yet! :-) )

Jan M's icon

I get your point. Have you tried just the filename (without path)? If your code is part of a package maybe they are recognized?

Jan M's icon

oh just saw you tried filename only. but are they part of a package/in the search path?

Tom Swirly's icon

The code sample has a list of all the possible paths I've tried - and I uploading three possible files too.

So I did quite a bit more testing here and did find one case that works - but I found another "feature deficiency" as well. ;-) (This is all on a vanilla Mac OS/X 10.9 "The Decider" "Mavericks".)

Basically, anything involving loading .js and subdirectories doesn't work.

First, your .js file has to be in the same directory as your .maxpat file. If you save the .js in a subdirectory, it will not be found when you close and open the patch again. (This worked fine in Max 6.)

If you open the .maxpat in an external text editor, you can see that it does seem to know about the subdirectory, but the actual JS when you close and reopen the box becomes blank.

Second, any require statements you use must also reference files in that same directory as your .maxpat file.

This kinda works, but my library has 113 .js files and over a dozen subdirectories so "throwing them all in the same directory" is a suboptimal solution.

As I said, these are very early days in Max 7 and you expect such glitches. The CommonJS spec requires relative paths to work, so I'm highly confident that this will get fixed at some point. I'm hoping also to again be able to use .js that's not in the same directory as the .maxpat. :-)

Thanks for reading!

Tom Swirly's icon

Oh, didn't get that last comment till after I finished. Good suggestion!

The patch wasn't in the path initially, but I made sure to add it to the path and restart. No change!

Jan M's icon

Hi Tom,

i poked around a little with it and actually it works as I would expect: I'd say the most elegant way of using include/require outside your project is to create a package containing your JS library (put your folder structure inside the "javascript" folder of your package). You don't need any paths to include/require. Just write: include("myJsFile") and it is found.

As no location needs to be specified you might want to use pseudo-class-prefixes for your filenames in order to avoid duplicate filenames when working with large amounts. This is what I have in mind:

(let's say the package is called "ts")

folder:ts
   -> folder:javascript
        -> file:ts.file001.js
        -> folder:something
            ->ts.something.file002.js

btw: both versions include("myJsFile")/include("myJsFile.js") work fine here.

Cheers.

Tom Swirly's icon

What you're proposing is that I put everything into the system path, and turn on "use subdirectories", and don't use subdirectory paths when referring to .js files, and rename all my files so that they include their path names (to avoid collisions).

That "works" but I probably won't put it in unless it's clear that this issue won't be fixed. CommonJs is clear that you have to respect paths; Max 6 respected paths for Javascript; I don't see any reason that Max 7 shouldn't do likewise!

For the moment, I can keep my current workflow of "compiling" into a .jso and see what shakes out.

Jan M's icon

Yes, just with the difference that packages are always included in the search path (including subdirectories). You don't need to add them manually.
You are right about the CommonJS specs, but i guess it's a conceptional point of view if this is a "bug". The Max path paradigm is simply different from the one of common JS: To load an abstraction or object the filename is the only reference (same for files loaded into [js]) therefor the filename has to be unique.

Under this paradigm there is no relative path. It would be interesting to hear what Cycling '74 thinks about that. I'd say the way it is implement now is coherent within the Max paradigm and therefore the CommonJS paradigm is implemented only as close as possible.

If it would be implemented strictly that would make things with Max more confusing: Think about an abstraction that uses a JS script from a package: as soon as the location of the abstraction would change (which it can according to the Max approach) the include/require functionality might break.

Lots of words ... :) in short I would be surprised if it changes.

It's the same for the file extensions: within Max js files can be loaded with and without .js. Therefore it makes sense to stay closes to the Max specs and to make it optional.

Jan M's icon

Think about an abstraction that uses a JS script from a package: as soon as the location of the abstraction would change (which it can according to the Max approach) the include/require functionality might break.

well, that's wrong :) the location of the loaded js would be the root...

Tom Swirly's icon

> To load an abstraction or object the filename is the only reference (same for files loaded into [js]) therefor the filename has to be unique

The filename - *including* a path - would continue to be unique. Relative paths *did* work in Max 6, so it's not like it was impossible. (What's interesting is that my Max 6 patches that contain Javascript in a relative path continue to function, but if I edit them and save the JS again, they no longer work...)

> it’s a conceptional point of view if this is a "bug".

I don't buy that argument. Specifications aren't like the theory of art! :-)

There's a specification which says what a CommonJs module is, what a path is, and so on - and that specification requires absolute and relative paths to be recognized.

Following the CommonJS specification isn't just a check-off box. It gives you automatic access to a zillion packages from node.js and other Javascript package collections. Most of these are browser-oriented - many of them are not and do really useful things you might want to use in your own code.

(And basically all the node.js packages use relative and absolute paths.)

And it really isn't very difficult. The CommonJS spec fits into a single page.

Overall, the idea that all your .js files are in a big flat space and then found without respect to path (with collisions invisibly resolved in some arbitrary fashion) is a fragile way to work. One day you're going to drop in some other package and get unresolvable collisions which you might not even notice until you go to try to use that section of your program.

Edsko de Vries's icon

6 years later, I am running into the same issue. Does that mean that Jan's point of view was indeed shared by Cycling '74 and require does not support a module hierarchy? Everything must live in a single directory, and filenames should be unique?