Parcel 2: Specific resolvers per file type
See original GitHub issueThe way Parcel currently resolves all files is the same across all file types, whether that be JS, CSS, HTML, or anything else, but sometimes it makes sense for there to be differences. For Parcel 2, we are considering making resolvers configurable per file type. Something like this:
"resolvers": {
"*.{js,mjs,jsm,jsx,es6,ts,tsx,coffee}": ["@parcel/resolver-path", "@parcel/resolver-alias", "@parcel/resolver-node"],
"*.{css,styl,stylus,sss,sass,scss,less}": ["@parcel/resolver-url", "@parcel/resolver-path", "@parcel/resolver-alias", "@parcel/resolver-node"],
"*.{htm,html}": ["@parcel/resolver-url", "@parcel/resolver-path", "@parcel/resolver-alias"]
}
These would run as pipelines, similar to other plugin types (e.g. transformers, optimizers, etc.). Each resolver would run, and the result if any would be passed to the next resolver and so on. Allowing resolvers to be configured this way would allow different languages to resolve slightly differently, and for users to adjust smaller aspects of the resolution more easily than rewriting the entire default resolver plugin.
The proposed resolver plugins above are the following:
@parcel/resolver-url
- resolves URL specifiers to file paths, e.g. from CSS and HTML.@parcel/resolver-path
- resolves file path specifiers to absolute, e.g. tilde, relative, etc.@parcel/resolver-alias
- resolves aliases defined in package.json@parcel/resolver-node
- resolves node_modules.
The actual resolution algorithms for each language are described in the following sections.
JavaScript
The JS resolution algorithm is a modified version of the Node resolution algorithm. It also supports Parcel’s tilde and absolute paths, aliases, and a few other main fields like source
, browser
and module
. This is essentially the same as how Parcel 1 resolved modules.
One change from Parcel 1 is to begin treating ES module import specifiers are URLs rather than file paths. This means resolving with URL semantics rather than platform specific path semantics, and allowing things like query strings and percent escaping. See also #3477.
Some examples:
import 'foo'; // node_modules/foo
import 'foo/bar'; // node_modules/foo/bar.js
import './foo.js'; // ./foo.js
import './foo'; // ./foo.js
import '~/foo'; // <nearest dir with package.json>/foo.js
import '/foo'; // <projectRoot>/foo.js
Extensionless specifiers are allowed, but only for the following extensions: '.js', '.jsx', '.mjs', '.json', '.ts', '.tsx', '.coffee'
. This is different from Parcel 1, which allowed importing any extension supported by Parcel without specifying it. This was slow since Parcel needed to check for every possible extension as it searched for files. Also in Parcel 2 there is no definitive list of extensions that are supported, as we use globs to match files instead, and I don’t think it was really ever expected to be able to import non-JS without specifying the extension anyway. It is mostly for compatibility with the Node CommonJS ecosystem.
There are a few exceptions, where resolution is treated differently. These are for JS features that produce dependencies other than ES modules and CommonJS, which are generally treated as URLs. Examples include workers, service workers, etc.
new Worker('./foo') // ./foo.js
new Worker('foo') // ./foo.js || node_modules/foo
The main difference with these specifiers is that they are always treated as URLs, and bare specifiers are resolved as relative by default but fall back to node_modules. See #670. I’m still debating whether extensionless specifiers should be supported here. We could support them only for relative paths and not bare specifiers, or as above at all times. Would like feedback here.
CSS
The resolution algorithm for CSS is similar to the JS one, but has some additional restrictions. It still allows relative, tilde, and absolute path specifiers, but all specifiers are treated as URLs. This means that in addition to allowing query params, percent encoding, etc., relative URLs are allowed without prefixing with ./
. Unfortunately, this conflicts with node_modules
resolution, but that’s less expected than in JS. For CSS, we will first check whether a relative path exists for a bare specifier, and if not, check if a node_modules
path exists.
Some examples:
@import "foo"; /* node_modules/foo */
@import "foo/bar.css"; /* node_modules/foo/bar.css */
@import "foo.css"; /* ./foo.css || node_modules/foo.css */
@import "./foo"; /* ERROR - extensionless imports not allowed */
@import "./foo.css"; /* ./foo.css */
@import "~/foo.css"; /* <nearest dir with package.json>/foo.js */
@import "/foo.css"; /* <projectRoot>/foo.js */
Extensionless imports are not allowed (unlike JS, unlike Parcel 1), and directory index imports are also not allowed (e.g. no foo/index.css
). This more closely matches the CSS spec, which treats imports are URLs.
When resolving from node_modules
, the package.json#style
field is considered as an entry point. Other entry fields are mostly JS specific, so are not supported for CSS.
HTML
The resolution algorithm for HTML is much simpler than for JS and CSS. node_modules
resolution is not generally needed or expected in HTML, so it is not supported by default. That just leaves relative, tilde, and absolute paths, and URLs.
Some examples:
<a href="foo.html"></a> <!-- ./foo.html -->
<script src="foo.js"></script> <!-- ./foo.js -->
<script src="./foo.js"></script> <!-- ./foo.js -->
<script src="~/foo.js"></script> <!-- <nearest dir with package.json>/foo.js -->
<script src="/foo.js"></script> <!-- <projectRoot>/foo.js -->
<script src="./foo"></script> <!-- ERROR - extensionless -->
<script src="foo"></script> <!-- ERROR - node_modules resolution is not allowed -->
Extensionless imports and directory index imports are not supported. All specifiers are treated as URLs, and are resolved with URL semantics. Bare specifiers are treated as relative URLs, just like normal HTML.
Other languages
Other languages generally use their own import semantics and are handled by the individual transformer plugins. A few have Parcel specific extensions:
- SASS, LESS, Stylus - these languages are extended to allow node_modules resolution, along with absolute and tilde paths (the CSS resolution path described above). If the Parcel resolver doesn’t find anything, then the transformer falls back to the default resolver for those languages.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:9
- Comments:9 (7 by maintainers)
The problem is script and link elements in the browser resolve bare specifiers (without
'./
or/
prefixes) as relative paths, not as npm modules. Parcel generally tries to follow existing browser standards, and makes deviations explicit. That’s why we are thinking of using thenpm:
prefix for explicitly referencing node_modules in URL specifiers.Now thinking of possibly adjusting this slightly to use an
npm:
/node:
/package:
protocol to load modules from node_modules where specifiers are URLs. This has the advantage of not being ambiguous, so we don’t need to try the relative path with fallback. It’s also clearer to the reader of the code, and more compatible with other tools and the spec.