Angular Modernizer
    Preparing search index...

    Module @angular-modernizer/plugin-standalone - v1.2.0

    Standalone migration plugin for the Angular Modernizer.

    Migrates Angular components, directives and pipes to standalone. StandalonePlugin is the entry point for the kernel and registers StandaloneMigrationRule (standalone:migrate-to-standalone), which delegates to StandaloneMigrationOrchestrator. StandaloneMigrationConfig lists the rule options.

    @angular-modernizer/plugin-standalone

    Standalone migration plugin for the Angular Modernization Platform.

    This package migrates Angular components, directives, and pipes from NgModule-based architecture to the standalone architecture introduced in Angular 14+. It automates the transformation including adding standalone: true, resolving template dependencies, and updating the imports array.

    The plugin (StandalonePlugin, name @angular-modernizer/plugin-standalone) contributes one transform rule, StandaloneMigrationRule (ID standalone:migrate-to-standalone), a thin rule that delegates to StandaloneMigrationOrchestrator.

    This plugin follows the API-driven plugin pattern. The orchestrator takes no constructor dependencies; everything comes in via TransformContext:

    // Constructor injection - tight coupling, avoid this
    class OldOrchestrator {
    constructor(private project: Project) {}
    }

    // Context-driven - use this
    class StandaloneMigrationOrchestrator {
    constructor() {} // No injected dependencies

    async run(context: TransformContext<PublicApi>): Promise<void> {
    const { project, api } = context;
    // All capabilities come from PublicApi
    }
    }

    The orchestrator uses these tools from PublicApi:

    1. SymbolLocator (analysis.symbolLocator): builds a project-wide symbol map of components, directives and pipes.
    2. TemplateAnalyzer (analysis.templateAnalyzer): extracts all usages from templates (elements, directives, pipes) without filtering or heuristics.
    3. NgModuleManager (transformation.ngModuleManager): finds the NgModule declaring a component and reads its imports.
    4. ImportManager (transformation.importManager): adds import statements without introducing duplicates.

    It also creates a TemplateUsageAnalyzer from @angular-modernizer/api to keep only the NgModule imports the template uses.

    • Migrates Components to standalone
    • Migrates Directives to standalone
    • Migrates Pipes to standalone
    • Auto-detects CommonModule dependencies (*ngIf, *ngFor)
    • Resolves component-to-component, component-to-directive, and component-to-pipe dependencies
    • Reads external templates (templateUrl) from disk
    interface StandaloneMigrationConfig {
    /**
    * Auto-add CommonModule when common directives are detected.
    * Default: true
    */
    autoAddCommonModule?: boolean;

    /**
    * Auto-add FormsModule when forms directives are detected.
    * Default: true. Read, but not applied by the current migration.
    */
    autoAddFormsModule?: boolean;

    /**
    * Preserve existing imports array if standalone is already true.
    * Default: false (overwrite). Read, but not applied by the current migration.
    */
    preserveExistingImports?: boolean;
    }

    The options are read from context.config.

    import { Kernel, RealFileSystemAdapter } from '@angular-modernizer/core';
    import { StandalonePlugin } from '@angular-modernizer/plugin-standalone';
    import { createPublicApi } from '@angular-modernizer/api';
    import { ContextFactory } from '@angular-modernizer/plugin-system';
    import path from 'node:path';
    import { glob } from 'glob';

    const kernel = new Kernel({
    tsConfigPath: path.join(projectPath, 'tsconfig.json'),
    fileSystem: new RealFileSystemAdapter(),
    plugins: [new StandalonePlugin()],
    });

    await kernel.initialize();

    const project = kernel.getProject();
    const api = createPublicApi(project);
    const plugin = kernel.getPlugin('@angular-modernizer/plugin-standalone')!;
    const transformRules = plugin.getTransformRules();
    const rule = transformRules[0]; // StandaloneMigrationRule

    const files = await glob('src/**/*.{ts,tsx}', {
    ignore: ['**/node_modules/**', '**/dist/**', '**/*.spec.ts'],
    });

    for (const filePath of files) {
    const sourceFile = project.addSourceFileAtPath(filePath);

    const context = ContextFactory.createTransformContext({
    sourceFile,
    project,
    api,
    config: {
    autoAddCommonModule: true,
    },
    });

    const result = await rule.transform(context);

    if (result.modified) {
    await sourceFile.save();
    console.log('Transformed:', filePath);
    }
    }
    import { StandaloneMigrationOrchestrator } from '@angular-modernizer/plugin-standalone';
    import { ContextFactory } from '@angular-modernizer/plugin-system';
    import { createPublicApi } from '@angular-modernizer/api';
    import { Project } from 'ts-morph';

    const project = new Project();
    const sourceFile = project.addSourceFileAtPath('my-component.ts');
    const api = createPublicApi(project);

    const orchestrator = new StandaloneMigrationOrchestrator();
    const context = ContextFactory.createTransformContext({
    sourceFile,
    project,
    api,
    config: {},
    });

    await orchestrator.run(context);
    packages/plugin-standalone/
    src/
    orchestrators/
    standalone-migration-orchestrator.ts
    rules/
    standalone-migration-rule.ts
    standalone-plugin.ts
    index.ts
    __tests__/
    standalone-migration-orchestrator.test.ts
    standalone-migration.integration.test.ts
    standalone-plugin.test.ts
    orchestrator-selector-map.test.ts
    integration/
    fixtures/
    package.json
    tsconfig.json
    jest.config.js
    README.md
    pnpm --filter @angular-modernizer/plugin-standalone test
    pnpm --filter @angular-modernizer/plugin-standalone test -- --watch
    pnpm --filter @angular-modernizer/plugin-standalone build

    Test coverage validates:

    • Stateless instantiation (context-driven design)
    • Component, directive, and pipe migration to standalone
    • PublicApi consumption from context rather than constructor
    • TransformContext acceptance and usage

    The orchestrator builds a Map<selector, className> lazily on first use and reuses it for all template analyses within a run. Custom decorators such as @RegisteredComponent are included via the two-tier NgModuleManager scan.

    The migration only copies NgModule imports that are actually used in the component template, via TemplateUsageAnalyzer (Tier 1: regex ~2ms, Tier 2: Angular Compiler ~15ms, Tier 3: conservative include-all fallback). Do not copy NgModule imports directly.

    When updating existing @Component properties, use the remove-and-add pattern to avoid edge cases with replaceWithText():

    const prop = objLiteral.getProperty('standalone');
    if (prop) {
    prop.remove();
    }
    objLiteral.addPropertyAssignment({ name: 'standalone', initializer: 'true' });
    • @angular-modernizer/api - Public API with analysis and transformation tools
    • @angular-modernizer/core - Kernel and infrastructure
    • @angular-modernizer/plugin-system - Plugin contracts and contexts
    • ts-morph - TypeScript AST manipulation
    • @angular/compiler, lru-cache
    1. No constructor dependencies: use TransformContext
    2. API-first: use PublicApi, do not reimplement AST logic
    3. TSDoc comments: document all public APIs
    4. Test-driven: write tests before implementation
    5. Functional style: prefer pure functions and immutability

    Classes

    StandaloneMigrationOrchestrator
    StandaloneMigrationRule
    StandalonePlugin

    Interfaces

    StandaloneMigrationConfig