From 0ce15ed8fe998a8e09836796fbfa5ad6a8161fdf Mon Sep 17 00:00:00 2001 From: Martin Man Date: Fri, 4 Oct 2024 17:22:32 +0200 Subject: [PATCH] WIP on migration towards eslint 9 --- .eslintrc.js | 15 - babel.config.json | 8 + config/env.js | 68 +- config/getHttpsConfig.js | 58 +- config/jest/babelTransform.js | 22 +- config/jest/cssTransform.js | 8 +- config/jest/fileTransform.js | 18 +- config/modules.js | 84 +- config/paths.js | 90 +- config/webpack.config.js | 623 +++--- .../persistentCache/createEnvironmentHash.js | 9 + config/webpackDevServer.config.js | 224 +- eslint.config.js | 41 + package-lock.json | 1927 +++++++++++------ package.json | 41 +- scripts/build.js | 206 +- scripts/start.js | 177 +- scripts/test.js | 47 +- 18 files changed, 2205 insertions(+), 1461 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 babel.config.json create mode 100644 config/webpack/persistentCache/createEnvironmentHash.js create mode 100644 eslint.config.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 100063250..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - extends: ["react-app", "react-app/jest", "plugin:prettier/recommended"], - rules: { - "no-console": "off", - "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", - "prettier/prettier": [ - "warn", - { - printWidth: 120, - semi: false, - singleQuote: false, - }, - ], - }, -} diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 000000000..a6811a9af --- /dev/null +++ b/babel.config.json @@ -0,0 +1,8 @@ +{ + "presets": [ + ["@babel/preset-react"], ["@babel/preset-env"] + ], + "plugins": [ + ["@babel/plugin-transform-react-jsx", { "runtime": "automatic" }] + ] +} diff --git a/config/env.js b/config/env.js index fae806a74..ffa7e496a 100644 --- a/config/env.js +++ b/config/env.js @@ -1,13 +1,17 @@ -const fs = require("fs") -const path = require("path") -const paths = require("./paths") +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const paths = require('./paths'); // Make sure that including paths.js after env.js will read .env variables. -delete require.cache[require.resolve("./paths")] +delete require.cache[require.resolve('./paths')]; -const NODE_ENV = process.env.NODE_ENV +const NODE_ENV = process.env.NODE_ENV; if (!NODE_ENV) { - throw new Error("The NODE_ENV environment variable is required but was not specified.") + throw new Error( + 'The NODE_ENV environment variable is required but was not specified.' + ); } // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use @@ -16,25 +20,25 @@ const dotenvFiles = [ // Don't include `.env.local` for `test` environment // since normally you expect tests to produce the same // results for everyone - NODE_ENV !== "test" && `${paths.dotenv}.local`, + NODE_ENV !== 'test' && `${paths.dotenv}.local`, `${paths.dotenv}.${NODE_ENV}`, paths.dotenv, -].filter(Boolean) +].filter(Boolean); // Load environment variables from .env* files. Suppress warnings using silent // if this file is missing. dotenv will never modify any environment variables // that have already been set. Variable expansion is supported in .env files. // https://github.com/motdotla/dotenv // https://github.com/motdotla/dotenv-expand -dotenvFiles.forEach((dotenvFile) => { +dotenvFiles.forEach(dotenvFile => { if (fs.existsSync(dotenvFile)) { - require("dotenv-expand")( - require("dotenv").config({ + require('dotenv-expand')( + require('dotenv').config({ path: dotenvFile, }) - ) + ); } -}) +}); // We support resolving modules according to `NODE_PATH`. // This lets you use absolute paths in imports inside large monorepos: @@ -45,29 +49,29 @@ dotenvFiles.forEach((dotenvFile) => { // Otherwise, we risk importing Node.js core modules into an app instead of webpack shims. // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 // We also resolve them to make sure all tools using them work consistently. -const appDirectory = fs.realpathSync(process.cwd()) -process.env.NODE_PATH = (process.env.NODE_PATH || "") +const appDirectory = fs.realpathSync(process.cwd()); +process.env.NODE_PATH = (process.env.NODE_PATH || '') .split(path.delimiter) - .filter((folder) => folder && !path.isAbsolute(folder)) - .map((folder) => path.resolve(appDirectory, folder)) - .join(path.delimiter) + .filter(folder => folder && !path.isAbsolute(folder)) + .map(folder => path.resolve(appDirectory, folder)) + .join(path.delimiter); // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be // injected into the application via DefinePlugin in webpack configuration. -const REACT_APP = /^REACT_APP_/i +const REACT_APP = /^REACT_APP_/i; function getClientEnvironment(publicUrl) { const raw = Object.keys(process.env) - .filter((key) => REACT_APP.test(key)) + .filter(key => REACT_APP.test(key)) .reduce( (env, key) => { - env[key] = process.env[key] - return env + env[key] = process.env[key]; + return env; }, { // Useful for determining whether we’re running in production mode. // Most importantly, it switches React into the correct mode. - NODE_ENV: process.env.NODE_ENV || "development", + NODE_ENV: process.env.NODE_ENV || 'development', // Useful for resolving the correct path to static assets in `public`. // For example, . // This should only be used as an escape hatch. Normally you would put @@ -82,21 +86,19 @@ function getClientEnvironment(publicUrl) { WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH, WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT, // Whether or not react-refresh is enabled. - // react-refresh is not 100% stable at this time, - // which is why it's disabled by default. // It is defined here so it is available in the webpackHotDevClient. - FAST_REFRESH: process.env.FAST_REFRESH !== "false", + FAST_REFRESH: process.env.FAST_REFRESH !== 'false', } - ) + ); // Stringify all values so we can feed into webpack DefinePlugin const stringified = { - "process.env": Object.keys(raw).reduce((env, key) => { - env[key] = JSON.stringify(raw[key]) - return env + 'process.env': Object.keys(raw).reduce((env, key) => { + env[key] = JSON.stringify(raw[key]); + return env; }, {}), - } + }; - return { raw, stringified } + return { raw, stringified }; } -module.exports = getClientEnvironment +module.exports = getClientEnvironment; diff --git a/config/getHttpsConfig.js b/config/getHttpsConfig.js index 7efa29a82..013d493c1 100644 --- a/config/getHttpsConfig.js +++ b/config/getHttpsConfig.js @@ -1,25 +1,33 @@ -const fs = require("fs") -const path = require("path") -const crypto = require("crypto") -const chalk = require("react-dev-utils/chalk") -const paths = require("./paths") +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); +const chalk = require('react-dev-utils/chalk'); +const paths = require('./paths'); // Ensure the certificate and key provided are valid and if not // throw an easy to debug error function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { - let encrypted + let encrypted; try { // publicEncrypt will throw an error with an invalid cert - encrypted = crypto.publicEncrypt(cert, Buffer.from("test")) + encrypted = crypto.publicEncrypt(cert, Buffer.from('test')); } catch (err) { - throw new Error(`The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`) + throw new Error( + `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}` + ); } try { // privateDecrypt will throw an error with an invalid key - crypto.privateDecrypt(key, encrypted) + crypto.privateDecrypt(key, encrypted); } catch (err) { - throw new Error(`The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${err.message}`) + throw new Error( + `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${ + err.message + }` + ); } } @@ -27,30 +35,32 @@ function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { function readEnvFile(file, type) { if (!fs.existsSync(file)) { throw new Error( - `You specified ${chalk.cyan(type)} in your env, but the file "${chalk.yellow(file)}" can't be found.` - ) + `You specified ${chalk.cyan( + type + )} in your env, but the file "${chalk.yellow(file)}" can't be found.` + ); } - return fs.readFileSync(file) + return fs.readFileSync(file); } // Get the https config // Return cert files if provided in env, otherwise just true or false function getHttpsConfig() { - const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env - const isHttps = HTTPS === "true" + const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env; + const isHttps = HTTPS === 'true'; if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { - const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE) - const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE) + const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE); + const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE); const config = { - cert: readEnvFile(crtFile, "SSL_CRT_FILE"), - key: readEnvFile(keyFile, "SSL_KEY_FILE"), - } + cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), + key: readEnvFile(keyFile, 'SSL_KEY_FILE'), + }; - validateKeyAndCerts({ ...config, keyFile, crtFile }) - return config + validateKeyAndCerts({ ...config, keyFile, crtFile }); + return config; } - return isHttps + return isHttps; } -module.exports = getHttpsConfig +module.exports = getHttpsConfig; diff --git a/config/jest/babelTransform.js b/config/jest/babelTransform.js index 9bf17b074..5b391e405 100644 --- a/config/jest/babelTransform.js +++ b/config/jest/babelTransform.js @@ -1,27 +1,29 @@ -const babelJest = require("babel-jest") +'use strict'; + +const babelJest = require('babel-jest').default; const hasJsxRuntime = (() => { - if (process.env.DISABLE_NEW_JSX_TRANSFORM === "true") { - return false + if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { + return false; } try { - require.resolve("react/jsx-runtime") - return true + require.resolve('react/jsx-runtime'); + return true; } catch (e) { - return false + return false; } -})() +})(); module.exports = babelJest.createTransformer({ presets: [ [ - require.resolve("babel-preset-react-app"), + require.resolve('babel-preset-react-app'), { - runtime: hasJsxRuntime ? "automatic" : "classic", + runtime: hasJsxRuntime ? 'automatic' : 'classic', }, ], ], babelrc: false, configFile: false, -}) +}); diff --git a/config/jest/cssTransform.js b/config/jest/cssTransform.js index a97276c8d..8f6511481 100644 --- a/config/jest/cssTransform.js +++ b/config/jest/cssTransform.js @@ -1,12 +1,14 @@ +'use strict'; + // This is a custom Jest transformer turning style imports into empty objects. // http://facebook.github.io/jest/docs/en/webpack.html module.exports = { process() { - return "module.exports = {};" + return 'module.exports = {};'; }, getCacheKey() { // The output is always the same. - return "cssTransform" + return 'cssTransform'; }, -} +}; diff --git a/config/jest/fileTransform.js b/config/jest/fileTransform.js index d72065560..aab67618c 100644 --- a/config/jest/fileTransform.js +++ b/config/jest/fileTransform.js @@ -1,20 +1,22 @@ -const path = require("path") -const camelcase = require("camelcase") +'use strict'; + +const path = require('path'); +const camelcase = require('camelcase'); // This is a custom Jest transformer turning file imports into filenames. // http://facebook.github.io/jest/docs/en/webpack.html module.exports = { process(src, filename) { - const assetFilename = JSON.stringify(path.basename(filename)) + const assetFilename = JSON.stringify(path.basename(filename)); if (filename.match(/\.svg$/)) { // Based on how SVGR generates a component name: // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 const pascalCaseFilename = camelcase(path.parse(filename).name, { pascalCase: true, - }) - const componentName = `Svg${pascalCaseFilename}` + }); + const componentName = `Svg${pascalCaseFilename}`; return `const React = require('react'); module.exports = { __esModule: true, @@ -30,9 +32,9 @@ module.exports = { }) }; }), - };` + };`; } - return `module.exports = ${assetFilename};` + return `module.exports = ${assetFilename};`; }, -} +}; diff --git a/config/modules.js b/config/modules.js index 6fcf390c2..d63e41d78 100644 --- a/config/modules.js +++ b/config/modules.js @@ -1,8 +1,10 @@ -const fs = require("fs") -const path = require("path") -const paths = require("./paths") -const chalk = require("react-dev-utils/chalk") -const resolve = require("resolve") +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const paths = require('./paths'); +const chalk = require('react-dev-utils/chalk'); +const resolve = require('resolve'); /** * Get additional module paths based on the baseUrl of a compilerOptions object. @@ -10,23 +12,23 @@ const resolve = require("resolve") * @param {Object} options */ function getAdditionalModulePaths(options = {}) { - const baseUrl = options.baseUrl + const baseUrl = options.baseUrl; if (!baseUrl) { - return "" + return ''; } - const baseUrlResolved = path.resolve(paths.appPath, baseUrl) + const baseUrlResolved = path.resolve(paths.appPath, baseUrl); // We don't need to do anything if `baseUrl` is set to `node_modules`. This is // the default behavior. - if (path.relative(paths.appNodeModules, baseUrlResolved) === "") { - return null + if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { + return null; } // Allow the user set the `baseUrl` to `appSrc`. - if (path.relative(paths.appSrc, baseUrlResolved) === "") { - return [paths.appSrc] + if (path.relative(paths.appSrc, baseUrlResolved) === '') { + return [paths.appSrc]; } // If the path is equal to the root directory we ignore it here. @@ -34,17 +36,17 @@ function getAdditionalModulePaths(options = {}) { // not transpiled outside of `src`. We do allow importing them with the // absolute path (e.g. `src/Components/Button.js`) but we set that up with // an alias. - if (path.relative(paths.appPath, baseUrlResolved) === "") { - return null + if (path.relative(paths.appPath, baseUrlResolved) === '') { + return null; } // Otherwise, throw an error. throw new Error( chalk.red.bold( "Your project's `baseUrl` can only be set to `src` or `node_modules`." + - " Create React App does not support other values at this time." + ' Create React App does not support other values at this time.' ) - ) + ); } /** @@ -53,18 +55,18 @@ function getAdditionalModulePaths(options = {}) { * @param {*} options */ function getWebpackAliases(options = {}) { - const baseUrl = options.baseUrl + const baseUrl = options.baseUrl; if (!baseUrl) { - return {} + return {}; } - const baseUrlResolved = path.resolve(paths.appPath, baseUrl) + const baseUrlResolved = path.resolve(paths.appPath, baseUrl); - if (path.relative(paths.appPath, baseUrlResolved) === "") { + if (path.relative(paths.appPath, baseUrlResolved) === '') { return { src: paths.appSrc, - } + }; } } @@ -74,59 +76,59 @@ function getWebpackAliases(options = {}) { * @param {*} options */ function getJestAliases(options = {}) { - const baseUrl = options.baseUrl + const baseUrl = options.baseUrl; if (!baseUrl) { - return {} + return {}; } - const baseUrlResolved = path.resolve(paths.appPath, baseUrl) + const baseUrlResolved = path.resolve(paths.appPath, baseUrl); - if (path.relative(paths.appPath, baseUrlResolved) === "") { + if (path.relative(paths.appPath, baseUrlResolved) === '') { return { - "^src/(.*)$": "/src/$1", - } + '^src/(.*)$': '/src/$1', + }; } } function getModules() { // Check if TypeScript is setup - const hasTsConfig = fs.existsSync(paths.appTsConfig) - const hasJsConfig = fs.existsSync(paths.appJsConfig) + const hasTsConfig = fs.existsSync(paths.appTsConfig); + const hasJsConfig = fs.existsSync(paths.appJsConfig); if (hasTsConfig && hasJsConfig) { throw new Error( - "You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file." - ) + 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' + ); } - let config + let config; // If there's a tsconfig.json we assume it's a // TypeScript project and set up the config // based on tsconfig.json if (hasTsConfig) { - const ts = require(resolve.sync("typescript", { + const ts = require(resolve.sync('typescript', { basedir: paths.appNodeModules, - })) - config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config + })); + config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config; // Otherwise we'll check if there is jsconfig.json // for non TS projects. } else if (hasJsConfig) { - config = require(paths.appJsConfig) + config = require(paths.appJsConfig); } - config = config || {} - const options = config.compilerOptions || {} + config = config || {}; + const options = config.compilerOptions || {}; - const additionalModulePaths = getAdditionalModulePaths(options) + const additionalModulePaths = getAdditionalModulePaths(options); return { additionalModulePaths: additionalModulePaths, webpackAliases: getWebpackAliases(options), jestAliases: getJestAliases(options), hasTsConfig, - } + }; } -module.exports = getModules() +module.exports = getModules(); diff --git a/config/paths.js b/config/paths.js index f73f7846e..f0a6cd9c9 100644 --- a/config/paths.js +++ b/config/paths.js @@ -1,11 +1,13 @@ -const path = require("path") -const fs = require("fs") -const getPublicUrlOrPath = require("react-dev-utils/getPublicUrlOrPath") +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath'); // Make sure any symlinks in the project folder are resolved: // https://github.com/facebook/create-react-app/issues/637 -const appDirectory = fs.realpathSync(process.cwd()) -const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath) +const appDirectory = fs.realpathSync(process.cwd()); +const resolveApp = relativePath => path.resolve(appDirectory, relativePath); // We use `PUBLIC_URL` environment variable or "homepage" field to infer // "public path" at which the app is served. @@ -14,56 +16,62 @@ const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath) // We can't use a relative path in HTML because we don't want to load something // like /todos/42/static/js/bundle.7289d.js. We have to know the root. const publicUrlOrPath = getPublicUrlOrPath( - process.env.NODE_ENV === "development", - require(resolveApp("package.json")).homepage, + process.env.NODE_ENV === 'development', + require(resolveApp('package.json')).homepage, process.env.PUBLIC_URL -) +); -const buildPath = process.env.BUILD_PATH || "build" +const buildPath = process.env.BUILD_PATH || 'build'; const moduleFileExtensions = [ - "web.mjs", - "mjs", - "web.js", - "js", - "web.ts", - "ts", - "web.tsx", - "tsx", - "json", - "web.jsx", - "jsx", -] + 'web.mjs', + 'mjs', + 'web.js', + 'js', + 'web.ts', + 'ts', + 'web.tsx', + 'tsx', + 'json', + 'web.jsx', + 'jsx', +]; // Resolve file paths in the same order as webpack const resolveModule = (resolveFn, filePath) => { - const extension = moduleFileExtensions.find((extension) => fs.existsSync(resolveFn(`${filePath}.${extension}`))) + const extension = moduleFileExtensions.find(extension => + fs.existsSync(resolveFn(`${filePath}.${extension}`)) + ); if (extension) { - return resolveFn(`${filePath}.${extension}`) + return resolveFn(`${filePath}.${extension}`); } - return resolveFn(`${filePath}.js`) -} + return resolveFn(`${filePath}.js`); +}; // config after eject: we're in ./config/ module.exports = { - dotenv: resolveApp(".env"), - appPath: resolveApp("."), + dotenv: resolveApp('.env'), + appPath: resolveApp('.'), appBuild: resolveApp(buildPath), - appPublic: resolveApp("public"), - appHtml: resolveApp("public/index.html"), - appIndexJs: resolveModule(resolveApp, "src/index"), - appPackageJson: resolveApp("package.json"), - appSrc: resolveApp("src"), - appTsConfig: resolveApp("tsconfig.json"), - appJsConfig: resolveApp("jsconfig.json"), - yarnLockFile: resolveApp("yarn.lock"), - testsSetup: resolveModule(resolveApp, "src/setupTests"), - proxySetup: resolveApp("src/setupProxy.js"), - appNodeModules: resolveApp("node_modules"), - swSrc: resolveModule(resolveApp, "src/service-worker"), + appPublic: resolveApp('public'), + appHtml: resolveApp('public/index.html'), + appIndexJs: resolveModule(resolveApp, 'src/index'), + appPackageJson: resolveApp('package.json'), + appSrc: resolveApp('src'), + appTsConfig: resolveApp('tsconfig.json'), + appJsConfig: resolveApp('jsconfig.json'), + yarnLockFile: resolveApp('yarn.lock'), + testsSetup: resolveModule(resolveApp, 'src/setupTests'), + proxySetup: resolveApp('src/setupProxy.js'), + appNodeModules: resolveApp('node_modules'), + appWebpackCache: resolveApp('node_modules/.cache'), + appTsBuildInfoFile: resolveApp('node_modules/.cache/tsconfig.tsbuildinfo'), + swSrc: resolveModule(resolveApp, 'src/service-worker'), publicUrlOrPath, -} +}; + + -module.exports.moduleFileExtensions = moduleFileExtensions +module.exports.moduleFileExtensions = moduleFileExtensions; diff --git a/config/webpack.config.js b/config/webpack.config.js index 893f3d87d..fd9469a35 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -1,115 +1,176 @@ -const fs = require("fs") -const path = require("path") -const webpack = require("webpack") -const resolve = require("resolve") -const HtmlWebpackPlugin = require("html-webpack-plugin") -const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin") -const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin") -const TerserPlugin = require("terser-webpack-plugin") -const MiniCssExtractPlugin = require("mini-css-extract-plugin") -const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") -const { WebpackManifestPlugin } = require("webpack-manifest-plugin") -const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin") -const WorkboxWebpackPlugin = require("workbox-webpack-plugin") -const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin") -const getCSSModuleLocalIdent = require("react-dev-utils/getCSSModuleLocalIdent") -const ESLintPlugin = require("eslint-webpack-plugin") -const paths = require("./paths") -const modules = require("./modules") -const getClientEnvironment = require("./env") -const ModuleNotFoundPlugin = require("react-dev-utils/ModuleNotFoundPlugin") -const ForkTsCheckerWebpackPlugin = require("react-dev-utils/ForkTsCheckerWebpackPlugin") -const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin") -const CompressionPlugin = require("compression-webpack-plugin"); +'use strict'; -const appPackageJson = require(paths.appPackageJson) +const fs = require('fs'); +const path = require('path'); +const webpack = require('webpack'); +const resolve = require('resolve'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); +const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin'); +const TerserPlugin = require('terser-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); +const { WebpackManifestPlugin } = require('webpack-manifest-plugin'); +const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); +const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); +const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); +const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); +const ESLintPlugin = require('eslint-webpack-plugin'); +const eslintConfig = require('../eslint.config') +const paths = require('./paths'); +const modules = require('./modules'); +const getClientEnvironment = require('./env'); +const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); +const ForkTsCheckerWebpackPlugin = + process.env.TSC_COMPILE_ON_ERROR === 'true' + ? require('react-dev-utils/ForkTsCheckerWarningWebpackPlugin') + : require('react-dev-utils/ForkTsCheckerWebpackPlugin'); +const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); + +const createEnvironmentHash = require('./webpack/persistentCache/createEnvironmentHash'); // Source maps are resource heavy and can cause out of memory issue for large source files. -const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false" +const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; -const webpackDevClientEntry = require.resolve("react-dev-utils/webpackHotDevClient") -const reactRefreshOverlayEntry = require.resolve("react-dev-utils/refreshOverlayInterop") +const reactRefreshRuntimeEntry = require.resolve('react-refresh/runtime'); +const reactRefreshWebpackPluginRuntimeEntry = require.resolve( + '@pmmmwh/react-refresh-webpack-plugin' +); +const babelRuntimeEntry = require.resolve('babel-preset-react-app'); +const babelRuntimeEntryHelpers = require.resolve( + '@babel/runtime/helpers/esm/assertThisInitialized', + { paths: [babelRuntimeEntry] } +); +const babelRuntimeRegenerator = require.resolve('@babel/runtime/regenerator', { + paths: [babelRuntimeEntry], +}); // Some apps do not need the benefits of saving a web request, so not inlining the chunk // makes for a smoother build process. -const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== "false" +const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false'; -const emitErrorsAsWarnings = process.env.ESLINT_NO_DEV_ERRORS === "true" -const disableESLintPlugin = process.env.DISABLE_ESLINT_PLUGIN === "true" +const emitErrorsAsWarnings = process.env.ESLINT_NO_DEV_ERRORS === 'true'; +const disableESLintPlugin = process.env.DISABLE_ESLINT_PLUGIN === 'true'; -const imageInlineSizeLimit = parseInt(process.env.IMAGE_INLINE_SIZE_LIMIT || "10000") +const imageInlineSizeLimit = parseInt( + process.env.IMAGE_INLINE_SIZE_LIMIT || '10000' +); // Check if TypeScript is setup -const useTypeScript = fs.existsSync(paths.appTsConfig) +const useTypeScript = fs.existsSync(paths.appTsConfig); + +// Check if Tailwind config exists +const useTailwind = fs.existsSync( + path.join(paths.appPath, 'tailwind.config.js') +); // Get the path to the uncompiled service worker (if it exists). -const swSrc = paths.swSrc +const swSrc = paths.swSrc; // style files regexes -const cssRegex = /\.css$/ -const cssModuleRegex = /\.module\.css$/ -const sassRegex = /\.(scss|sass)$/ -const sassModuleRegex = /\.module\.(scss|sass)$/ +const cssRegex = /\.css$/; +const cssModuleRegex = /\.module\.css$/; +const sassRegex = /\.(scss|sass)$/; +const sassModuleRegex = /\.module\.(scss|sass)$/; const hasJsxRuntime = (() => { - if (process.env.DISABLE_NEW_JSX_TRANSFORM === "true") { - return false + if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { + return false; } try { - require.resolve("react/jsx-runtime") - return true + require.resolve('react/jsx-runtime'); + return true; } catch (e) { - return false + return false; } -})() +})(); // This is the production and development configuration. // It is focused on developer experience, fast rebuilds, and a minimal bundle. module.exports = function (webpackEnv) { - const isEnvDevelopment = webpackEnv === "development" - const isEnvProduction = webpackEnv === "production" + const isEnvDevelopment = webpackEnv === 'development'; + const isEnvProduction = webpackEnv === 'production'; // Variable used for enabling profiling in Production // passed into alias object. Uses a flag if passed into the build command - const isEnvProductionProfile = isEnvProduction && process.argv.includes("--profile") + const isEnvProductionProfile = + isEnvProduction && process.argv.includes('--profile'); // We will provide `paths.publicUrlOrPath` to our app // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. // Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz. // Get environment variables to inject into our app. - const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)) + const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)); - const shouldUseReactRefresh = env.raw.FAST_REFRESH + const shouldUseReactRefresh = env.raw.FAST_REFRESH; // common function to get style loaders const getStyleLoaders = (cssOptions, preProcessor) => { const loaders = [ - isEnvDevelopment && require.resolve("style-loader"), + isEnvDevelopment && require.resolve('style-loader'), isEnvProduction && { loader: MiniCssExtractPlugin.loader, // css is located in `static/css`, use '../../' to locate index.html folder // in production `paths.publicUrlOrPath` can be a relative path - options: paths.publicUrlOrPath.startsWith(".") ? { publicPath: "../../" } : {}, + options: paths.publicUrlOrPath.startsWith('.') + ? { publicPath: '../../' } + : {}, }, { - loader: require.resolve("css-loader"), + loader: require.resolve('css-loader'), options: cssOptions, }, { // Options for PostCSS as we reference these options twice // Adds vendor prefixing based on your specified browser support in // package.json - loader: require.resolve("postcss-loader"), + loader: require.resolve('postcss-loader'), options: { + postcssOptions: { + // Necessary for external CSS imports to work + // https://github.com/facebook/create-react-app/issues/2677 + ident: 'postcss', + config: false, + plugins: !useTailwind + ? [ + 'postcss-flexbugs-fixes', + [ + 'postcss-preset-env', + { + autoprefixer: { + flexbox: 'no-2009', + }, + stage: 3, + }, + ], + // Adds PostCSS Normalize as the reset css with default options, + // so that it honors browserslist config in package.json + // which in turn let's users customize the target behavior as per their needs. + 'postcss-normalize', + ] + : [ + 'tailwindcss', + 'postcss-flexbugs-fixes', + [ + 'postcss-preset-env', + { + autoprefixer: { + flexbox: 'no-2009', + }, + stage: 3, + }, + ], + ], + }, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, }, }, - ].filter(Boolean) + ].filter(Boolean); if (preProcessor) { loaders.push( { - loader: require.resolve("resolve-url-loader"), + loader: require.resolve('resolve-url-loader'), options: { sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, root: paths.appSrc, @@ -121,77 +182,69 @@ module.exports = function (webpackEnv) { sourceMap: true, }, } - ) + ); } - return loaders - } + return loaders; + }; return { - mode: isEnvProduction ? "production" : isEnvDevelopment && "development", - target: "web", + target: ['browserslist'], + // Webpack noise constrained to errors and warnings + stats: 'errors-warnings', + mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development', // Stop compilation early in production bail: isEnvProduction, devtool: isEnvProduction ? shouldUseSourceMap - ? "source-map" + ? 'source-map' : false - : isEnvDevelopment && "cheap-module-source-map", + : isEnvDevelopment && 'cheap-module-source-map', // These are the "entry points" to our application. // This means they will be the "root" imports that are included in JS bundle. - entry: - isEnvDevelopment && !shouldUseReactRefresh - ? [ - // Include an alternative client for WebpackDevServer. A client's job is to - // connect to WebpackDevServer by a socket and get notified about changes. - // When you save a file, the client will either apply hot updates (in case - // of CSS changes), or refresh the page (in case of JS changes). When you - // make a syntax error, this client will display a syntax error overlay. - // Note: instead of the default WebpackDevServer client, we use a custom one - // to bring better experience for Create React App users. You can replace - // the line below with these two lines if you prefer the stock client: - // - // require.resolve('webpack-dev-server/client') + '?/', - // require.resolve('webpack/hot/dev-server'), - // - // When using the experimental react-refresh integration, - // the webpack plugin takes care of injecting the dev client for us. - webpackDevClientEntry, - // Finally, this is your app's code: - paths.appIndexJs, - // We include the app code last so that if there is a runtime error during - // initialization, it doesn't blow up the WebpackDevServer client, and - // changing JS code would still trigger a refresh. - ] - : paths.appIndexJs, + entry: paths.appIndexJs, output: { // The build folder. - path: isEnvProduction ? paths.appBuild : undefined, + path: paths.appBuild, // Add /* filename */ comments to generated require()s in the output. pathinfo: isEnvDevelopment, // There will be one main bundle, and one file per asynchronous chunk. // In development, it does not produce real files. filename: isEnvProduction - ? "static/js/[name].[contenthash:8].js" - : isEnvDevelopment && "static/js/[name].bundle.js", + ? 'static/js/[name].[contenthash:8].js' + : isEnvDevelopment && 'static/js/bundle.js', // There are also additional JS chunk files if you use code splitting. chunkFilename: isEnvProduction - ? "static/js/[name].[contenthash:8].chunk.js" - : isEnvDevelopment && "static/js/[name].chunk.js", - assetModuleFilename: "static/media/[name].[hash][ext]", + ? 'static/js/[name].[contenthash:8].chunk.js' + : isEnvDevelopment && 'static/js/[name].chunk.js', + assetModuleFilename: 'static/media/[name].[hash][ext]', // webpack uses `publicPath` to determine where the app is being served from. // It requires a trailing slash, or the file assets will get an incorrect path. // We inferred the "public path" (such as / or /my-project) from homepage. publicPath: paths.publicUrlOrPath, // Point sourcemap entries to original disk location (format as URL on Windows) devtoolModuleFilenameTemplate: isEnvProduction - ? (info) => path.relative(paths.appSrc, info.absoluteResourcePath).replace(/\\/g, "/") - : isEnvDevelopment && ((info) => path.resolve(info.absoluteResourcePath).replace(/\\/g, "/")), - // Prevents conflicts when multiple webpack runtimes (from different apps) - // are used on the same page. - chunkLoadingGlobal: `webpackJsonp${appPackageJson.name}`, - // this defaults to 'window', but by setting it to 'this' then - // module chunks which are built will work in web workers as well. - globalObject: "this", + ? info => + path + .relative(paths.appSrc, info.absoluteResourcePath) + .replace(/\\/g, '/') + : isEnvDevelopment && + (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')), + }, + cache: { + type: 'filesystem', + version: createEnvironmentHash(env.raw), + cacheDirectory: paths.appWebpackCache, + store: 'pack', + buildDependencies: { + defaultWebpack: ['webpack/lib/'], + config: [__filename], + tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f => + fs.existsSync(f) + ), + }, + }, + infrastructureLogging: { + level: 'none', }, optimization: { minimize: isEnvProduction, @@ -236,40 +289,18 @@ module.exports = function (webpackEnv) { }, }, }), - new CssMinimizerPlugin({ - minimizerOptions: { - // Other options are available https://webpack.js.org/plugins/css-minimizer-webpack-plugin/#minify - preset: [ - "default", - { - discardComments: { removeAll: true }, - minifyFontValues: { removeQuotes: false }, - }, - ], - }, - }), + // This is only used in production mode + new CssMinimizerPlugin(), ], - // Automatically split vendor and commons - // https://twitter.com/wSokra/status/969633336732905474 - // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366 - splitChunks: { - chunks: "all", - name: false, - }, - // Keep the runtime chunk separated to enable long term caching - // https://twitter.com/wSokra/status/969679223278505985 - // https://github.com/facebook/create-react-app/issues/5358 - runtimeChunk: { - name: (entrypoint) => `runtime-${entrypoint.name}`, - }, }, resolve: { // This allows you to set a fallback for where webpack should look for modules. // We placed these paths second because we want `node_modules` to "win" // if there are any conflicts. This matches Node resolution mechanism. // https://github.com/facebook/create-react-app/issues/253 - modules: ["node_modules", paths.appNodeModules].concat(modules.additionalModulePaths || []), - preferRelative: true, + modules: ['node_modules', paths.appNodeModules].concat( + modules.additionalModulePaths || [] + ), // These are the reasonable defaults supported by the Node ecosystem. // We also include JSX as a common component filename extension to support // some tools, although we do not recommend using it, see: @@ -277,96 +308,106 @@ module.exports = function (webpackEnv) { // `web` extension prefixes have been added for better support // for React Native Web. extensions: paths.moduleFileExtensions - .map((ext) => `.${ext}`) - .filter((ext) => useTypeScript || !ext.includes("ts")), + .map(ext => `.${ext}`) + .filter(ext => useTypeScript || !ext.includes('ts')), alias: { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ - "react-native": "react-native-web", + 'react-native': 'react-native-web', // Allows for better profiling with ReactDevTools ...(isEnvProductionProfile && { - "react-dom$": "react-dom/profiling", - "scheduler/tracing": "scheduler/tracing-profiling", + 'react-dom$': 'react-dom/profiling', + 'scheduler/tracing': 'scheduler/tracing-profiling', }), ...(modules.webpackAliases || {}), }, plugins: [ - // Adds support for installing with Plug'n'Play, leading to faster installs and adding - // guards against forgotten dependencies and such. // Prevents users from importing files from outside of src/ (or node_modules/). // This often causes confusion because we only process files within src/ with babel. // To fix this, we prevent you from importing files out of src/ -- if you'd like to, // please link the files into your node_modules/ and let module-resolution kick in. // Make sure your source files are compiled, as they will not be processed in any way. - new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson, reactRefreshOverlayEntry]), + new ModuleScopePlugin(paths.appSrc, [ + paths.appPackageJson, + reactRefreshRuntimeEntry, + reactRefreshWebpackPluginRuntimeEntry, + babelRuntimeEntry, + babelRuntimeEntryHelpers, + babelRuntimeRegenerator, + ]), ], + // VICTRON: BEGIN // Some libraries import Node modules but don't use them in the browser. // Tell webpack to provide empty mocks for them so importing them works. fallback: { - module: false, - dgram: false, - fs: false, - http2: false, - net: false, - tls: false, - child_process: false, - stream: require.resolve("stream-browserify"), + 'process/browser': require.resolve('process/browser'), buffer: require.resolve("buffer"), - url: require.resolve("url/") - }, + } + // VICTRON: END }, module: { strictExportPresence: true, rules: [ - // Disable require.ensure as it's not a standard language feature. - { - // The new webpack5 parser rules applies both for Js and Json, but Json module - // hasn't `requireEnsure` parameter in config, therefore we apply this rule only for Js - test: /\.[cm]?js$/, - parser: { requireEnsure: false }, + // Handle node_modules packages that contain sourcemaps + shouldUseSourceMap && { + enforce: 'pre', + exclude: /@babel(?:\/|\\{1,2})runtime/, + test: /\.(js|mjs|jsx|ts|tsx|css)$/, + loader: require.resolve('source-map-loader'), }, { // "oneOf" will traverse all following loaders until one will // match the requirements. When no loader matches it will fall // back to the "file" loader at the end of the loader list. oneOf: [ - { - test: /\.svg$/, - // inline SVGs only for Marine2 app - include: [paths.appSrc + "/app/Marine2"], - use: [ - { loader: require.resolve("babel-loader") }, - { - loader: require.resolve("react-svg-loader"), - options: { jsx: true }, - }, - ], - }, - // TODO: Merge this config once `image/avif` is in the mime-db // https://github.com/jshttp/mime-db { test: [/\.avif$/], - loader: require.resolve("url-loader"), - options: { - limit: imageInlineSizeLimit, - mimetype: "image/avif", - name: "static/media/[name].[hash:8].[ext]", + type: 'asset', + mimetype: 'image/avif', + parser: { + dataUrlCondition: { + maxSize: imageInlineSizeLimit, + }, }, }, - { - test: /\.(woff|woff2|eot|ttf|otf)$/i, - type: "asset/resource", - }, // "url" loader works like "file" loader except that it embeds assets // smaller than specified limit in bytes as data URLs to avoid requests. // A missing `test` is equivalent to a match. { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], - loader: require.resolve("url-loader"), - options: { - limit: imageInlineSizeLimit, - name: "static/media/[name].[hash:8].[ext]", + type: 'asset', + parser: { + dataUrlCondition: { + maxSize: imageInlineSizeLimit, + }, + }, + }, + { + test: /\.svg$/, + use: [ + { + loader: require.resolve('@svgr/webpack'), + options: { + prettier: false, + svgo: false, + svgoConfig: { + plugins: [{ removeViewBox: false }], + }, + titleProp: true, + ref: true, + }, + }, + { + loader: require.resolve('file-loader'), + options: { + name: 'static/media/[name].[hash].[ext]', + }, + }, + ], + issuer: { + and: [/\.(ts|tsx|js|jsx|md|mdx)$/], }, }, // Process application JS with Babel. @@ -374,32 +415,25 @@ module.exports = function (webpackEnv) { { test: /\.(js|mjs|jsx|ts|tsx)$/, include: paths.appSrc, - loader: require.resolve("babel-loader"), + loader: require.resolve('babel-loader'), options: { - customize: require.resolve("babel-preset-react-app/webpack-overrides"), + customize: require.resolve( + 'babel-preset-react-app/webpack-overrides' + ), presets: [ [ - require.resolve("babel-preset-react-app"), + require.resolve('babel-preset-react-app'), { - runtime: hasJsxRuntime ? "automatic" : "classic", + runtime: hasJsxRuntime ? 'automatic' : 'classic', }, ], ], plugins: [ - [ - require.resolve("babel-plugin-named-asset-import"), - { - loaderMap: { - svg: { - ReactComponent: "@svgr/webpack?-svgo,+titleProp,+ref![path]", - }, - }, - }, - ], - isEnvDevelopment && shouldUseReactRefresh && require.resolve("react-refresh/babel"), + isEnvDevelopment && + shouldUseReactRefresh && + require.resolve('react-refresh/babel'), ].filter(Boolean), - // This is a feature of `babel-loader` for webpack (not Babel itself). // It enables caching results in ./node_modules/.cache/babel-loader/ // directory for faster rebuilds. @@ -414,12 +448,17 @@ module.exports = function (webpackEnv) { { test: /\.(js|mjs)$/, exclude: /@babel(?:\/|\\{1,2})runtime/, - loader: require.resolve("babel-loader"), + loader: require.resolve('babel-loader'), options: { babelrc: false, configFile: false, compact: false, - presets: [[require.resolve("babel-preset-react-app/dependencies"), { helpers: true }]], + presets: [ + [ + require.resolve('babel-preset-react-app/dependencies'), + { helpers: true }, + ], + ], cacheDirectory: true, // See #6846 for context on why cacheCompression is disabled cacheCompression: false, @@ -443,7 +482,12 @@ module.exports = function (webpackEnv) { exclude: cssModuleRegex, use: getStyleLoaders({ importLoaders: 1, - sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, + modules: { + mode: 'icss', + }, }), // Don't consider CSS imports dead code even if the // containing package claims to have no side effects. @@ -457,8 +501,11 @@ module.exports = function (webpackEnv) { test: cssModuleRegex, use: getStyleLoaders({ importLoaders: 1, - sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, modules: { + mode: 'local', getLocalIdent: getCSSModuleLocalIdent, }, }), @@ -472,9 +519,14 @@ module.exports = function (webpackEnv) { use: getStyleLoaders( { importLoaders: 3, - sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, + modules: { + mode: 'icss', + }, }, - "sass-loader" + 'sass-loader' ), // Don't consider CSS imports dead code even if the // containing package claims to have no side effects. @@ -489,12 +541,15 @@ module.exports = function (webpackEnv) { use: getStyleLoaders( { importLoaders: 3, - sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, modules: { + mode: 'local', getLocalIdent: getCSSModuleLocalIdent, }, }, - "sass-loader" + 'sass-loader' ), }, // "file" loader makes sure those assets get served by WebpackDevServer. @@ -503,21 +558,18 @@ module.exports = function (webpackEnv) { // This loader doesn't use a "test" so it will catch all modules // that fall through the other loaders. { - loader: require.resolve("file-loader"), // Exclude `js` files to keep "css" loader working as it injects // its runtime that would otherwise be processed through "file" loader. // Also exclude `html` and `json` extensions so they get processed // by webpacks internal loaders. - exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/], - options: { - name: "static/media/[name].[hash:8].[ext]", - }, + exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/], + type: 'asset/resource', }, // ** STOP ** Are you adding a new loader? // Make sure to add the new loader(s) before the "file" loader. ], }, - ], + ].filter(Boolean), }, plugins: [ // Generates an `index.html` file with the