Published on August 1, 2018
In my last post we took a look at Sculpin, the PHP-based static site generator which is currently the most starred on Github. Today we’re exploring Jigsaw, a tool which promises to bring a Laravel-based approach to the world of PHP static site generators.
Sign up for a free Contentful account and start building in moments.
I’m gonna be honest with you—I’m not the biggest Laravel user. I appreciate what it's done for PHP, and how it has helped push forward the world of PHP development in a time where older frameworks were struggling to keep up with innovations coming from other platforms.
It’s also a prime example of the good kind of cross pollination happening in the PHP world: it employs packages from the Symfony world, The PHP League, Doctrine, and more. It’s a good lesson in building something cool on top of great foundations, without having to continuously reinvent the wheel.
Overall, I think that Laravel is an excellent framework, but I'm definitely inexperienced with it. If you think I have overlooked or misjudged something (both Laravel and Jigsaw-related), please feel free to reach out to me on Twitter: constructive criticism is always welcome!
Just like I did with Sculpin, I’ll jump straight into the action, so let’s create a sample project and play with it! We’ll open the official docs and start from there.
Unlike Sculpin, Jigsaw doesn’t provide a skeleton project we can use as quickstart. Instead, we must create a normal project, add tightenco/jigsaw
as dependency, and use its CLI:
Let’s go through the default directory structure that was just generated. In the top level directory, we have a config.php
with this code:
I’m not sure yet what this means, but having a configuration file clearly placed seems like a good approach 👍. The second PHP file is called bootstrap.php
, and it’s empty (only some commented-out code), but it hints at how we could use the event system at some point.
We also have Javascript dependencies. I see a webpack.mix.js
, which lets me know that Laravel Mix is being used. So I run npm install
and in the meantime I’ll explore the docs to try to understand a bit more of what I’m doing 😉.
About 10 dependencies and 300MB in node_modules
later, we’re back. Now, let’s try to build our site. It appears we have 2 ways of doing so: we can use the Jigsaw CLI to build and then serve, or use npm and its live reload capabilities provided by Browsersync:
Regardless, both work and you get this!
Now let’s explore the skeleton project that was generated earlier. The order in which we’re exploring parts of the project might appear to be random because, well, it is. That’s the cool thing about discovering something new, right? The exploratory phase, where you peek at this, take a look at that, and oh, a source
directory! What lies therein?
Being a Laravel-based project, I’m not surprised to find an index.blade.php
file, which contains the shiny Hello world! from above. The @extends('_layouts.master')
directive hints to me that I should go look in the _layouts
directory for a master.blade.php
template, and lo and behold, there it is. Everything seems nice and tidy with the templates, let’s go check how assets are handled.
Laravel Mix provides out of the box support for Sass, so source/_assets/sass/main.scss
is your starting point. SCSS might not be the latest and shiniest thing on the CSS landscape, but it’s battle-tested and definitely gets the job done, so I approve of its inclusion. Let’s turn that page into something that will get your attention:
Let’s hit save and webpack should do its magic and turn our background red, right? Yes, it does! I'll spare you the screenshot for your eyes' sake, and now let's continue our exploration.
I’d like to set up a basic blog, with posts written in Markdown files. To do that, let’s introduce the concept of collections, which, as the docs state, give you the ability to access your content at an aggregate level. Sounds about right for a blog. Remember the config.php
file from earlier? Let’s tweak it to tell Jigsaw about our collection of blog posts:
Jigsaw will look for the corresponding directory for every collection we define. In our example, posts
will become source/_posts
, so in that directory let’s create a blog post to show our love for one of the greatest songs of all times.
Let’s save this file as the-fresh-prince.md
, and then let’s create the appropriate template as specified in the extends
part of the front matter: source/_layouts/post.blade.php
.
Let’s run ./vendor/bin/jigsaw build
, and we’ll have a nice build_local
directory generated with our shiny website, so let’s open the URL (which we still have to build ourselves) and… It works!
(Author’s note: at this point I would like to add more GIFs, but I have to keep it professional. Let’s all just pretend to have a GIF of Jake Peralta saying noice, thank you.)
We’ve covered some of the basics, so now the million-dollar question: is Jigsaw Contentful-ready? With this, of course, I mean how easily content stored in Contentful can be integrated in a Jigsaw-powered website. We open the documentation page about event listeners, and with that page open in the tab, we can give this a try. Remember bootstrap.php
? It contained some commented-out code which hinted at how events work, so let’s reopen that:
Ok, seems to be reasonably easy. As extra help, after some googling I found an article about creating a sitemap with Jigsaw, and the original pull request for the events feature, so I have enough to figure this out. Let’s composer require contentful/contentful
and hack this thing together!
This code is more of a proof of concept than complete implementation (it relies a lot on conventions, and it lacks handling of special fields such as locations, links, etc); however, it should get the job done for a basic example.
First of all, we pass a fully-qualified class name (FQCN) to $events->beforeBuild()
, and Jigsaw will know that it must create an instance of that class and execute the handle
method of it. Truth be told, I think there should be an interface in order to have a stronger contract, something like this:
Or perhaps just requiring any valid callable (thus including objects implementing __invoke
). It works now in its present state but I like strong contracts, and you should know by now that I'm picky—it's in the title of the series!
This is an overview of what ContentfulFetcher
actually does:
If the local
environment is detected, the whole process will be stopped. This is done to avoid the performance impact due to having to fetch all data from Contentful on every build (which can likely be very often, if running on watch mode). The idea is to run a ./vendor/bin/jigsaw build production
initially to fetch all content, and then operate with what’s been stored locally. Of course, a good idea would be to add the generated files to the local .gitignore
, to avoid keeping them in version control (content doesn't belong there).
We create an instance of a client object using the Contentful Delivery SDK for PHP.
We iterate through all the defined collections, and we use a convention to map a local collection to a content type ID in Contentful. We then clean the corresponding directory, where we will dump the generated files.
We fetch entries for the given content type, and we use the Contentful\Delivery\Resource\ContentType
object to get a list of fields. With those fields, we build the appropriate YAML front matter section, and then we add the body at the end (string handling here is very basic and could be improved).
We finally dump the contents of the generated file. We use the Contentful ID to save them, as we can define a pretty URL when configuring the collection.
There’s a lot of gluing going on, but it works on my machine™️. This could be done a thousand times better, with more flexible configuration, but as a proof of concept I’m reasonably satisfied with it.
Before our final verdict, I think it's only fair to mention that during the process of writing this article, I experienced a couple of issues which have since been fixed. Before publishing, I got in touch with Matt Stauffer (one of the people behind Jigsaw) and explained that I was experiencing some difficulties with the handling of assets and events (misconfiguration for the former, and lack of docs for the latter). Over the course of a weekend, both issues have been fixed 👏
I quite like Jigsaw, but it’s rather barebones in its out-of-the-box configuration. Unlike Sculpin, it makes no assumption over what kind of use you’ll have for it, which is a double-edged sword: it avoids clutter and gives you complete control, but it also creates a higher barrier of entry for simple use cases such as blog.
To solve this issue, Matt revealed to me that they’re building a generator with website skeletons, to address the most common needs (such as a blog, a documentation website, etc), with a Github issue to track progress. He also mentioned that they just launched a new showcase of websites built with Jigsaw, which I encourage you to check out.
As a non-Laravel developer, I was afraid that Jigsaw would be difficult to pick up and use, but it definitely wasn’t the case. It is actively being developed (the events system was added just a few months ago) and is on track to quickly become the most popular PHP static site generator, in terms of stars on Github. It provides very good defaults, and assets are preconfigured to use a modern stack (Sass for CSS, and webpack with Babel for JS).
The only real criticisms I can make are actually the same as when I discussed Sculpin: content and presentation (assets and templates) could be more separate; instead, they belong to the same source
directory, and I would love to be able to have a more defined structure for collections, instead of relying exclusively on the YAML bit before the actual Markdown contents.
And in the meantime, check out our PHP docs if you're building a project in PHP. And if you haven't done so already, sign up for a free account with Contentful and start building in minutes.
Subscribe for updates
Build better digital experiences with Contentful updates direct to your inbox.