Загрузка данных


/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable @typescript-eslint/no--requires */

const path = require('path');

require('dotenv').config({ path: '.env.local' });
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { ESBuildPlugin } = require('esbuild-loader');
const ForkTSCheckerPlugin = require('fork-ts-checker-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ReactRefreshTypeScript = require('react-refresh-typescript');
const TerserPlugin = require('terser-webpack-plugin');
const { ids } = require('webpack');

const { HashedModuleIdsPlugin } = ids;

let standSwitcher;

try {
  standSwitcher = require('./dev/stands/index.js');
} catch {}

const PORT = 3000;

const MAX_CYCLES = 84;
let numCyclesDetected = 0;

module.exports = (env) => {
  const isDevelopment = env.type === 'start';
  const currentStand = process.env.DEV_STAND || env.stand || standSwitcher?.getDefaultStand();

  const localDevUrl = `${standSwitcher?.getLocalDevUrl(currentStand) || 'localhost'}:${PORT}`;

  const proxy = {
    target: '',
    secure: false,
    changeOrigin: true,
    ws: true,
    // Этот заголовок нужен для правильного редиректа обратно на локальный домен после авторизации
    headers: {
      'X-Dev-Redirect': localDevUrl,
    },
  };

  return {
    entry: './src/index.tsx',
    devtool: isDevelopment ? 'source-map' : false,
    output: {
      filename: '[name].[contenthash].js',
      path: path.resolve(__dirname, 'dist'),
    },
    plugins: [
      new CircularDependencyPlugin({
        // exclude detection of files based on a RegExp
        exclude: /a\.js|node_modules/,
        // include specific files based on a RegExp
        // include: /dir/,
        // add errors to webpack instead of warnings
        // failOnError: true,
        // allow import cycles that include an asyncronous import,
        // e.g. via import(/* webpackMode: "weak" */ './file.js')
        allowAsyncCycles: false,
        // set the current working directory for displaying module paths
        cwd: process.cwd(),
        onStart({ compilation }) {
          numCyclesDetected = 0;
        },
        onDetected({ module: webpackModuleRecord, paths, compilation }) {
          numCyclesDetected += 1;
          compilation.warnings.push(new Error(paths.join(' -> ')));
        },
        onEnd({ compilation }) {
          if (numCyclesDetected > MAX_CYCLES) {
            compilation.errors.push(
              new Error(`Detected ${numCyclesDetected} cycles which exceeds configured limit of ${MAX_CYCLES}`),
            );
          }
        },
      }),
      new HtmlWebpackPlugin({
        template: 'src/index.html',
        publicPath: env.staticContextPath || 'auto',
      }),
      new MiniCssExtractPlugin({
        filename: '[name].[contenthash].css',
      }),
      new CopyWebpackPlugin({
        patterns: [
          { from: 'public' },
          isDevelopment && { from: standSwitcher?.getPathForCopyPlugin(currentStand) || 'env' },
        ].filter(Boolean),
      }),
      isDevelopment && new ReactRefreshWebpackPlugin({ overlay: false }),
      new HashedModuleIdsPlugin(),
      new ForkTSCheckerPlugin({}),
    ].filter(Boolean),
    resolve: {
      extensions: ['.ts', '.tsx', '.js'],
      extensionAlias: {
        '.js': ['.js', '.ts'],
        '.cjs': ['.cjs', '.cts'],
        '.mjs': ['.mjs', '.mts'],
      },
      alias: {
        '@modules': path.resolve(__dirname, 'src/modules'),
        '@widgets': path.resolve(__dirname, 'src/widgets'),
        '@core': path.resolve(__dirname, 'src/core'),
        '@uikit': path.resolve(__dirname, 'src/uikit'),
        '@pages': path.resolve(__dirname, 'src/pages'),
        '@components': path.resolve(__dirname, 'src/components'),
        '@styles': path.resolve(__dirname, 'src/styles'),
        '@store': path.resolve(__dirname, 'src/store'),
        types: path.resolve(__dirname, 'src/types'),
        '@configs': path.resolve(__dirname, 'src/configs'),
        '@hooks': path.resolve(__dirname, 'src/hooks'),
        '@api': path.resolve(__dirname, 'src/api'),
        '@utils': path.resolve(__dirname, 'src/utils'),
        '@localisation': path.resolve(__dirname, 'src/localisation'),
        '@features': path.resolve(__dirname, 'src/features'),
        '@terminal': path.resolve(__dirname, 'src/terminal'),
        '@testing': path.resolve(__dirname, 'src/testing'),
      },
    },
    optimization: isDevelopment
      ? {
          minimizer: [
            new TerserPlugin({
              extractComments: false,
            }),
          ],
        }
      : {
          runtimeChunk: 'single',
          splitChunks: {
            chunks: 'all',
            maxInitialRequests: Infinity,
            minSize: 0,
            cacheGroups: {
              vendor: {
                test: /[\\/]node_modules[\\/]/,
                name(module) {
                  // получает имя, то есть node_modules/packageName/not/this/part.js
                  // или node_modules/packageName
                  const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];

                  // имена npm-пакетов можно, не опасаясь проблем, использовать
                  // в URL, но некоторые серверы не любят символы наподобие @
                  return `npm.${packageName.replace('@', '')}`;
                },
              },
            },
          },
          minimizer: [
            new TerserPlugin({
              extractComments: false,
            }),
          ],
        },
    module: {
      rules: [
        {
          test: /\.d\.ts$/,
          use: 'null-loader',
        },
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/i,
          type: 'asset/resource',
          generator: {
            filename: 'static/fonts/[name][ext]',
          },
        },
        {
          test: /\.[jt]sx?$/,
          exclude: /(node_modules)/,
          use: [
            {
              loader: 'ts-loader',
              options: {
                transpileOnly: true,
                getCustomTransformers: () => ({
                  before: [isDevelopment && ReactRefreshTypeScript()].filter(Boolean),
                }),
              },
            },
          ],
        },
        {
          test: /\.s[ac]ss$/i,
          use: [
            env.production ? MiniCssExtractPlugin.loader : 'style-loader',
            {
              loader: 'css-loader',
              options: isDevelopment
                ? {
                    modules: {
                      auto: true,
                      localIdentName: '[local]--[hash:base64:5]',
                    },
                  }
                : {
                    modules: {
                      auto: true,
                      hashStrategy: 'minimal-subset',
                      localIdentHashDigestLength: 5,
                    },
                  },
            },
            {
              loader: 'sass-loader',
              options: {
                sassOptions: {
                  includePaths: ['./src/styles'],
                },
              },
            },
          ],
        },
        {
          test: /\.(png|jpe?g|gif|mp(3|4))$/i,
          use: [
            {
              loader: 'file-loader',
            },
          ],
        },
        {
          test: /\.css$/i,
          use: [env.production ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader'],
        },
        {
          test: /\.mdx$/,
          use: [
            {
              loader: 'babel-loader',
              options: {
                presets: ['@babel/preset-react'],
              },
            },
            {
              loader: '@mdx-js/loader',
            },
          ],
        },
        {
          test: /\.svg$/,
          issuer: /\.[jt]sx?$/,
          use: ['@svgr/webpack', 'url-loader'],
        },
      ],
    },
    devServer: {
      server: 'https',
      allowedHosts: 'all',
      static: {
        directory: path.join(__dirname, 'public'),
      },
      compress: true,
      port: PORT,
      client: {
        overlay: false,
        progress: true,
      },
      historyApiFallback: true,
      open: {
        target: localDevUrl,
      },
      proxy: {
        '/session': standSwitcher?.modifyProxy(currentStand, proxy) || proxy,
        '/ntbagro': standSwitcher?.modifyProxy(currentStand, proxy) || proxy,
      },
    },
  };
};