Tilted sphere with longitudinal stripes Modern Web Guides Docs Blog

Writing Plugins: Web Sockets

The dev server has a web socket API for communicating with the browser. To use web sockets, your plugin must set the injectWebSocket option to true. If one plugin has this option set, a web socket script will be injected into the pages server by the dev server.

After setting the option, the server will pass the connected web sockets to the serverStart hook.

Read more
function myPlugin() {
  return {
    name: 'my-plugin',
    injectWebSocket: true,
    serverStart({ webSockets }) {
      // print a console.log in the browser after 1sec
      setTimeout(() => {
        webSockets.sendConsoleLog('my-plugin', 'Hello world!');
      }, 1000);
    },
  };
}

export default {
  plugins: [myPlugin()],
};

Built-in messages

The web sockets manager has two built-in messages. sendConsoleLog is shown above, and will log any message to the browser console.

Another built-in message is sendImport. This will send a module path to be imported by the browser. You can use this to execute code in the browser.

Read more

In this example, /foo.js, will be imported in the browser using a dynamic import. The imported file can be a real file on the file system or a virtual file served by your plugin.

The module should have a default export, this is called each time sendImport is called.

function myPlugin() {
  let webSockets;
  return {
    name: 'my-plugin',
    injectWebSocket: true,
    serverStart(options) {
      ({ webSockets } = options);

      setTimeout(() => {
        // this will import /foo.js in the browser
        webSockets.sendImport('/foo.js');
      }, 1000);
    },

    serve(context) {
      // you can serve a virtual module to be imported
      if (context.path === '/foo.js') {
        return 'export default () => console.log("/foo.js");';
      }
    },
  };
}

You can pass parameters to the function in the browser with the third parameter of the sendImport function.

function myPlugin() {
  let webSockets;
  return {
    name: 'my-plugin',
    injectWebSocket: true,
    serverStart(options) {
      ({ webSockets } = options);

      setTimeout(() => {
        // this will import /foo.js in the browser
        webSockets.sendImport('/foo.js', ['a', 'b', 'c']);
      }, 1000);
    },

    serve(context) {
      // you can serve a virtual module to be imported
      if (context.path === '/foo.js') {
        return 'export default (...args) => console.log(...args);';
      }
    },
  };
}

If the code you want to execute is very simple, you can also send the import as a data URL. Data URLs are valid import paths, and very useful for this use case.

function myPlugin() {
  let webSockets;
  return {
    name: 'my-plugin',
    injectWebSocket: true,
    serverStart(options) {
      ({ webSockets } = options);

      setTimeout(() => {
        // this will reload the browser
        webSockets.sendImport('data:text/javascript,window.location.reload()');
      }, 1000);
    },
  };
}

Custom messages

To send custom messages to opened web sockets you can use the send method.

Read more
function myPlugin() {
  return {
    name: 'my-plugin',
    injectWebSocket: true,
    serverStart({ webSockets }) {
      setTimeout(() => {
        // this will send a message to all opened web sockets
        // the message must be parsable as JSON
        webSockets.send(JSON.stringify({ type: 'x' }));
      }, 1000);
    },
  };
}

To respond to messages from the browser, you can listen to the message event from the web socket manager.

Read more
function myPlugin() {
  return {
    name: 'my-plugin',
    injectWebSocket: true,
    serverStart({ webSockets }) {
      webSockets.on('message', ({ webSocket, data }) => {
        console.log('received message', data);
        webSocket.send('message response');
      });
    },
  };
}

The message event provides the web socket that fired the event. You can use this to keep track of which sockets send which messages and send messages to specific sockets.