Following this pole on Twitter I thought I’d take a look at spinning up a project using Parceljs.

I’ve used Gulp for years and Grunt before that. Parcel seemed like a logical progression. That, plus, I still don’t have the stomach to try and get Webpack working!

Parcel is touted as ‘zero configuration’ and although that is largely true, if you are moving from Gulp there are definitely some things you need to keep in mind.

My requirements for a task runner/web-app builder ‘thing’ are incredibly modest. I’m typically making a prototype of something so I just want it to compile any meta-languages such as PostCSS or TypeScript to CSS/JS respectively and spin up a local server where any iterations are immediately kept up to date in a browserSync/live reload style.

Before I get to Parcel, I thought it might be useful to see the kind of thing I have in Gulp that I’m trying to replicate in Parcel:

PostCSS

In Gulp, working with PostCSS I require the plugins I want like this in my gulpfile.js:

var cssMixins = require("./global/css/mixins.js");
var cssVariables = require("./global/css/variables.js");
var postcss = require("gulp-postcss");
var nested = require("postcss-nested");
var mixins = require("postcss-mixins")({ mixins: cssMixins });
var simplevars = require("postcss-simple-vars")({ variables: cssVariables });
var autoprefixer = require("autoprefixer");
var postcssimport = require("postcss-import");
var postcssAssets = require("postcss-assets");
var postcssColorFunction = require("postcss-color-function");
var reporter = require("postcss-reporter");
var cssnano = require("cssnano");

And the CSS task using PostCSS looks like this:

gulp.task("css", function() {
    var processors = [
        postcssimport({ glob: true }),
        mixins,
        simplevars,
        nested,
        postcssAssets,
        postcssColorFunction(),
        autoprefixer({ browsers: ["iOS >= 6", "ie_mob >= 8", "android 4"] }),
        cssnano({
            zindex: false,
            reduceIdents: false,
        }),
    ];

    // We only want to compile the root files, as these import the partials
    var cssRootFiles = ["./interface/*.css", "./global/css/*.css"];

    return (
        gulp
            .src(cssRootFiles)
            .pipe(sourcemaps.init())
            .pipe(postcss(processors))
            .pipe(concat("interface.css"))
            .pipe(sourcemaps.write())
            .pipe(gulp.dest("./_build/css/"))
            .pipe(browserSync.stream())
    );
});

I tend to group visual components in a ‘interface’ folder and writing the task like this means it goes through each component, processes the CSS partials and sends them out to a folder.

Lets start by getting an equivalent CSS task up and running with Parcel.

The basics

You install Parcel with NPM (or Yarn):

npm install -g parcel-bundler

Parcel looks to index.html as the default ‘entry point’ and builds stuff auto-magically from there to a dist folder at the same level as the index.html file. So, assuming you have an index file to kick things off you can run parcel index.html from Terminal and it spins up a local server at localhost:1234.

So far so good. However, Parcel still needs how to handle PostCSS. In the past my PostCSS configuration was largely encapsulated in the Gulp task. We need that configuration isolated into something Parcel can understand.

PostCSS configuration with Parcel

PostCSS configuration can be done a few ways in Parcel. I opted for creating a postcss.config.js file and sticking it in the root of the project.

In terms of folder structure, I have a ‘global’ folder in the root, which contains everything I need to run my mixins (one lot as JS file, and another file as CSS — postcss-mixins is happy to mix the two) and variables; also defined in JS here.


global/
  css/
    app-reset.css
    variables.js
    mixins/
      mixins.js
      scrollbars.css

The contents of postcss.config.js:

var colors = require("./global/css/variables");

module.exports = {
    plugins: [
        require("autoprefixer"),
        require("postcss-mixins")({
            mixinsDir: "./global/css/mixins",
        }),
        require("postcss-simple-vars")({ variables: colors }),
        require("postcss-nested"),
        require("postcss-color-function"),
    ],
};

You’ll need to run a npm i after creating that file to ensure that any pulgins you use that aren’t part of the standard ones in Parcel are downloaded.

This file is obviously a lot shorter than the (nearly) equivalent Gulp example. Parcel does a lot of the asset management I was handling with plugins in Gulp all by itself. Globbing patterns for example are already part of Parcel. Nice!

You might be wondering about that first line var colors = require("./global/css/variables"); in that config file. I usually have a standard list of colours I use and this allows me to pass it from a separate file to the postcss-simple-vars plugin.

PostCSS configuration can silently fail

It’s worth noting that if you have a PostCSS plugin that conflicts with Parcel, your PostCSS will silently fail and you will end up with unprocessed CSS being exported. For example, if you author:

.item-Thing {
    &:checked {
        background-color: #f90;
    }
}

You want to see this exported to CSS:

.item-Thing:checked {
    background-color: #f90;
}

But you still see what you had in your authoring style sheets:

.item-Thing {
    &:checked {
        background-color: #f90;
    }
}

I had to use trial and error when writing that postcss.config.js config file, removing from the original list of plugins I had one at a time until things processed correctly.

Failing silently is out of character for Parcel as one of the great features it enjoys is spitting out on the page problems with paths and the like when your configuration is off. That was a common issue for me at first and it was a big help in getting on the right path.

Typescript and Gulp

I confess I have let myself get behind the curve when it comes to best-practice of making ‘partials’ of TypeScript/JavaScript files.

I have had a few failed attempts at switching to JavaScript ES6 modules in the last year. As such, I kept coming back to the simplicity and dependability of the triple-slash file imports that TypeScript allows:

/// <reference path="path/to/partial" />

If you aren’t familiar with TypeScript triple-slash file imports, they work similarly to @import "sausages.css"; in CSS land which makes them easy for me to reason about. I split my TypeScript into separate partial files and bring them into the top of my main app.ts file with a TypeScript triple-slash import.

Parcel seems to flatly refuse to allow triple-slash imports. At least I couldn’t get them working. It’s possibly due to my current ineptitude with Parcel and setting the correct TypeScript compiler options.

However, I actually ended up using ES6 style module syntax with a tsconfig.json file in my project that looked like this:

{
    "compilerOptions": {
        "module": "ES6",
        "noImplicitAny": true,
        "allowJs": true,
        "lib": ["ES2016"],
        "target": "ES6"
    }
}

With that in place, I was able to import/export with ES6 syntax (finally!).

I’m thrilled I’m finally there with ES6 modules. Indulge me while I exemplify their syntax and use with Parcel. Suppose we have a project structure like this:

index.html
app.ts
postcss.config.js
tsconfig.js
interface/
    header/
        header.css
        header.ts
    menu/
        menu.css
        menu.ts

In that root index.html, we actually link to a TS file, not the resultant JS file. For example here is the contents of the index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Parcel Test</title>
    <link rel="stylesheet" type="text/css" href="/styles.css">
</head>
<body>
    <script src="/app.ts"></script>
</body>
</html>
When working in Parcel, for my own sanity I’ve found it best to stick to root relative links. However there are choices which are explained fully here: https://parceljs.org/module_resolution.html.

The link to that app.ts file imports the header.ts and menu.ts files like this:

import * as header from "/interface/header/header";
import * as menu from "/interface/menu/menu";

That’s assuming we have exported relevant functions/const etc from those files like this:

function menuEle() {
    // stuff
}
export { menuEle };

It took me a bit of trial and error with paths and wrapping my head around imports/exports but it makes sense now and it’s a syntax I like a lot. I’m thankful TypeScript had the triple-slash imports but I’ll try to continue using ES6 modules to solve that need moving forward.

Hot Module Reloading

One essential part of modern front-end tooling is live-reloading. Changes made in authoring code (style sheets, JavaScript, HTML) instantly pushed into the browser for rapid iteration.

When I had v1.11.0 of Parcel installed I was running into an issue where duplicate items were being loaded into the DOM. A reduction of my app.ts file looked like this:

import * as header from "/interface/header/header";
import * as menu from "/interface/menu/menu";

var app = (function() {
    var root = document.documentElement;
    var body = document.body;
    function init() {
        body.appendChild(header.headerEle());
        body.appendChild(menu.menuEle());
    }
    init();
})();

Where both header.headerEle and menu.menuEle were functions that returned elements I was appending into the DOM. Any time I made a change in app.ts, menu.ts or header.ts Parcel would run things again, and append both of those elements back into the body; giving me multiples of everything as I worked.

Trying to follow this issue on HMR (the acronym by which Hot Module Replacement is referred) on the Parcel repository: https://github.com/parcel-bundler/parcel/issues/289 it seemed like v1.12.0 was the answer as it inverted the approach for Hot Module Replacement, making things reload rather than do HMR by default.

Sure enough, an upgrade to v1.12.0 fixed the issue and I finally had everything working as desired.

  • PostCSS compiling: check
  • TypeScript compiling: check
  • Instant browser updates while iterating: check

Publishing output/Running a build

It’s worth calling out that at some point you may want to export or ‘build’ whatever you are making to merely the essential files with no source maps etc. It’s probable that if you copy the files from the `dist` folder ‘as is’ to another location that isn’t running the local host server, none of the assets, such as style sheets and scripts, will load.

The correct way to get a build is to use the specific build command. So, here is the command I run as an example (Note: You might need to type this out as sometimes the quotes get screwed up when pasting to the command line!):


parcel build index.html ‑‑public‑url './' ‑‑no‑source-maps ‑‑no‑cache

There are a bunch of additional options/arguments available with the build command. A full list is at https://parceljs.org/cli.html. The options I chose were to set the public url, to remove any cache and not output any source maps.

Summary

With a little adapting I was able to move from Gulp to Parcel. As a consequence it pulled some of my practices into 2019! Principally I switched from triple-slash TypeScript imports to ES6 modules and extracted my PostCSS configuration into a separate file.

Parcel is capable of a lot more and I’ll be happy to use it again for the next thing I spin up. I’ve stuck my postcss.config.js and tsconfig.json in the root of the folder that holds all my projects so starting a project with Parcel should be far more straightforward next time.