8. Namespaces

Now, let's talk about namespaces and how we, as a community, can use them to extend Frontity and create a better tool for everyone.

In Frontity state, actions and libraries belong to a shared space among all packages, so each package needs to use its own namespace.

To avoid conflicts between packages we could simply use the name of the package, but we use namespaces instead because some packages are interchangeable. For example, it doesn't matter if you install wp-comments (native WordPress comments) or disqus-comments (Disqus comments) in your Frontity project because the theme is going to access it using the common comments namespace and everything is going to work. In the future, another person could create a third comments package, based on a new service, and as long as it respects the same structure (written in TypeScript), all the themes (even the old ones!) will work perfectly.

More examples of namespaces are:

  • source: for example wp-source, wpgrahql-source or even drupal-sourceโ€ฆ

  • analytics: for example google-analytics, gtm-analytics, mixpanel-analyticsโ€ฆ

  • notifications: for example onesignal-notifications, pushwoosh-notificationsโ€ฆ

  • share: for example modal-share, native-shareโ€ฆ

  • router: for example tiny-router, 3d-routerโ€ฆ

But let's start from the beginning.

Using namespaces in package exports

As we've already seen, this could be a typical theme package:

/packages/my-awesome-theme/src/index.js
import Theme from "./components";

export default {
  roots: {
    theme: Theme,
  },
  state: {
    theme: {
      menu: [
        ["Home", "/"],
        ["About", "/about"],
      ],
      isMenuOpen: false,
      featuredImage: {
        showOnList: false,
        showOnPost: false,
      },
    },
  },
  actions: {
    theme: {
      toggleMenu: ({ state }) => {
        state.theme.isMenuOpen = !state.theme.isMenuOpen;
      },
    },
  },
};

One thing you might notice is that roots, state, and actions have a namespace called theme. It may seem like it is not adding much value because it is the only namespace. Why not write it like this instead?

/packages/my-awesome-theme/src/index.js
import Theme from "./components";

export default {
  namespace: "theme",
  roots: Theme,
  state: {
    menu: [
      ["Home", "/"],
      ["About", "/about"],
    ],
    isMenuOpen: false,
    featuredImage: {
      showOnList: false,
      showOnPost: false,
    },
  },
  actions: {
    toggleMenu: ({ state }) => {
      state.theme.isMenuOpen = !state.theme.isMenuOpen;
    },
  },
};

There are several reasons:

1. It's easier to be aware of the final structure

When you access state or actions, it's much easier to see what you need when you write it like this:

state: {
  theme: {
    isMenuOpen: false,
  }
},
actions: {
  theme: {
    toggleMenu: ({ state }) => {
      state.theme.isMenuOpen = !state.theme.isMenuOpen; // <- Easy, right?
    }
  }
}

2. It's easier for TypeScript

Even though TypeScript is optional in Frontity, we make sure it has excellent support in case you want to use it. TypeScript gets really complex when you try to modify the structure of your objects, and in order to make it as simple as possible, it's good to create objects with the same structure that they will be consumed later. So yes, TypeScript just works :)

3. Multiple namespaces per package

Packages can export multiple namespaces and that's good. It makes Frontity more flexible.

For example, imagine we want to create a theme that implements its own share:

/packages/my-awesome-theme-with-share/src/index.js
import Theme from "./components/theme";
import Share from "./components/share";

export default {
  roots: {
    theme: Theme,
    share: Share
  },
  state: {
    theme: {
      ... // State for the theme
    },
    share: {
      ... // State for the share
    }
  },
  actions: {
    theme: {
      ... // Actions for the theme
    },
    share: {
      ... // Actions for the share
    }
  }
}

Making Frontity extensible through namespaces

This is the main reason namespaces exist in Frontity and a big part of how Frontity itself works.

We use namespaces to create abstractions on top of packages and, by doing so, they can communicate between each other without really knowing the specific implementation.

It's easier to understand with some examples.

Example: comments

Take for example the @frontity/wp-comments package which has an actions.comments.submit method. As you can see this method is defined under the comments namespace.

In the case of the @frontity/wp-comments package the actions.comments.submit is responsible for sending the content of the fields in the comment form to WordPress.

Now, all the theme packages that want to submit a comments form can check if there is a package with the comments namespace with an actions.comments.submit method available. If there is, it can be used from any React component in the project to submit comment form data.

// Submit the comment to the post with ID 60
// using the values passed as the second argument.
actions.comments.submit(60, {
  content: "This is a comment example. Hi!",
  authorName: "Frontibotito",
  authorEmail: "frontibotito@frontity.com",
});

Users can use their frontity.settings.js to install and configure wp-comments:

frontity.settings.js
export default {
  packages: [
    "my-awesome-theme",
    "@frontity/tiny-router",
    "@frontity/wp-source",
    "@frontity/wp-comments", // <- That's it. You have native wp comments now.
  ],
};

But what if (and now this is where things become interesting) users don't want to use WordPress native comments but Disqus comments?

Then they just have to install a possible disqus-comments package instead:

frontity.settings.js
export default {
  packages: [
    "my-awesome-theme",
    "@frontity/tiny-router",
    "@frontity/wp-source",
    "@frontity/disqus-comments", // <- That's it. You have disqus now.
  ],
};

This possible disqus-comments package might define a different implementation of actions.comments.submit that instead of submitting the comment form data to WordPress it would instead submit it to be handled by Disqus.

Actually, the theme has no idea about what specific implementation of comments you have installed. Everything works and the theme didn't need to change.

Example: analytics

Let's take a look at another example: two actions in the analytics namespace. All the packages that want to implement analytics need to have these two actions:

  • actions.analytics.sendPageview: send a pageview to the analytics service.

  • actions.analytics.sendEvent: send an event to the analytics service.

The first one, actions.analytics.sendPageview, is used by packages that implement router, each time actions.router.set is used.

The second one, actions.analytics.sendEvent, is used by the theme when something interesting happens. For example:

Post.js
const Post = ({ actions }) => (
  <Post>
    <Title />
    <Content />
    <ShareButtons onClick={actions.theme.openShareModal} />
  </Container>
);

export default connect(Post);
/packages/theme/src/index.js
export default {
  state: {
    theme: {
      shareOpen: false,
    },
  },
  actions: {
    theme: {
      openShareModal: ({ state, actions }) => {
        state.theme.shareOpen = true;
        if (actions.analytics) {
          actions.analytics.sendEvent("share-modal-open");
        }
      },
    },
  },
};

When users open the share modal, a new event is sent to the analytics service of the analytics package that is installed in the Frontity project, no matter which one it is ๐ŸŽ‰๐ŸŽ‰

If you still have any questions about Namespaces in Frontity, please check out the community forum, which is packed full of answers and solutions to all sorts of Frontity questions. If you don't find what you're looking for, feel free to start a new post.

Last updated