const fs = require('fs');
const webpack = require('webpack');
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const WebpackAssetsManifest = require('webpack-assets-manifest');
const TerserPlugin = require("terser-webpack-plugin");
// Куда собираем, old или modern
const BROWSER = process.env.BROWSERSLIST_ENV;
const IS_OLD = BROWSER === 'old';
const IS_MODERN = BROWSER === 'modern';
// Тип сборки, dev или prod
const MODE = process.env.NODE_ENV;
const IS_DEV = MODE === 'development' || MODE === 'watch';
const IS_PROD = MODE === 'production';
const IS_WATCH = MODE === 'watch';
// const makeSourceMap = true
// const makeSourceMap = true
const MAKE_SOURCE_MAP = process.env.S_MAPS;
const ANALYZE = process.env.IS_ANALYZE;
// файлы ложатся в корень проекта
const OUTPUT_PATH = IS_WATCH ? './dist' : '../';
const FILENAME = `js/${BROWSER}/${IS_DEV ? '[name]__' : ''}[contenthash].js`;
const getFullPath = filepath => path.resolve(__dirname, filepath);
function getEntryPoints() {
let res = {
default: [getFullPath('./src/js/app.js')]
};
const styles = getFiles('./src/sass/pages/promo', /\.s(c|a)ss$/);
const scripts_folders = getFolders('./src/js/pages');
styles.map((style) => {
const [name, ext] = style.split('.');
res[name] = [
getFullPath('./src/js/app.js'),
getFullPath(`./src/sass/pages/promo/${style}`)
];
});
scripts_folders.map((folder_name) => {
res[folder_name] = [
getFullPath(`./src/js/pages/${folder_name}/${folder_name}.js`)
];
});
return res;
}
function getFolders(dir) {
return fs.readdirSync(path.resolve(__dirname, dir))
.filter(item => !item.startsWith('.'))
}
function getFiles(dir, ext) {
return fs.readdirSync(
path.resolve(__dirname, dir)
).filter(
item => ext.test(item)
);
}
function getPlugins() {
const cssFilename = `./css/${IS_DEV ? '[name]__' : ''}[contenthash].css`;
const plugins = [
new MiniCssExtractPlugin({
filename: cssFilename,
chunkFilename: cssFilename,
}),
new webpack.ProvidePlugin({
'$': 'cash-dom',
}),
new webpack.DefinePlugin({
"isDev": JSON.stringify(IS_DEV),
"isProd": JSON.stringify(IS_PROD),
"isOld": JSON.stringify(IS_OLD),
"isModern": JSON.stringify(IS_MODERN),
})
];
if (ANALYZE) {
plugins.push(new BundleAnalyzerPlugin());
}
if (!IS_WATCH) {
plugins.push(
new WebpackAssetsManifest({
entrypoints: true,
publicPath: true,
output: BROWSER + '--manifest.json',
entrypointsKey: 'root',
transform(assets, manifest) {
return assets['root']
}
})
)
} else {
const templateFiles = fs.readdirSync(path.resolve(__dirname, './src/html/pages'))
templateFiles.filter(file => file.split('.')[1] === 'html').map(html => {
const [name, extension] = html.split('.');
const item = new HtmlWebpackPlugin({
filename: IS_WATCH ? `${name}.html` : `html/${name}.html`,
title: name,
chunks: [name],
entry: `./src/js/pages/${name}.js`,
template: path.resolve(__dirname, `./src/html/pages/${name}.${extension}`)
});
plugins.push(item);
});
}
return plugins;
}
function getOptimization() {
const optimization = {
splitChunks: {
chunks: 'async',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
};
if (IS_PROD) {
optimization.minimize = true;
optimization.minimizer = [new TerserPlugin({
parallel: true,
extractComments: false,
terserOptions: {
output: {
comments: false
},
compress: {
drop_console: true,
},
}
})];
}
return optimization;
}
function getCacheLoader(isJS = false) {
const loader = {
loader: 'cache-loader',
options: {}
}
// эти манипуляции с путями до кеша нужны так как без этого он не различает __TARGET__
let path = 'node_modules/.cache/cache-loader/'
if (isJS) {
path += IS_OLD ? 'oldjs' : 'modernjs'
}
loader.options.cacheDirectory = getFullPath(path)
return loader
}
function getBabelSettings() {
const settings = {};
if (IS_OLD) {
settings.useBuiltIns = "usage"
settings.corejs = 3
}
return settings
}
if (!IS_WATCH && IS_MODERN) {
if (fs.existsSync('../js')) {
fs.promises.rmdir('../js', { recursive: true })
}
if (fs.existsSync('../css')) {
fs.promises.rmdir('../css', { recursive: true })
}
}
const config = {
mode: MODE,
node: {
global: false,
},
entry: getEntryPoints(),
plugins: getPlugins(),
optimization: getOptimization(),
output: {
filename: FILENAME,
chunkFilename: FILENAME,
path: getFullPath(OUTPUT_PATH),
publicPath: '/',
},
resolve: {
extensions: ['.js', '.sass', '.scss'],
alias: {
'@': getFullPath('src'),
'@js': getFullPath('src/js'),
'@css': getFullPath('src/sass'),
'@css-pages': getFullPath('src/sass/pages'),
'@libs': getFullPath('src/js/libs'),
'@parts': getFullPath('src/js/parts/'),
'@other': getFullPath('src/js/other'),
'@pages': getFullPath('src/js/pages'),
'@components': getFullPath('src/js/components'),
}
},
module: {
rules: [
{
test: /\.s(c|a)ss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
},
{
test: /\.js$/,
exclude: /node_modules/,
use: [
// getCacheLoader(true),
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
getBabelSettings()
]
],
plugins: [
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-proposal-class-properties',
]
}
},
...IS_DEV ? ['eslint-loader'] : [],
]
},
{
test: /\.(woff|ttf|otf|eot|woff2|svg)$/i,
type: 'asset/resource',
}
]
}
};
if (IS_WATCH) {
config.watchOptions = {
ignored: /node_modules/
}
config.devServer = {
port: 7777,
writeToDisk: true,
watchContentBase: true,
contentBase: '../'
}
}
module.exports = config;