Inline CSS to your Ghost theme with Grunt, for great AMP

in #technology8 years ago

I've been tinkering with the bleak Ghost theme recently, getting it ready with some cool new features for its version 1.0.0 release.

Amongst those features is a bleak implementation for Ghost's new /amp pages (if you weren't aware, new versions of Ghost support AMP out of the box by appending /amp to their post URLs), since the default implementation uses a standard Ghost theme. Good for mobile search speed, bad for branding!

Supporting AMP in your Ghost theme boils down to implementing the amp.hbs top level template file, which will be used in place of the traditional post.hbs. However, it's not so simple - AMP's by-design strictness means that you need to be careful to design using amp-xxx tags such as amp-img instead of their vanilla HTML counterparts. This is pretty easily done using the #is handlebars helper:

{{#is "amp"}}
<amp-img src="https://my.image.url/img.jpg" width="800" height="600" layout="responsive"></amp-img>
{{else}}
<img src="https://my.image.url/img.jpg"></img>
{{/is}}

However, one more pain point is the requirement of AMP to have a single, in-HTML <style amp-custom> tag containing styling, rather than one or multiple externally linked stylesheets. I'll show you how I extended bleak's Grunt build process to accomplish this.

Setting the scene

Let's assume for the sake of things that you've got the following file structure, with the relevant bits for our task left in:

scss/
  post.scss          <-- source SCSS
  partials/
    ...

css/
  post.compiled.css  <-- compiled from post.scss

partials/            <-- handlebars template partials
  ...

default.hbs

bleak already used Grunt for compiling post.scss to post.css with the grunt-contrib-sass plugin, so I decided to extend by Grunt builds to inline that CSS to the AMP page template.

Compiling a template

We're going to create a handlebars template to go into partials/ which can be post-processed by a Grunt task and filled with the contents of post.css. That way, the CSS can be included directly in the template and we can pass AMP validation.

With that in mind, the key new file to create is partials/inline_css.hbs:

<style amp-custom>
@@compiled_css
</style>

If you're following along, you'll be able to use that file as-is: nothing in that is specific to bleak. The whole file simply defines a <style amp-custom> tag with the slightly odd content of @@compiled_css. This is the string which will be replaced in a second with the full CSS code.

Let's see how we can do that. First up, we need to add a Grunt plugin to do the replacement:

$ npm install --save-dev grunt-replace

Next, we'll add a replace:inlinecss task to our Gruntfile:

var fs = require('fs');

module.exports = function(grunt) {

  // ...
  grunt.loadNpmTasks('grunt-replace');

  // ...
  replace: {
    inlinecss: {
      options: {
        patterns: [
          {
            match: 'compiled_css',
            replacement: function () {
              return fs.readFileSync('./css/post.compiled.css');
            }
          }
        ]
      },
      files: [
        {expand: true, flatten: true, src: ['partials/inline_css.hbs'], dest: 'partials/compiled'}
      ]
    }
  }

}

After adding this definition you should be able to run grunt replace:inlinecss and you'll end up with a new file in your structure:

scss/
  post.scss          <-- source SCSS
  partials/
    ...

css/
  post.compiled.css  <-- compiled from post.scss

partials/            <-- handlebars template partials
  inline_css.hbs     <-- original
  compiled/
    inline_css.hbs   <-- compiled and full of CSS
  ...

default.hbs

The original template at partials/inline_css.hbs is untouched, but you've now got a new template file at partials/compiled/inline_css.hbs! This one is full of the styles from post.css.

Integrating into your pages

Finally, we need a bit of boilerplate to include this partial when we're in AMP (although you could easily make the case for inlining your styles always, especially if they're quite light).

In your default.hbs, or wherever you handle the contents of your <head>:

{{#is "amp"}}
  {{>compiled/inline_css}}
{{else}}
  <link rel="stylesheet" ...>
{{/is}}

Simples! To make the actual styling of the AMP page easier, I'd also recommend another tiny tweak to default.hbs:

<body class="{{body_class}} {{#is "amp"}}amp{{/is}}">

That .amp class on the body will enable you to make different styling decisions for elements in the page.

There you have it! One of the trickiest migration tasks for your themes to AMP-compatibility needn't be so scary. ⚡️⚡️⚡️

Sort:  

Hi! I am a content-detection robot. I found similar content that readers might be interested in:
http://unwttng.com/inline-css-to-your-ghost-theme-with-grunt-for-great-amp/