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!
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.
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.
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';
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.
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.