Node.js has different resolve algorithm for require
(hereinafter, called "CJS resolver") and import
(hereinafter, called "ESM resolver").
Spec of CJS resolver: https://nodejs.org/api/modules.html#all-together Spec of ESM resolver: https://nodejs.org/api/esm.html#resolver-algorithm-specification
Vite needs to support both of them.
Specifier is a string pass to import
in ESM or require
in CJS.
import 'foo'
import('foo')
require('foo')
For example, foo
is a specifier in this case.
CJS resolver treats specifier as a simple file path. ESM resolver treats specifier as a URL unless it's a bare specifier.
So there's a difference between them handling special characters in URL.
module
field points to a ESM file. But because Node.js doesn't use that field, there are many files that won't work with ESM resolver in wild. So it needs to use CJS resolver.
Support all feature of ESM resolver and fallback to CJS resolver where it is possible.
[ESM] means that step is related to ESM resolver. [CJS] means that step is related to CJS resolver.
condition: conditions to use for exports/imports
is_windows: whether it is running on Windows
RESOLVE(id, importer): (id, importer is both path)
- [ESM/CJS] Let resolved be undefined.
- [ESM/CJS] If id begins with "./" or "/" or "../" or (is_windows and matches /^\w:/)
- PATH_RESOLVE(id, importer)
- [ESM/CJS] Otherwise, if id begins with "#", then
- Set resolved to the result of PACKAGE_IMPORTS_RESOLVE(id, importer, condition).
- RESOLVE_ABSOLUTE(resolved)
- [ESM/CJS] Otherwise, if id matches /^\w+:/, then
- Note: CJS doesn't support URL protocols.
- [ESM] If id begins with "file:" or "data:" or "http:" or "https:" or "node:", then
- [ESM] RESOLVE_ABSOLUTE(id)
- [ESM/CJS] Fallback to other rollup/vite plugins. STOP
- [ESM/CJS] Otherwise,
- Note: id is now a bare specifier.
- Set resolved to the result of PACKAGE_RESOLVE(id, importer).
- If resolved is undefined, fallback to other rollup/vite plugins. STOP
- RESOLVE_ABSOLUTE(resolved)
RESOLVE_ABSOLUTE(id):
- [ESM] If id begins with "node:", then
- resolve that. STOP
- [ESM] If id begins with "file:", then
- If fileURLToPath(id) exists, resolve that. STOP
- Throw "not found"
- [ESM] If id begins with "data:" or "http:" or "https:", then
- resolve that. STOP
- [ESM/CJS] If id exists, resolve that. STOP
- [ESM/CJS] Throw "not found"
PATH_RESOLVE(id, importer)
- [ESM/CJS] Let absolute be path.resolve(importer, id).
- [ESM/CJS] LOAD_AS_FILE(absolute)
- [CJS] LOAD_AS_DIRECTORY(absolute)
- [ESM/CJS] Throw "not found"
LOAD_AS_FILE(id)
Same with LOAD_AS_FILE of CJS resolver
LOAD_INDEX(id)
Same with LOAD_INDEX of CJS resolver
LOAD_AS_DIRECTORY(id)
Same with LOAD_AS_DIRECTORY of CJS resolver
PACKAGE_RESOLVE(id, importer)
Similar with PACKAGE_RESOLVE of ESM resolver.
- [ESM/CJS] Let packageName be undefined.
- [ESM/CJS] If id is an empty string, then
- Fallback to other plugins. STOP
- [ESM/CJS] If id is a Node.js builtin module name, then
- resolve that. STOP
- [ESM/CJS] If id does not start with "@", then
- Set packageName to the substring of id until the first "/" separator or the end of the string.
- [ESM/CJS] Otherwise,
- If id does not contain a "/" separator, then
- Fallback to other plugins. STOP
- Set packageName to the substring of id until the second "/" separator or the end of the string.
- If id does not contain a "/" separator, then
- [ESM/CJS] Let packageSubpath be "." concatenated with the substring of id from the position at the length of packageName.
- [ESM/CJS] Let selfPath be the result of PACKAGE_SELF_RESOLVE(packageName, packageSubpath, pathToFileURL(importer)).
- [ESM/CJS] If selfPath is not undefined, return selfPath
- [ESM/CJS] Let resolved be result of LOAD_NODE_MODULES(packageName, packageSubpath, importer).
- [ESM/CJS] If resolved is not undefined, return resolved
- [ESM/CJS] Fallback to other plugins. STOP
PACKAGE_SELF_RESOLVE(packageName, packageSubpath, importer)
Same with PACKAGE_SELF_RESOLVE of ESM resolver. Note this corresponds to LOAD_PACKAGE_SELF of CJS resolver.
LOAD_NODE_MODULES(packageName, packageSubpath, importer)
Similar with LOAD_NODE_MODULES of CJS resolver.
- [ESM/CJS] Let dirs to be result of NODE_MODULES_PATHS(path.dir(importer)).
- [ESM/CJS] For each dir in dirs,
- LOAD_PACKAGE_EXPORTS(packageName, packageSubpath, dir)
- If packageSubpath is not ".", then
- LOAD_AS_FILE(dir + packageName + packageSubpath)
- LOAD_AS_DIRECTORY(dir + packageName + packageSubpath)
NODE_MODULES_PATHS(dir)
Similar with NODE_MODULES_PATHS of CJS resolver.
- [ESM/CJS] let parts = path split(dir)
- [ESM/CJS] let i = count of parts - 1
- [ESM/CJS] let dirs = []
- [ESM/CJS] while i >= 0,
- if parts[i] = "node_modules" continue
- dir = path join(parts[0 .. i] + "node_modules")
- dirs = dir + dirs
- i = i - 1
- [ESM/CJS] return dirs
LOAD_PACKAGE_EXPORTS(packageName, packageSubpath, dir)
Similar with LOAD_PACKAGE_EXPORTS of CJS resolver.
- Let packageDir to be dir + packageName
- Let pjson be the result of READ_PACKAGE_JSON(packageDir).
- If pjson is not null and pjson.exports is not null or undefined, then
- Return the result of PACKAGE_EXPORTS_RESOLVE(packageDir, packageSubpath, pjson.exports, conditions).
PACKAGE_IMPORTS_RESOLVE(id, importer, condition)
Same with PACKAGE_IMPORTS_RESOLVE of ESM resolver except the param takes path instead of URL.
PACKAGE_EXPORTS_RESOLVE(id, importer, condition)
Same with PACKAGE_EXPORTS_RESOLVE of ESM resolver except the param takes path instead of URL.
READ_PACKAGE_JSON(packageDir)
Same with PACKAGE_SELF_RESOLVE of ESM resolver except the param takes path instead of URL.
CJS resolver supports omitting extension and omitting /index
part.
ESM resolver doesn't support that, but Vite core support this in some cases.
CJS resolver supports using invalid package name used with bare specifier.
Example:
require('@foo')
This is not supported by ESM resolver. Vite core won't support this.
CJS resolver supports using a single file instead of a directory containing a package.
Example:
- node_modules
- foo.js
- file.js
// file.js
require('foo') // this resolves to node_modules/foo.js
This is not supported by ESM resolver. Vite core won't support this.
CJS resolver supports package.json
inside a directory in a package.
Example:
- node_modules
- foo
- bar
- package.json # main field has "main.js"
- main.js
- package.json
- file.js
// file.js
require('foo/bar') // resolves to node_modules/foo/bar/main.js
This is not supported by ESM resolver. Vite core does support this.
CJS resolver supports global node_modules paths (NODE_PATH
).
This is not supported by ESM resolver as you can see from the spec. Also will not likely be supported.
Vite core won't support this.