High-level overview
MorJS is built in layers.@morjs/takin is the lowest-level engine; @morjs/cli registers commands and compiler plugins on top of it; your mor.config.ts configures what runs.
Takin engine
Takin is the main entry class (packages/takin/src/takin.ts). It extends Config, owns the global lifecycle hooks, and orchestrates one or more Runner instances — one per user config entry when multi-config mode is active.
Takin lifecycle hooks
All hooks are tapable instances.| Hook | Type | Description |
|---|---|---|
initialize | AsyncSeriesHook<[Takin]> | Fires after construction, before run() processes any config. |
prepare | AsyncSeriesHook<RunnerOptions> | Pre-run preparation phase run through a dedicated inner Runner. Use it for early feature interception. |
configLoaded | AsyncSeriesHook<[Takin, CommandOptions]> | Fires after the config file is loaded from disk. Not called when config is passed directly via API. |
configFiltered | AsyncSeriesWaterfallHook<[UserConfig[], CommandOptions]> | Fires after multi-config filtering. Modify which configs get executed. |
extendRunner | SyncWaterfallHook<[typeof Runner, RunnerOptions]> | Replace or extend the Runner class itself before it is instantiated. |
Run sequence
initialize
takin.hooks.initialize fires. Plugins registered via takin.use() have already run their onUse callbacks at this point.prepare
A dedicated inner Runner executes
ChangeCwdPlugin, LoadEnvPlugin, MultiConfigPlugin, and PluginConfigPlugin to load .env files, discover the config file, and compute multi-config filters.configLoaded / configFiltered
After the config file is read,
configLoaded fires. Then configFiltered trims the config array based on --name filters or index.extendRunner
extendRunner allows swapping the Runner class. The result is used for all subsequent Runner instantiations.Runner
EachRunner processes a single user config object. It parses the CLI, validates config, then invokes the run hook where plugins do their actual work (compilation, mocking, etc.).
Runner lifecycle hooks
| Hook | Type | Description |
|---|---|---|
initialize | SyncHook<Runner> | Runner is constructed and plugins are loaded. |
cli | SyncHook<Cli> | Register commands and options with the CLI parser. |
matchedCommand | AsyncSeriesHook<CommandOptions> | A CLI command was matched. |
loadConfig | AsyncSeriesHook<CommandOptions> | Load user config from disk. |
modifyUserConfig | AsyncSeriesWaterfallHook<[UserConfig, CommandOptions]> | Transform the resolved user config before validation. |
registerUserConfig | AsyncSeriesWaterfallHook<[AnyZodObject, Zod]> | Register additional zod schema fields for config validation. |
shouldRun | SyncBailHook<Runner, boolean> | Return false to abort execution (e.g., for --help or --version). |
shouldValidateUserConfig | SyncBailHook<Runner, boolean> | Return false to skip config validation. |
userConfigValidated | AsyncSeriesHook<UserConfig> | Config has passed validation. |
beforeRun | AsyncSeriesHook<Runner> | Pre-run hook for preparing data needed by commands. |
run | HookMap<AsyncParallelHook<CommandOptions>> | The main execution hook, keyed by command name. |
done | AsyncParallelHook<Runner> | Runner completed successfully. |
failed | AsyncSeriesHook<Error> | Runner encountered an error. |
shutdown | AsyncSeriesHook<Runner> | Runner is being shut down (e.g., during reload()). |
Plugin system
All extensibility in MorJS goes through thePlugin interface defined in packages/takin/src/plugin.ts:
Plugin execution order
Plugins are sorted into three buckets before the Runner executes:| Order | Condition | Description |
|---|---|---|
| 1 | enforce: 'pre' | Runs before all other plugins. |
| 2 | (no enforce) | Normal plugins in registration order. |
| 3 | enforce: 'post' | Runs after all other plugins. |
Plugin registration sources
Plugins can enter the system from multiple sources (PluginTypes enum in packages/takin/src/config.ts):
| Source | Description |
|---|---|
use | Registered via takin.use([...]) |
config | Listed in mor.config.ts under the plugins field |
cli | Passed via CLI options |
runner | Passed directly to Runner.run() |
auto | Auto-loaded from package.json dependencies by pattern matching |
Writing a plugin
Compilation pipeline
The@morjs/plugin-compiler package drives the build. It hooks into runner.hooks.run.for('compile') and runs this pipeline:
Compile modes
ThecompileMode user config field controls how modules are resolved:
| Mode | Description |
|---|---|
bundle | Full webpack bundling with npm resolution. Recommended for projects using npm packages. |
transform | Source-only transformation without bundling. Faster for projects where dependencies are pre-bundled. |
default and transfer are legacy aliases for transform. They produce a warning and are normalized automatically.Source types
ThesourceType field in user config tells the compiler which DSL the source is written in:
wechat— source written in WeChat mini-program syntaxalipay— source written in Alipay mini-program syntax
Internal plugins in @morjs/cli
When you install@morjs/cli, these plugins are always registered:
| Plugin | Role |
|---|---|
CliPlugin | Registers version/help CLI options, verbose mode, and the default command. |
CleanPlugin | Cleans the output directory before compilation. |
WebpackPlugin | Wraps webpack configuration and exposes webpackWrapper hook. |
CompilerPlugin | Entry point for the full compilation pipeline. |
ComposerPlugin | Handles multi-module composition (integrated mini-programs). |
GeneratorPlugin | Backs the mor create scaffold command. |
AnalyzerPlugin | Provides bundle analysis output. |
AutoReloadPlugin | Watches for config changes and triggers takin.reload(). |
MockerPlugin | Provides local API mocking during development. |
PrettyErrorPlugin | Formats runtime errors for readable terminal output. |