Skip to content

Instantly share code, notes, and snippets.

@jonschlinkert
Last active February 3, 2018 05:58
Show Gist options
  • Save jonschlinkert/f30f1f882feb01ec21dc9a6d8086dbc7 to your computer and use it in GitHub Desktop.
Save jonschlinkert/f30f1f882feb01ec21dc9a6d8086dbc7 to your computer and use it in GitHub Desktop.
title description related
Plugins overview
High level overview of plugins and how they work.
docs libs
streams
assemble-fs
base-fs
assemble-streams

Depending on your needs, there are two different kinds of plugins available. PThis document provides a brief description of each, along with links to addtional information.

  • instance plugins: plugins that are used to add features and functionality to the app instance.
  • pipeline plugins: plugins that are used to transform vinyl files in a stream.

Instance plugins

Used to add features and functionality to the application instance (or app).

Type: Function

Method: .use

Usage: Plugin functions are invoked by the .use method and take the current application instance, or app, as the first argument.

Example

// --- my-plugin.js ---
function myPlugin(app) {
  // do stuff to `app`
  app.doubleStuff = function(num) {
    return num * 2;
  };
}

// --- index.js ---
var {%= name %} = require('{%= name %}');
var app = {%= name %}();

app.use(require('./my-plugin'));

var num = app.doubleStuff(5);
console.log(num);
//=> 10

Pipeline plugins

Used to transform [vinyl][] files in a stream. All [gulp][] plugins are pipeline plugins.

Type: Stream

Method: .pipe

Usage: Pipeline plugins are registered with .pipe and are used on vinyl file objects in a stream.

Example

This example requires the [base-fs][] plugin (a wrapper for [vinyl-fs][], install with $ npm i base-fs):

var base = require('base');
var vfs = require('base-fs');
// any gulp plugin can be used
var gulpPlugin = require('fictitious-gulp-plugin');

app.src('foo.hbs')
  .pipe(gulpPlugin())

Special note

If you're using [templates][] (or any application that inherits templates, like [assemble][] or [verb][]), all views are vinyl files (e.g. view is an instance of Vinyl).

title description related
Smart plugins
docs
plugins-*

By design, plugins are pretty simple to create and use in [base][]. This keeps application logic "light" and plugins as generic as possible.

In other words, we want to encourage plugins to keep any base-specific code to an absolute minimum. You should be able to write plain JavaScript, or use any npm module in your plugin.

Making plugins smarter

There are times when "generic" isn't good enough. In advanced use cases, it's nice to have greater control over how and when plugins are invoked.

This goal can be accomplished using the [base-plugins][] plugin, which adds functionality and enhancements to existing features for managing plugins.

Pit stop

Before we review these features, if you'd like to be able to follow along and run the examples in this document, you'll first need to install and register the [base-plugins][] plugin.

Install base-plugins

$ npm install base-plugins

Use the plugin

Add the plugin to your [base][] application:

var plugins = require('base-plugins');
var base = require('base');
var app = new Base();

app.use(plugins());

All of the following examples will assume the previous code has already been defined.

With that out of the way, let's get started!

Smart plugins

Adding [base-plugins][] to your application will enable the following features:

  • [.fns][] - used for storing functions returned by plugins
  • [.run][] - used for running plugins stored on the fns array
  • [.use][] - the use method is updated to take addtional arguments

Let's go through each in more detail.

.fns

Type: array

Description

If a plugin returns a function after being invoked by .use, the function is pushed onto a fns array (on app.fns), allowing the function to be called later by the .run method.

Example

// todo

.run

Type: function

Description

The .run method takes an object as its only argument and, when called, iterates over the fns array and calls each function in the array on the given object. Without [base-plugins][], plugins are only run once when the .use method is called, that's it. End of story.

Example

// todo

Note that the [base-plugins][] plugin must be the first plugin registered on your base application..

Instance plugins

Instance plugins are functions that are immediately invoked by the .use method.

Example

This is all you need to create an instance plugin:

var templates = require('{%= name %}');
var app = templatse();

// create a plugin
function myPlugin(app) {
  // do stuff to "app"
}

// use the plugin
app.use(myPlugin);

app, collection, or view

Additionally, view collections and views

app.use(), collection.use() or view.use() methods.

Example instance plugin

var {%= name %} = require('{%= name %}');
var app = {%= name %}();

app.use(function(app) {
  // do stuff to app ({%= name %} instance, also exposed as `this)
});

Smart plugins

A "smart" plugin is an instance plugin that automatically registers itself on the correct instance (app, collection or view).

To make your plugin "smart", just check for the instance type and return the plugin function if the condition is not met.

Example

function myPlugin(view) {
  if (!view.isView) return myPlugin;

}

When a plugin returns a function, the function is pushed onto an array.

add the following:

var isValid = require('is-valid-app');

// by default `isValid` will only register plugins on `app`
module.exports = function(app) {
  if (!isValid(app, 'my-plugin')) return;
  // do plugin stuff
};

That's it. The is-valid-app plugin returns true if both of the following conditions are met:

  1. app is an instance of Templates
  2. the my-plugin plugin is not already registered.

If my-plugin is not already registered, isValid will automatically register the plugin it the first time it's called. Thus, the next time the check is done, isValid will return false (preventing the plugin from being registered twice).

Collection plugins

Collections themselves are like mini-application instances, and collection plugins are registered and used the same way as instance plugins, with the .use() method, but on a specific collection. Collection plugins are called immediately upon instantiation of the collection.

Example collection plugin

var {%= name %} = require('{%= name %}');
var app = {%= name %}();

// register a plugin to be used on all collections
app.use(function(app) {
   // do stuff to app or `this` (the {%= name %} instance)

   // return a function to be use used as a collection plugin
   return function(collection) {
      // do stuff to `collection`
   };
});

The app.create() method (used for creating custom collections) returns the collection instance. So collection plugins can be chained from create as well.

app.use(function() {
  return function(collection) {
    // do stuff to (every) `collection`
  }
});

app.create('pages')
  .use(function(pages) {
    // do stuff to `pages` collection
  });

app.create('posts')
  .use(function(posts) {
    // do stuff to `posts` collection
  });

Example view plugin

var {%= name %} = require('{%= name %}');
var app = {%= name %}();

// register a plugin to be used on all views, from all collections
app.use(function(app) {
  return function(collection) {

    // return a function from a collection plugin to be used
    // as a view plugin
    return function(view) {
      // do stuff to `view`
    };
  };
});

Register view plugins on specific collections

app.use(function(app) {
  // do stuff to `app`
  return function(collection) {
    // do stuff to (every) `collection`
    return function(view) {
      // do stuff to (every) `view`
    };
  };
});

app.create('pages')
  .use(function(pages) {
    return function(page) {
      // do stuff to `page`
    };
  });

app.create('posts')
  .use(function(posts) {
    return function(post) {
      // do something to `post`
    };
  });

Use cases for collection/view plugins

Here are just a few examples

  • permalinks: You might have a permalink plugin that modifies the dest path a particular way for blog posts, and a different way for pages. You could register the same plugin with both collections, just using different settings/options. Also, you could implement this functionality at the view level or collection level, depending on how granular your plugin needs to be.
  • pagination
  • groups and lists
  • sorting

(this is for the templates docs, but it will help explain how plugins work in base. you obviously already know a lot about plugins, so this is for anyone who might find it useful)

Plugins

Overview of how Templates plugins work

App

The following example shows a plugin that will be invoked by the .use method. Example: app.use(plugin). When invoked, the plugin function is passed the application instance (app) as its only argument.

plugins-app

The plugin stops there and will not be called again, unless the plugin returns a function. If a function is returned it will be pushed onto the app.fns array and then called on each collection that is created using the app.create, app.collection, or app.list methods.

In the next example, a function is returned from the plugin so that it can be called again later.

Collection

plugins-collection

The plugin stops there and will not be called again, unless the plugin returns a function. If a function is returned it will be pushed onto the collection.fns array and then called on each view that is added to the collection.

In the next example, we'll return a function inside the collection plugin, so that it can be called again later.

View

plugins-view

End of the line...

Short-circuit: "smart plugins"

Don't like all the function nesting? Want to register your plugin with app but only have it run on specific objects? No problem, just short-circuit the plugin!

Every class has a boolean .is* property that is useful for checking the instance name. For example, the Templates class has an isTemplates property, view collections have isViews, and views have isView. (all "apps", like Templates, Assemble, Generate, Verb, etc. also have isApp as a convenience).

To make your plugins "smarter" and eliminate the nesting, try the following:

plugins-short-circuit

Generators

plugins-generators

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment