Parcel v2.9.0

Parcel v2.9.0 includes many long requested features, including a brand new resolver with support for package.json "exports" and tsconfig.json features, support for ESM plugins and configs, and local Parcel plugins. It also improves performance with a new default JS minifier powered by SWC, incremental symbol propagation, and improved bundler data structures, and includes a new build performance tracing feature. This is a big release – let's dive in!

New resolver

#

Parcel's resolver is responsible for finding a file path for a dependency such as import "react". This may seem simple, but as the JavaScript ecosystem has developed over the years, it has become more and more complex. Parcel's current resolver implementation was originally written way back in 2018, and is missing some modern features like package.json "exports" and "imports".

Parcel v2.9.0 includes a brand new resolver implementation written from scratch in Rust. It supports all of the existing Parcel resolution features, while adding support for package.json "exports" and "imports", and tsconfig.json "baseUrl", "paths", and "moduleSuffixes".

Unfortunately, enabling support for package.json "exports" is a breaking change. When a package declares "exports", consumers can no longer import files that are not exported. In addition, due to exports conditions like "import" and "require", there is a possibility of a dual package hazard. For these reasons, exports support is currently opt-in and must be explicitly enabled in your project. This can be done by adding the following to your project root package.json:

{
"@parcel/resolver-default": {
"packageExports": true
}
}

Check out the documentation for package.json exports and tsconfig.json to learn more about the new features!

ESM plugins and configs

#

Parcel now supports plugins and config files written as native ES modules in addition to the existing support for CommonJS. This means that plugins can be published to npm using the .mjs format, or use "type": "module" in their package.json to enable ESM syntax in .js files. Config files such as postcss.config.mjs are also now supported.

This was surprisingly difficult to implement, which is why it has taken us so long. Parcel supports granular caching for everything it does to improve development performance. For this to work, it needs to track every file that is involved with your build. This includes all of the dev dependencies such as plugins, compilers, and config files that influenced the output. If any of these change, Parcel recompiles only the necessary files.

Internally, this has relied on patching Node's require implementation so that we can track all of the dev dependencies that are loaded. But this only works for CommonJS modules. With native ESM, there is currently no stable way to tap into the module loader in Node, which meant we couldn't track what files were imported.

To track ESM dependencies, Parcel now uses a fork of the excellent es-module-lexer project, which is an extremely fast parser written in C, specifically designed to analyze ES module import and export syntax. We've integrated this with a Rust library that resolves those dependencies using our new resolver implementation, and collects all of the files used by an ESM plugin or config file. This allows us to invalidate the cache whenever one of these dependencies changes.

We do our best to statically analyze as much as we can, but some syntax like import(someVariable) isn't supported and will result in the cache being invalidated every build. You'll see a warning in those cases.

ESM support is experimental in this release. If you have feedback, please open an issue or discussion on Github.

Local plugins

#

Historically, Parcel v2 plugins have been required to be npm packages. Local plugins within your project were possible if you were using a monorepo, but we always encourage plugins to be published to npm or to an internal company registry so they can be reused by others. However, we heard feedback that this made building and prototyping plugins harder for projects not using a monorepo setup.

In Parcel v2.9.0, plugins can be referenced as relative paths from your .parcelrc config. They don't need to have their own package.json – you can reference a JavaScript file directly. Plugins hot reload when you make changes, so you don't even need to restart Parcel as you develop. While we still encourage publishing plugins to npm, this should make it easier to prototype new plugins.

You can learn more about local plugins in the docs.

SWC minifier

#

Parcel was one of the first tools to switch to the SWC JavaScript compiler, and it helped us improve performance by 10x back in 2021. In Parcel v2.9.0, we are switching the default minifier from Terser to SWC as well.

The SWC minifier is around 7x faster than Terser, while producing comparable to even smaller output size. Most Terser configuration options are also supported by SWC, so if you have a .terserrc it should continue to work.

Huge thanks to the SWC team for their fantastic work improving minification performance for the whole ecosystem!

Incremental symbol propagation

#

One of the main goals for Parcel from the beginning was to make rebuilds scale based on the size of the changes rather than the size of the entire project. This means it should be fast to rebuild if your project is 1 file or 100,000. That's why we have implemented granular caching since the very first version in 2017, and why we have continued to introduce new features like incremental bundling.

Symbol propagation is an algorithm that walks the full dependency graph of your project, and determines which exports of each module are actually used and which ones can be tree shaken away. In Parcel v2.9.0, this algorithm is now incremental. Instead of traversing over the full graph every time you make a change, it tracks what dependencies have been modified in the files you changed, and surgically updates the existing graph in place. For very large projects, this helps significantly reduce the amount of work that needs to be done each time you save a file.

Symbol propagation now also runs in development rather than only in production builds. This enables us to emit proper errors during development when you try to import something that isn't exported from a module, which can be helpful when debugging.

Build performance tracing

#

Parcel now includes a --trace CLI flag, which will track how much time is spent in each phase of your build, which plugins were called into, and how long is spent in each. A Parcel trace can help you to optimize your build by answering questions such as, "Which plugin is taking the most time during my build?" or "Which file in my project takes the longest to transform?".

Parcel has supported a --profile flag for a while, which runs V8's sampling CPU profiler. --trace is much higher level, making it easier to see performance data at a plugin level instead of a function level. Both output data in the Chrome Tracing Format, which you can load into the Chrome Dev Tools or other more advanced analysis tools such as Perfetto. There you can run SQL queries against the data to answer questions like the ones listed above.

Check out the documentation to learn more.

And more!

#

In addition to the major features above, there were a ton more smaller features and bug fixes in this release. Check out the full release notes for details.

Thanks to everyone who contributed to this release!