Progressive Decoupling Made Easy

A chain of paperclips with one paperclip separated

Decoupling separates the system that stores the content from how that content is displayed on other independent systems. This can come with many benefits but also some downsides and tradeoffs. Go in with both eyes open as you decide whether to decouple or not.

With progressive decoupling, you can get some of the benefits of decoupling while avoiding some of the downsides. 

There are several ways to decouple a website progressively, but this article makes the case that widgets provide the most flexibility.

What are widgets?

Widgets are stand-alone JavaScript (JS) applications that are framework-agnostic and are designed to be embedded and configured by CMS editors.

Widgets can be vanilla JS or use frameworks like Vue or React.

Why JS over server-generated HTML?

Better reactivity and interactivity

The pages can be static or served from cache (very fast), and JS can be sprinkled on top.  The server can provide the unchanging parts, while the JS application adds interactivity. 

This reduces the load on your servers while increasing website performance. You keep the benefits of built-in CMS performance tooling.

Distributed delivery

Different development teams can write software independently. They can publish software on the same platform without coordinating complex deployment efforts.

  • Teams write the JS code in isolation
  • The browser executes the JS
  • Different deployment pipelines and servers can be used.
Example page with widgets: main navigation, main content, and pricing widget
An example page with widgets.

One team works on the navigation, one team works on the main feature set, and one team works on a price calculator.

Biggest talent pool

According to extensive surveys, JS and TypeScript (a superset of JS) are the most commonly used languages, based on Stackoverflow’s yearly survey.

By building pages and experiences in JS, you can pull talent from a bigger pool. You have more options.

Better developer experience

Since JS is so popular, your developers can leverage many tools, services, and frameworks. Jest, Storybook, Husky, Gulp, for things like unit testing, component management, setting githooks, etc. Many services integrate with the technology.

Many platforms will give you better support, which leads to better workflows, which hopefully leads to better code—things like visual diffs, code quality analysis,  and code deployment. Popularity leads to a flourishing ecosystem.

In addition, frameworks like Vue can take care of some of the rough edges.

Should we just build JS applications then?

Yes and no. We still care about the content. Content is the heart of the web. You can have a great user experience, but without content, your project is doomed to fail.

To manage content, you need a CMS. Especially if content is your product or is central to your business. A CMS provides many features that are hard to build from scratch.

  • Managing pages and setting up URLs
  • Users and access restrictions
  • SEO metadata
  • Media library
  • Security patches
  • Editorially controlled layouts
  • Moderation and previews

Why widgets?

We have a CMS. We know we want to use some JS. Why not put JS in our CMS templates?

This works. You can certainly go that route. But widgets have some advantages over JS in the template.

They require no CMS deployments

A developer creates a new widget in the registry, and it appears in the CMS editorial interface for embedding. No additional effort. Bug fixes and enhancements are also instantaneous.

Here is what a traditional deployment might look like:

Development, both JS & CMS, to Editorial to End User
  1. Develop JS app
  2. Integrate it with a CMS template (and with the content model if you want the app to receive editorial input)
  3. Deploy both in conjunction since they are coupled together
  4. Editors can expose the JS app to end-users

Widgets allow you to skip the two middle steps. When you use the existing CMS integrations, development is only done in JS, and it can be deployed on its own. No need to call in a CMS developer to add new widgets or update existing widgets.

A widget deployment looks like this:

Development & Editorial are grouped, then straight to the End User

Embedded and controlled by editors

JS developers can create flexible applications that allow for tweaked experiences and configuration. A single widget can act as multiple similar widgets.

JS developers define the input data they expect from editors, and the CMS creates a form for the editors to input that data. This allows many instances of the same type of widget to be embedded with different configurations: different content, color palettes, external integrations, etc.

The following example defines a customizable button that the editor can configure.

settingsSchema: {
  type: 'object',
  additionalProperties: false,
  properties: {
    fields: {
      type: 'object',
      properties: {
        'button-text': {
          type: 'string',
          title: 'Button text',
          description:
            'Some random string to be displayed.',
          examples: ['I am a button', 'Please, click me'],
        },
      },
    },
  },
},
title: 'Example Widget',
status: 'stable',

The CMS integration, which can be defined up-front, reads the definition and presents the proper form elements to the editor.

Example of the CMS integration in Drupal: configuration of the button text
Configuring a widget's button text within Drupal

Embedded anywhere

Since widgets are not embedded at build time, but editorially, they can be placed anywhere. If the JS is in the template, you can’t choose, for example, to insert the JS app between two paragraphs of the body field. And changing the position would require a CMS deployment.

Example UI for inserting a widget into the body field
Widgets can be inserted into the body field

With widgets, editors can insert them anywhere.

  • Using layout building tools
  • Using WYSIWYG integrations
  • Using content modeling tools (entity reference field that points to a widget instance)
  • Using 3rd party JavaScript
Embedding widgets via Drupal's layout builder. Shows a calculator widget being placed and then rendered on the page.

And the same widget can work for any CMS. As long as the CMS subscribes to the registry and can read the schema, it can embed the JS application. When you change or fix something in the JS app, it is distributed to all CMSs. Widgets can also work in static HTML pages and Optimizely pages. Anywhere.

When are widgets a good fit?

Structured content is still the way to go. You don’t have to use widgets everywhere, but they are useful in several contexts.

  • Interacting with 3rd party APIs - reviews sites (g2crowd), commenting
  • Interactive tools - pricing calculators, checklists saving progress
  • Data visualizations - maps, charts of COVID data
  • Adding some pop to a page - you can do some things with JS that may be difficult to achieve when limited to HTML and CSS

How to get started

Create a widget

From a technical perspective, a widget is a function that takes a DOM id and renders JS in it. A widget can also receive arguments as HTML data.

Here is an example of rendering a React component:

window.renderExampleWidget = function(instanceId) {
  const element = document.getElementById(instanceId);
  const title = element.getAttribute('data-button-text');
  ReactDOM.render(
    <Widget title={title} />,
    element,
  );
};

It is very easy to port existing components and re-use them.

Upload the app code

The code needs to live somewhere accessible to the internet (Github pages, Amazon s3 bucket, etc.). The CMS can use this to either download the files or serve them from there. We don’t want to bundle the files within the CMS because that introduces coupling again.

Publish the metadata

This is the tricky part. Without the metadata, this is just another JS application in some repo. 

We need a registry, which is just a JSON document containing the metadata about all the available apps that can be downloaded from the internet. An array of objects. This includes the “directoryUrl,” which defines exactly where the files live. You can also see the “settingsSchema” property, which defines the shape of the input data this widget will accept.

[
  {
    "repositoryUrl": "https://github.com/js-widgets/example-widget",
    "shortcode": "example-widget",
    "version": "v1.0.4",
    "title": "Example Widget"
    "description": "This is a widget example that showcases some of the features of the JS Widgets project."
    "directoryUrl": "https://static.mateuaguilo.com/widgets/sandbox/example-widget/v1",
    "files": [
      "css/main.css",
      "js/main.js",
      "media/logo.png",
      "thumbnail.svg"
    ],
    "availableTranslations": [
      "en",
      "de",
      "es",
      "pl",
      "pt",
      "zh-tw"
    ],
    "settingsSchema": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "fields": {"type": "object"...}
      },
    },
  },
]

This file will need to be uploaded somewhere that is accessible via HTTP.

The CMS pulls that JSON object and knows about all the widgets and where to grab their assets. You’ll start seeing widgets appear in your editorial tools.

A list widgets pull from the registry, being able to select one within Drupal

 

Ok…but where do I actually start?

There are lots of existing tooling and examples at https://github.com/js-widgets. It includes a registry boilerplate and catalog, widget examples, and CI/CD integration.

If you fork it, you’ll get a lot of nice things out of the box.

Stakeholder-ready catalog

The same registry that provides information to the CMS can provide information to a single-page application that is browsable and searchable. This requires zero effort. Everyone involved can see what is available: editors, developers, stakeholders, etc.

The catalog can also render a widget as a live sample, even if the widget requires editorial inputs. Examples utilize the “examples” key as shown in the widget definition above.

The widget catalog rendering an example calculator widget

Governance like you need it

All of this might seem like a governance nightmare. Do you really want JavaScript updated in a remote location and immediately deployed to your live site?

Of course not. 

You decide what registries to accept into your CMS. You decide what widgets and updates go into your registry. You decide who has access to change those widgets and registries.

Production-ready dependencies

We want these widgets as light as possible. What if there was a way not to bundle big dependencies in every single JS app? We don’t want to download React for every widget, for example.

Shared dependencies are possible with this paradigm. Widgets can be configured to pull certain dependencies from the parent container. This requires some Webpack configuration and telling the CMS where to find the excluded libraries. Read the documentation for external dependencies here.

Conclusion

We hope this makes you excited to start taking advantage of widgets and progressive decoupling. For more videos on the specifics of setting this up, take a look at these additional videos:

Lullabot has been helping companies take advantage of progressive decoupling for years and on websites that get a lot of traffic. This paradigm is battle-tested. If you want help getting started, please contact us.

 

Featured Work

Latest Podcasts

Let's Connect

Want to learn more about working with us or just say hello?

Contact Us