Skip to content

Latest commit

 

History

History
193 lines (147 loc) · 8.9 KB

Hooks.md

File metadata and controls

193 lines (147 loc) · 8.9 KB

Hook system

Roc features a hook system that allows extensions to create hooks that other extensions, and themselves, can register against using actions. Projects also have the possibility to register to hooks if needed using their configuration file, see more here.

Hooks

Hooks can be seen as an event that actions can register themselves to, allowing them to return something back to the hook, like redefining what plugins to use with Babel, or run some side effect, like starting Browsersync.

Running hooks

There exists two ways to run hooks in Roc. One where the hook needs to be registered before hand through the Roc object and one where a hook is invoked directly. The registered method is the recommended one since that will allow Roc to generate documentation for the hook that will make it easier for developers to interact with it.

Example

import { runHook, runHookDirectly } from 'roc';

// Run a hook without needing to define it in the extensions Roc object
runHookDirectly({
    arguments,
    description,
    extension,
    initialValue,
    name,
    returns
}, args, callback);

// Run registered hook with callback
runHook(extensionName)(name, ...args)(callback);

// Run registered hook without callback
runHook(extensionName)(name, ...args);

Common way to use runHook in extensions

import { runHook } from 'roc';

const name = require('../../package.json').name;

// Can then be called in the extension like this
// invokeHook('hook-name', arg1, arg2, ...);
// invokeHook('hook-name', arg1, arg2, ...)(callback); - if callback
export function invokeHook(...args) {
    return runHook(name)(...args);
}

Define hooks

It is easy to define hooks that can be registered with Roc through the Roc object.

Example

{
    'babel-load-presets': { // The name of the hook, important as this is used by actions
        description: 'Expected to return a presets to add to the array of presets to use.', // A description on what it does, used for documentation generation and can use Markdown - optional
        hasCallback: true, // If it uses a callback - optional
        initialValue: [], // The initial value - optional
        returns: isArrayOrSingle(isString), // What it expects to get after all the actions has been processed, used for validation and for documentation - optional
        arguments: { // The arguments that the hook will call the actions with - optional
            target: { // The name of the argument
                validator: isString, // The validator for the argument
                description: 'Lorem bacon' // A description
            }
        }
    },

    'babel-load-plugins': {
        description: 'Expected to return a concatenated array with the final presets to use.', // A description on what it does, used for documentation generation and can use Markdown
        initialValue: [], //
        returns: isArray(isString),
        arguments: {
            target: {
                description: 'Lorem bacon'
                validation: isString,
            }
        }
    },
}

arguments

An object that documents the arguments that the hook uses, what actions will be called with, and where the keys are the name of the arguments. The order is important and need match the order that is used when running the hook. Will bring better validation and documentation, optional. They key will be used as the name for the argument and used when generating documentation.

description
A string that describes what the argument is to be used for. Can contain markdown and will be used when generating documentation.

validation
A validator function that will validate the argument that. Will also be used for the documentation generation.

description

A string that describes what the hook is for and how actions can interact with it. Will be used for the markdown documentation generation and can contain markdown.

hasCallback

A boolean that defines if the hook uses a callback. When set to true a callback is expected to be provided.

initialValue

The initial value for the hook. Hooks runs once for each action and each of them can return a value that the next action can access through a previousValue in the action signature. This value will be used the first time an action is called if defined, otherwise undefined.

returns

A validator function that checks the return value from the actions to make sure it is valid. Will also be used for documentation generation.

Actions

Actions are functions that react to hooks and can both be generic, they run for all hooks that are invoked, or registered to a specific ones.

Action signature

Actions are using call chain with three functions that each serve a specific purpose. This signature are used for all actions, both the ones that are registered as a plain function and those that uses an object.

({ context, description, extension, hook, previousValue }) => (...args) => (previousValue) => { /* do something */ }

context
The Roc context object.

description
A string that is the hook description, can be used for logging what the calling hook is supposed to do.

extension
A string that is the calling extension name.

hook
A string that is the calling hook name.

previousValue
The previous value, will either be the initalValue defined for the hook or the return value from the action that run before the current one.

This value can also be accessed from the third function in the chain and is the recommended way to access it. It should only be accessed here if the value of it is needed to "short-circuit" and not run the action.

The call chain

  1. The first function will normally be invoked for every single action for every single hook. In this case it is up to the action to determine if it should process this and in that case return a new function. If one has registered the action with extension and/or hook it will only be called if these match.

  2. The second function will be called with possible arguments and it might return another function if it should run.

  3. The third function will be invoked and in some instances it might be expected that it returns a value, in others not. The previous value can be accessed here if one exists and it will either be the initalValue defined for the hook or the return value from the action that run before the current one.

Tip: to display the order of hooks being invoked, you can start roc in verbose mode by passing -V from the CLI.

Define actions

Actions are used in both projects and extensions.

A function

In some cases it can be useful to create an action that reacts to all hooks and manually filter out the relevant ones. This can be done by conditionally return function for the first and the second step in the call chain.

Example

({ hook }) => {
    if (hook === 'hook-name') {
        return (arg1) => {
            if (arg1 > 0) {
                return () => {
                    // Run the action
                }
            }
        }
    }
}

See above for the complete signature.

An object

An object can be used to register with a specific hook and/or belong to a specific extension.

Example

{
  action: () => () => () => { },
  description: 'Some __description__.'
  extension: 'roc-package-core-dev', // For which extension this action should run - optional
  hook: 'before-clean', // For which hook this action should run - optional
  post: () => () => () => { }, // A function following the same interface as the plain function and that runs last - optional
}

action
A function following the same interface as the plain function, required. See above for more details about the signature.

description
An optional string with a description on what the action does, used for documentation generation and can use Markdown.

extension
An optional string for which extension the action should run.

hook
An optional string for which hook the action should run.

post
An optional function following the same interface as the plain function. Will run after all the normal actions has been invoked. Can be used to do something after normal actions has been completed.

An example would be a action that is used to build a Webpack configuration object. A post action could then be responsible for reading something from the roc.config.js inside the project after everything else, making sure that it will be added on top of eveything else.

Add in extensions

Actions are registered using the Roc object and can be both defined as generic functions that are invoked for all actions and objects that connect to specific hooks.

Add in projects

Actions in projects can be added using the project.actions in roc.config.js.