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.
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.
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.
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.
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.
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.
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.
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
.
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.
Single export
#If a package has only a single exported module, the "exports"
field may be defined as a string:
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.
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
.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 $
.
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.
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.
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.
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:
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.
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:
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.