πŸ”Œ Plugin Configuration

How to use plugins and create named pipelines

Contrary to what this page's title might suggest, this is not about configuring individual plugins, but how to tell Parcel which plugin is responsible for (among others) which file type(s).

Parcel is designed to be very modular, @parcel/core itself is (almost) not specific to bundling Javascript or Webpages. To actually specify the behaviour, there are different plugins (see Plugin System).

Here is an excerpt from the default config that the parcel CLI uses. Generally, there are three categories of plugin types (with regards to the configuration):

.parcelrc:
{
"bundler": "@parcel/bundler-default",
"transformers": {
"*.{js,jsx,ts,tsx}": [
"@parcel/transformer-babel",
"@parcel/transformer-js"
],
"url:*": ["@parcel/transformer-raw"]
},
"namers": ["@parcel/namer-default"],
"runtimes": {
"browser": ["@parcel/runtime-js", "@parcel/runtime-browser-hmr"],
"service-worker": ["@parcel/runtime-js"],
"web-worker": ["@parcel/runtime-js"],
"node": ["@parcel/runtime-js"]
},
"optimizers": {
"*.js": ["@parcel/optimizer-terser"]
},
"packagers": {
"*.html": "@parcel/packager-html",
"*": "@parcel/packager-raw"
},
"resolvers": ["@parcel/resolver-default"],
"reporters": ["@parcel/reporter-cli"]
}

A filetype is specified by a glob which is matched against the whole filepath (the pipelines are matched in order of declaration), so you could use different plugins depending on the input/output filepath:

ΒΆ Extending configs

A common usecase is extending the default config, for this reason the extends field can be a config package or an array of config packages to extend.

.parcelrc:
{
"extends": "@parcel/config-default",
"transformers": {
"*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"]
}
}

To add additional plugins without overwriting the default ones, add "..." to use the plugins provided by the config specified in the extends field. For example to add another reporter:

.parcelrc:
{
"extends": "@parcel/config-default",
"reporters": ["...", "parcel-reporter-custom"]
}

ΒΆ Pipelines

The observant reader might have noticed that the last config example didn't include @parcel/transformer-js, which is required for @parcel/runtime-js and @parcel/runtime-packager.

This is solved with pipelines. A Typescript asset is first processed by the ts pipeline and once the @parcel/transformer-typescript-ts plugin sets the asset type (which is essentially equivalent to the file extension) to js, Parcel reevaluates how the asset should be further processed. In this case, it will be put into the js pipeline specified in @parcel/config-default. This way, @parcel/transformer-js will still be executed.

Once a transformer sets the asset type to a type that is not covered by the current pipeline, the asset wil either be put into a different pipeline or transformation is finished. Transformers that should still be run afterwards according the current pipeline will not be run.

If a transformer doesn't change the asset type and you still want to continue processing this asset, add "..." to continue the transformation (in an extended config). This can be useful if you want to modify an asset without changing its type and let an already-defined pipeline handle the translation/dependency registration.

.parcelrc:
{
"extends": "@parcel/config-default",
"transformers": {
"*.js": ["parcel-transformer-add-comment", "..."]
}
}

ΒΆ Named Pipelines

In addition to the asset type-based pipelines, there are named pipelines, which enable you to import a single asset type in different ways (e.g. formats).

Named pipelines are specified using a procotol-like syntax, e.g. import myLogo from "url:./logo.png";

Here is an example on how you achieve a url dependency that doesn't create a new bundle but is rather inlined as a data url.

.parcelrc:

(Note: this config is already contained in @parcel/config-default. This config is just for illustration.)

{
"extends": "@parcel/config-default",
"transformers": {
"data-url:*": ["@parcel/transformer-inline-string", "..."]
},
"optimizers": {
"data-url:*": ["...", "@parcel/optimizer-data-url"]
}
}
index.js:
import x from "./other.js";

new Worker("data-url:./worker.js");

// ...

As you can see, ... is now used to make sure that data-url:./worker.js will still be processed with the js pipeline (the named pipeline specifier only applies for the first pipeline match).

If you're curious how this can be achieved without a deeper integration with Parcel core:

@parcel/transformer-inline-string marks the asset as an inlined asset. @parcel/packager-js then inlines this inline bundle (as a string "${contents}"). This inline bundle was previously processed by @parcel/optimizer-data-url, which encoded the JavaScript code into a data url.

Named pipelines are currently implemented for transformers and optimizers (the named pipeline is inheirited from the entry asset).

ΒΆ Predefined (offical) named pipelines

index.js:
import logo from "url:./logo.svg";

document.body.innerHTML = `<img src="${logo}">`;

You might ask why we chose to use this explicit syntax. The reason is that this way, adding a new asset type to Parcel isn't a breaking change anymore (this happened in the past when import foo from "./other.html" didn't return the URL anymore, but rather the HTML contents).

It's also possible to modify the parcel config to opt into the old behaviour: see Migration.

style.less:
@myColor: #143352;

span {
color: @myColor;
}
index.js:
import style from "bundle-text:./logo.less";

class MyTest extends HTMLElement {
constructor() {
super();

let shadow = this.attachShadow({ mode: "open" });

let style = document.createElement("style");
style.textContent = style;
shadow.appendChild(style);

let info = document.createElement("span");
info.textContent = this.getAttribute("label");
shadow.appendChild(info);
}
}

customElements.define("my-test", MyTest);