Blog

Updating Middleman extensions to work with Middleman v4

I created and maintain a two Middleman extensions and always planned on upgrading them to work with Middleman v4 (which was released more than a year ago!). Since I hadn't upgraded any of my Middleman powered websites to v4, there was never an urgend need though, but I finally found the time.

There's a great guide on how to create an extension with the new v4 syntax, but unfortunately no official guide (yet!) on how to upgrade an extension to work with Middleman v4. So I simply looked at a few other extensions that had been upgraded already and also checked the Middleman source code for what had actually changed.

If you have more tips or experienced other difficulties in upgrading an extension, please leave a comment below! I'd be happy to extend this post to make it more comprehensive, and maybe it can become the source for an Extension Upgrade Guide in the official Middleman docs.

Version bump

Upgrading your extension to work with Middleman v4 definitely warrants a major version bump to comply with Semantic Versioning. The question is whether that new version should support only Middleman v4 or both Middleman v3 and v4.

I've found that it's cleaner to support only one major Middleman version per major extension version, but then of course you end up with a v1.x (for example) of your extension that people use for Middleman v3, and a v2.x that people use for Middleman v4. If you then fix a bug in v2.x of your extension, the fix potentially needs to be backported to v1.x as well (same for new features). If you had released v2.x of your extension with support for both Middleman v3 and v4, new features and bug fixes would immediately benefit all users.

So keep this issue in mind, and make your own decision! :)

Testing

So how do you actually verify that your extension works with Middleman v4? Well, you add tests of course! If your extension didn't have tests until now, this is the perfect opportunity to add some!

If you decided to support Middleman v3 and v4 simultaneously (see section above), there is a great tool by the Thoughbot gang that can help you to test both versions: Appraisal

Simply follow the install instructions for Appraisal, add a (runtime) dependency for Middleman >= 3.0 in your Gemspec, and add the Middleman version you want to test to the Appraisals file. (example)

This way you can easily run your tests consecutively for each Middleman version you want to support.

Changes

Below are (some of) the changes you have to implement to make your extension work with Middleman v4. In addition to those, there's a section on extension API improvements in the official "Upgrading to v4" guide.

Setup

It used to be possible to define your extension's logic in a module. This is no longer supported, it must be a class inheriting from Middleman::Extension now.

# before
module Middleman
  module MyExtension
    class << self
      def registered(app, options_hash = {}, &block)
        ...
      end
      alias :included :registered
    end
  end
end

# after
module Middleman
  module MyExtension
    class Extension < ::Middleman::Extension
      def initialize(app, options_hash = {}, &block)
        ...
      end
    end
  end
end

CLI commands superclass

If your extension contains CLI commands, the class that defines them must inherit from Thor::Group, not Thor as before.

# before
module Middleman
  module Cli
    class MyExtension < Thor
      ...
    end
  end
end

# after
module Middleman
  module Cli
    class MyExtension < Thor::Group
      ...
    end
  end
end

Register CLI commands

If your extension contains CLI commands, you need to register those explicitly now by calling Middleman::Cli::Base.register. This was not necessary before

# after
module Middleman
  module Cli
    class MyExtension < Thor::Group
      def my_cli_command
        ...
      end

      Base.register(self, 'my_cli_command', 'my_cli_command [options]', 'Description for my_cli_command')
    end
  end
end

Server instance

If you want to access the current server instance from your extension, the code to do so changed slightly.

# before
server_instance = Middleman::Application.server.inst

# after
server_instance = Middleman::Application.new

Callbacks

All callbacks used to live in your extension's initialize method, now some of them (before_configuration, after_configuration, before_build, after_build, and ready) must be defined as separate methods that receive a Middleman::Builder instance as a parameter. Others (before_render and after_render) stay where they were before.

# before
module Middleman
  module MyExtension
    class Extension < Middleman::Extension
      def initialize(app, options_hash = {}, &block)
        app.after_configuration do
          # Do something after configuration
        end

        app.after_build do
          # Do something after build
        end

        app.before_render do
          # Do something before render
        end
      end
    end
  end
end

# after
module Middleman
  module MyExtension
    class Extension < Middleman::Extension
      def initialize(app, options_hash = {}, &block)
        app.before_render do
          # Do something before render
        end
      end

      def after_configuration(builder)
        # Do something after configuration
      end

      def after_build(builder)
        # Do something after build
      end
    end
  end
end

More?

Do you know of any other changes necessary to make your extension work with Middleman v4? Comment below and I'd be happy to add them!

Ideas? Constructive criticism? Think I'm stupid? Let me know in the comments!