Register additional Laravel Components in Jigsaw

• 7 min read

If you ask the wider web developer community,Jigsaw is probably not well known to be a static site generator, but rather known as a toy. But Laravel developers have surely heard about it in the last few months (I hope!).

I love Jigsaw! And so does the rest of my team. We've used Jigsaw in 40+ projects so far and are very pleased with it.

My most recent project relied heavily on remote collections and custom helper methods. Many of those helper methods made API requests to third party services.
To keep the local builds fast and snappy I've integrated Laravel's Cache component into Jigsaw to cache the API responses for a couple of minutes.

To make all of this work, I first had to figure out how to register something complex – like the cache component from Laravel – in Jigsaw's IoC Container.

That's what we cover first: How to work with the Container in Jigsaw. Then we will add the illumiante/cache package to a Jigsaw project and connect everything together.

Last, I will show you an example how you can use the Jigsaw Container to access your sites configuration in plain old PHP Classes.

I took quite a while for me to understand that Jigsaw already comes with an IoC Container.

In essence, you can call \Illuminate\Container\Container\Container::getInstance() anywhere in your code to get access to the Jigsaw container.

/** @var Container $cache */
$container = \Illuminate\Container\Container\Container::getInstance();

Then, when you have the container, you can call $container->make(ContractInterface::class) to get a ContractInterface instance resolved from the container. (In a Laravel app, you would use app(), resolve() or Constructor Injection for this.)

It's that simple! I use Laravel's Container all the time in other projects and I know how it works, but it never clicked for me that I could use it in Jigsaw sites too. ¯\(ツ)

Well, now that we have learned that we have a full blown IoC container at our fingertips, let's use it to register the cache component in our project.

First we need to include the illuminate/cache package in our Jigsaw project. Note that at the time of writing this article, Jigsaw doesn't support Laravel v8 components yet. That's why we stick to v7.

Run the following command in your favourite terminal to add the package.

composer require "illuminate/cache:^7"

Next we need to register our installed packages in Jigsaw. For that we open bootstrap.php and add the following beforeBuild-hook.

use Illuminate\Cache\CacheManager;
use Illuminate\Filesystem\Filesystem;

/**
 * Register Cache Component in Jigsaw
 */
$events->beforeBuild(function (Jigsaw $jigsaw) {
    $container = $jigsaw->app;

    $container['config'] = $container['config']->merge(collect([
        'cache.default' => 'file',
        'cache.stores.file' => [
            'driver' => 'file',
            'path' => __DIR__ . '/storage/cache',
        ]
    ]));

    $container['files'] = new Filesystem;

    $container->singleton(CacheManager::class, function () use ($container) {
        return new CacheManager($container);
    });
});

The code above does the same as what Laravel does in it's Service Providers:

  • We take the $container from $jigsaw->app (We will use Container::getInstance() later)
  • Add caching related values to the $config
  • Register a new Filesystem
  • Create a new singleton instance of the CacheManager and provide it with our container / configuration.

(Hat tip to the mattstauffer/Torch project for this example.)

Now, whenever we resolve an instance of CacheManager and store values by using $cache->remember() or $cache->put(), the values are cached in the ./storage/cache-folder.

Note that the folder has to exist to make this work. Best create the folder now by running the following command.

mkdir -p ./storage/cache
touch ./storage/cache/.gitkeep

Commit the changes we've just made. (Including the ./storage/cache/.gitkeep file.) Next time the repo is cloned, everything will work as expected.

But to prevent commiting your cache, update .gitignore with the following line.

./storage/cache

As mentioned in the beginning, my project uses a lot of API heavy helper methods. So let me show you how to use the CacheManager in a helper method. (The concept can be applied to every other PHP class or function though.)

Let's say we have a fetchWeather()-helper method which makes an API request to get the current weather data of a location. The method returns a JSON string.

// config.php
'fetchWeather' => function ($page) {
    return file_get_contents('http://wttr.in/Zurich?format=j1');
},

This is fine if you rebuild the site every other hour and only use the fetchWeather() helper in a single page. But it significantly slows down your build process if you use fetchWeather() on every single site of your project or when you use a file-watcher to rebuild your site constantly.

Adding caching is straightforward. Here is the updated featchWeather() method.

// config.php

use Carbon\Carbon;
use Illuminate\Cache\CacheManager;

'fetchWeather' => function ($page) {
    /** @var CacheManager $cache */
    $cache = Container::getInstance()->make(CacheManager::class);

    $cacheValidFor = Carbon::now()->addMinutes(15);

    return $cache->remember('wttr-data', $cacheValidFor, function () {
        return file_get_contents('http://wttr.in/Zurich?format=j1');
    });
},

A quick run down what we have done here:

  1. We resolve an instance of the CacheManager by using Container::getInstance().
  2. We define for how long the response should be cached. (15 minutes)
  3. We wrap the API call in $cache->remember().

The API request is now only made on the first build, and then cached for all subsequent builds for the next 15 minutes.

If this is too strict for you, you could update the method to look for the production config value. You can access your project configuration like so:

Container::getInstance()->config['production'];

A couple of months (years?) ago, Ryan Chandler showed me, that he replaced the default Markdown parser of Jigsaw with his own version. (He now uses a full blown Laravel app for his website). This allowed him to register and use CommonMark extensions.

I was intrigued! First this introduced me to the world of Markdown Extensions. And second, I've learned about Block Rendering.

For years I've used highlight.php to generate the syntax highlighting for code samples during build time. This allowed me to ditch JavaScript completely for this website.

My implementation was … special: I've used a couple of Regex expressions to search the already rendered HTML pages for code snippets and then used str_replace to patch in the syntax highlighting. 🥴

But now that I know I can simply swap Jigsaw's Markdown parser with my own version, I could finally clean up that code and use something more stable like spatie/commonmark-highlighter.

But I digress. What I want to show you, is how you can access your Jigsaw page configuration in a PHP class; like my custom Markdown parser.

Let's imagine that my custom parser is registered in Jigsaw's bootstrap.php file like this:

$container->bind(MarkdownParser::class, CustomParser::class);

And now let's say, that I only want to highlight code snippets in production. (Code highlighting slows the build down by a few seconds). In my CustomParser class I can achieve this by adding this code:

// ...

$environment = Environment::createCommonMarkEnvironment();

if (Container::getInstance()->config['production']) {
    $environment->addBlockRenderer(FencedCode::class, new FencedCodeRenderer(['html', 'php', 'js']));
    $environment->addBlockRenderer(IndentedCode::class, new IndentedCodeRenderer(['html', 'php', 'js']));
}
// ...

I resolve the Container, check the configuration value for production and only add my BlockRenderers if the current build is in production.

You can find the whole CustomParser class here.

I hope this excourse gave you some ideas on how to add more functionality to your Jigsaw projects. Be it by adding Illuminate Components or maybe some Markdown extensions.

If you want to add more Laravel Illuminate components to a Jigsaw project and need examples, I can highly recommend Matt Stauffer's Torch project. (Here is an example on how to add the translation component).