Skip to content
Snippets Groups Projects
webpack.config.js 6.2 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 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 rootDir = path.resolve(__dirname, "../");

const nodeModulesDir = path.join(rootDir, "node_modules");
const publicDir = path.join(rootDir, "src/assets/public");
const sourcesDir = path.join(rootDir, "src");
const buildDir = path.join(rootDir, "build");
const indexHtmlPath = path.join(rootDir, "index.html");
const entryPointFile = path.join(rootDir, "src/index.tsx");
Fabien Amarger's avatar
Fabien Amarger committed

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

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

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

const requiredFiles = [indexHtmlPath, entryPointFile];
Fabien Amarger's avatar
Fabien Amarger committed

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 (_, argv) {
  const isDevelopment = argv.mode === "development";
  const isProduction = argv.mode === "production";
Fabien Amarger's avatar
Fabien Amarger committed
  const port = process.env.PORT || 8080;

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

  return {
    target: "web",
Fabien Amarger's avatar
Fabien Amarger committed
    output: {
Fabien Amarger's avatar
Fabien Amarger committed
      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: [sourcesDir, nodeModulesDir],
Fabien Amarger's avatar
Fabien Amarger committed
      fallback: {
Fabien Amarger's avatar
Fabien Amarger committed
        events: false,
        url: false,
      },
    },
    plugins: [
      new ESLintPlugin({ formatter: eslintFormatter }),
      new HtmlWebpackPlugin({
Fabien Amarger's avatar
Fabien Amarger committed
      }),
      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: [
          {
Fabien Amarger's avatar
Fabien Amarger committed
            to: ".",
          },
        ],
      }),
      isDevelopment && new CheckPortPlugin(port),
      isProduction && new BuildFolderWiper(buildDir),
Fabien Amarger's avatar
Fabien Amarger committed
      new webpack.DefinePlugin({
        "process.env.NODE_ENV": JSON.stringify(argv.mode),
Fabien Amarger's avatar
Fabien Amarger committed
      }),
      new webpack.EnvironmentPlugin([
        // Pass all public env variables here
      ]),
    ].filter(Boolean),
  };
};