One of the very special things about Ionic and Capacitor is that a huge bulk of your app development can happen right in a browser on your desktop. That means full access to your traditional desktop web development tools (Chrome/Safari/Firefox dev tools) and the development velocity of building without having to recompile or deploy to a simulator or device.

One of the guiding design goals of Capacitor, Ionic’s new native web app container project that runs your web app natively on iOS, Android, Electron, and the web as a Progressive Web App, was to increase the amount of time you can spend developing your app on desktop before having to mess with simulators or devices.

On top of that, building web-first means that your app will likely run well as a Progressive Web App with minimal additional work, assuming you’re able to achieve the functionality your app needs with Web APIs.

Let’s take a look at how that works.

A Fake Native Plugin

Chances are, your Ionic/Capacitor app will have some native functionality that it needs, that goes beyond what is available out of the box in Capacitor (such as integrating with a 3rd party SDK).

Let’s say we have this simple Capacitor plugin:

import Foundation
import Capacitor

@objc(ThirdPartyPlugin)
public class ThirdPartyPlugin: CAPPlugin {
  @objc func connect(_ call: CAPPluginCall) {
    let service = ThirdPartySDK(apiKey)
    service.connect()
    call.resolve(service.getUser())
  }
}

This plugin connects to the ThirdPartySDK and then returns some user information. Unfortunately, this theoretical SDK is only available on iOS and Android, so when we’re building the app in the browser, we will need to either add custom code to detect which platform we’re running on (not ideal), or mock out the service so we can keep building quickly (much better!)

Mocking Plugins on the Web

In order to mock out this plugin, we need to create what is known as a Web Plugin for Capacitor. Web Plugins have a lot of power and aren’t just for mocking though! For example, Capacitor ships with a full Filesystem implementation for the web that uses IndexedDB so you can work with files just like on iOS, Android, and Electron.

In this case, our web plugin will be pretty basic and just return some fake data, so let’s create it:

import { WebPlugin } from '@capacitor/core';

export class ThirdPartyPluginWeb extends WebPlugin {
  constructor() {
    // Register the plugin with Capacitor so it is aware of it
    super({
      name: 'ThirdPartyPlugin',
      platforms: ['web']
    });
  }

  async connect(options: any) {
    return {
      id: 123,
      name: 'Barnaby Jones',
      email: '[email protected]'
    }
  }
}

export const ThirdPartyPlugin = new ThirdPartyPluginWeb();

Next, we need to register this plugin with Capacitor so it is aware of it. This makes your plugin available at runtime, but only when running on the platforms specified in the constructor (in this case, web):

import { ThirdPartyPlugin } from './plugins';
import { registerWebPlugin } from '@capacitor/core';
registerWebPlugin(ThirdPartyPlugin);

Where you put this code does matter, to ensure it loads before this plugin is accessed, so we recommend putting it in your root index JS/TS file.

Testing the Plugin

Once you have your plugin built, using it is as simple as just serving your app normally (npm start, for example), and then invoking the plugin like so:

import { Plugins } from '@capacitor/core';

function connectToThirdParty() {
  const { ThirdPartyPlugin } = Plugins;

  const user = await ThirdPartyPlugin.connect();
}

You should see your mock web data come back when running on the web, otherwise see the normal full connection to the Third Party SDK when running natively!

If you’re having trouble, try accessing your plugin on Plugins as close to where it is used. Unfortunately, with modern bundlers and module loaders, your code may be referencing Plugins.ThirdPartyPlugin before the web plugin has loaded, so be mindful of that.

Conclusion

Web Plugins are wonderful for building out consistent cross-platform APIs (for example, the Geolocation and Share APIs have the same code on all platforms), but they can also be used for mocking out functionality that is only used on native platforms.

If you end up building a web plugin for Capacitor and find this guide helpful, let us know below!

Signup for the Ionic Newsletter to get the latest news and updates!