Modules are the basic building blocks of Statiq functionality. If the out-of-the-box modules don’t satisfy your use case, it’s easy to customize generation by creating new modules.
Modules implement the IModule
interface which defines a single Task<IEnumerable<IDocument>> ExecuteAsync(IExecutionContext context)
method. The execution context passed to the ExecuteAsync
method contains the input documents to the module as well as providing access to output documents from other pipelines and various engine and utility functionality.
While you can implement IModule
easily enough yourself, in practice most modules are derived from a number of different base module classes:
Module
is a common module base class that's appropriate for most modules.ParallelModule
can be used when documents should be processed in parallel.SyncModule
can be used when the module execution should be synchronous (I.e. notasync
).ParallelSyncModule
can be used when documents should be processed in parallel but the module execution should be synchronous (I.e. notasync
).
ParentModule
can be used to execute child modules.SyncParentModule
can be used when the module execution should be synchronous (I.e. notasync
).ForEachDocument
can be used to execute child modules against each input document one at a time.ForAllDocuments
can be used to execute child modules against all documents and is very useful for grouping a sequence of child modules into a single parent module.
ChildDocumentsModule
can be used to execute child modules and then combine or manipulate input documents based on the output documents of the child modules.SyncChildDocumentsModule
can be used when the module execution should be synchronous (I.e. notasync
).
ConfigModule<TValue>
andMultiConfigModule
can be used to manageConfig<TValue>
delegates for configuring the module (you can also manage configuration delegates yourself, these modules just make it a little easier).ParallelConfigModule<TValue>
andParallelMultiConfigModule
can be used when documents should be processed in parallel.SyncConfigModule<TValue>
andSyncMultiConfigModule
can be used when the module execution should be synchronous (I.e. notasync
).ParallelSyncConfigModule<TValue>
andParallelSyncMultiConfigModule
can be used when documents should be processed in parallel but the module execution should be synchronous (I.e. notasync
).
ReadDataModule
can be used to convert data from arbitrary data sources into output documents.
When using the base module classes you should never call IModule.ExecuteAsync(...)
directly. Instead, most of the module base classes above have both an ExecuteContext
virtual method and/or an ExecuteInput
virtual method.
- Overload the
ExecuteContext
method to have your code called once for all the input documents (available viaIExecutionContext.Inputs
). This is useful for modules that need to create new documents from scratch or that need to aggregate or operate on the input documents as a set. - Overload the
ExecuteInput
method to have your code called once per document. This is useful when the module transforms or manipulates documents that are unrelated to each other.
Here are some other guidelines and tips to follow when writing a module:
- Consider using the built-in
ExecuteConfig
module instead of writing your own.- You may not even need a new module. The
ExecuteConfig
module lets you specify a delegate that can return documents, content, and other types of data which will be converted to output documents as appropriate.
- You may not even need a new module. The
- Use
Config<T>
configuration delegates.- If your module needs to accept user-configurable values, use
Config<T>
. - Consider using one of the base module classes that deals with
Config<T>
likeConfigModule
orMultiConfigModule
.
- If your module needs to accept user-configurable values, use
- Avoid document-to-document references (especially to/from children):
- Try to avoid creating documents that reference other documents, especially in the top-level output documents (parent documents that reference children may be okay in some cases). If a document references another document and a following module clones the referenced document, the reference will still point to the old document and not the new clone.
- Preserve input ordering:
- Many modules output documents in a specific order and following modules should preserve that order whenever possible. The base module classes do this by default, but any explicit parallel operations should preserve ordering as well (I.e., by calling
.AsParallel().AsOrdered()
).
- Many modules output documents in a specific order and following modules should preserve that order whenever possible. The base module classes do this by default, but any explicit parallel operations should preserve ordering as well (I.e., by calling
- Only reference
Statiq.Common
:- If a module is in a separate assembly from your application you shouldn’t need a reference to
Statiq.Core
, and if you find that you do please open an issue so the appropriate functionality can be moved toStatiq.Common
.
- If a module is in a separate assembly from your application you shouldn’t need a reference to
- Name modules using a VerbNoun convention when possible.