Plugin System
Modern.js adopts a highly extensible, plugin-based architecture, where its core functionalities and extended capabilities are implemented through plugins. The plugin system not only ensures the framework's flexibility but also provides developers with powerful customization options. This document focuses on how to write Modern.js plugins, helping you quickly get started with plugin development.
Core Concept: Everything is a Plugin
Modern.js adheres to the design philosophy of "everything is a plugin," modularizing the framework's various functional components and assembling and extending them through plugins. This design brings several advantages, including:
- High Cohesion, Low Coupling: Each functional module is independently developed, tested, and maintained, reducing system complexity.
- Flexible and Extensible: Users can easily customize the framework's behavior by writing or combining plugins without modifying the core code.
- Easy to Reuse: Plugins can be shared across projects, improving development efficiency.
- Progressive Enhancement: Plugins are introduced on demand, without the complexity of carrying all functionalities from the start.
Plugin Types and Use Cases
Modern.js provides three main plugin types, corresponding to different stages of application development:
CLI Plugins:
- Active Stage: Build time (when executing the
moderncommand). - Typical Scenarios:
- Extending command-line tools.
- Modifying build configurations.
- Listening for file changes.
- Controlling the build process.
- Configuration Method: The
pluginsfield inmodern.config.ts.
Runtime Plugins:
- Active Stage: Application runtime (browser/Node.js environment).
- Typical Scenarios:
- Initializing global state or services.
- Encapsulating React Higher-Order Components (HOCs).
- Intercepting or modifying routing behavior.
- Controlling the rendering process.
- Configuration Method: The
pluginsfield insrc/modern.runtime.ts.
Plugin Structure
A typical Modern.js plugin consists of the following key parts:
Field Descriptions:
name
- Type:
string - Description: Identifies the name of the plugin. This name must be unique within the plugin system; otherwise, the plugin will fail to load.
The plugin names declared in pre, post, and required refer to this name field.
setup
- Type:
(api: PluginAPI) => MaybePromise<void> - Description: The main entry point for the plugin's logic.
api
- Type:
PluginAPI - Description: The plugin's API, containing the Hooks and utility functions supported by the plugin.
pre
- Type:
string[] - Description: Used to insert the plugin execution order. Plugins declared in
prewill be executed before this plugin.
post
- Type:
string[] - Description: Used to determine the plugin execution order. Plugins declared in
postwill be executed after this plugin.
required
- Type:
string[] - Description: Other plugins that this plugin depends on. Before running, it will check if the dependent plugins are registered.
If unregistered plugin names are configured in pre or post, these plugin names will be automatically ignored and will not affect the execution of other plugins.
If you need to explicitly declare that the plugins that the current plugin depends on must exist, you need to use the required field.
usePlugins
- Type:
Plugin - Description: Actively register other plugins of the same type within the plugin.
Plugins declared in usePlugins are executed before the current plugin by default. To execute them after, use the post declaration.
registryHooks
- Type:
Record<string, PluginHook<(...args: any[]) => any>> - Description: Extend the currently supported Hook functions to implement custom functionality.
Plugin Hook Model
The core of the Modern.js plugin system is its Hook model, which defines the communication mechanism between plugins. Modern.js mainly provides two types of Hooks:
Async Hook
- Characteristics:
- Hook functions are executed asynchronously, supporting
async/await. - The return value of the previous Hook function is passed as the first argument to the next Hook function.
- Finally returns the return value of the last Hook function.
- Hook functions are executed asynchronously, supporting
- Use Cases: Scenarios involving asynchronous operations (such as network requests, file reading/writing, etc.).
- Creation Method: Created using
createAsyncHook.
Example:
Sync Hook (Synchronous Hook)
- Characteristics:
- Hook functions are executed synchronously.
- The return value of the previous Hook function is passed as the first argument to the next Hook function.
- Finally returns the return value of the last Hook function.
- Use Cases: Scenarios where data needs to be modified synchronously (such as modifying configurations, routes, etc.).
- Creation Method: Created using
createSyncHook.
Example:
Plugin Development Best Practices
- Single Responsibility: Each plugin should focus on implementing a specific, cohesive function. Avoid creating plugins with complex functionalities and unclear responsibilities.
- Naming Conventions: Plugin names should be clear, concise, and follow certain naming conventions (such as
plugin-xxxor@scope/plugin-xxx). - Type Safety: Make full use of TypeScript's type system to ensure the type safety of the plugin API and reduce runtime errors.
- Comprehensive Documentation: Write clear documentation for the plugin, including API descriptions, usage examples, configuration explanations, and change logs.
- Thorough Testing: Conduct unit tests and integration tests on the plugin to ensure its stability, reliability, and compatibility in various scenarios.
- Minimize Side Effects: Plugins should minimize modifications to the external environment (such as global variables, file systems, etc.) to maintain the plugin's independence and portability.
- Error Handling: Plugins should properly handle potential errors to prevent the entire application from crashing due to plugin exceptions.
- Performance Optimization: Pay attention to the performance impact of the plugin, avoid unnecessary calculations and resource consumption, especially in loops or frequently called Hooks.
- Version Control: Follow Semantic Versioning (SemVer) to ensure the backward compatibility of the plugin and facilitate user upgrades.
Following these best practices can help you develop high-quality, easy-to-maintain, and easy-to-use Modern.js plugins.