Dependency resolution
As Parcel builds your source code, it discovers dependencies , which allow code to be broken into separate files and reused in multiple places. Dependencies describe where to find the file containing the code you rely on, as well as metadata about how to build it.
Dependency specifiers
#
A
dependency specifier
is a string that describes the location of a dependency relative to the file that imports it. For example, in JavaScript the
import
statement or
require
function may be used to create dependencies. In CSS,
@import
and
url()
may be used. Typically, these dependencies do not specify a full absolute path, but rather a shorter specifier that is resolved to an absolute path by Parcel and other tools.
Parcel implements an enhanced version of the Node module resolution algorithm . It is responsible for turning a dependency specifier into an absolute path that can be loaded from the file system. In addition to the standard dependency specifiers supported across many tools, Parcel also supports some additional specifier types and features.
Relative specifiers
#
Relative specifiers start with
.
or
..
, and resolve a file relative to the importing file.
import './utils.js';
import '../constants.js';
In the above example, the first import would resolve to
/path/to/project/src/utils.js
and the second would resolve to
/path/to/project/constants.js
.
File extensions
#It is recommended to include the full file extension in all import specifiers. This both improves dependency resolution performance and reduces ambiguity.
That said, for compatibility with CommonJS in Node, and with TypeScript, Parcel allows the file extension to be omitted for certain file types. The file extensions that may be omitted include
.ts
,
.tsx
,
.mjs
,
.js
,
.jsx
,
.cjs
, and
.json
. A file extension is required to import all other file types.
The following example resolves to the same files as above.
import './utils';
import '../constants';
Note that these may only be omitted when importing from a JavaScript or TypeScript file. File extensions are always required for dependencies defined in other file types like HTML and CSS.
In TypeScript files, Parcel will also try replacing JavaScript extensions including
.js
,
.jsx
,
.mjs
, and
.cjs
with their TypeScript equivalents (
.ts
,
.tsx
,
.mts
, and
.cts
). For example, a dependency on
./foo.js
will resolve to
./foo.ts
. This matches TSC's behavior. However, unlike TSC, if the original
./foo.js
exists, it will be used instead of the TS version, matching the behavior of Node and other bundlers.
Directory index files
#
In JavaScript, Typescript, and other JS-based languages, dependency specifiers may resolve to a directory rather than a file. If the directory contains a
package.json
file, the main entry will be resolved as described in the
Package entries
section. If no
package.json
is present, it will attempt to resolve to an index file within the directory, such as
index.js
or
index.ts
. All extensions listed above are supported for index files.
import './client';
For example, if
/path/to/project/src/client
were a directory, the above specifier could resolve to
/path/to/project/src/client/index.js
.
Bare specifiers
#
Bare specifiers start with any character except
.
,
/
,
~
, or
#
. In JavaScript, TypeScript, and other JS-based languages, they resolve to a package in
node_modules
. For other types of files, such as HTML and CSS, bare specifiers are treated the same way as
relative specifiers
.
import 'react';
In the above example,
react
may resolve to something like
/path/to/project/node_modules/react/index.js
. The exact location will depend on the location of the
node_modules
directory, as well as configuration within the package.
node_modules
directories are searched upwards from the importing file. The search stops at the project root directory. For example, if the importing file was at
/path/to/project/src/client/index.js
the following locations would be searched:
-
/path/to/project/src/client/node_modules/react
-
/path/to/project/src/node_modules/react
-
/path/to/project/node_modules/react
Once a module directory is found, the package entry is resolved. See Package entries for more details on this process.
Package sub-paths
#Bare specifiers may also specify a sub-path within a package. For example, a package may publish multiple entry points rather than only a single one.
import 'lodash/clone';
The above example resolves
lodash
within a
node_modules
directory as described above, and then resolves the
clone
module within the package rather than its main entrypoint. This could be a
node_modules/lodash/clone.js
file, for example.
Builtin modules
#
Parcel includes shims for many builtin Node.js modules, e.g.
path
and
url
. When a dependency specifier references one of these module names, the builtin module is preferred over any module installed in
node_modules
with the same name. When building for a node environment, builtin modules are excluded from the bundle, otherwise a shim is included. See the
Node docs
for a full list of builtin modules.
When building for an Electron environment, the
electron
module is also considered a builtin and excluded from the bundle.
Absolute specifiers
#
Absolute specifiers start with
/
, and resolve a file relative to the project root. The
project root
is the base directory of your project, which would typically contain a package manager lock file (e.g.
yarn.lock
or
package-lock.json
), or a source control directory (e.g.
.git
). Absolute specifiers could be useful to avoid very long relative paths in deeply nested hierarchies.
import '/src/client.js';
The above example could be placed in any file, at any point in your project’s directory structure, and will always resolve to
/path/to/project/src/client.js
.
Tilde specifiers
#
Tilde specifiers start with
~
, and resolve relative to the nearest package root from the importing file. A
package root
is a directory with a
package.json
file, which would typically be found in
node_modules
, or as the root of a package in a monorepo. Tilde specifiers are useful for similar purposes as absolute specifiers, but are more useful when you have more than one package.
import '~/src/utils.js';
The above example would resolve to
/path/to/project/packages/frontend/src/utils.js
.
Hash specifiers
#
Hash specifiers start with the
#
character, and the behavior depends on the file type they are contained within. In JavaScript and TypeScript files, hash specifiers are treated as internal package imports, described below. In other files, these are treated as relative URL hashes.
The
"imports"
field in
package.json
can be used to define private mappings that apply to import specifiers in JavaScript or TypeScript files within the package. This allows a package to define conditional imports depending on the environment, as documented
below
and in the
Node.js docs
.
{
"imports": {
"#dep": {
"node": "dep-node",
"browser": "dep-browser"
}
}
}
import '#dep';
Query parameters
#Dependency specifiers may also include query parameters, which specify transformation options for the resolved file. For example, you can specify the width and height to resize an image when loading it.
.logo {
background: url(logo.png?width=400&height=400);
}
See the Image transformer docs for more details on images. You can also use query parameters in custom Transformer plugins.
Note
: Query parameters are not supported for CommonJS specifiers (created by the
require
function).
URL schemes
#
Dependency specifiers may use URL schemes to target
Named pipelines
. These allow you to specify a different pipeline to compile a file with than the default one. For example, the
bundle-text:
scheme can be used to inline a compiled bundle as text. See
Bundle inlining
for more details.
There are a few reserved URL schemes that may not be used for named pipelines, and have builtin behavior.
-
node:
– an alternative way of specifying a builtin Node module. See Builtin modules . -
npm:
– a way for URL dependencies (e.g. in HTML, CSS, or web workers) to import files from anode_modules
package. -
http:
andhttps:
– a fully qualified URL dependency. These are resolved at runtime, and left untouched by Parcel. -
data:
– A data URL including the dependency source code inline. Currently not implemented by Parcel, but reserved for future use. -
file:
– A file URL . Reserved for future use. -
mailto:
andtel:
- Commonly used URL schemes. These are left untouched by Parcel.
Glob specifiers
#
Parcel supports importing multiple files at once via globs, however, since glob imports are non-standard, they are not included in the default Parcel config. To enable them, add
@parcel/resolver-glob
to your
.parcelrc
.
{
"extends": "@parcel/config-default",
"resolvers": ["@parcel/resolver-glob", "..."]
}
Once enabled, you can import multiple files using a specifier like
./files/*.js
. This returns an object with keys corresponding to the files names.
import * as files from './files/*.js';
is equivalent to:
import * as foo from './files/foo.js';
import * as bar from './files/bar.js';
let files = {
foo,
bar
};
Specifically, the dynamic parts of the glob pattern become keys of the object. If there are multiple dynamic parts, a nested object will be returned. For example, if a
pages/profile/index.js
file existed, the following would match it.
import * as pages from './pages/*/*.js';
console.log(pages.profile.index);
This also works with URL schemes like
bundle-text:
, as well as with dynamic import. When using dynamic import, the resulting object will include a mapping of filenames to functions. Each function can be called to load the resolved module. This means that each file is loaded on demand rather than all up-front.
let files = import('./files/*.js');
async function doSomething() {
let foo = await files.foo();
let bar = await files.bar();
return foo + bar;
}
Globs may also be used to import files from npm packages:
import * as locales from '@company/pkg/i18n/*.js';
console.log(locales.en.message);
Glob imports also work with CSS:
@import "./components/*.css";
is equivalent to:
@import "./components/button.css";
@import "./components/dropdown.css";
Package entries
#
When resolving a package directory, the
package.json
file is consulted to determine the package entry. Parcel checks the following fields (in order):
-
source
– If the module is behind a symlink (e.g. in a monorepo, or vianpm link
), then Parcel uses thesource
field to compile the module from source. Thesource
field can also be used as an alias mapping if a package has multiple entry points – see Aliases below for details. -
exports
– Package exports , see below for details. -
browser
– A browser-specific version of a package. If building for a browser environment , the browser field overrides other fields. Thebrowser
field can also be used as an alias mapping if a package has multiple entry points – see Aliases below for details. -
module
– An ES module version of the package. -
main
– A CommonJS version of the package.
If none of these fields are set, or the files they point to do not exist, then resolution falls back to an index file. See Directory index files for more details.
Package exports
#
The
"exports"
field in
package.json
can be used to define the publically accessible entrypoints for a package. These can also define conditional behavior based on the environment, allowing resolution to change depending on where a module is imported from (e.g. node or browser).
Enabling package exports
#
Package exports are disabled by default because they may break existing projects that weren't designed with them in mind. You can enable support by adding the following to the
package.json
file in your project root directory.
{
"@parcel/resolver-default": {
"packageExports": true
}
}
Single export
#
If a package has only a single exported module, the
"exports"
field may be defined as a string:
{
"name": "foo",
"exports": "./dist/index.js"
}
When a user imports the
"foo"
package in the above example, the
node_modules/foo/dist/index.js
module will be resolved.
Multiple exports
#
If a package exports multiple modules, the
"exports"
field can provide mappings defining where to find each of these exports. The
"."
export defines the main entry point, and other entries are defined as subpaths.
{
"name": "foo",
"exports": {
".": "./dist/index.js",
"./bar": "./dist/bar.js"
}
}
With the above package, a user may import
"foo"
, which resolves to
node_modules/foo/dist/index.js
, or
"foo/bar"
, which resolves to
node_modules/foo/dist/bar.js
.
Any subpath that is not explicitly exported by a package containing the
"exports"
field will not be accessible by a consumer. For example, attempting to import
"foo/abc"
in the above package would result in a build time error.
The
*
character may be used within an exports mapping as a wildcard. Only one
*
may appear in the lefthand side of the mapping, and the matched string is replaced into each instance in the righthand side. This allows you to avoid manually listing every file you want to export.
{
"name": "foo",
"exports": {
"./*": "./dist/*.js"
}
}
In the above example, all
.js
files in the
dist
directory are exported from the package, without their extension. For example, importing
"foo/bar"
would resolve to
node_modules/foo/dist/bar.js
.
Conditional exports
#
The
"exports"
field may also define different mappings for the same specifier in different
environments
or other conditions. For example, a package may provide different entrypoints for ES modules and CommonJS, or for Node and browsers.
{
"name": "foo",
"exports": {
"node": "./dist/node.js",
"default": "./dist/default.js"
}
}
In the above example, if a consumer imports the
"foo"
module from a Node environment, it will resolve to
node_modules/foo/dist/node.js
, otherwise it will resolve to
node_modules/foo/default.js
.
Conditional exports may also be nested within subpath mappings.
{
"name": "foo",
"exports": {
"./bar": {
"node": "./dist/bar-node.js",
"default": "./dist/bar-default.js"
}
}
}
This allows importing
"foo/bar"
to resolve to a different file for Node and other environments.
Conditional exports may also be nested within each other to create more complex logic.
{
"name": "foo",
"exports": {
"node": {
"import": "./dist/node.mjs",
"require": "./dist/node.cjs"
},
"default": "./dist/default.js"
}
}
The above example defines different versions of the module depending on whether the package is loaded via ESM
import
or CommonJS
require
within a Node environment.
Parcel supports the following exports conditions:
-
"import"
– Package was referenced using the ESMimport
syntax. -
"require"
– Package was referenced using the CommonJSrequire
function. -
"module"
– Package was referenced from either the ESMimport
syntax or the CommonJSrequire
function. -
"sass"
– Package was referenced from a Sass stylesheet. -
"less"
– Package was referenced from a Less stylesheet. -
"stylus"
– Package was referenced from a Stylus stylesheet. -
"style"
– Package was referenced from a stylesheet (e.g. CSS, Sass, Stylus, etc.). -
"node"
– Output will run in a Node environment. -
"browser"
– Output will run in a browser environment. -
"worker"
– Output will run in a web worker or service worker environment. -
"worklet"
– Output will run in a worklet environment. -
"electron"
– Output will run in an Electron environment. -
"development"
– Package was loaded in a development build. -
"production"
– Package was loaded in a production build. -
"default"
– The default condition in case no other conditions matched.
The order that exports conditions are resolved follows the order they are defined in the package.json, not the order they are listed above.
More examples
#For more details about package.json exports, see the Node.js documentation .
Aliases
#An alias can be used to override the normal resolution of a dependency. For example, you may want to override a module with a different but API-compatible replacement, or map a dependency to a global variable defined by a library loaded from a CDN.
Aliases are defined via the
alias
field in package.json. They can be defined either locally in the nearest
package.json
to the source file containing the dependency, or globally in the
package.json
in the project root directory. Global aliases apply to all files and packages in the project, including those in
node_modules
.
Package aliases
#
Package aliases map a
node_modules
dependency to a different package, or to a local file within your project. For example, to replace
react
and
react-dom
with Preact across both files in your project as well as any other libraries in
node_modules
, you could define a global alias in the
package.json
in your project root directory.
{
"alias": {
"react": "preact/compat",
"react-dom": "preact/compat"
}
}
You can also map a module to a file within your project by using a relative path from the
package.json
in which the alias is defined.
{
"alias": {
"react": "./my-react.js"
}
}
Aliasing only certain
sub-paths
of a module is also supported. This example will alias
lodash/clone
to
tiny-clone
. Other imports within the
lodash
package will be unaffected.
{
"alias": {
"lodash/clone": "tiny-clone"
}
}
This also works the other way: if an entire module is aliased, then any sub-path imports of that package will be resolved within the aliased module. For example, if you aliased
lodash
to
my-lodash
and imported
lodash/clone
, this would resolve to
my-lodash/clone
.
File aliases
#
Aliases can also be defined as relative paths to replace a specific file within a package with a different file. This can be done using the
alias
field to replace the file unconditionally, or with the
source
or
browser
fields to do conditionally. See
Package entries
above for details about these fields.
For example, to replace a certain file with a browser-specific version, you could use the
browser
field.
{
"browser": {
"./fs.js": "./fs-browser.js"
}
}
Now, if
my-module/fs.js
is imported in a browser environment, they'll actually get
my-module/fs-browser.js
. This applies both to imports from outside (e.g.
package sub-paths
), as well as internally within the module.
Glob aliases
#
File aliases can also be defined using globs, which allows replacing many files using a single pattern. The replacement can include patterns such as
$1
to access the captured glob matches. This can be done using the
alias
field to replace files unconditionally, or with the
source
or
browser
fields to do conditionally. See
Package entries
above for details about these fields.
For example, you could use the
source
field to provide a mapping between compiled code in a package and the original source code. When the module is symlinked, or within a monorepo, this will allow Parcel to compile the module from source rather than use the pre-compiled version.
{
"source": {
"./dist/*": "./src/$1"
}
}
Now, any time a file in the
dist
directory is imported, the corresponding file in the
src
folder will be loaded instead.
Shim aliases
#
Files or packages can be aliased to
false
to be excluded from the build, and replaced with an empty module. This could be useful to exclude certain modules from browser builds that only work in Node.js, for example.
{
"alias": {
"fs": false
}
}
Global aliases
#Files or packages may also be aliased to global variables that will be defined at runtime. For example, a particular library may be loaded from a CDN. Rather than bundling it, any time a dependency on that library is resolved, it will be replaced with a reference to that global variable instead of being bundled.
This can be done by creating an alias to an object with a
global
property. The following example aliases the
jquery
dependency specifier to the global variable
$
.
{
"alias": {
"jquery": {
"global": "$"
}
}
}
TSConfig
#
Parcel supports some settings defined in TypeScript's
tsconfig.json
config file, including
baseUrl
,
paths
, and
moduleSuffixes
. Parcel searches upward from the file containing the dependency to find the nearest
tsconfig.json
file. It also supports using the
extends
option to merge the settings from multiple tsconfigs together. See the
TypeScript docs
for more details.
baseUrl
#
The
baseUrl
field defines the base directory from which to resolve
bare specifiers
.
{
"compilerOptions": {
"baseUrl": "./src"
}
}
import 'Home';
In the above example,
Home
will resolve to
src/Home.js
if it exists. Otherwise, it will fall back to
node_modules/Home
, for example.
paths
#
The
paths
field can be used to define mappings from
bare specifiers
to file paths. You can also define wildcard patterns using the
*
character.
File paths referenced in the
paths
field are relative to the
baseUrl
if defined, otherwise to the directory containing the
tsconfig.json
file.
{
"compilerOptions": {
"paths": {
"jquery": ["./vendor/jquery/dist/jquery"],
"app/*": ["./src/app/*"]
}
}
}
import 'jquery';
import 'app/foo';
In the above example,
jquery
resolves to
./vendor/jquery/dist/jquery.js
, and
app/foo
resolves to
./src/app/foo.js
.
moduleSuffixes
#
The
moduleSuffixes
field allows you to specify the file name suffixes to search for when resolving a module.
{
"compilerOptions": {
"moduleSuffixes": [".ios", ".native", ""]
}
}
import './foo';
In the above example, Parcel will look for
./foo.ios.ts
,
./foo.native.ts
, and
./foo.ts
(in addition to other extensions like
.tsx
,
.js
, etc.).
Configuring other tools
#This section covers how to configure other tools to work with Parcel’s extensions to the Node resolution algorithm.
TypeScript
#
TypeScript will need to be configured to support Parcel features like absolute and tilde dependency specifiers, and aliases. This can be done using the
paths
option in
tsconfig.json
. See the
TypeScript Module Resolution docs
for more information.
For example, to map tilde paths to the root directory, this configuration could be used:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~*": ["./*"]
}
}
}
Support for
URL schemes
can also be enabled by creating an
ambient module
declaration in your project. For example, to map dependencies loaded with the
bundle-text:
scheme to a string, you could use the following declaration. This can be placed in a file such as
parcel.d.ts
anywhere in your project.
declare module 'bundle-text:*' {
const value: string;
export default value;
}
Flow
#
Flow needs to be configured to support absolute and tilde specifiers, and aliases. This can be done using the
module.name_mapper
feature in your
.flowconfig
.
For example, to map absolute specifiers to resolve from the project root, this configuration could be used:
[options]
module.name_mapper='^\/\(.*\)$' -> '<PROJECT_ROOT>/\1'
To enable
URL schemes
, you'll need to create a mapping to a
.flow
declaration file
which exports the expected type. For example, to map dependencies loaded with the
bundle-text:
scheme to a string, you could create a file called
bundle-text.js.flow
and map all dependencies referencing the scheme to it.
// @flow
declare var value: string;
export default value;
[options]
module.name_mapper='^bundle-text:.*$' -> '<PROJECT_ROOT>/bundle-text.js'