You may have thought about swapping Webpack for another asset bundler such as Parcel. We made this change while developing an app with Phoenix, and it wasn’t too difficult (aside from a couple gotchas). But first, why would you want to use Parcel instead of Webpack?

What drew me to Parcel was its focus on developer experience. It accomplishes this in a couple of ways:

  1. Making builds faster
  2. Eliminating the boilerplate code/configuration you need to write and maintain

If those things are important to you, and Parcel has the features you need, it may be worth checking out. Let’s take a look at what we need to do to swap out Webpack for Parcel.

Step 1: Install Parcel

First, let’s install Parcel and add it to our dependencies in package.json:

npm install parcel --save-dev
yarn add parcel --dev

Step 2: Update package.json scripts

Then we can update the scripts section of our package.json to use Parcel instead of Webpack:

  "scripts": {
    "deploy": "parcel build js/app.js --out-dir ../priv/static/js --public-url /js --no-cache",
    "watch": "parcel watch js/app.js --out-dir ../priv/static/js --public-url /js"

Step 3: Update watchers in

We also need to update our endpoint’s :watchers to call npm run watch instead of node_modules/webpack/bin/webpack.js …. The default watcher calls webpack.js directly for performance reasons, but I decided to replace this with a call to npm run watch.

config :your_app, YourAppWeb.Endpoint,
  watchers: [
    npm: ["run", "watch", cd: Path.expand("../assets", __DIR__)]

With Parcel, configuration is done via command line arguments passed to parcel instead of editing a file such as webpack.config.js. If we were to call node_modules/parcel/bin/cli.js directly within, any Parcel config change would require updating both package.json and to match. For this reason, it makes sense to just call npm run watch so we don’t have to worry about updating both files.

Step 4: Replace copy plugin functionality

Our Webpack config copies files such as `robots.txt` and favicon.ico into our priv/static directory.

new CopyWebpackPlugin([{ from: 'static/', to: '../' }])

Instead of a plugin, we can accomplish this by prefixing our Parcel commands with cp -R static/* ../priv/static/ &&.


“scripts”: {
    "deploy": "cp -R static/* ../priv/static/ && parcel build js/app.js --out-dir ../priv/static/js --public-url /js --no-cache",
    "watch": "cp -R static/* ../priv/static/ && parcel watch js/app.js --out-dir ../priv/static/js --public-url /js",

Step 5: Install parcel-plugin-stdin

At this point everything should work, but after running mix phx.server a few times I noticed some node processes running in the background that weren’t being stopped with the server. Luckily, we can fix this issue by installing the parcel-plugin-stdin package. This mimics the --watch-stdin flag we were passing to Webpack in

npm install --save-dev parcel-plugin-stdin

That’s it

After swapping things out, it was easy to set up other tools like TypeScript, React, ESLint, etc. All that’s required is to configure each individual tool. The most difficult part was getting them working together nicely. Parcel itself didn’t require any additional configuration—it just worked!

As far as compilation speed, I haven’t noticed any annoyingly slow builds. In a small React project, incremental builds during development are usually <50ms.

Overall, I’ve been impressed with Parcel. I like the philosophy behind the project, and it wasn’t too difficult to switch. If you’d like to reduce the amount of configuration you have to write and speed up your development builds I would recommend checking Parcel out.

Start building in our sandbox for free, right now. Get a feel for how our API works before going live in production.

Demo code for this post is available at


Stay Updated