//@ts-check 'use strict'; const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const PnpWebpackPlugin = require(`pnp-webpack-plugin`); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const ESLintWebpackPlugin = require('eslint-webpack-plugin'); const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); const { WebpackPlugin } = require('../webpack.plugin'); require('dotenv').config(); /** * @template T * @param arr {(T | false | undefined)[]} * @returns {T[]} */ // @ts-ignore const truthyArray = arr => arr.filter(Boolean); /** * @param options {{ mode?: 'development' | 'production'; watch?: boolean; serve?: boolean; env: object }} */ module.exports = (env, options) => { options = { mode: 'development', ...env.WEBPACK_SERVE && { serve: true }, ...options, }; console.log('options:', options); const isEnvDevelopment = options.mode === 'development'; const isEnvProduction = options.mode === 'production'; process.env.NODE_ENV = options.env.NODE_ENV = options.mode; // In serve mode, we serve inside VS Code through localhost:3000 const publicPath = options.serve ? 'http://localhost:3000/' : '/'; /** @type {webpack.Configuration & { devServer: any }} */ const config = { mode: options.mode, target: 'web', bail: isEnvProduction, devtool: 'source-map', entry: './src/index.tsx', output: { path: isEnvProduction ? path.resolve('./build') : undefined, pathinfo: isEnvDevelopment, filename: 'static/js/[name].bundle.js', chunkFilename: 'static/js/[name].chunk.js', publicPath, devtoolModuleFilenameTemplate(info) { if (isEnvProduction) return path.relative('./src', info.absoluteResourcePath).replace(/\\/g, '/'); return path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'); }, clean: true, }, optimization: { minimize: isEnvProduction, minimizer: [new CssMinimizerPlugin(), '...'], splitChunks: { chunks: 'all', name: isEnvDevelopment ? undefined : false }, runtimeChunk: { name: entrypoint => `runtime-${entrypoint.name}` }, }, resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], plugins: [PnpWebpackPlugin] }, resolveLoader: { plugins: [ PnpWebpackPlugin.moduleLoader(module), ], }, module: { rules: [ { parser: { requireEnsure: false, strictExportPresence: true, } }, { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], loader: require.resolve('url-loader'), options: { limit: 10000, name: 'static/media/[name].[hash:8].[ext]', }, }, { test: /\.(mjs|jsx?)$/, include: path.resolve('src'), loader: require.resolve('babel-loader'), options: { presets: [ ['@babel/preset-react', { runtime: 'automatic' }], ], cacheDirectory: true, cacheCompression: false, compact: isEnvProduction, }, }, { test: /\.(tsx?)$/, use: [ { loader: require.resolve('babel-loader'), options: { presets: [ ['@babel/preset-react', { runtime: 'automatic' }], ], cacheDirectory: true, cacheCompression: false, compact: isEnvProduction, plugins: [ options.serve && require.resolve('react-refresh/babel'), ].filter(Boolean), }, }, { loader: 'ts-loader' }, ], }, { test: /\.css$/, use: ['style-loader', 'css-loader'], sideEffects: true, }, ], }, plugins: truthyArray([ new HtmlWebpackPlugin({ inject: true, template: 'public/index.html', publicPath }), options.serve && new webpack.HotModuleReplacementPlugin(), options.serve && new ReactRefreshWebpackPlugin(), new webpack.DefinePlugin(options.env), new WebpackPlugin(), isEnvProduction && new MiniCssExtractPlugin({ filename: 'static/css/[name].[contenthash:8].css', chunkFilename: 'static/css/[name].[contenthash:8].chunk.css', }), // @ts-ignore new ESLintWebpackPlugin({ extensions: ['js', 'mjs', 'jsx', 'ts', 'tsx'], eslintPath: require.resolve('eslint'), failOnError: !isEnvDevelopment, context: path.resolve('src'), cache: true, cacheLocation: '.eslintcache', cwd: __dirname, resolvePluginsRelativeTo: __dirname, baseConfig: { extends: [require.resolve('eslint-config-react-app/base')], rules: { 'react/react-in-jsx-scope': 'error', }, }, }), ]), node: false, performance: false, devServer: { hot: 'only', open: false, host: 'localhost', port: 3000, allowedHosts: 'all', headers: { 'Access-Control-Allow-Origin': '*', }, }, stats: { ids: true, assets: false, chunks: false, entrypoints: true, modules: true, groupModulesByPath: true, modulesSpace: 50, }, }; return config; };