-
-
Notifications
You must be signed in to change notification settings - Fork 33.5k
module: support multi-dot file extensions #23416
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a0a401d
47ca0eb
06f1afd
64aab09
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -219,6 +219,22 @@ function tryExtensions(p, exts, isMain) { | |
| return false; | ||
| } | ||
|
|
||
| // find the longest (possibly multi-dot) extension registered in | ||
| // Module._extensions | ||
| function findLongestRegisteredExtension(filename) { | ||
| const name = path.basename(filename); | ||
| let currentExtension; | ||
| let index; | ||
| let startIndex = 0; | ||
| while ((index = name.indexOf('.', startIndex)) !== -1) { | ||
| startIndex = index + 1; | ||
| if (index === 0) continue; // Skip dotfiles like .gitignore | ||
|
||
| currentExtension = name.slice(index); | ||
| if (Module._extensions[currentExtension]) return currentExtension; | ||
|
||
| } | ||
| return '.js'; | ||
| } | ||
|
|
||
| var warned = false; | ||
| Module._findPath = function(request, paths, isMain) { | ||
| if (path.isAbsolute(request)) { | ||
|
|
@@ -600,8 +616,7 @@ Module.prototype.load = function(filename) { | |
| this.filename = filename; | ||
| this.paths = Module._nodeModulePaths(path.dirname(filename)); | ||
|
|
||
| var extension = path.extname(filename) || '.js'; | ||
| if (!Module._extensions[extension]) extension = '.js'; | ||
| var extension = findLongestRegisteredExtension(filename); | ||
| Module._extensions[extension](this, filename); | ||
| this.loaded = true; | ||
|
|
||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| 'use strict'; | ||
|
|
||
| // Refs: https://github.com/nodejs/node/issues/4778 | ||
|
|
||
| const common = require('../common'); | ||
| const assert = require('assert'); | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const Module = require('module'); | ||
| const tmpdir = require('../common/tmpdir'); | ||
| const file = path.join(tmpdir.path, 'test-extensions.foo.bar'); | ||
| const dotfile = path.join(tmpdir.path, '.bar'); | ||
| const dotfileWithExtension = path.join(tmpdir.path, '.foo.bar'); | ||
|
|
||
| tmpdir.refresh(); | ||
| fs.writeFileSync(file, 'console.log(__filename);', 'utf8'); | ||
| fs.writeFileSync(dotfile, 'console.log(__filename);', 'utf8'); | ||
| fs.writeFileSync(dotfileWithExtension, 'console.log(__filename);', 'utf8'); | ||
|
|
||
| { | ||
| require.extensions['.bar'] = common.mustNotCall(); | ||
| require.extensions['.foo.bar'] = common.mustCall(); | ||
| const modulePath = path.join(tmpdir.path, 'test-extensions'); | ||
| require(modulePath); | ||
| require(file); | ||
| delete require.cache[file]; | ||
| delete require.extensions['.bar']; | ||
| delete require.extensions['.foo.bar']; | ||
| Module._pathCache = Object.create(null); | ||
| } | ||
|
|
||
| { | ||
| require.extensions['.foo.bar'] = common.mustCall(); | ||
| const modulePath = path.join(tmpdir.path, 'test-extensions'); | ||
| require(modulePath); | ||
| assert.throws( | ||
| () => require(`${modulePath}.foo`), | ||
| new Error(`Cannot find module '${modulePath}.foo'`) | ||
| ); | ||
| require(`${modulePath}.foo.bar`); | ||
| delete require.cache[file]; | ||
| delete require.extensions['.foo.bar']; | ||
| Module._pathCache = Object.create(null); | ||
| } | ||
|
|
||
| { | ||
| const modulePath = path.join(tmpdir.path, 'test-extensions'); | ||
| assert.throws( | ||
| () => require(modulePath), | ||
| new Error(`Cannot find module '${modulePath}'`) | ||
| ); | ||
| delete require.cache[file]; | ||
| Module._pathCache = Object.create(null); | ||
| } | ||
|
|
||
| { | ||
| require.extensions['.bar'] = common.mustNotCall(); | ||
| require.extensions['.foo.bar'] = common.mustCall(); | ||
| const modulePath = path.join(tmpdir.path, 'test-extensions.foo'); | ||
| require(modulePath); | ||
| delete require.cache[file]; | ||
| delete require.extensions['.bar']; | ||
| delete require.extensions['.foo.bar']; | ||
| Module._pathCache = Object.create(null); | ||
| } | ||
|
|
||
| { | ||
| require.extensions['.foo.bar'] = common.mustNotCall(); | ||
| const modulePath = path.join(tmpdir.path, 'test-extensions.foo'); | ||
| assert.throws( | ||
| () => require(modulePath), | ||
| new Error(`Cannot find module '${modulePath}'`) | ||
| ); | ||
| delete require.extensions['.foo.bar']; | ||
| Module._pathCache = Object.create(null); | ||
| } | ||
|
|
||
| { | ||
| require.extensions['.bar'] = common.mustNotCall(); | ||
| require(dotfile); | ||
| delete require.cache[dotfile]; | ||
| delete require.extensions['.bar']; | ||
| Module._pathCache = Object.create(null); | ||
| } | ||
|
|
||
| { | ||
| require.extensions['.bar'] = common.mustCall(); | ||
| require.extensions['.foo.bar'] = common.mustNotCall(); | ||
| require(dotfileWithExtension); | ||
| delete require.cache[dotfileWithExtension]; | ||
| delete require.extensions['.bar']; | ||
| delete require.extensions['.foo.bar']; | ||
| Module._pathCache = Object.create(null); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're only interested in the extension, is calling
path.basename()necessary? You could just scan backwards for dots, checking that there are no path separators in between:Only one slice == less garbage to collect.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current algorithm scans forwards to short-circuit at the longest matched extension. So say you have a file like
mr.robot.coffee.md, and a loader registered for.coffee.md. The current algorithm will iterate like:.robot.coffee.mdregistered? No, continue..coffee.mdregistered? Yes, break.This way, even if
.mdis also registered, the.coffee.mdloader takes precedence. This allows multi-dot extensions like.es6.js, for example.If I’m reading your code correctly, it’s checking only the longest possible multi-dot extension, which for this example would be
.robot.coffee.md, a false match. We want multi-dot extensions to work without prohibiting periods elsewhere in the filename.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps naming the function
findRegisteredExtensionwould be more self-descriptive?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@richardlau Yes, or perhaps
findLongestRegisteredExtensionwould be even more descriptive (because we’re returning.coffee.mdinstead of.md, if both of those happened to be registered).