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.