Типизация конфигурации через JSDoc и TypeScript

Базовая типизация через JSDoc

Конфигурационный файл Webpack традиционно представляет собой JavaScript-модуль, экспортирующий объект или функцию. Для добавления статической типизации без перехода на TypeScript применяется JSDoc-аннотация типов, использующая декларации из пакета Webpack.

Основной механизм основан на @type и импорте типов из webpack через import():

// webpack.config.js

/** @type {import('webpack').Configuration} */
const config = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: __dirname + '/dist',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader',
      },
    ],
  },
};

module.exports = config;

Использование import('webpack').Configuration позволяет редактору и TypeScript Language Service анализировать структуру объекта конфигурации без преобразования файла в .ts.

Ключевой эффект такого подхода заключается в появлении автодополнения и статической проверки корректности полей конфигурации, включая mode, entry, output, module, plugins, resolve.


Расширенная JSDoc-типизация с функциями-конфигурациями

Webpack поддерживает экспорт функции, возвращающей конфигурацию. JSDoc сохраняет типизацию и в этом случае:

/** @type {import('webpack').Configuration} */
const baseConfig = {
  entry: './src/index.js',
  output: {
    filename: 'app.js',
    path: __dirname + '/dist',
  },
};

/**
 * @param {import('webpack').Configuration} env
 * @param {import('webpack').Configuration} argv
 * @returns {import('webpack').Configuration}
 */
module.exports = (env, argv) => {
  return {
    ...baseConfig,
    mode: argv.mode || 'production',
  };
};

В реальной модели Webpack параметры функции чаще типизируются через отдельные интерфейсы окружения и аргументов CLI, однако базовая типизация через Configuration обеспечивает структурную совместимость.


Включение проверки через @ts-check

JSDoc становится значительно более строгим при активации проверки типов в JavaScript-файлах:

// @ts-check

/** @type {import('webpack').Configuration} */
const config = {
  mode: 'production',
  entry: './src/index.js',
};

module.exports = config;

Директива // @ts-check включает полноценную проверку файла как TypeScript-контекста, но без компиляции в .ts. Это позволяет выявлять:

  • неправильные поля конфигурации
  • несовместимые типы значений
  • ошибки в структуре module.rules
  • некорректные значения devtool, target, stats

Типизация отдельных частей конфигурации

module.rules

Локальная типизация часто применяется для сложных правил загрузчиков:

/** @type {import('webpack').RuleSetRule} */
const jsRule = {
  test: /\.js$/,
  exclude: /node_modules/,
  use: {
    loader: 'babel-loader',
    options: {
      presets: ['@babel/preset-env'],
    },
  },
};

/** @type {import('webpack').Configuration} */
module.exports = {
  module: {
    rules: [jsRule],
  },
};

Тип RuleSetRule обеспечивает проверку структуры загрузчиков, включая вложенные конфигурации use, oneOf, loader, options.


output и path

Типизация output помогает избежать частых ошибок с путями:

const path = require('path');

/** @type {import('webpack').Configuration} */
module.exports = {
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
};

Поле clean и другие современные свойства становятся доступными только при актуальной версии типов webpack.


Переход на TypeScript-конфигурацию

Webpack напрямую поддерживает конфигурацию на TypeScript при использовании Node-совместимого выполнения.

webpack.config.ts

import type { Configuration } from 'webpack';
import path from 'path';

const config: Configuration = {
  mode: 'development',
  entry: './src/index.ts',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

export default config;

Тип Configuration обеспечивает строгую проверку всей структуры конфигурации на этапе компиляции TypeScript.


Конфигурация через функцию в TypeScript

Webpack поддерживает экспорт функции с типизированными аргументами:

import type { Configuration } from 'webpack';

type Env = {
  production?: boolean;
};

type Argv = {
  mode?: 'development' | 'production';
};

export default (env: Env, argv: Argv): Configuration => {
  return {
    mode: argv.mode ?? 'production',
    entry: './src/index.ts',
    output: {
      filename: 'bundle.js',
      path: __dirname + '/dist',
    },
  };
};

Такой подход позволяет разделять окружения сборки, сохраняя строгую типизацию входных параметров.


Типизация плагинов

Webpack-плагины также поддерживают типизацию через webpack.Plugin и специализированные интерфейсы:

import type { Configuration } from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';

const config: Configuration = {
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
  ],
};

export default config;

TypeScript проверяет корректность конструкторов и параметров плагинов при наличии типов у конкретного пакета.


Resolve и alias с типами

Раздел resolve часто содержит ошибки из-за сложных структур путей и алиасов.

import type { Configuration } from 'webpack';
import path from 'path';

const config: Configuration = {
  resolve: {
    extensions: ['.ts', '.js'],
    alias: {
      '@src': path.resolve(__dirname, 'src'),
      '@utils': path.resolve(__dirname, 'src/utils'),
    },
  },
};

export default config;

Типизация предотвращает передачу некорректных значений в extensions и alias, обеспечивая соответствие строковым массивам и словарным структурам.


DevServer и типизация

Конфигурация devServer в Webpack требует отдельного типа из webpack-dev-server:

import type { Configuration } from 'webpack';
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';

interface ExtendedConfiguration extends Configuration {
  devServer?: DevServerConfiguration;
}

const config: ExtendedConfiguration = {
  devServer: {
    port: 3000,
    hot: true,
    open: true,
  },
};

export default config;

Разделение типов необходимо из-за вынесенного пакета сервера разработки.


Гибридный подход: JSDoc + TypeScript модули

В проектах с частичной миграцией применяется смешанная схема: часть конфигурации остаётся в .js, часть в .ts.

// webpack.config.js

/** @type {import('webpack').Configuration} */
const config = {
  entry: './src/index.js',
};

module.exports = config;

И отдельные типизированные модули могут экспортировать вспомогательные части:

// webpack.parts.ts
import type { RuleSetRule } from 'webpack';

export const tsRule: RuleSetRule = {
  test: /\.ts$/,
  use: 'ts-loader',
};

Типизация через декларации окружения

Для более строгого контроля применяется tsconfig.json с включением проверки JS:

{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "types": ["webpack"]
  }
}

Дополнительно подключаются типы:

  • @types/webpack
  • @types/node
  • webpack-dev-server

Это формирует единое типовое пространство для конфигурации.


Частые источники потери типизации

Типизация конфигурации Webpack часто деградирует при следующих условиях:

  • использование any в кастомных функциях загрузчиков
  • динамическое формирование объектов module.rules
  • отсутствие @ts-check в JS-конфигурациях
  • смешивание CommonJS и ESM без корректных деклараций типов
  • использование сторонних плагинов без типовых деклараций

Особенно часто теряется типизация в use: [], где массив загрузчиков не описан явно и превращается в неструктурированную конфигурацию.


Структурная строгость конфигурации

Типы Webpack формируют сложную иерархию:

  • Configuration
  • ModuleOptions
  • RuleSetRule
  • ResolveOptions
  • Optimization
  • OutputOptions

Каждый уровень вложенности проверяется отдельно, что позволяет выявлять ошибки не только синтаксические, но и семантические, например:

  • несовместимость mode и devtool
  • некорректные комбинации optimization.splitChunks
  • ошибки в assetModuleFilename

Роль типизации в масштабируемых конфигурациях

При росте конфигурации Webpack до десятков плагинов и правил типизация становится механизмом контроля структуры сборки. JSDoc используется как минимальный уровень проверки, TypeScript — как строгая модель описания всей сборочной системы.

Статическая типизация позволяет фиксировать контракт между частями конфигурации, разделёнными по модулям, и предотвращает расхождения между средами разработки и production-сборкой.