Hook Model

First, let's introduce some content about the basic plugin system in Modern.js, including the working mode of the Hook model, the operating mode of each Hook model, and the working mode of the Manager.

Each Hook model is independent and can manage running functions independently.

Basic Working Mode

Taking the Pipeline as an example, let's briefly introduce the working mode of the Hook model. Let's take a look at a simple example:

import { createPipeline } from '@modern-js/plugin';

// 1. create
const pipeline = createPipeline<number, number>();

// 2. add function
pipeline.use((count, next) => {
  return next(count + 1);
});
pipeline.use((count, next) => {
  return count * 2;
});

// 3. exec
pipeline.run(1); // 4
pipeline.run(5); // 12

In this example, a Pipeline<number, number> is created on line 3. This means that when you run it, you need to pass in a number, and you will get a number as a result, the type is:

(count: number, next: (nextCount: number) => number) => number;

The reason why there are only numbers here is because we created a Pipeline<number, number>. If we had created a Pipeline<number, string>, then when we run it, we would pass in a number and get back a string. the type is:

(count: number, next: (nextCount: number) => string) => string;

After creating a Pipeline, you can add functions using the use method (lines 5 and 8). It is important to note that the order in which you add the functions is the order in which they will run by default.

Within these functions, you can manipulate the count value and return a value. If you call the next function, the next function in the pipeline will run. For example, if you add three functions: A, B, and C, and you call next in function A, then function B will run. Similarly, if you call next in function B, then function C will run.

In the example above, the first function added on line 5 calls next, which causes the second function added on line 8 to run. The return value of this function is the return value of the entire pipeline. If the first function does not call next and simply returns a value, then the pipeline will return that value without running any other functions.

For example:

import { createPipeline } from '@modern-js/plugin';

// 1. create
const pipeline = createPipeline<number, number>();

// 2. add function
pipeline.use((count, next) => {
  return count + 1;
});
pipeline.use((count, next) => {
  return count * 2;
});

// 3. Run
pipeline.run(1); // 2
pipeline.run(5); // 6

If the first function does not call next, the second function will not run and the return value of the pipeline will be the return value of the first function.

Finally, the way to run the Pipeline is simply to call pipeline.run().

Differences between different Hook models

The above section describes the general working mode of the Pipeline, and the working modes of other Hook models are similar. The main differences lie in the function type, execution order, and parameters.

Pipeline

The example above describes the Pipeline, so we won't go into details here. In the Pipeline category, there are two subcategories: Sync and Async, which manage functions of either Sync or Async type, respectively.

INFO

If there are no functions in the Pipeline or all functions have called the next function, then you need to provide a value when running the Pipeline.

pipeline(
  {},
  {
    onLast: () => {
      // do something
    },
  },
);

:::

Waterfall

This model, as the name suggests, is characterized by the sequential passing of parameters, where the return value of the previous function becomes the input parameter of the next function. Let's look at an example::

import { createWaterfall } from '@modern-js/plugin';

// 1. create
const waterfall = createWaterfall<number>();

// 2. add function
waterfall.use(count => {
  return count + 1;
});
waterfall.use(count => {
  return count * 2;
});

// 3. exec
waterfall.run(1); // 4
waterfall.run(5); // 12

In this example, a Waterfall<number> type is created, which means that the input and output types of this model are the same. In this case, both the input and output types are number, the type is:

(count: number) => number;

At first glance, this example may seem to have the same functionality as the Pipeline above, but there are some important differences to note. Firstly, the functions managed by Waterfall do not have a next function as the second argument, so they cannot modify the execution order by calling next within the function. Secondly, the input and output types of the functions must be the same (unlike in the Pipeline where they can be different).

Similarly to Pipeline, Waterfall has Sync and Async subcategories that respectively manage Sync and Async functions.

Workflow

This Hook model is different from the two Hook models above in that there is no strong concept of passing parameters and return values in a sequential order. In this model, each function runs independently based on the same input parameter.

for example:

import { createWorkflow } from '@modern-js/plugin';

// 1. create
const workflow = createWorkflow<number, number>();

// 2. add plugin
workflow.use(count => {
  return count + 1;
});
workflow.use(count => {
  return count * 2;
});

// 3. Run
workflow.run(1); // [2, 2]
workflow.run(5); // [6, 10]

In this example, two functions are added to the Workflow, so the result of running the Workflow is an array of the results of these two functions.

Although there is no strong concept of passing parameters and return values in a sequential order in this model, there are still differences in the execution order. In the Workflow category, there are three subcategories: Sync, Async, and Parallel.

The difference between them lies in the execution order of the functions. By default, they are all executed in the order they are added, but in Sync and Async mode, the execution order is strictly based on the order in which they are added, while in Parallel mode, a variant of Async mode, Promise.all is used to execute all the functions, while in Async mode, await is used to wait for the previous function to finish running.

Comparison of Hook models

Function Type Execution Order Source of Function Parameters Source of Execution Returns Preferred Task Type Function TS Type
Pipeline Sync Executes the first added function by default, can execute subsequent functions through next The parameters of the first function come from the input, while the parameters of subsequent functions come from the parameters passed to next Returns from the first function
  • Needs to modify initial parameters
  • Needs to modify function execution order
(input: I, next: Next<I, O>) => O
AsyncPipeline Sync/Async Executes the first added function by default, can execute subsequent functions through next The parameters of the first function come from the input, while the parameters of subsequent functions come from the parameters passed to next Returns from the first function
  • Needs to modify initial parameters
  • Needs to modify function execution order
(input: I, next: AsyncNext<I, O>) => O | Promise<O>
Waterfall Sync Executes functions in order The parameters of the first function come from the input, while the parameters of subsequent functions come from the return value of the previous function Returns from the last function
  • Needs to modify initial parameters
  • Does not need to modify function execution order
(I: I) => I
AsyncWaterfall Sync/Async Executes functions in order The parameters of the first function come from the input, while the parameters of subsequent functions come from the return value of the previous function Returns from the last function
  • Needs to modify initial parameters
  • Does not need to modify function execution order
(I: I) => I | Promise<I>
Workflow Sync Executes functions in order All function parameters come from the input An array of all function returns
  • Does not need to modify initial parameters
  • Does not need to modify function execution order
(I: I) => O
AsyncWorkflow Sync/Async Executes functions in order All function parameters come from the input An array of all function returns
  • Does not need to modify initial parameters
  • Does not need to modify function execution order
(I: I) => O | Promise<O>
ParallelWorkFlow Sync/Async Executes functions asynchronously All function parameters come from the input An array of all function returns
  • Does not need to modify initial parameters
  • Does not care about function execution order
(I: I) => O | Promise<O>

"Workflow" and "Waterfall" are actually variants of the "Pipeline" model. While it's possible to implement "Workflow" and "Waterfall" using a specific writing style with "Pipeline", it can be more complicated with many implicit conventions. To make it easier to use, these two variants are provided to satisfy specific use cases.