Migration from Parcel 2

Parcel 3 is a ground up rewrite of Parcel in Rust. For the most part, it works quite similarly to Parcel 2, and most apps will only need a few small changes. However, some features have changed or been removed, and plugins written for Parcel 2 will need to be updated. This page covers the things you'll need to change when upgrading.

Getting started

#

Let's walk through a couple basic steps to upgrade from Parcel 2 to Parcel 3.

Package name

#

Update the parcel dependency in your package.json to the latest version.

package.json:
{
"devDependencies": {
"parcel": "^2.0.0"
}
}
package.json:
{
"devDependencies": {
"parcel": "^3.0.0-0"
}
}

You should also remove any @parcel/* plugin packages from Parcel 2 that are now built into Parcel, or that are no longer supported. See Plugins below for details.

If you'd like to try Parcel 3 before switching over entirely, it is also published as @parcel/parcel3. This package installs a parcel3 command instead of parcel, so it can be installed alongside Parcel 2 in the same project. Use parcel3 in place of parcel when running the commands on this page, and pass a different --dist-dir so the two versions don't overwrite each other's output.

package.json:
{
"scripts": {
"build": "parcel build src/index.html",
"build:v3": "parcel3 build src/index.html --dist-dir dist-v3"
},
"devDependencies": {
"parcel": "^2.0.0",
"@parcel/parcel3": "^3.0.0-0"
}
}

Commands

#

In Parcel 2, running parcel with a list of entries and no command started the development server. Parcel 3 always requires a command, so you'll need to update your package scripts to use parcel serve.

package.json:
{
"source": "src/index.html",
"scripts": {
"start": "parcel",
"build": "parcel build"
}
}
package.json:
{
"source": "src/index.html",
"scripts": {
"start": "parcel serve",
"build": "parcel build"
}
}

Parcel 3 also adds a new parcel run command, which builds and runs a Node.js entry with hot module replacement. This replaces running parcel watch alongside a tool like nodemon.

Cache

#

Parcel 3 is fast enough that it no longer writes a cache to disk. You can delete the .parcel-cache directory and remove it from your .gitignore.

.gitignore:
dist
.parcel-cache
.gitignore:
dist

Installing dependencies

#

Parcel 2 automatically installed plugins and other dependencies it needed when they were missing from your project. Parcel 3 no longer does this, so you'll need to install them yourself. This includes:

Configuration

#

Plugin configuration

#

In Parcel 2, many plugins were configured using a key in your package.json named after the plugin, or using a tool-specific config file. In Parcel 3, plugin options are specified in your .parcelrc instead, by replacing the plugin name with an object containing a plugin and a config. See Plugin configuration for details.

package.json:
{
"@parcel/bundler-default": {
"minBundleSize": 30000,
"manualSharedBundles": [
{"name": "vendor", "assets": ["node_modules/**"]}
]
}
}
.parcelrc:
{
"extends": "@parcel/config-default",
"bundler": {
"plugin": "@parcel/bundler-default",
"config": {
"minBundleSize": 30000,
"manualSharedBundles": [
{"assets": ["node_modules/**"]}
]
}
}
}

The following table lists where configuration that was previously read from package.json or other config files now lives.

Parcel 2 Parcel 3
package.json#@parcel/transformer-js @parcel/transformer-js config in .parcelrc (inlineEnvironment, inlineFS). See Node emulation.
package.json#@parcel/transformer-css @parcel/transformer-css config in .parcelrc (cssModules, pseudoClasses). See CSS.
package.json#@parcel/bundler-default @parcel/bundler-default config in .parcelrc. See Code splitting.
package.json#@parcel/resolver-default No longer needed. Package exports are always enabled.
svgo.config.json @parcel/transformer-svg config in .parcelrc (optimize). See SVG.
.svgrrc @parcel/transformer-svg-jsx config in .parcelrc. See SVG.
.lessrc, .sassrc, .stylusrc, .pugrc Plugin config in .parcelrc. See Less, Sass, Stylus, Pug.
sharp.config.json No longer supported.
.htmlnanorc @parcel/transformer-html config in .parcelrc (optimize). See HTML.
.terserrc No longer supported. JavaScript minifier options are not configurable.

The following package.json options have also changed:

Plugins

#

Some plugins that were included in Parcel 2's default config are not included in Parcel 3's default config. If you use any of these, you'll need to install the plugin and add it to your .parcelrc.

File types Plugin
Babel @parcel/transformer-babel
PostCSS @parcel/transformer-postcss
PostHTML @parcel/transformer-posthtml
*.sass, *.scss @parcel/transformer-sass
*.less @parcel/transformer-less
*.styl, *.stylus @parcel/transformer-stylus
*.sss @parcel/transformer-sugarss
*.pug @parcel/transformer-pug
*.coffee @parcel/transformer-coffeescript

For example, to use Sass:

yarn add @parcel/transformer-sass@next --dev
.parcelrc:
{
"extends": "@parcel/config-default",
"transformers": {
"*.{sass,scss}": ["@parcel/transformer-sass"]
}
}

In particular, Parcel no longer runs Babel or PostCSS automatically when it finds a config file in your project.

Some plugins are now built into Parcel, and no longer need to be installed separately. These include:

The following plugins are no longer needed, since their functionality is built into Parcel or has been replaced:

Targets

#

Library targets

#

In Parcel 2, libraries were configured using the main, module, browser, and types fields in package.json, along with the targets field. Parcel 3 uses the exports field instead. Add a source condition to each export to reference the source file, and Parcel will build each of the other conditions. The output format is determined by the file extension, or the "type" field in your package.json. See Library targets for details.

package.json:
{
"name": "my-library",
"source": "src/index.js",
"main": "dist/main.js",
"module": "dist/module.js"
}
package.json:
{
"name": "my-library",
"exports": {
"source": "./src/index.js",
"import": "./dist/module.mjs",
"require": "./dist/main.cjs"
}
}

Parcel 3 also builds libraries differently than Parcel 2. Each module is written to a separate output file rather than concatenated into a single bundle, which allows consumers of your library to tree shake it more effectively. dependencies and peerDependencies are not bundled, but devDependencies are.

TypeScript declaration file (.d.ts) generation is not yet supported in Parcel 3. In the meantime, you can run tsc --emitDeclarationOnly alongside Parcel.

Target options

#

The following target options have been removed:

Top-level package.json fields named after custom targets (e.g. "modern": "dist/modern.js") are also no longer supported.

In development, Parcel now builds for your configured browserslist rather than for the latest browsers.

Differential bundling has been removed: Parcel no longer generates <script nomodule> fallbacks. When a <script type="module"> is used and some of your browser targets don't support ES modules, Parcel compiles it to a classic <script defer> instead, which loads in both modern and older browsers. All modern browsers have supported ESM for many years, so this should be rare.

Code changes

#

Glob imports

#

Glob specifiers still require adding @parcel/resolver-glob to your .parcelrc, but the plugin is now built into Parcel. Globs now resolve to a default export, and the values are the default export of each file rather than a namespace object. To load each file lazily, use the ?async=true query parameter instead of a dynamic import(). Glob imports of files in npm packages (e.g. @company/pkg/i18n/*.js) are not supported yet. See Glob specifiers for details.

app.js:
import * as files from './files/*.js';
app.js:
import files from './files/*.js';

Worklets

#

The worklet: URL scheme has been replaced by import attributes. To import a URL to a web audio worklet, use the url type along with env: 'worklet'. See Worklets for details.

app.js:
import workletUrl from 'worklet:./worklet.js';
app.js:
import workletUrl from './worklet.js' with {type: 'url', env: 'worklet'};

Macros

#

Macros now run inside Parcel's embedded JavaScript runtime rather than in Node.js, so some Node APIs such as fetch, timers, and child_process are not available. See Runtime environment for details. In addition, the this.invalidateOnFileChange and related APIs have been removed. Parcel now tracks the files your macros read automatically.

Scope hoisting

#

Parcel 3 no longer concatenates modules into a single scope ("scope hoisting"). Tree shaking is still supported, and unused exports are removed from your production builds. The --no-scope-hoist CLI flag has been removed. See Tree shaking for details on how to make sure your code can be tree shaken.

CLI

#

The following CLI options have been removed:

Option Replacement
--no-cache, --cache-dir Parcel no longer writes a cache to disk.
--no-autoinstall Parcel no longer installs dependencies automatically.
--no-scope-hoist Scope hoisting has been removed.
--reporter Add reporters to your .parcelrc.
--target All targets are built.
--lazy Lazy mode has been removed.
--profile, --trace Profiling has been removed.
--watch-dir, --watch-for-stdin None.
--hmr-host Use --host.
--no-cors The dev server no longer adds CORS headers to served files.

In addition, --hmr-port is now only supported by parcel run, and --config must be a path to a file rather than a package name. See the CLI docs for a full list of options, including new ones such as --open, --analyze, and --detailed-report.

Development

#

Production

#

Removed features

#

The following features from Parcel 2 are not currently supported in Parcel 3. Some of these may be added again in the future, or moved to third party plugins.

Plugins

#

Parcel 3 supports plugins written in JavaScript or TypeScript, as well as native plugins written in Rust, Go, Swift, or C. If you have written a Parcel 2 plugin, you'll need to update it for Parcel 3's plugin API. The main differences are:

See the Plugin System docs for full details.