Parcel: A zero configuration web application bundler

14 October, 2018

Webpack configuration is complicated? Take a look at Parcel, a “blazing fast, zero configuration web application bundler”.

I started learning some React the last months and I realize how far JavaScript development went recently. The React community created good practices for developing single page JavaScript projects. While the ecosystem is very open and many technologies and packages can be involved, the processes after all crystallized. As a result tooling has been developed. We have the create-react-app tool, the Next.js and many more.

These tools normally use Webpack as a module builder. It brings together all the dependencies. It does the necessary transforms of front-end assets, programming language preprocessing (wow, we can use F#), CSS preprocessing. It can include images, etc. At the end, the application usually is compiled into a self contained single file.

While the idea is charming, the Webpack configuration is pretty complicated. I was wondering if there is something easy to kick off a project and do these things without using starter kit and without all the complicated configuration around Webpack.

Sure, there is a tool, and this is Parcel.


Table of content:


Parcel can be used to build both client side apps ad well as Node.js server side apps.

It includes all the important things we need to develop modern JavaScript apps:

  • Fast bundle times
  • Bundling supports JS, CSS, HTML, files, and more
  • Import/Export module system
  • Code transforms that include Babel, TypeScript, Flow, PostCSS, SCSS, PostHTML, etc.
  • Hot module replacement
  • Code splitting
  • Tree shaking (dead code elimination)
  • Error handling
  • Minification and Source maps

and more.

Parcel has good documentation that also includes section with helpful recipes, following which, It is very easy to start new project.

Start a project

I already have Node.js and npm installed. I use npm to create new project inside the directory I create. I use -y to automatically accept all the default values and create package.json file:

mkdir parcel-example
cd parcel-example
npm init -y

As next I install the parcel-bundler module, which provides the parcel command line tool I’ll be using for the build. I install the parcel-bundler as development dependency on the project, that way it does not need to be installed globally:

npm install --save-dev parcel-bundler

The parcel command line tool is now installed under ./node_modules/.bin/.

The build will take the files from ./src directory. I create this directory add few files in it:

mkdir -p src src/scripts src/styles
touch ./src/index.html ./src/scripts/index.js ./src/styles/index.scss

Next I edit the index.html. I create simple HTML5 document and add a script reference to the JavaScript file.

Note: If you are using Visual Studio Code, you can just type html:5 to bring html5 document snippet.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <h1>Parcel Example</h1>
  <div id="hello-world"></div>
  <script src="./scripts/index.js"></script></body>
</html>

Next I add some code to the index.js file:

const helloWorldElement = document.getElementById('hello-world');

helloWorldElement && (helloWorldElement.innerText = `Hello Atanas Hristov!`);

Npm script

I modify the package.json file and add npm script to run parcel. All we need is, point to the src/index.html file.

{
  "name": "parcel-example",
  "version": "1.0.0",
  "description": "Contains example project using [Parcel](https://parceljs.org/) as a build tool.",
  "main": "index.js",
  "scripts": {
    "develop": "./node_modules/.bin/parcel src/index.html"
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://git@bitbucket.org/ahristov/parcel-example.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "homepage": "https://bitbucket.org/ahristov/parcel-example#readme",
  "devDependencies": {
    "parcel-bundler": "^1.10.3"
  }
}

At this point we already have bundling and this is all the configuration that we need. Now we can run the npm develop script:

npm run develop

This starts development server on port 1234:

Parcel started

If we open in Chrome the above url, we can see our page and the JavaScript working:

Parcel running

Hot reload

From now, if we change the html or the javascript file, we see instant result in the browser.

Let’s add another javascript file and see how the import/export works.

I create ./src/scripts/utils/names.js, export a function from it:

const getFullName = (firstName, lastName) => {
  return `${firstName} ${lastName}`;
}

export { getFullName };

Then I use it in the ./src/scripts/index.js file:

import { getFullName } from './utils/names';

const fullName = getFullName('Atanas', 'Hristov');
const helloWorldElement = document.getElementById('hello-world');

helloWorldElement && (helloWorldElement.innerText = `Hello ${fullName}!`);

After we save the changes, our page still looks the way it was, so things are working.

If we inspect the sources tab in the Developer tools, we can see the source maps working:

Parcel source maps

Note: There is an issue with the source maps. In order to get the source maps working after changes in the JavaScript, we need to refresh the browser.

Customizing Babel

The Babel compatibility is set to env preset by default. This is simply because it is the default setting for Babel.

Let’s install an experimental Babel plugin. Experimental plugins are bringing nonstandard proposal ECMAScript language features with Babel.

In this example I install nullish-coalescing-operator plugin.

With nullish coalescing operator I can normalize null parameters:

var foo = object.foo ?? "default";

I change ./src/scripts/utils/names.js like this:

const getFullName = (firstName, lastName) => {
  firstName = firstName ?? 'John';
  lastName = lastName ?? 'Doe';

  return `${firstName} ${lastName}`;
}

export { getFullName };

If we refresh the page, we get build error:

Experimental features browser error

And also in the command line our server explains in the console:

Experimental features console error

To fix this, we install We install the babel plugin @babel/plugin-proposal-nullish-coalescing-operator:

npm install --save-dev @babel/plugin-proposal-nullish-coalescing-operator

As next, we add .babelrc babel configuration file at the home directory of the project:

{
  "plugins": [
    "@babel/plugin-proposal-nullish-coalescing-operator"
  ]
}

Let’s now remove the actual parameters to getFullName() in ./src/scripts/index.js:

import { getFullName } from './utils/names';

const fullName = getFullName();
const helloWorldElement = document.getElementById('hello-world');

helloWorldElement && (helloWorldElement.innerText = `Hello ${fullName}!`);

If we run the development server again:

npm run develop

and refresh the page, the parameters to the getFullName are defaulted now:

Defaulted parameters

Using Sass

Let’s add some Sass in the file ./src/styles/index.scss:

$primary-color: darkmagenta;
$secondary-color: green;

h1 {
  color: $primary-color;
}

#hello-world {
  color: $secondary-color;
  font-family: 'Courier New', Courier, monospace;
  font-size: 20pt;
  font-weight: bold;
}

I install the sass npm package:

npm install sass

After the installation finished, I import the index.scss file into ./src/scripts/index.js:

import { getFullName } from './utils/names';
import '../styles/index.scss';

const fullName = getFullName();
const helloWorldElement = document.getElementById('hello-world');

helloWorldElement && (helloWorldElement.innerText = `Hello ${fullName}!`);

Now, if we restart the web server and refresh the page, we get Sass into our project and we should see the styling modified.

Parcel Sass example

Production build

To compile our project we add another npm script in the package.json file:

  "scripts": {
    "develop": "./node_modules/.bin/parcel src/index.html",
    "build": "./node_modules/.bin/parcel build src/index.html --out-dir public"
  },

By default it will build the content into ./dist directory. This directory already exists and I set the output directory to ./public. If we run:

npm run build

, we get the production build of the compiled assets:

Parcel build

And now we have our simple setup and we can keep working with it.

Links

Code repository for this blog

Parcel

Babel nullish-coalescing-operator