Skip to content
Snippets Groups Projects
webpack.config.js 7.1 KiB
Newer Older
Fabien Amarger's avatar
Fabien Amarger committed
//------------------------------------------------------------------------------
// This app is configured to use typescript.
// See https://webpack.js.org/concepts/ on how to configure this file.
//------------------------------------------------------------------------------

"use-strict";

//------------------------------------------------------------------------------

const path = require("path");
const webpack = require("webpack");

Frank Bessou's avatar
Frank Bessou committed
// Configure envirement (NOTE: This must be done as early as possible)
Fabien Amarger's avatar
Fabien Amarger committed
require("dotenv").config();

//------------------------------------------------------------------------------

const TerserPlugin = require("terser-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const ESLintPlugin = require("eslint-webpack-plugin");

const CheckPortPlugin = require("./plugins/check-port");
const BuildFolderWiper = require("./plugins/build-wiper");

const paths = require("./paths");

//------------------------------------------------------------------------------

const checkRequiredFiles = require("react-dev-utils/checkRequiredFiles");
const eslintFormatter = require("react-dev-utils/eslintFormatter");

//------------------------------------------------------------------------------

const requiredFiles = [paths.index_html, paths.index];

if (!checkRequiredFiles(requiredFiles)) {
  process.exit(1);
}

//------------------------------------------------------------------------------

const mainTsRegex = /\.(ts|tsx)$/;
const mainCssRegex = /\.(css)$/;
const assetsRegex = /\.(jpg|jpeg|png|gif|mp3|mp4|svg)$/;
Fabien Amarger's avatar
Fabien Amarger committed

//------------------------------------------------------------------------------

module.exports = function (_, webpackEnv) {
  const isDevelopment = webpackEnv.mode === "development";
  const isProduction = webpackEnv.mode === "production";
  const isProfile = process.argv.includes("--profile");
  const isProductionProfile = isProduction && isProfile;
  const port = process.env.PORT || 8080;

  const getStyleLoaders = () => [
    isDevelopment ? "style-loader" : MiniCssExtractPlugin.loader,
    {
      loader: "css-loader",
      options: {
        sourceMap: isDevelopment,
      },
    },
  ];

  return {
    target: "web",
    entry: paths.index,
    output: {
      path: paths.build,
      filename: isDevelopment
        ? "static/js/[name].js"
        : isProduction && "static/js/[name].[contenthash:8].js",
      chunkFilename: isDevelopment
        ? "static/js/[name].chunk.js"
        : isProduction && "static/js/[name].[contenthash:8].chunk.js",
      publicPath: "/",
      assetModuleFilename: "assets/[hash][ext]",
    },
    devtool: isDevelopment ? "source-map" : false,
    devServer: {
      client: {
        logging: "none",
        overlay: {
          // Show only errors on the browser
          errors: true,
          warnings: false,
        },
      },
      headers: [
        // Security headers
        {
          key: "Strict-Transport-Security",
          value: "max-age=63072000; preload",
        },
        {
          key: "X-XSS-Protection",
          value: "1; mode=block",
        },
        {
          key: "X-Frame-Options",
          value: "deny",
        },
        {
          key: "X-Content-Type-Options",
          value: "nosniff",
        },
        {
          key: "Referrer-Policy",
          value: "origin-when-cross-origin",
        },
      ],
      historyApiFallback: true, // Nessessary for react router to work
      host: "0.0.0.0", // Allow local network access to the server
      hot: true,
      port,
    },
    module: {
      rules: [
        {
          test: mainTsRegex,
Fabien Amarger's avatar
Fabien Amarger committed
          exclude: /node_modules/,
          use: ["ts-loader"],
        },
        {
          test: mainCssRegex,
Fabien Amarger's avatar
Fabien Amarger committed
          exclude: /node_modules/,
          use: getStyleLoaders(),
        },
        {
          test: assetsRegex,
Fabien Amarger's avatar
Fabien Amarger committed
          exclude: [
            /node_modules/,
            /\.(js|mjs|jsx|ts|tsx)$/,
            /\.html$/,
            /\.json$/,
          ],
          use: [
            {
              loader: "file-loader",
              options: {
                outputPath: "static/media",
                name: "[name].[contenthash:8].[ext]",
              },
            },
          ],
        },
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/i,
          type: "asset/resource",
        },
      ],
    },
    resolve: {
      extensions: ["*", ".js", ".ts", ".tsx"],
      modules: [paths.src, paths.modules],
      fallback: {
        contentBase: paths.build,
        events: false,
        url: false,
      },
    },
    optimization: {
      minimize: isProduction,
      minimizer: [
        new TerserPlugin({
          terserOptions: {
            parse: {
              ecma: 8,
            },
            compress: {
              ecma: 5,
              warnings: false,
              comparisons: false,
              inline: 2,
            },
            mangle: {
              safari10: true,
            },
            keep_classnames: isProductionProfile,
            keep_fnames: isProductionProfile,
            output: {
              ecma: 5,
              comments: false,
              ascii_only: true,
            },
            sourceMap: false,
          },
        }),
      ],
    },
    plugins: [
      new ESLintPlugin({ formatter: eslintFormatter }),
      new HtmlWebpackPlugin({
        template: paths.index_html,
        minify: {
          removeComments: true,
          collapseWhitespace: true,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeEmptyAttributes: true,
          removeStyleLinkTypeAttributes: true,
          keepClosingSlash: true,
          minifyJS: true,
          minifyCSS: true,
          minifyURLs: true,
        },
      }),
      isProduction &&
        new MiniCssExtractPlugin({
          filename: "static/css/[name].[contenthash:8].css",
          chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
        }),
      new WebpackManifestPlugin({
        fileName: "asset-manifest.json",
        publicPath: "/",
        generate: (seed, files, entrypoints) => {
          const manifestFiles = files.reduce((manifest, file) => {
            manifest[file.name] = file.path;
            return manifest;
          }, seed);

          const entrypointFiles = entrypoints.main.filter((fileName) => {
            return !fileName.endsWith(".map");
          });

          return {
            files: manifestFiles,
            entrypoints: entrypointFiles,
          };
        },
      }),
      new CopyWebpackPlugin({
        patterns: [
          {
            from: paths.public,
            to: ".",
          },
        ],
      }),
      isDevelopment && new CheckPortPlugin(port),
      isProduction && new BuildFolderWiper(paths.build),
      new webpack.DefinePlugin({
        "process.env.NODE_ENV": JSON.stringify(webpackEnv.mode),
      }),
      new webpack.EnvironmentPlugin([
        // Pass all public env variables here
      ]),
    ].filter(Boolean),
  };
};