CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vite-plugin-dts

A Vite plugin that generates TypeScript declaration files from source files in library mode

Pending
Overview
Eval results
Files

custom-resolvers.mddocs/

Custom Resolvers

Extensible resolver system for handling non-standard file types during TypeScript declaration generation, enabling support for custom file formats and preprocessing pipelines.

Capabilities

Resolver Interface

Core interface defining how custom resolvers integrate with the plugin's TypeScript compilation process.

/**
 * Custom resolver for transforming non-standard files to TypeScript declarations
 */
interface Resolver {
  /** Unique name identifier (later resolvers with same name overwrite earlier ones) */
  name: string;
  
  /** Determine whether this resolver can handle the given file */
  supports: (id: string) => void | boolean;
  
  /** Transform source code to declaration file outputs */
  transform: (payload: {
    id: string;
    code: string;
    root: string;
    outDir: string;
    host: ts.CompilerHost;
    program: ts.Program;
  }) => MaybePromise<
    | { outputs: { path: string; content: string }[]; emitSkipped?: boolean; diagnostics?: readonly ts.Diagnostic[] }
    | { path: string; content: string }[]
  >;
}

Creating Custom Resolvers

Factory pattern for creating resolvers that handle specific file types or transformation requirements.

/**
 * Create a custom resolver for specific file types
 * @param name - Unique identifier for the resolver
 * @param supportsPattern - File extension or pattern matching function
 * @param transformFn - Function to transform source to declarations
 * @returns Configured resolver instance
 */
function createCustomResolver(
  name: string,
  supportsPattern: string | RegExp | ((id: string) => boolean),
  transformFn: (payload: ResolverPayload) => ResolverResult
): Resolver;

interface ResolverPayload {
  id: string;
  code: string;
  root: string;
  outDir: string; 
  host: ts.CompilerHost;
  program: ts.Program;
}

type ResolverResult = MaybePromise<
  | { outputs: { path: string; content: string }[]; emitSkipped?: boolean; diagnostics?: readonly ts.Diagnostic[] }
  | { path: string; content: string }[]
>;

Usage Examples:

import dts from "vite-plugin-dts";

// Custom resolver for .yaml configuration files
const yamlResolver: Resolver = {
  name: "yaml-config",
  supports: (id) => id.endsWith(".yaml") || id.endsWith(".yml"),
  transform: async ({ id, code, outDir, root }) => {
    // Parse YAML and generate TypeScript definitions
    const configName = path.basename(id, path.extname(id));
    const capitalizedName = configName.charAt(0).toUpperCase() + configName.slice(1);
    
    const declaration = `
export interface ${capitalizedName}Config {
  [key: string]: any;
}

declare const config: ${capitalizedName}Config;
export default config;
`;
    
    return [{
      path: path.resolve(outDir, `${configName}.d.ts`),
      content: declaration
    }];
  }
};

// Custom resolver for GraphQL schema files
const graphqlResolver: Resolver = {
  name: "graphql-schema",
  supports: (id) => id.endsWith(".graphql") || id.endsWith(".gql"),
  transform: async ({ id, code, outDir }) => {
    // Generate TypeScript types from GraphQL schema
    const schemaName = path.basename(id, path.extname(id));
    
    // Simple example - in practice you'd parse the GraphQL schema
    const typeDefinitions = `
export interface ${schemaName}Schema {
  query: Query;
  mutation?: Mutation;
  subscription?: Subscription;
}

interface Query {
  [field: string]: any;
}

interface Mutation {
  [field: string]: any;
}

interface Subscription {
  [field: string]: any;
}
`;

    return {
      outputs: [{
        path: path.resolve(outDir, `${schemaName}.schema.d.ts`),
        content: typeDefinitions
      }],
      emitSkipped: false
    };
  }
};

// Use custom resolvers in plugin configuration
export default defineConfig({
  plugins: [
    dts({
      resolvers: [yamlResolver, graphqlResolver]
    })
  ]
});

Resolver Registration and Management

System for managing multiple resolvers and handling conflicts between resolver names.

/**
 * Parse and deduplicate resolvers by name
 * Later resolvers with the same name overwrite earlier ones
 * @param resolvers - Array of resolver instances
 * @returns Deduplicated array of resolvers
 */
function parseResolvers(resolvers: Resolver[]): Resolver[];

Usage Examples:

import { parseResolvers } from "vite-plugin-dts/resolvers";

// Multiple resolvers with potential name conflicts
const resolvers = [
  { name: "json", supports: () => false, transform: () => [] }, // Will be overwritten
  { name: "custom", supports: () => true, transform: () => [] },
  { name: "json", supports: (id) => id.endsWith(".json"), transform: () => [] } // Overwrites first
];

const finalResolvers = parseResolvers(resolvers);
// Result: [custom resolver, second json resolver]

// Use in plugin configuration
dts({
  resolvers: finalResolvers
});

Advanced Resolver Patterns

Complex resolver implementations for sophisticated file transformation scenarios.

// Multi-output resolver (generates multiple declaration files from one source)
const multiOutputResolver: Resolver = {
  name: "multi-output",
  supports: (id) => id.endsWith(".multi.ts"),
  transform: async ({ id, code, outDir, program }) => {
    // Generate multiple declaration files from analysis
    return {
      outputs: [
        {
          path: path.resolve(outDir, "types.d.ts"),
          content: "export interface GeneratedType {}"
        },
        {
          path: path.resolve(outDir, "constants.d.ts"), 
          content: "export declare const GENERATED_CONSTANT: string;"
        }
      ],
      emitSkipped: false
    };
  }
};

// Conditional resolver with diagnostics
const conditionalResolver: Resolver = {
  name: "conditional",
  supports: (id) => id.includes(".conditional."),
  transform: async ({ id, code, host, program }) => {
    const diagnostics: ts.Diagnostic[] = [];
    
    try {
      // Validate file content
      if (!code.includes("export")) {
        diagnostics.push({
          file: program.getSourceFile(id),
          start: 0,
          length: code.length,
          messageText: "File must contain exports",
          category: ts.DiagnosticCategory.Warning,
          code: 9999
        } as ts.Diagnostic);
      }
      
      const declaration = `// Generated from ${path.basename(id)}\nexport {};`;
      
      return {
        outputs: [{
          path: id.replace(/\.(conditional\.\w+)$/, ".d.ts"),
          content: declaration
        }],
        emitSkipped: false,
        diagnostics
      };
    } catch (error) {
      return {
        outputs: [],
        emitSkipped: true,
        diagnostics: [{
          file: undefined,
          start: 0,
          length: 0,
          messageText: `Resolver error: ${error.message}`,
          category: ts.DiagnosticCategory.Error,
          code: 1
        } as ts.Diagnostic]
      };
    }
  }
};

TypeScript Program Integration

Advanced resolvers that leverage the TypeScript compiler's program and host for sophisticated analysis.

// Program-aware resolver
const programAwareResolver: Resolver = {
  name: "program-aware",
  supports: (id) => id.endsWith(".analyzed.ts"),
  transform: async ({ id, code, host, program, root, outDir }) => {
    // Access TypeScript compiler services
    const sourceFile = program.getSourceFile(id);
    if (!sourceFile) {
      return { outputs: [], emitSkipped: true };
    }
    
    // Analyze the source file using TypeScript APIs
    const typeChecker = program.getTypeChecker();
    const exports: string[] = [];
    
    // Walk the AST to find exports
    ts.forEachChild(sourceFile, (node) => {
      if (ts.isExportDeclaration(node) || ts.isFunctionDeclaration(node) && node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
        // Extract export information
        if (ts.isFunctionDeclaration(node) && node.name) {
          const symbol = typeChecker.getSymbolAtLocation(node.name);
          if (symbol) {
            exports.push(`export declare function ${symbol.getName()}(): any;`);
          }
        }
      }
    });
    
    const content = exports.length > 0 
      ? exports.join('\n') 
      : '// No exports found\nexport {};';
    
    return [{
      path: path.resolve(outDir, path.relative(root, id).replace(/\.analyzed\.ts$/, '.d.ts')),
      content
    }];
  }
};

Error Handling and Diagnostics

Best practices for handling errors and providing useful diagnostics in custom resolvers.

const robustResolver: Resolver = {
  name: "robust",
  supports: (id) => id.endsWith(".custom"),
  transform: async ({ id, code }) => {
    try {
      // Attempt transformation
      const result = await processCustomFile(code);
      
      return {
        outputs: [{
          path: id.replace(/\.custom$/, '.d.ts'),
          content: result
        }],
        emitSkipped: false
      };
    } catch (error) {
      // Provide helpful diagnostics on failure
      console.warn(`Custom resolver failed for ${id}: ${error.message}`);
      
      return {
        outputs: [{
          path: id.replace(/\.custom$/, '.d.ts'),
          content: `// Error processing ${path.basename(id)}\n// ${error.message}\nexport {};`
        }],
        emitSkipped: false,
        diagnostics: [{
          file: undefined,
          start: 0,
          length: 0,
          messageText: `Custom resolver error: ${error.message}`,
          category: ts.DiagnosticCategory.Warning,
          code: 9999
        } as ts.Diagnostic]
      };
    }
  }
};

Install with Tessl CLI

npx tessl i tessl/npm-vite-plugin-dts

docs

built-in-resolvers.md

custom-resolvers.md

index.md

plugin-configuration.md

utility-functions.md

tile.json