Skip to content
This repository was archived by the owner on Apr 22, 2023. It is now read-only.

Conversation

sgentle
Copy link

@sgentle sgentle commented Apr 27, 2013

Hi,

I ran into some strange behaviour recently when trying to require a file with a .foo.bar style extension that was defined in require.extensions. tryExtensions works fine since it just uses simple string concatenation, but Module.load only passes the last part of the extension (eg .bar) to require.extensions, so it barfs, usually with a cryptic parse error because it's trying to read the file as javascript.

I figured this qualified as a bug (and so pointed the commit at 0.10) because it's inconsistent behaviour between different parts of the module loader, and I can't imagine a situation where you'd add '.foo.bar' to require.extensions and expect it to not be called.

Fixes behaviour where multiple extensions would be picked up by tryFile
but not passed properly to require.extensions, leading to strange parse
errors if multiple extensions are used.
@isaacs
Copy link

isaacs commented Apr 27, 2013

Sorry, the Module system is locked. Changes in this area are too risky.

Even if it wasn't, require.extensions is a wart that we are unfortunately bound to by virtue of backwards compatibility. If I could change that feature, it would be to remove it. Just specify your filenames all the way.

@jashkenas
Copy link

In this case, it's not so much a "change" as a "bugfix". At the moment, Node can properly handle .foo.js or .js.foo files in some cases, but Module.load is unfortunately not one of them.

The immediate use case is for folks who want to write literate-style scripts, where the code is both valid Markdown and valid JavaScript (after a little bit of pre-processing). If Node allows .js.md files to be passed, this is super easy -- but without it, we'd have to mint a new file extension.

If there's some way to "specify the filename all the way" that I'm missing here, let me know. The use case is for the simple: node myscript.js.md

@ricardobeat
Copy link

Changes in this area are too risky.

That's what tests are for, it's not a huge change. If this isn't fixed before 1.0 it never will.

@bnoordhuis
Copy link
Member

I'm somewhat sympathetic to this PR but if there is the slightest chance that it breaks existing code, then I agree with Isaac that it needs to be rejected. The thing that it fixes is not important enough for that.

@jashkenas
Copy link

Fortunately, there isn't even the slightest chance ;) If you examine the code for a minute, you'll see that it's simply looking for each compound extension, from longest to shortest, as a key in Module._extensions -- instead of the current behavior of only looking for the final extension in that object.

Since the final extension is always still checked for, and no one has ever been able to register multi-part extensions in the lookup object before this point, it can't change the behavior of anyones code.

To wander a bit off topic, "if there is the slightest chance that it breaks existing code" doesn't feel like a real argument. If you actually mean that, then there probably needs to be better test cases in Node surrounding the Module._extensions list -- something that @sgentle would probably be happy to contribute, to everyone's better peace of mind. Also, y'all are excellent JavaScript programmers, and certainly knowledgeable enough to read a patch and confirm that it does what it should be doing.

Here are some actual reasons for closing this ticket without fixing it:

  • "I'm too busy to bother looking at it." ... that's fine, have a different Node contributor take a peek.
  • "The Module system being frozen means that no changes or bug fixes will be made to it for any reason whatsoever." ... alright, but if that's the case, it sounds like it's time to fork Node and elect a maintainer who isn't bound by arbitrary constraints to never improve certain parts of the codebase.
  • "I'm antipathetic to users of require.extensions, Alt-JS languages, or anyone with the vague taint of CoffeeScript on them, and don't wish to make any changes that might make their life easier." ... ah-ha, perhaps the most realistic reason, and one that's hard to argue with ;)

And please don't take this comment in ill-humor -- I'm not trying to be combative, but I am trying to be persuasive. I'm looking forward to a day when folks can write beautifully annotated Node libraries, and painlessly view them as rich documents and run them directly with Node.

@myrne
Copy link

myrne commented Apr 27, 2013

I'm looking forward to a day when folks can write beautifully annotated Node libraries, and painlessly view them as rich documents and run them directly with Node.

@jashkenas: Out of interest, are you arguing here for publishing node modules as something else than Javascript?

I write practically all my code in CoffeeScript, but I really like the simplicity for the ecosystem to only deal with javascript (with sourcemaps, if needed). Any beauty and/or increased readability made possible by the alt-js language can be easily found in the module's src directory.

When developing (private) apps however, there's convenience to not having to compile the code manually before running it.

@jashkenas
Copy link

Out of interest, are you arguing here for publishing node modules as something else than Javascript?

No, I'm not. I agree with your comment.

@isaacs
Copy link

isaacs commented Apr 28, 2013

I'll say it again:

If I had a time machine, or could do so without breaking a bunch of extant JavaScript programs, I'd do away with require.extensions entirely. Relying on Node auto-extension-ing your files is an antipattern that I find disagreeable and destroys more value than it creates.

So, no, we're not going to make it more powerful to use this anti-feature. Don't use require.extensions. Ever, for anything. It's terrible and obnoxious. Giving Node the power to load something other than Node modules was a giant mistake that I will regret forever.

Compile your code to JavaScript if it's not already. Load it using the built-in JavaScript loader. Specify the filename fully.

@ricardobeat
Copy link

I fail to see how the best course of action is to keep a broken feature in core untouched, or why require.extensions is an antipattern or "destroys value". It's a very simple feature that caters to real-world needs of node users.

Specifying the filename fully makes code not as portable. Right now we can move between .js and any compile-to-js language while keeping the source unchanged.

Using require extensions, instead of pre-compiling to .js, also makes it easier to not commit generated js files to a project without adding specific rules to a project's .gitignore, thus it's easier to mix plain js with other languages, have a Gruntfile.js with .coffee sources, or a index.js that does require('coffee-script'); require('./app') for deployment.

@isaacs
Copy link

isaacs commented Apr 29, 2013

It's a very simple feature that caters to real-world needs of node users.

They're all simple features on their own. What real-world need does it cater to?

Specifying the filename fully makes code not as portable. Right now we can move between .js and any compile-to-js language while keeping the source unchanged.

Or, even better, you can specify .js in your code, and compile your compile-to-js to js.

Node is a JavaScript platform, built on a JavaScript interpreter. I don't think it's unreasonable that you give it JavaScript, or that you tell it the path to the file that you want it to run.

If you must run the coffee-script files directly, run them with the coffee command which is installed with the coffee-script package. require('coffee-script');require('./app.coffee') is 100% unnecessary.

I don't see any reason why require.extensions should exist, except for backwards compatibility.

@jashkenas
Copy link

Just go ahead and remove require.extensions then. Before Node 1.0 is a fine time to remove things you don't want to have around, and if you really think it "destroys value", then everyone will be better off without it, and you'll certainly be bothered less.

Leaving it half-implemented leads to more headaches for folks who either try to use it, or get hassled by folks that want to use it. Fixing it or killing it are both paths to a More Perfect Node™ -- leaving it broken isn't really.

@michaelficarra
Copy link

100% agreed with @jashkenas. Either remove it or fix it. It would really be an unwise decision to leave it as is.

👍 require.extensions working on suffixes instead of just extensions.

edit: rephrasing

@isaacs
Copy link

isaacs commented Apr 29, 2013

@jashkenas Would you accept a patch to make coffee-script not use this any more?

@jashkenas
Copy link

Yep. If you're not willing to support it in Node, I'll certainly be happy to remove it from CoffeeScript ... as soon as the deprecation/removal patch lands on Node master.

@isaacs
Copy link

isaacs commented Apr 29, 2013

@jashkenas 7bd8a5a and isaacs/coffee-script@8302a15 (Note: lots of "Unexpected token: INVALID" on the cofeescript tests with isaacs/coffee-script@8302a15)

@jashkenas
Copy link

With the caveat that none of the things that you assert in your commit message are in the slightest way true ... especially now that source maps are up and running properly -- thanks for the patch. I'll merge a version of it with the tests running under older-style loading.

@ricardobeat
Copy link

require('coffee-script'); require('./app.coffee') is 100% unnecessary.

Yes. On the other hand you are clearly not a coffee-script user, so that is just an opinion. Many things in node core are not strictly necessary, but there for convenience, isn't that the point of a standard library? Disregard this example.

I might be misguided, but removing support for require.extensions in coffee-script means it will be forced to either monkey-patch require(), or write all compiled .js to the filesystem when using the coffee binary so that require works. That will make it useless, and enormously raise the entry cost for using any kind of compile-to-js language in node, by making build/watch tools a pre-requisite.

CoffeeScript is the 6th most popular module in node.js, with ~300.000 downloads per month. It's more popular than connect, socket.io, wildly more than jshint. And it makes me money. That's real enough for me.

The Node experiment with JIT compile-to-js on require() is a failure

Care to elaborate? I think it's a complete success, if not for bugs like this. Javascript is becoming a compile target, even Mozilla and Brendan have already acknowledged and encouraged it.

@isaacs
Copy link

isaacs commented Apr 29, 2013

@ricardobeat I'm not complaining about compile-to-js as such. I'm complaining about the unnecessary complexity added to Node's module system in order to compile coffeescript to JS just in time at require() time. Brendan Eich's opinions on the matter have nothing to do with this case. He's not a Node contributor, he's the CTO of a browser company. Please refrain from appeals to authority.

Compile your CoffeeScript to JavaScript, or run your coffee programs with the coffee interpreter.

The require.extensions approach is unnecessarily convoluted, and not any more portable. It encourages depending directly on the coffee-script module, which 1298 modules in npm do today. Most of those are not in any way related to coffee-script directly, but are merely written in the CoffeeScript language, and doing generic node stuff.

Many of them unnecessarily rely on not only downloading an extra dependency, but also running a brittle install script when they could instead use a deterministic approach, compiling their CoffeeScript ahead of time, and publishing JavaScript to npm. Many others rely on require.extensions, which is a shared global mutation that cannot be reliably un-done. (What happens if two coffee-script modules rely on different versions of coffee-script?)

I'd like to repeat: I'm not complaining about CoffeeScript, or any other compile-to-js module. I'm complaining about the unnecessary complication added to Node programs by having require.extensions, by using extension-less filenames in require(), and using require.extensions to do just-in-time compilation to javascript.

I think it's a complete success, if not for bugs like this.

Sure. Except for all the failures, bugs, unnecessarily complexity, tight-coupling, global mutation, and brittleness, it's a resounding success.

If you compile your CoffeeScript ahead of time, there are zero problems. If there is a require.extensions hook, no one will do that. That is why I say it was a huge mistake to add that feature. And the same commitment to stability that keeps me from removing it also keeps me from making changes like the one suggested here.

@isaacs
Copy link

isaacs commented Apr 29, 2013

none of the things that you assert in your commit message are in the slightest way true ... especially now that source maps are up and running properly

@jashkenas I don't see how source maps are relevant. My complaint is about the brittleness of JIT compilation via require.extensions hooks, not with compile-to-js as such.

@vendethiel
Copy link

It encourages depending directly on the coffee-script module, which 1298 modules in npm do today.

This is definitely a big problem. ALL npm modules (for node) should be in npm as js files.

run them with the coffee command which is installed with the coffee-script package.

but that means you have to precompile everything to simply run it

@Mithgol
Copy link

Mithgol commented Apr 29, 2013

Coffeescripts should be compiled to JavaScript in npm prepublish scripts. That's a textbook example of what prepublish scripts are for.

@geNAZt
Copy link

geNAZt commented Apr 29, 2013

Well i use the require.extensions for loading private modules where people paid for.
They should pretend you from copyin the whole software onto another box.
If you deprecate the require.extensions, i would not be able to write such crypted javascript anymore.
Its no option to decrypt the code before starting the app.
Goal is to never touch the disk with the original sources

@isaacs
Copy link

isaacs commented Apr 29, 2013

In node, "Deprecated" means "Please don't use this. It might have bugs, and we won't fix them. It was a bad idea, and you should find other options if at all possible. It MAY disappear or break in the future." See: https://github.com/joyent/node/wiki/deprecation

Deprecation is not a promise that it WILL be removed, merely a statement that we really really think you shouldn't be relying on it, and that it is officially unsupported.

Since require.extensions is buried deep in the module system, which is locked (and thus, unlikely to ever change), it's probably not going to go anywhere any time soon.

@geNAZt
Copy link

geNAZt commented Apr 29, 2013

Well i think its bad to not support it. And deprecation says it CAN be removed. So i can not build on it and be sure it works on every version the same way.

@isaacs
Copy link

isaacs commented Apr 29, 2013

So i can not build on it and be sure it works on every version the same way.

@geNAZt More importantly, judging by the response to this bug, you cannot be sure that bugs you find in it will ever be fixed!

@geNAZt
Copy link

geNAZt commented Apr 29, 2013

Well i can be sure if you set the require.extensions thing to WONTFIX, that it acts the same way in every version. If you say one point extensions are enough then it is okay. But to turn off this feature and say "compile" anything to javascript to use it in node is not the best solution it think.

@ricardobeat
Copy link

@isaacs that wasn't an appeal to authority, just goes to show the direction that JS is heading. This freeze is effectively shutting out innovation from the ecosystem, like supporting .xxx.md literate files.

"Global mutations" is a red-herring; nobody will ever need two versions of coffee-script in a single project, just as they won't need two nodejs versions. Publishing a module depending on coffee-script is in the same league as committing node_modules to your repo, or including huge archives in a npm package.

Computers are completely capable of compiling to javascript on-demand, without any hit on application performance, and I prefer not to have machine-generated code hitting the disk. require.extensions work just fine today. Extension-less module loading is a nice convenience feature, and aligns with good programming practices (DRY etc.), AMD and CommonJS. Removing it would be throwing the baby out with the bathwater.

Anyway, this discussion is clearly going nowhere and boils down to personal preference. Let's see where we're at in a year or two.

@isaacs
Copy link

isaacs commented Apr 30, 2013

"Global mutations" is a red-herring; nobody will ever need two versions of coffee-script in a single project

Actually, that example was taken from a real life bug. I've had long frustrating discussions with actual humans about why I'm not going to even entertain the possibility of making require.extensions a prototype chain that inherits from the parent module so that you can have two different require.extensions['.coffee'] functions that don't clobber one another. But it was a few years ago, iirc, so perhaps coffeescript is more stable now and it's less of an issue.

@martinheidegger
Copy link

I am trying to find a good solution to this, not unsolvable - I think, problem. Have you ever given a proper extension system a thought? If the supported modules were referred in the package.json then multiple sub-modules could have their language support:

"fileSupport": {
".coffee": "coffee-script"
}
"dependency": {
"coffee-script": "2.0.0"
}

It would allow people to enjoy their syntax of choice for any files and it wouldn't break any logic. The coffee-script project could then extend their install process to something like:

sudo npm install cpm -g

cpm init . // analog to npm init, with the extended fileSupport parameters

If coffee-script would be deployed using this strategy, others will join too and overtime the global .extensions hook will become really obsolete. (as people have a common way to deal with file formats)

Also this would make your logic cleaner/easier and encourage the use of npm/package.json even more.

Wishful thinking?

@sgentle
Copy link
Author

sgentle commented May 6, 2013

@isaacs: it seems to me that there are two distinct issues here, which is leading to some confusion.

In terms of your position as reigning node and npm monarch, you have the right to take strong stances on node-as-an-ecosystem, including things like JIT compile-to-js in npm and whether certain patterns of use should be encouraged or discouraged. I think people here largely agree with you on that front and respect the work you're doing to make node a platform where it's easier to write good code than bad code.

However, that can't supersede our right as developers to use that platform as we like. I hope you'll agree that there is something deeply unsettling about being told what workflow you're allowed to use by someone without the context to make that decision properly. It feels eerily similar to that "Steve didn't think anyone would want to do this so there's no way to do it" feeling I get with my Apple products sometimes. Ultimately, if I want to JIT compile in my own projects, for my own reasons, ain't nobody going to stop me because it's my damn machine running my damn code.

So here's what I propose:

  1. Get rid of require.extensions. It is, as you've noted, pretty gross for a number of reasons. Do it now, before the drag of history gets even worse. You say you don't want to break compatibility, but it's not helping anyone to have the feature slowly drown in bitrot either. I spent hours trying to figure out why requiring a .coffee.md file didn't work. I could have spent those hours enjoying the sunshine and/or internet if I'd got a simple "no" instead.
  2. Provide a way for people who use require.extensions to get what they want from a module. There's precedent for this - Fibers, for example, uproots the node paradigm so completely as to make require.extensions look like a missing semicolon. I could write an npm module right now that emulates require.extensions by hooking the module loader (inspired by, ahem, coffee-cleanse). That's just as gross, though, so I'd rather have a clean way to add non-isaacs-approved module loading into my code. I noticed a proposal floating around for require middleware. That would be a sane option.
  3. Prevent anything depending on the runtime behaviour of an alternate module loader from being published to npm. You've said npm is for js only - make it so! I think it's fair to enforce "plays well with others" policies if you want your ecosystem to be healthy: I can do what I want in private, but to be on npm I have to play by npm's rules. If you're concerned about this, it could be grandfathered in by, say, only applying to modules targeting node 0.12. It's not like adding a pre-publish script takes that much work. Someone will probably even write one of those github pull request robots for it.

The consequence for you would be that you can now disavow all knowledge of altjs and require.extensions. For people like myself who like being able to require non-js files directly, we can continue to do so where it makes sense for us, up to the point where we want our code to be part of the js-only node ecosystem. We can fix (or even improve!) require.extensions, and probably do other more exciting things besides.

We get to keep our workflow, you get a victory in your quest to build a more perfect node. Everyone wins.

@isaacs
Copy link

isaacs commented May 6, 2013

@sgentle You're neglecting the dramatic difference in cost, as well as my personal commitment to backwards compatibility.

The cost of leaving things as they are is close to zero. If you want to build a better require.extensions, no one is stopping you. Why haven't you built that yet? Simple: It's silly feature no one actually needs, the only purpose of which is to split the node community and limit the utility of modules you write with it.

When no one relies on require.extensions, we'll remove it. Until then, it stays where it is.

@myrne
Copy link

myrne commented May 6, 2013

@isaacs Would it be sensible - given require.exentions's deprecation status - to make npm install warn when encountering a dependency loaded via npm (git dependencies would not apply) that in turn has a dependency which is known to use require.extensions (i.e. coffee-script)? Maybe mark them with a "bad" label on npmjs.org?

I just realized this won't work well. Packages could depend on coffee-script for other reasons than loading coffee files at runtime.

Maybe write different kind of detection code? Maybe see if there are coffee files without associated js files (indicating they were most likely pre-compiled)? This may be relatively hard to do for existing packages, but wouldn't be too hard at publish time.

@isaacs
Copy link

isaacs commented May 6, 2013

I'd like to only mark packages (or rather, nudge authors) that actually use this feature, not just ones that happen to depend on coffee-script. There are some new cool things coming soon that will make this possible :)

@myrne
Copy link

myrne commented May 6, 2013

@isaacs I assume you're replying from email. See my edit for my a better suggestion. But you might have found something better still. Waiting patiently for the cool things. ;)

@robertkowalski
Copy link

i am looking foward to this

@sgentle
Copy link
Author

sgentle commented May 6, 2013

@isaacs I don't suppose sending the maintainer of the current require.extensions implementation a pull request would qualify as "building a better require.extensions", would it?

Instead of chalking up the lack of an improved system to my disinterest, it might make more sense to assume the simple thing: that I'm writing messages and sending you pull requests not because I'm so disinterested that I've somehow wrapped around right back to motivated action, but rather because I am trying to engage in good faith with the open source process to find a decent solution that will satisfy both you and people who use this feature.

Since that hasn't worked, I'm happy to solve this in a way that suits me by - as you say - just writing a better require.extensions.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.