const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); const FaviconsWebpackPlugin = require('favicons-webpack-plugin'); const sharp = require('sharp'); const webpack = require('webpack'); const HOUSE_IMAGE_WIDTH = 260; module.exports = (env, argv) => { const isProduction = argv.mode === 'production'; return { entry: './src/index.tsx', output: { path: path.resolve(__dirname, 'dist'), filename: isProduction ? '[name].[contenthash:8].js' : 'bundle.js', chunkFilename: isProduction ? '[name].[contenthash:8].js' : '[name].bundle.js', clean: true, publicPath: '/', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.tsx?$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ '@babel/preset-env', ['@babel/preset-react', { runtime: 'automatic' }], '@babel/preset-typescript', ], plugins: isProduction ? [] : ['react-refresh/babel'], }, }, }, { test: /\.css$/, use: [ isProduction ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader', 'postcss-loader', ], }, ], }, plugins: [ new webpack.DefinePlugin({ __DEV__: JSON.stringify(!isProduction), }), new HtmlWebpackPlugin({ template: './src/index.html', }), new CopyWebpackPlugin({ patterns: [ { from: 'public', noErrorOnMissing: true, globOptions: { ignore: ['**/house.png'], }, }, isProduction ? { from: 'public/house.png', to: 'house.png', noErrorOnMissing: true, transform: { transformer(content) { return sharp(content) .resize({ width: HOUSE_IMAGE_WIDTH, withoutEnlargement: true }) .png({ compressionLevel: 9, palette: true, quality: 85 }) .toBuffer(); }, }, } : { from: 'public/house.png', to: 'house.png', noErrorOnMissing: true, }, ], }), new FaviconsWebpackPlugin({ logo: './public/favicon.svg', favicons: { background: '#0c0a09', icons: { favicons: true, android: false, appleIcon: false, appleStartup: false, yandex: false, windows: false, }, }, }), ...(isProduction ? [ new MiniCssExtractPlugin({ filename: '[name].[contenthash:8].css', chunkFilename: '[name].[contenthash:8].css', }), ] : [new ReactRefreshWebpackPlugin()]), ], optimization: isProduction ? { splitChunks: { chunks: 'all', cacheGroups: { maplibre: { test: /[\\/]node_modules[\\/]maplibre-gl[\\/]/, name: 'vendor-maplibre', chunks: 'async', priority: 50, enforce: true, reuseExistingChunk: true, }, h3: { test: /[\\/]node_modules[\\/]h3-js[\\/]/, name: 'vendor-h3', chunks: 'async', priority: 45, enforce: true, reuseExistingChunk: true, }, deckMapbox: { test: /[\\/]node_modules[\\/]@deck\.gl[\\/]mapbox[\\/]/, name: 'vendor-deck-mapbox', chunks: 'async', priority: 44, enforce: true, reuseExistingChunk: true, }, deckCore: { test: /[\\/]node_modules[\\/]@deck\.gl[\\/]core[\\/]/, name: 'vendor-deck-core', chunks: 'async', priority: 43, enforce: true, reuseExistingChunk: true, }, deckLayers: { test: /[\\/]node_modules[\\/]@deck\.gl[\\/](?:layers|geo-layers)[\\/]/, name: 'vendor-deck-layers', chunks: 'async', priority: 42, enforce: true, reuseExistingChunk: true, }, deck: { test: /[\\/]node_modules[\\/]@deck\.gl[\\/]/, name: 'vendor-deck', chunks: 'async', priority: 40, enforce: true, reuseExistingChunk: true, }, luma: { test: /[\\/]node_modules[\\/]@luma\.gl[\\/]/, name: 'vendor-luma', chunks: 'async', priority: 35, enforce: true, reuseExistingChunk: true, }, webgpuSupport: { test: /[\\/]node_modules[\\/]wgsl_reflect[\\/]/, name: 'vendor-webgpu-support', chunks: 'async', priority: 34, enforce: true, reuseExistingChunk: true, }, mapData: { test: /[\\/]node_modules[\\/](?:@mapbox[\\/]tiny-sdf|@protomaps[\\/]basemaps|earcut|supercluster)[\\/]/, name: 'vendor-map-data', chunks: 'async', priority: 33, enforce: true, reuseExistingChunk: true, }, mapSupport: { test: /[\\/]node_modules[\\/](?:@loaders\.gl|@math\.gl|@probe\.gl|@vis\.gl|mjolnir\.js|react-map-gl)[\\/]/, name: 'vendor-map-support', chunks: 'async', priority: 30, enforce: true, reuseExistingChunk: true, }, joyride: { test: /[\\/]node_modules[\\/](?:react-joyride|deepmerge|scroll|scrollparent|react-innertext)[\\/]/, name: 'vendor-joyride', chunks: 'async', priority: 25, enforce: true, reuseExistingChunk: true, }, }, }, } : undefined, devServer: { host: '0.0.0.0', port: 3001, allowedHosts: 'all', client: { webSocketURL: 'auto://0.0.0.0:0/ws', }, historyApiFallback: { index: '/index.html', }, hot: true, liveReload: true, static: { directory: path.resolve(__dirname, 'public'), watch: { ignored: ['**/assets/**'], }, }, proxy: [ { context: ['/api', '/s'], target: process.env.API_PROXY_TARGET || 'http://localhost:8001', }, { context: ['/pb'], target: process.env.PB_PROXY_TARGET || 'http://localhost:8090', pathRewrite: { '^/pb': '' }, }, ], }, }; };