npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

fusion-plugin

v0.1.1

Published

Fusion plugins can be either [functional](#functional-plugin-api) or [class-based](#class-based-plugin-api). Typically it's recommended that you use functional plugins, unless you need to provide a service that is object-oriented or stateful.

Downloads

14

Readme

Plugin

Fusion plugins can be either functional or class-based. Typically it's recommended that you use functional plugins, unless you need to provide a service that is object-oriented or stateful.

A Fusion plugin normally looks like this:

export default function(dependencies) {
  return async function(ctx, next) {
    // do things before render
    await next();
    // do things after render
  }
}

Exporting a function ensures that dependencies can be mocked for tests, if required, and provide self-documentation of the interfaces that any given plugin requires in order to work correctly.

The following example shows a hello world plugin:

export default function(dependencies) {
  return function(ctx, next) {
    ctx.body = 'Hello world';
    return next();
  }
}

The examples above are functional plugins. It's also possible to create class-based plugins, similar to how it is possible to create functional and class-based components in React.

The return value of a functional plugin is sometimes referred to as a middleware (because they are similar to Koa middlewares). The return value of a class-based plugin is often referred to as a service.

Functional Plugin API

Plugins work in a similar way to Koa middlewares. In a nutshell, anything that needs to be setup before rendering the virtual DOM should be written at the top of the function, and everything that should run after rendering the virtual DOM should be written after await next() (or in the then callback of return next().then(...)).

On the server, the plugin function runs on every request, and on the client, it runs on page load.

When multiple plugins are registered in an application, all code before await next() is guaranteed to run before render, and all code after await next() is guaranteed to run after render.

Code before await next() runs in the order that the plugins were registered, but the code after that promise resolution runs in reverse plugin registration order. The following example demonstrates the order of execution when composing a list of plugins:

[
  (ctx, next) => {
    console.log(1);
    await next();
    console.log(6);
  },
  (ctx, next) => {
    console.log(2);
    await next();
    console.log(5);
  },
  (ctx, next) => {
    console.log(3);
    await next();
    console.log(4);
  },
]

ctx

The ctx argument is an object that lives for the duration of a HTTP request. In addition to Koa context properties, it contains the following property that is consumable from plugins:

  • element: React.Element|Preact.Element

In addition, it also currently exposes protected properties that Fusion uses internally. You should not rely on these properties, as they will ultimately be abstracted away into core plugins.

  • scripts
  • nonce
  • prefix
  • syncChunkIds
  • preloadChunkIds

Class-based Plugin API

Plugins can be implemented with a class. In that case, a plugin looks like this:

import {Plugin} from 'fusion-plugin';

export default function(dependencies) {
  return new Plugin({
    Service: class Service {/* ... */}
    middleware(ctx, next) {
      /* ... */
    }
  });
}

The Plugin class has 2 methods called of and middleware.

of

of: (ctx: KoaContext) => A

The of method creates a memoized instance of the plugin. For example:

const Logger = new Plugin({
  Service: class Logger {
    log() {}
  }
});

const logger = Logger.of(); // a global logger
logger.log('hello world');

const perRequestLogger = Logger.of(ctx);

logger === perRequestLogger; // false
Logger.of(ctx1) === Logger.of(ctx1); // true
Logger.of(ctx1) === Logger.of(ctx2); // false

This memoization mechanism allows a plugin consumer to implement complex and potentially asynchronous dependency trees with great flexibility.

Typically, you should not override this method.

middleware

middleware: (ctx: KoaContext, next: () => Promise) => Promise

Typically, you would override this method to implement the logic of your plugin. This method behaves the same way a functional plugin does:

function getHello() {
  class Hello extends Plugin {
    static async middleware(ctx, next) {
      // do things before render
      await next();
      // do things after render
    }
  }
}

The ctx object (in both middleware function and in functional plugins) acts as a core interface to the underlying HTTP request, as well as the memoization key for stateful plugins. For example, to consume a Session plugin that is instantiated once per HTTP request, you would do this:

export default function({Session}) {
  return (ctx, next) {
    if (req.method === 'GET' && req.url === '/api/user') {
      const session = Session.of(ctx); // get instance of session specific to this request
      const user = await session.get(ctx.query.id)
      ctx.type = 'application/json'
      ctx.body = user; // serve the data
    }
    return next();
  }
}

Since Session.of(ctx) is memoized per request, you can use it from other plugins without incurring extra instantiation costs. The plugin itself may also choose to memoize its own API calls, if desired.

Note that overriding a middleware method is optional. For example, a Logger plugin would typically only expose a standalone service and therefore would not need to implement a middleware method. The base method is a pass-through middleware (i.e. (ctx, next) => next()).


Plugin registration

Plugins are registered via an App class. Typically you'll want to use the App class provided by the fusion-react package.

// src/main.js
import App from 'fusion-react';

export default function() {
  const app = new App();
  app.plugin(Plugin1);
  app.plugin(Plugin2);
  app.plugin(Plugin3);
  app.start();
}

This interface allows Fusion to provide seamless HMR in development mode, while also providing flexibility to run code after server start.


App Registration

Plugins may function as standalone global services (e.g. a Logger plugin), but when they are used in HTTP requests or within a React/Preact context, they need to be registered to a Fusion application.

To register a plugin, call app.plugin().

// src/main.js
import App from 'fusion-cli';
import HelloWorld from './hello-world';

export default function() {
  const app = new App();
  app.plugin(HelloWorld);
};

// src/hello-world.js
export default function() {
  return new Plugin({
    middleware(ctx, next) {
      ctx.type = 'text/html';
      ctx.body = '<h1>Hello world</h1>';
      return next();
    }
  });
}

The example above sets up a server that responds with a hello world for all requests.


Middleware semantics

Middlewares represent the lifetime of an HTTP request.

The middleware function runs in two stages, before await next() and after. It's your responsibility to call await next() appropriately when implementing a middleware method.

The code before await next() runs before the virtual dom rendering happens, and the code after await next() runs after the rendering. In the server, the request is flushed after your middleware method returns.

Typically, you'll want to run all your logic before rendering. In this case, you can simply run return next() at the end of your function.

Alternatives for async/await

While async/await makes middlewares slightly easier to read, it may be preferrable to avoid using it (for example, if a size increase due to polyfills is unacceptable for a given project). In that case, one can simply use promises instead:

export default function() {
  return class extends Plugin {
    static middleware(ctx, next) {
      // before render
      return next().then(() => {
        // after render
      });
    }
  }
}

Normally, you won't need to do things after render, so your code will simply look like this:

export default function() {
  return class extends Plugin {
    static middleware(ctx, next) {
      // your code here
      return next();
    }
  }
}

Typical Usage

Here's how a typical entry point might look like:

// src/main.js
import App from 'fusion-react';
import Root from './components/root';
import Router from 'fusion-plugin-react-router';

export default function() {
  const app = new App(Root);
  app.plugin(Router);
  return app;
}

// src/components/root.js
import React from 'react';
import {Switch, Route} from 'fusion-plugin-react-router';

const Hello = () => <div>Hello</div>;

export default (
  <Switch>
    <Route path="/" component="Hello" />
  </Switch>
);

Universal plugins

A plugin can be atomically responsible for serialization/deserialization of data from the server to the client.

The example below shows a plugin that grabs the project version from package.json and logs it in the browser:

// src/version-plugin.js
import util from 'util';
import fs from 'fs';
import {html, unescape} from 'fusion-cli'; // html sanitization

export default () => {
  if (__NODE__) {
    const read = util.promisify(fs.readFile);

    return (ctx, next) => {
      return read('package.json').then(data => {
        const {version} = JSON.parse(data);
        ctx.body.head.push(html`<meta id="app-version" content="${version}">`);
        return next();
      })
    }
  }
  else {
    return (ctx, next) => {
      const version = unescape(document.getElementById('app-version').content);
      console.log(`Version: ${version}`);
    }
  }
}

We can then consume the plugin like this:

// src/main.js
import React from 'react';
import App from 'fusion-react';
import Version from './plugins/version-plugin';

const root = <div>Hello world</div>;

export default function() {
  const app = new App(root);
  app.plugin(Version);
  return app;
}

Implementing HTTP endpoints

A plugin can be used to implement an HTTP endpoint. To achieve this, simply run code conditionally based on the url of the request

export default () => async (ctx, next) => {
  if (ctx.path === '/api/v1/users') {
    ctx.body = await getUsers();
  }
  return next();
}