question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Parcel 2: Specific resolvers per file type

See original GitHub issue

The 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:closed
  • Created 4 years ago
  • Reactions:9
  • Comments:9 (7 by maintainers)

github_iconTop GitHub Comments

3reactions
devongovettcommented, Feb 17, 2021

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 the npm: prefix for explicitly referencing node_modules in URL specifiers.

2reactions
devongovettcommented, Sep 7, 2019

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.

new Worker('foo.js') // relative -> ./foo.js
new Worker('npm:foo') // node_modules/foo
@import "foo.css"; /* relative -> ./foo.css */
@import "npm:foo"; /* node_modules/foo */
@import "npm:foo/bar.css"; /* node_modules/foo/bar.css */
<script src="foo.js"></script> <!-- ./foo.js -->
<script src="npm:foo"></script> <!-- node_modules/foo -->
Read more comments on GitHub >

github_iconTop Results From Across the Web

Resolver - Parcel
Resolver plugins are responsible for turning a dependency specifier into a full file path that will be processed by transformers. Resolvers run in...
Read more >
Plugins - Parcel
Resolvers. Resolver plugins are responsible for turning a dependency specifier into a full file path that will be processed by transformers. See Dependency ......
Read more >
Targets - Parcel
“Entries” are the files that Parcel starts at when building your source code. They can be specified on the CLI, or using the...
Read more >
CLI - Parcel
The parcel CLI is the most common way to use Parcel. It supports three different commands: serve, watch, and build.
Read more >
CSS - Parcel
When the @parcel/resolver-glob plugin is enabled, you can also use globs to import multiple CSS files at once. See Glob specifiers for more...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found