Worker thread script for parallel analysis in the Angular Modernization Platform.
@angular-modernizer/core can run analysis rules in Node.js worker threads (WorkerPool, ParallelExecutor). Each worker is started from a script file. That script lives in this package, and getWorkerScriptPath() returns its path.
The package exports:
getWorkerScriptPath() - absolute path to the compiled worker script (dist/analysis-worker.js)WorkerTask, WorkerResult - message types, re-exported from @angular-modernizer/coreThe script itself is also published as the subpath export @angular-modernizer/worker/analysis-worker. It can only run as a worker thread; importing it on the main thread throws.
Worker threads run in isolated V8 contexts, so the worker script has to load all analysis plugins itself. The plugins depend on core, and core contains the WorkerPool. Placing the script in core would create a circular dependency.
This package resolves that by:
scan-architecture passes the path to the parallel executor)pnpm add @angular-modernizer/worker @angular-modernizer/core
The worker script is loaded from dist/, so in the monorepo all workspace packages must be built first (pnpm build from the root).
ParallelExecutor decides on its own whether to use worker threads (see Mode Selection). Pass the script path, the tsconfig the workers load and the plugin IDs whose rules the workers run:
import { getWorkerScriptPath } from '@angular-modernizer/worker';
import { ParallelExecutor } from '@angular-modernizer/core';
const executor = new ParallelExecutor({ maxWorkers: 4 });
const results = await executor.executeParallel(sourceFiles, rules, {
tsConfigPath: '/path/to/tsconfig.json',
workerScriptPath: getWorkerScriptPath(),
pluginIds: ['architecture', 'solid'],
});
await executor.shutdown();
import { getWorkerScriptPath } from '@angular-modernizer/worker';
import { WorkerPool } from '@angular-modernizer/core';
const pool = new WorkerPool({
tsConfigPath: '/path/to/tsconfig.json',
workerScriptPath: getWorkerScriptPath(),
maxWorkers: 4,
});
const result = await pool.executeTask({
id: 'task-1',
type: 'analyze-rule',
filePath: '/path/to/file.ts',
ruleIds: [], // empty: all rules of the listed plugins
pluginIds: ['architecture', 'solid'],
});
if (result.error) {
console.error(result.error);
}
console.log(result.results);
await pool.terminate();
WorkerPool throws when workerScriptPath is missing.
Each worker started from the script:
workerId and tsConfigPath from workerData and loads the whole project from that tsconfig into its own ts-morph Project. Rules need cross-file context (inheritance, imports), so a worker does not load single files.PublicApi and posts { type: 'ready', workerId }. If loading fails, it posts { type: 'init-error', workerId, error } and exits. The pool only hands out tasks to workers that sent ready.WorkerTask messages and answers each with a WorkerResult.For a task the worker:
pluginIds; rules are cached per plugin ID for later tasksruleIds when that list is not emptyContextFactory.createAnalysisContext() and runs the rules on filePathwarning (plugin results carry no severity)Accepted plugin IDs:
| Plugin ID | Package |
|---|---|
architecture |
@angular-modernizer/plugin-architecture |
solid |
@angular-modernizer/plugin-solid |
angular |
@angular-modernizer/plugin-angular |
analyzer |
@angular-modernizer/plugin-analyzer |
standalone |
@angular-modernizer/plugin-standalone (no analysis rules) |
The full package name works as well. Unknown IDs are logged and ignored. With no matching rules the task returns an empty result list, so pass pluginIds: the ParallelExecutor sends an empty list when pluginIds is not set.
Errors:
results, error set) when filePath is not part of the loaded project or the worker is not initialized.init-error and ends the worker.Workers log to the console with the prefix [Worker <id>].
ParallelExecutor.executeParallel() chooses between Promise-based parallelism on the main thread and worker threads. With autoSelectMode (default) and no useWorkerThreads, it computes a complexity score from 0 to 100:
| Factor | Weight |
|---|---|
| File count (2000 files = 100) | 30% |
| Estimated lines of code (1M = 100) | 35% |
| Large files over 50 KB | 20% |
| Current heap usage above 70% | 15% |
| Score | Mode |
|---|---|
| below 35 | Promise mode |
| 35 to below 50 | Promise mode with memory monitoring; aborts when heap usage reaches 85% and falls back to workers if workerScriptPath is set |
| 50 and above | Worker threads |
Overrides:
useWorkerThreads: true uses workers when there are at least minFilesForWorkers files (default 200), otherwise Promise modeuseWorkerThreads: false always uses Promise modeautoSelectMode: false without useWorkerThreads uses Promise modeIf worker execution fails, the executor terminates the pool and falls back to Promise mode.
Details: docs/architecture/parallelism.md.
| Setting | Default |
|---|---|
Workers (WorkerPool) |
CPU cores - 1, at least 1 |
Heap per worker (WorkerPool, workerHeapLimit) |
512 MB old generation; ParallelExecutor sets 4096 MB |
Task timeout (defaultTimeout / timeout) |
30000 ms, counted from the moment a worker is ready |
| Wait for the first ready worker | 120 s |
Each worker loads the full project, so memory grows with the number of workers. A worker that hits its heap limit is ended by Node.js; lower maxWorkers or raise workerHeapLimit for large projects. Workers do not report their heap usage, so the pool cannot recycle them based on real memory use.
Data crossing the thread boundary is copied with the structured clone algorithm. Use plain objects, primitives and arrays: functions and Symbols cause a DataCloneError, class instances arrive as plain objects without their methods. WorkerTask.config must follow this rule.
This package builds after all plugins in the monorepo:
1. core
2. plugin-system
3. api
4. plugin-analyzer, plugin-solid, plugin-angular, plugin-standalone, plugin-architecture, orchestration
5. worker (this package)
6. adapter-mcp
The package has no tests of its own. The worker pool tests live in @angular-modernizer/core and are skipped unless ENABLE_WORKER_TESTS=true is set and the worker script is built:
pnpm run build
ENABLE_WORKER_TESTS=true pnpm --filter @angular-modernizer/core test
@angular-modernizer/core - WorkerTask, WorkerResult@angular-modernizer/api - createPublicApi for each worker@angular-modernizer/plugin-system - ContextFactory, rule contracts@angular-modernizer/plugin-analyzer, plugin-solid, plugin-angular, plugin-standalone, plugin-architecture - rules run in workersts-morph - project loadingWorkerPool, ParallelExecutor
Worker thread script for parallel analysis with the Angular Modernizer.
The
WorkerPoolandParallelExecutorof@angular-modernizer/corestart Node.js worker threads from a script path. The script lives in this package because it loads all analysis plugins, and the plugins depend on core. getWorkerScriptPath returns that path. WorkerTask and WorkerResult are the messages exchanged with a worker.