CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-parcel--plugin

Plugin API for Parcel bundler - provides base classes for creating Parcel plugins including transformers, resolvers, bundlers, namers, runtimes, packagers, optimizers, compressors, reporters, and validators

67

1.06x
Overview
Eval results
Files

validation.mddocs/

Code Validation

The Validator plugin validates code for errors, style issues, and other quality concerns during the build process. Validators can run linting, type checking, and other code quality analysis.

Capabilities

Validator Class

Base class for creating code validation plugins.

/**
 * Base class for code validation plugins
 * @template T - Configuration type for this validator
 */
export declare class Validator<T> {
  constructor(opts: ValidatorOpts);
}

/**
 * Validator plugin configuration (union of two validator types)
 */
type ValidatorOpts = DedicatedThreadValidator | MultiThreadValidator;

/**
 * Validator that processes all assets at once in a dedicated thread
 */
interface DedicatedThreadValidator {
  /** Validate all assets together */
  validateAll(args: {
    assets: Asset[];
    resolveConfigWithPath: ResolveConfigWithPathFn;
    options: PluginOptions;
    logger: PluginLogger;
    tracer: PluginTracer;
  }): Promise<Array<ValidateResult | null>>;
}

/**
 * Validator that processes assets individually across multiple threads
 */
interface MultiThreadValidator {
  /** Get configuration for an asset */
  getConfig?(args: {
    asset: Asset;
    resolveConfig: ResolveConfigFn;
    options: PluginOptions;
    logger: PluginLogger;
    tracer: PluginTracer;
  }): Promise<ConfigResult>;

  /** Validate a single asset */
  validate(args: {
    asset: Asset;
    config: ConfigResult | void;
    options: PluginOptions;
    logger: PluginLogger;
    tracer: PluginTracer;
  }): Promise<ValidateResult | void>;
}

Validation Results

/**
 * Result from validating an asset
 */
interface ValidateResult {
  /** Validation diagnostics (errors, warnings) */
  diagnostics: Array<Diagnostic>;
  
  /** Whether validation passed */
  isValid: boolean;
  
  /** Additional metadata */
  meta?: Record<string, any>;
}

/**
 * Configuration loading result
 */
interface ConfigResult {
  /** Configuration object */
  config: any;
  
  /** Files that were read to load this config */
  files: Array<FilePath>;
}

/**
 * Function to resolve configuration with path information
 */
type ResolveConfigWithPathFn = (
  configNames: Array<string>,
  fromPath: FilePath
) => Promise<ConfigResult | null>;

/**
 * Function to resolve configuration
 */
type ResolveConfigFn = (
  configNames: Array<string>
) => Promise<any>;

Single Asset Validator Example:

import { Validator } from "@parcel/plugin";
import eslint from "eslint";

export default new Validator({
  // Get configuration for validation
  async getConfig({asset, resolveConfig}) {
    // Only validate JavaScript files
    if (!asset.filePath.endsWith('.js')) {
      return null;
    }

    // Load ESLint configuration
    const config = await resolveConfig([
      '.eslintrc.js',
      '.eslintrc.json',
      '.eslintrc'
    ]);

    return { config, files: [] };
  },

  // Validate single asset
  async validate({asset, config, logger}) {
    if (!config) {
      return null; // Skip validation if no config
    }

    const code = await asset.getCode();
    const linter = new eslint.ESLint({
      baseConfig: config.config,
      useEslintrc: false
    });

    try {
      const results = await linter.lintText(code, {
        filePath: asset.filePath
      });

      const diagnostics = [];
      
      for (const result of results) {
        for (const message of result.messages) {
          diagnostics.push({
            level: message.severity === 2 ? 'error' : 'warning',
            message: message.message,
            filePath: asset.filePath,
            start: {
              line: message.line,
              column: message.column
            },
            hints: message.fix ? ['Run ESLint with --fix to auto-correct this issue'] : undefined
          });
        }
      }

      return {
        diagnostics,
        isValid: diagnostics.every(d => d.level !== 'error')
      };

    } catch (error) {
      return {
        diagnostics: [{
          level: 'error',
          message: `ESLint validation failed: ${error.message}`,
          filePath: asset.filePath
        }],
        isValid: false
      };
    }
  }
});

Batch Validator Example:

import { Validator } from "@parcel/plugin";
import typescript from "typescript";

export default new Validator({
  // Validate all TypeScript assets together
  async validateAll({assets, resolveConfigWithPath, options, logger}) {
    // Filter TypeScript assets
    const tsAssets = assets.filter(asset => 
      asset.filePath.endsWith('.ts') || asset.filePath.endsWith('.tsx')
    );

    if (tsAssets.length === 0) {
      return [];
    }

    // Load TypeScript configuration
    const configResult = await resolveConfigWithPath([
      'tsconfig.json'
    ], options.projectRoot);

    const tsConfig = configResult?.config || {};

    // Create TypeScript program
    const fileNames = tsAssets.map(asset => asset.filePath);
    const program = typescript.createProgram(fileNames, tsConfig.compilerOptions || {});

    // Get diagnostics
    const diagnostics = typescript.getPreEmitDiagnostics(program);

    // Convert to validation results
    const results = [];
    
    for (const asset of tsAssets) {
      const assetDiagnostics = diagnostics
        .filter(d => d.file?.fileName === asset.filePath)
        .map(d => this.convertTSDiagnostic(d));

      results.push({
        diagnostics: assetDiagnostics,
        isValid: assetDiagnostics.every(d => d.level !== 'error')
      });
    }

    return results;
  },

  convertTSDiagnostic(diagnostic) {
    const message = typescript.flattenDiagnosticMessageText(
      diagnostic.messageText, 
      '\n'
    );

    let start = undefined;
    if (diagnostic.file && diagnostic.start != null) {
      const pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
      start = {
        line: pos.line + 1,
        column: pos.character
      };
    }

    return {
      level: diagnostic.category === typescript.DiagnosticCategory.Error ? 'error' : 'warning',
      message,
      filePath: diagnostic.file?.fileName,
      start,
      documentationURL: `https://typescript.tv/errors/#TS${diagnostic.code}`
    };
  }
});

Common Validation Patterns

File Type Filtering:

// Only validate specific file types
if (!asset.filePath.match(/\.(js|ts|jsx|tsx)$/)) {
  return null;
}

Configuration Loading:

// Load validation configuration
const config = await resolveConfig([
  '.eslintrc.js',
  '.eslintrc.json',
  'package.json' // Look for eslintConfig key
]);

Error Aggregation:

// Collect errors from multiple sources
const allDiagnostics = [
  ...syntaxErrors,
  ...lintingErrors,
  ...typeErrors
];

return {
  diagnostics: allDiagnostics,
  isValid: allDiagnostics.every(d => d.level !== 'error')
};

Install with Tessl CLI

npx tessl i tessl/npm-parcel--plugin

docs

bundling.md

compression.md

index.md

naming.md

optimization.md

packaging.md

reporting.md

resolution.md

runtime.md

transformation.md

validation.md

tile.json