Dusty Candland | Thu May 09 2019 Eleventy & ParcelJS Working Together

Eleventy & ParcelJS Working Together

Here's the setup I'm using to build a headless e-commerce store. You can read about why a headless e-commerce site for an overview.

I decided on Eleventy and ParcelJS as the static site generator and JavaScript bundler. Let's get into the setup!

Eleventy Setup

I started with a Jekyll setup that I've used for other sites since Eleventy is somewhat structured after Jekyll. For the most part things just worked after moving a few files around.

Turning Jekyll up to Eleventy by Paul Lloyd is a good starting point.

I did have some HAML that needed to be converted by to HTML/Liquid. And I eventually converted everything to Nunjucks. But the different template engines work pretty well together so at first I had both, Liquid and Nunjucks.

One change I made was to move all the site files under a src directory.

Eleventy just uses a _includes directory by default so I moved all my _layouts files into that. You can configure that if you want to.

Also had to move the css/main.scss file from Jekyll into the css directory. The css, as well as the js and images, all moved under an assets folder.

src
├── _includes
│ ├── default.html
│ ├── footer.html
│ ├── head.html
│ ├── header.html
│ └── product_link.njk
├── _posts
├── assets
│ ├── css
│ │ ├── _custom.scss
│ │ └── main.scss
│ ├── images
│ │ ├── 500603.jpg
│ │ ├── apple-touch-icon.png
│ │ ├── favicon.png
│ │ ├── favicons.zip
│ │ ├── icon.png
│ │ ├── icon.svg
│ │ ├── logo-v.svg
│ │ └── mstile-144x144.png
│ └── js
│ └── index.js
├── favicon.ico
├── index.njk

That's pretty close to what I started with. Except assets are not being bundled at this point.

Here's the starting .eleventy.js config file.

module.exports = function (eleventyConfig) {
// Add a filter using the Config API

eleventyConfig.addPassthroughCopy('src/favicon.ico')
eleventyConfig.addPassthroughCopy('src/assets/css')
eleventyConfig.addPassthroughCopy('src/assets/images')
eleventyConfig.addPassthroughCopy('src/assets/js')

// You can return your Config object (optional).
return {
dir: {
input: 'src'
},
templateFormats: [
'html',
'md',
'njk',
'css',
'js'
],
passthroughFileCopy: true,
}
}

Nothing too interesting in the config yet, except maybe the templateFormats, which tells Eleventy to process all those types of files. Mostly just needed for the passthroughFileCopy.

But the assets are not being bundled so everything JS & CSS is broken.

ParcelJS Setup

Install with npm i -D parcel-bundler.

This was the first time I've used ParcelJS. So far it's been great to work with.

A little more restructuring is needed. I renamed the assets to _assets.

Then setup ParcelJS to build the assets. It's a zero config solution, so all the config is done at that command line. I used the NPM scripts to run it.

It's watching changes in src/_assets and building to _site/assets.

// package.json

"scripts": {
"start": "npm-run-all --parallel dev:*",
"dev:eleventy": "ELEVENTY_ENV=development eleventy --serve --quiet",
"dev:parcel": "parcel watch src/_assets/images/* src/_assets/css/main.scss src/_assets/js/index.js --out-dir _site/assets",
"build": "run-s prod:*",
"prod:eleventy": "ELEVENTY_ENV=production eleventy --output=./build",
"prod:parcel": "parcel build src/_assets/images/* src/_assets/css/main.scss src/_assets/js/index.js --out-dir _site/assets"
},

You'll need the npm-run-all package: npm i -D npm-run-all.

Now you should be able to build with npm run dev:parcel

Next I added Babel support. Node-fetch is a polyfill for fetch.

npm i -D "@babel/core" "@babel/plugin-proposal-class-properties" "@babel/preset-env" "node-fetch"

Also, need to add a .babelrc file to configure Babel.

// .babelrc
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-proposal-class-properties"]
}

I'm using Bootstrap and SASS, so added dependencies for those.

npm i -D "sass" "jquery" "popper.js" "bootstrap"

Lastly, added some stuff to work with Shopify. I'll dig into those more in the next post.

npm i -D "graphql" "shopify-buy" "stimulus"

Okay! I now we've got all the JS dependencies we need for the server side and client side.

Include Bootstrap & jQuery in the bundle

Since I'm bundling the client side JS I wanted to include Bootstrap and jQuery. There isn't a globals config like Webpack. Just need to include the references in the entry point files. _assets/js/index.js and _assets/css/main.scss.

/* js/index.js */
import $ from 'jQuery'
import popper from 'popper.js'
import bootstrap from 'bootstrap'

window.$ = window.jQuery = $
window.bootstrap = bootstrap
window.popper = popper
/* css/main.scss */
@import 'node_modules/bootstrap/scss/bootstrap';

Working together

This part was a bit tricky to get working, but it's at a good point. I was over-thinking things making it seem tricky.

First, I told Eleventy to ignore the src/_assets directory. If not, it will rebuild the static site and reload the browser, but ParcelJS is also trying to do that so there are two reloads and not always in working order.

Add a file .eleventyignore to the root with src/_assets/**/*.

echo "src/_assets/**/*" >> .eleventyignore

Then I removed some of the passthrough config that isn't needed anymore.

module.exports = function (eleventyConfig) {
// Add a filter using the Config API

eleventyConfig.addPassthroughCopy('src/favicon.ico')

// You can return your Config object (optional).
return {
dir: {
input: 'src'
},
templateFormats: [
'html',
'md',
'njk'
],
passthroughFileCopy: true
}
}

Restart NPM if needed and both should be working and updating the browser when needed.

That's it! I wasted a bit of time trying to have ParcelJS build into the src dir and have Eleventy copy those files to _site. But building directly to _site seems to work great.

Checkpoint

Here's how things are looking now.

src
├── _assets
│ ├── css
│ │ ├── _custom.scss
│ │ └── main.scss
│ ├── images
│ │ ├── 500603.jpg
│ │ ├── apple-touch-icon.png
│ │ ├── favicon.png
│ │ ├── favicons.zip
│ │ ├── icon.png
│ │ ├── icon.svg
│ │ ├── logo-v.svg
│ │ └── mstile-144x144.png
│ └── js
│ ├── index.js
├── _includes
│ ├── default.html
│ ├── footer.html
│ ├── head.html
│ ├── header.html
├── _posts
├── favicon.ico
├── index.njk

package.json

{
"name": "project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "npm-run-all --parallel dev:*",
"dev:eleventy": "ELEVENTY_ENV=development eleventy --serve --quiet",
"dev:parcel": "parcel watch src/_assets/images/* src/_assets/css/main.scss src/_assets/js/index.js --out-dir _site/assets",
"build": "run-s prod:*",
"prod:eleventy": "ELEVENTY_ENV=production eleventy --output=./build",
"prod:parcel": "parcel build src/_assets/images/* src/_assets/css/main.scss src/_assets/js/index.js --out-dir _site/assets"
},
"author": "project",
"license": "ISC",
"dependencies": {
},
"devDependencies": {
"@11ty/eleventy": "^0.8.2",
"@11ty/eleventy-plugin-rss": "^1.0.6",
"@babel/core": "^7.4.4",
"@babel/plugin-proposal-class-properties": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"bootstrap": "^4.3.1",
"graphql": "^14.2.1",
"jquery": "^3.4.0",
"node-fetch": "^2.4.0",
"npm-run-all": "^4.1.5",
"parcel-bundler": "^1.12.3",
"popper.js": "^1.15.0",
"sass": "^1.19.0",
"shopify-buy": "^2.2.2",
"stimulus": "^1.1.1"
}
}

.eleventy.js

module.exports = function (eleventyConfig) {
// Add a filter using the Config API

eleventyConfig.addPassthroughCopy('src/favicon.ico')

// You can return your Config object (optional).
return {
dir: {
input: 'src'
},
templateFormats: [
'html',
'md',
'njk'
],
passthroughFileCopy: true
}
}

.eleventyignore

src/_assets/**/*

.babelrc

{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-proposal-class-properties"]
}

Next I'll dive into creating dynamic collections in Eleventy.


unsplash-logoMarl Clevenger