Загрузка данных
/* eslint-disable @typescript-eslint/no-var-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 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 {
standSwitcher = undefined;
}
const PORT = 3000;
const MAX_CYCLES = 84;
let numCyclesDetected = 0;
function createCircularDependencyPlugin() {
return 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() {
numCyclesDetected = 0;
},
onDetected({ paths, compilation }) {
numCyclesDetected += 1;
compilation.warnings.push(
new Error(paths.join(' -> ')),
);
},
onEnd({ compilation }) {
if (numCyclesDetected <= MAX_CYCLES) {
return;
}
compilation.errors.push(
new Error(
`Detected ${numCyclesDetected} cycles which exceeds configured limit of ${MAX_CYCLES}`,
),
);
},
});
}
function createCopyPatterns({
isDevelopment,
currentStand,
}) {
const patterns = [{ from: 'public' }];
if (isDevelopment) {
patterns.push({
from:
standSwitcher?.getPathForCopyPlugin(
currentStand,
) || 'env',
});
}
return patterns;
}
function createPlugins({
env,
isDevelopment,
currentStand,
}) {
const plugins = [
createCircularDependencyPlugin(),
new HtmlWebpackPlugin({
template: 'src/index.html',
publicPath: env.staticContextPath || 'auto',
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
new CopyWebpackPlugin({
patterns: createCopyPatterns({
isDevelopment,
currentStand,
}),
}),
new HashedModuleIdsPlugin(),
new ForkTSCheckerPlugin({}),
];
if (isDevelopment) {
plugins.push(
new ReactRefreshWebpackPlugin({
overlay: false,
}),
);
}
return plugins;
}
function createMinimizer() {
return [
new TerserPlugin({
extractComments: false,
}),
];
}
function getPackageName(module) {
const match = module.context?.match(
/[\\/]node_modules[\\/](.*?)([\\/]|$)/,
);
return match?.[1] || 'unknown';
}
function createVendorCacheGroup() {
return {
test: /[\\/]node_modules[\\/]/,
name(module) {
// получает имя, то есть node_modules/packageName/not/this/part.js
// или node_modules/packageName
const packageName = getPackageName(module);
// имена npm-пакетов можно, не опасаясь проблем, использовать
// в URL, но некоторые серверы не любят символы наподобие @
return `npm.${packageName.replace('@', '')}`;
},
};
}
function createOptimization(isDevelopment) {
const minimizer = createMinimizer();
if (isDevelopment) {
return {
minimizer,
};
}
return {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
vendor: createVendorCacheGroup(),
},
},
minimizer,
};
}
function createCssLoaderOptions(isDevelopment) {
if (isDevelopment) {
return {
modules: {
auto: true,
localIdentName:
'[local]--[hash:base64:5]',
},
};
}
return {
modules: {
auto: true,
hashStrategy: 'minimal-subset',
localIdentHashDigestLength: 5,
},
};
}
function getStyleLoader(env) {
return env.production
? MiniCssExtractPlugin.loader
: 'style-loader';
}
function createTypeScriptLoader(isDevelopment) {
return {
loader: 'ts-loader',
options: {
transpileOnly: true,
getCustomTransformers: () => ({
before: isDevelopment
? [ReactRefreshTypeScript()]
: [],
}),
},
};
}
function createModuleRules({
env,
isDevelopment,
}) {
const styleLoader = getStyleLoader(env);
return [
{
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: [
createTypeScriptLoader(isDevelopment),
],
},
{
test: /\.s[ac]ss$/i,
use: [
styleLoader,
{
loader: 'css-loader',
options:
createCssLoaderOptions(isDevelopment),
},
{
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: [
styleLoader,
'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',
],
},
];
}
function createResolveConfig() {
return {
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',
),
},
};
}
function createProxy(localDevUrl) {
return {
target: '',
secure: false,
changeOrigin: true,
ws: true,
// Этот заголовок нужен для правильного редиректа обратно на локальный домен после авторизации
headers: {
'X-Dev-Redirect': localDevUrl,
},
};
}
function createDevServer({
currentStand,
localDevUrl,
proxy,
}) {
const getProxyConfig = () =>
standSwitcher?.modifyProxy(
currentStand,
proxy,
) || proxy;
return {
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': getProxyConfig(),
'/ntbagro': getProxyConfig(),
},
};
}
function createWebpackConfig(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 = createProxy(localDevUrl);
return {
entry: './src/index.tsx',
devtool: isDevelopment
? 'source-map'
: false,
output: {
filename: '[name].[contenthash].js',
path: path.resolve(
__dirname,
'dist',
),
},
plugins: createPlugins({
env,
isDevelopment,
currentStand,
}),
resolve: createResolveConfig(),
optimization:
createOptimization(isDevelopment),
module: {
rules: createModuleRules({
env,
isDevelopment,
}),
},
devServer: createDevServer({
currentStand,
localDevUrl,
proxy,
}),
};
}
module.exports = createWebpackConfig;