CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-rollup-plugin-typescript2

Seamless integration between Rollup and TypeScript with comprehensive error reporting.

Pending
Overview
Eval results
Files

custom-transformers.mddocs/

Custom Transformers

Experimental support for TypeScript transformers to modify the compilation process.

Capabilities

Transformer Interface

Define custom transformers that modify TypeScript compilation at different stages.

/**
 * Custom transformer configuration interface
 */
interface ICustomTransformer {
  /** Transformers to run before TypeScript compilation */
  before?: tsTypes.TransformerFactory<tsTypes.SourceFile>;
  
  /** Transformers to run after TypeScript compilation */
  after?: tsTypes.TransformerFactory<tsTypes.SourceFile>;
  
  /** Transformers to run after declaration file generation */
  afterDeclarations?: tsTypes.TransformerFactory<tsTypes.Bundle | tsTypes.SourceFile>;
}

/**
 * Function that creates transformer configurations from language service
 * @param ls - TypeScript language service instance
 * @returns Transformer configuration or TypeScript's native CustomTransformers
 */
type TransformerFactoryCreator = (
  ls: tsTypes.LanguageService
) => tsTypes.CustomTransformers | ICustomTransformer;

Transformer Factory Creation

Create transformers that have access to the TypeScript program and type information.

/**
 * TypeScript's native transformer factory type
 */
type TransformerFactory<T extends tsTypes.Node> = (
  context: tsTypes.TransformationContext
) => tsTypes.Transformer<T>;

/**
 * TypeScript's transformation context
 */
interface TransformationContext {
  /** Compiler options */
  getCompilerOptions(): tsTypes.CompilerOptions;
  
  /** Start lexical environment */
  startLexicalEnvironment(): void;
  
  /** End lexical environment and get hoisted declarations */
  endLexicalEnvironment(): tsTypes.Statement[] | undefined;
  
  /** Hoist function declaration */
  hoistFunctionDeclaration(node: tsTypes.FunctionDeclaration): void;
  
  /** Hoist variable declaration */
  hoistVariableDeclaration(node: tsTypes.Identifier): void;
  
  /** Request emit helper */
  requestEmitHelper(helper: tsTypes.EmitHelper): void;
  
  /** Read emit helper uniqueness */
  readEmitHelpers(): tsTypes.EmitHelper[] | undefined;
  
  /** Enable emit notification */
  enableEmitNotification(kind: tsTypes.SyntaxKind): void;
  
  /** Check if emit notification is enabled */
  isEmitNotificationEnabled(node: tsTypes.Node): boolean;
  
  /** Called when node is emitted */
  onEmitNode(hint: tsTypes.EmitHint, node: tsTypes.Node, emitCallback: (hint: tsTypes.EmitHint, node: tsTypes.Node) => void): void;
  
  /** Enable substitution */
  enableSubstitution(kind: tsTypes.SyntaxKind): void;
  
  /** Check if substitution is enabled */
  isSubstitutionEnabled(node: tsTypes.Node): boolean;
  
  /** Called when node is substituted */
  onSubstituteNode(hint: tsTypes.EmitHint, node: tsTypes.Node): tsTypes.Node;
}

Usage Examples

Basic Transformer:

import typescript from 'rollup-plugin-typescript2';

// Simple transformer that adds console.log to functions
const addLoggingTransformer = (program) => {
  return (context) => {
    return (sourceFile) => {
      function visit(node) {
        if (typescript.isFunctionDeclaration(node) && node.name) {
          // Add console.log at start of function
          const logStatement = typescript.factory.createExpressionStatement(
            typescript.factory.createCallExpression(
              typescript.factory.createPropertyAccessExpression(
                typescript.factory.createIdentifier('console'),
                typescript.factory.createIdentifier('log')
              ),
              undefined,
              [typescript.factory.createStringLiteral(`Entering ${node.name.text}`)]
            )
          );
          
          const updatedBody = typescript.factory.updateBlock(
            node.body,
            [logStatement, ...node.body.statements]
          );
          
          return typescript.factory.updateFunctionDeclaration(
            node,
            node.decorators,
            node.modifiers,
            node.asteriskToken,
            node.name,
            node.typeParameters,
            node.parameters,
            node.type,
            updatedBody
          );
        }
        return typescript.visitEachChild(node, visit, context);
      }
      return typescript.visitNode(sourceFile, visit);
    };
  };
};

// Use in plugin configuration
export default {
  plugins: [
    typescript({
      transformers: [
        (service) => ({
          before: [addLoggingTransformer(service.getProgram())]
        })
      ]
    })
  ]
};

Advanced Transformer with Type Information:

import keysTransformer from 'ts-transformer-keys/transformer';

// Using ts-transformer-keys for compile-time type key extraction
const keysTransformerFactory = (service) => {
  const program = service.getProgram();
  const typeChecker = program.getTypeChecker();
  
  return {
    before: [keysTransformer(program).before],
    after: []
  };
};

typescript({
  transformers: [keysTransformerFactory]
})

Transformer Categories

Transformers can be applied at different stages of the compilation process:

/**
 * Transformer execution stages
 */
interface TransformerStages {
  /** 
   * Before TypeScript compilation
   * - Operates on TypeScript AST before type checking
   * - Can modify source code structure
   * - Has access to full type information
   */
  before: tsTypes.TransformerFactory<tsTypes.SourceFile>[];
  
  /**
   * After TypeScript compilation
   * - Operates on JavaScript AST after compilation
   * - Can modify generated JavaScript
   * - Limited type information available
   */
  after: tsTypes.TransformerFactory<tsTypes.SourceFile>[];
  
  /**
   * After declaration file generation
   * - Operates on declaration files (.d.ts)
   * - Can modify type definitions
   * - Useful for documentation generation
   */
  afterDeclarations: tsTypes.TransformerFactory<tsTypes.Bundle | tsTypes.SourceFile>[];
}

Common Transformer Patterns

Metadata Collection:

// Collect class metadata for dependency injection
const metadataTransformer = (program) => {
  const typeChecker = program.getTypeChecker();
  
  return (context) => {
    return (sourceFile) => {
      function visit(node) {
        if (typescript.isClassDeclaration(node)) {
          // Extract constructor parameter types
          const constructor = node.members.find(
            member => typescript.isConstructorDeclaration(member)
          );
          
          if (constructor) {
            const paramTypes = constructor.parameters.map(param => {
              const type = typeChecker.getTypeAtLocation(param);
              return typeChecker.typeToString(type);
            });
            
            // Add metadata as static property
            const metadataProperty = typescript.factory.createPropertyDeclaration(
              undefined,
              [typescript.factory.createModifier(typescript.SyntaxKind.StaticKeyword)],
              'metadata',
              undefined,
              undefined,
              typescript.factory.createArrayLiteralExpression(
                paramTypes.map(type => 
                  typescript.factory.createStringLiteral(type)
                )
              )
            );
            
            return typescript.factory.updateClassDeclaration(
              node,
              node.decorators,
              node.modifiers,
              node.name,
              node.typeParameters,
              node.heritageClauses,
              [...node.members, metadataProperty]
            );
          }
        }
        return typescript.visitEachChild(node, visit, context);
      }
      return typescript.visitNode(sourceFile, visit);
    };
  };
};

Code Generation:

// Generate getter/setter methods for properties
const accessorTransformer = (program) => {
  return (context) => {
    return (sourceFile) => {
      function visit(node) {
        if (typescript.isClassDeclaration(node)) {
          const newMembers = [];
          
          for (const member of node.members) {
            newMembers.push(member);
            
            if (typescript.isPropertyDeclaration(member) && member.name) {
              const propertyName = member.name.getText();
              
              // Generate getter
              const getter = typescript.factory.createGetAccessorDeclaration(
                undefined,
                undefined,
                propertyName,
                [],
                undefined,
                typescript.factory.createBlock([
                  typescript.factory.createReturnStatement(
                    typescript.factory.createPropertyAccessExpression(
                      typescript.factory.createThis(),
                      `_${propertyName}`
                    )
                  )
                ])
              );
              
              // Generate setter
              const setter = typescript.factory.createSetAccessorDeclaration(
                undefined,
                undefined,
                propertyName,
                [typescript.factory.createParameterDeclaration(
                  undefined,
                  undefined,
                  undefined,
                  'value',
                  undefined,
                  member.type
                )],
                typescript.factory.createBlock([
                  typescript.factory.createExpressionStatement(
                    typescript.factory.createBinaryExpression(
                      typescript.factory.createPropertyAccessExpression(
                        typescript.factory.createThis(),
                        `_${propertyName}`
                      ),
                      typescript.SyntaxKind.EqualsToken,
                      typescript.factory.createIdentifier('value')
                    )
                  )
                ])
              );
              
              newMembers.push(getter, setter);
            }
          }
          
          return typescript.factory.updateClassDeclaration(
            node,
            node.decorators,
            node.modifiers,
            node.name,
            node.typeParameters,
            node.heritageClauses,
            newMembers
          );
        }
        return typescript.visitEachChild(node, visit, context);
      }
      return typescript.visitNode(sourceFile, visit);
    };
  };
};

Integration with TypeScript Language Service

Transformers have full access to TypeScript's language service capabilities:

/**
 * Language service features available to transformers
 */
interface LanguageServiceFeatures {
  /** Get the TypeScript program */
  getProgram(): tsTypes.Program;
  
  /** Get type checker for type analysis */
  getTypeChecker(): tsTypes.TypeChecker;
  
  /** Get semantic diagnostics */
  getSemanticDiagnostics(fileName: string): tsTypes.Diagnostic[];
  
  /** Get syntactic diagnostics */
  getSyntacticDiagnostics(fileName: string): tsTypes.Diagnostic[];
  
  /** Get quick info at position */
  getQuickInfoAtPosition(fileName: string, position: number): tsTypes.QuickInfo | undefined;
  
  /** Get definitions at position */
  getDefinitionAtPosition(fileName: string, position: number): tsTypes.DefinitionInfo[] | undefined;
}

Type-Aware Transformer:

const typeAwareTransformer = (service) => {
  const program = service.getProgram();
  const typeChecker = program.getTypeChecker();
  
  return {
    before: [(context) => {
      return (sourceFile) => {
        function visit(node) {
          if (typescript.isCallExpression(node)) {
            // Get type information for function calls
            const signature = typeChecker.getResolvedSignature(node);
            if (signature) {
              const returnType = typeChecker.getReturnTypeOfSignature(signature);
              const typeName = typeChecker.typeToString(returnType);
              
              // Add type information as comment
              typescript.addSyntheticLeadingComment(
                node,
                typescript.SyntaxKind.MultiLineCommentTrivia,
                ` Returns: ${typeName} `,
                false
              );
            }
          }
          return typescript.visitEachChild(node, visit, context);
        }
        return typescript.visitNode(sourceFile, visit);
      };
    }]
  };
};

Limitations and Considerations

Important Notes:

  • Transformers are experimental and the API may change
  • TypeScript may eventually add native transformer support to tsconfig
  • Not all transformer libraries are compatible with this plugin
  • Transformers can significantly impact build performance
  • Complex transformers may interfere with source maps

Best Practices:

  • Keep transformers as simple as possible
  • Test thoroughly with different TypeScript versions
  • Document any custom transformers used in your project
  • Consider performance impact on large codebases
  • Use verbosity level 3 to debug transformer issues

Install with Tessl CLI

npx tessl i tessl/npm-rollup-plugin-typescript2

docs

custom-transformers.md

file-processing.md

index.md

logging-diagnostics.md

plugin-configuration.md

typescript-integration.md

tile.json