diff --git a/.eslintrc.js b/.eslintrc.js index 36fa407..3f20294 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,38 +1,77 @@ +const jestRules = require('eslint-plugin-jest').rules; +const jestDomRules = require('eslint-plugin-jest-dom').rules; +const testLibRules = require('eslint-plugin-testing-library').rules; + +const disableAllRules = (pluginName, rules) => { + return Object.keys(rules).reduce((ac, ruleName) => { + return { + ...ac, + [`${pluginName}/${ruleName}`]: 'off', + }; + }, {}); +}; + +// We need to turn off jest rules for cypress because +// Cypress uses expect. +const cypressRuleDisables = { + ...disableAllRules('jest', jestRules), + ...disableAllRules('jest-dom', jestDomRules), + ...disableAllRules('testing-library', testLibRules), +}; + +// Default to development if we've somehow hit the linter without it being set +// react-scritps technically sets this to development also +if (process.env.NODE_ENV == null) { + process.env.NODE_ENV = 'development'; +} + module.exports = { + extends: ['@nciocpl/eslint-config-react', 'plugin:jest/recommended', 'plugin:jest-dom/recommended'], + plugins: ['testing-library', 'jest', 'jest-dom'], env: { browser: true, es6: true, node: true, jest: true, }, - extends: [ - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:react-hooks/recommended', - 'plugin:jsx-a11y/recommended', - 'plugin:prettier/recommended', - ], settings: { react: { version: 'detect', }, }, - parser: 'babel-eslint', + parser: '@babel/eslint-parser', parserOptions: { - ecmaVersion: 2016, + ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: { jsx: true, }, + babelOptions: { + presets: ['@babel/preset-react'], + }, }, - // Plugins are configured by the recommended extensions above rules: { - 'react/display-name': 'off', - 'react-hooks/exhaustive-deps': 'off', + 'testing-library/no-render-in-setup': 'off', // This is now no-render-in-lifecycle. Remove when NCIOCPL standards are updated. + 'testing-library/no-render-in-lifecycle': 'error', + + 'testing-library/prefer-wait-for': 'off', // This is now prefer-find-by. Remove when NCIOCPL standards are updated. + 'testing-library/prefer-find-by': 'error', + + 'jest/no-if': 'off', // Removed in eslint-plugin-jest 28. This is now no-conditional-in-test. Note: v28 and above only supports for node 20+. Remove when NCIOCPL standards are updated. + 'jest/no-conditional-in-test': 'error', + + 'react/jsx-filename-extension': [1, { allow: 'always' }], }, globals: { cy: true, Cypress: true, getFixture: true, }, + ignorePatterns: ['**/node_modules/***', 'build/', 'dist/', '*/node_modules/'], + overrides: [ + { + files: ['cypress/**'], + rules: cypressRuleDisables, + }, + ], }; diff --git a/.nvmrc b/.nvmrc index 53d838a..9de2256 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -lts/gallium +lts/iron diff --git a/babel.config.js b/babel.config.js index dbbde96..fb3b99c 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,6 +1,16 @@ const plugins = []; module.exports = { - presets: ['react-app'], + presets: [ + [ + 'react-app', + { + absoluteRuntime: false, + }, + ], + '@babel/preset-react', + '@babel/preset-env', + ], + plugins, }; diff --git a/config/paths.js b/config/paths.js index 6e360cf..3b8033e 100644 --- a/config/paths.js +++ b/config/paths.js @@ -24,6 +24,7 @@ const publicUrlOrPath = getPublicUrlOrPath( const moduleFileExtensions = [ 'web.mjs', 'mjs', + 'cjs', 'web.js', 'js', 'web.ts', diff --git a/config/webpack.config.js b/config/webpack.config.js index 26da4df..4dec071 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -10,12 +10,12 @@ 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 OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); -const safePostCssParser = require('postcss-safe-parser'); -const ManifestPlugin = require('webpack-manifest-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 ESLintPlugin = require('eslint-webpack-plugin'); const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); const paths = require('./paths'); const modules = require('./modules'); @@ -23,8 +23,6 @@ const getClientEnvironment = require('./env'); const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin'); -const postcssNormalize = require('postcss-normalize'); - const appPackageJson = require(paths.appPackageJson); // Source maps are resource heavy and can cause out of memory issue for large source files. @@ -33,12 +31,12 @@ const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; // makes for a smoother build process. const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false'; -const isExtendingEslintConfig = process.env.EXTEND_ESLINT === 'true'; - const imageInlineSizeLimit = parseInt( process.env.IMAGE_INLINE_SIZE_LIMIT || '10000' ); +const __webpack_base_uri__ = 'http://localhost:3000'; + // Check if TypeScript is setup const useTypeScript = fs.existsSync(paths.appTsConfig); @@ -87,46 +85,23 @@ module.exports = function (webpackEnv) { // package.json loader: require.resolve('postcss-loader'), options: { - // Necessary for external CSS imports to work - // https://github.com/facebook/create-react-app/issues/2677 - ident: 'postcss', - plugins: () => [ - require('postcss-flexbugs-fixes'), - require('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. - postcssNormalize(), - ], sourceMap: isEnvProduction && shouldUseSourceMap, }, }, ].filter(Boolean); if (preProcessor) { - loaders.push( - { - loader: require.resolve('resolve-url-loader'), - options: { - sourceMap: isEnvProduction && shouldUseSourceMap, - }, - }, - { + loaders.push({ loader: require.resolve(preProcessor), options: { sourceMap: true, }, - } - ); + }); } return loaders; }; return { + target: ['browserslist'], mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development', // Stop compilation early in production bail: isEnvProduction, @@ -137,8 +112,8 @@ module.exports = function (webpackEnv) { : 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. + context: __dirname + '/src', entry: [ - paths.setupPolyfill, // 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 @@ -159,7 +134,7 @@ module.exports = function (webpackEnv) { ].filter(Boolean), 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. @@ -167,8 +142,6 @@ module.exports = function (webpackEnv) { filename: isEnvProduction ? 'static/js/[name].js' : isEnvDevelopment && 'static/js/bundle.js', - // TODO: remove this when upgrading to webpack 5 - futureEmitAssets: true, // There are also additional JS chunk files if you use code splitting. chunkFilename: isEnvProduction ? 'static/js/[name].js' @@ -179,6 +152,7 @@ module.exports = function (webpackEnv) { publicPath: paths.publicUrlOrPath, library: 'nci-drug-dictionary-app', libraryTarget: 'umd', + hashFunction: "xxhash64", // Point sourcemap entries to original disk location (format as URL on Windows) devtoolModuleFilenameTemplate: isEnvProduction ? (info) => @@ -190,7 +164,7 @@ module.exports = function (webpackEnv) { path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')), // Prevents conflicts when multiple webpack runtimes (from different apps) // are used on the same page. - jsonpFunction: `webpackJsonp${appPackageJson.name}`, + 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', @@ -198,6 +172,7 @@ module.exports = function (webpackEnv) { optimization: { minimize: isEnvProduction, minimizer: [ + '...', // This is only used in production mode new TerserPlugin({ terserOptions: { @@ -240,9 +215,21 @@ module.exports = function (webpackEnv) { sourceMap: shouldUseSourceMap, }), // This is only used in production mode - new OptimizeCSSAssetsPlugin({ - cssProcessorOptions: { - parser: safePostCssParser, + new CssMinimizerPlugin({ + minimizerOptions: { + preset: [ + 'default', + { + discardComments: { removeAll: true }, + minifyFontValues: { removeQuotes: false }, + }, + ], + // If you want to use a custom CSS processor like postcss + // you can specify it here. Otherwise, cssnano is used by default. + processorOptions: { + parser: 'postcss-safe-parser', + }, + // Control source map generation here map: shouldUseSourceMap ? { // `inline: false` forces the sourcemap to be output into a @@ -254,24 +241,8 @@ module.exports = function (webpackEnv) { } : false, }, - cssProcessorPluginOptions: { - preset: ['default', { minifyFontValues: { removeQuotes: false } }], - }, }), ], - // 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. @@ -281,6 +252,13 @@ module.exports = function (webpackEnv) { modules: ['node_modules', paths.appNodeModules].concat( modules.additionalModulePaths || [] ), + + fallback: { + http: require.resolve('stream-http'), + https: require.resolve('https-browserify'), + buffer: require.resolve('buffer'), + url: require.resolve('url'), + }, // 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: @@ -325,25 +303,11 @@ module.exports = function (webpackEnv) { rules: [ // Disable require.ensure as it's not a standard language feature. { parser: { requireEnsure: false } }, - - // First, run the linter. - // It's important to do this before Babel processes the JS. { - test: /\.(js|mjs|jsx|ts|tsx)$/, - enforce: 'pre', - use: [ - { - options: { - cache: true, - formatter: require.resolve('react-dev-utils/eslintFormatter'), - eslintPath: require.resolve('eslint'), - resolvePluginsRelativeTo: __dirname, - }, - loader: require.resolve('eslint-loader'), - }, - ], - include: paths.appSrc, - exclude: paths.appExcludeFromBuild, + test: /\.m?js/, + resolve: { + fullySpecified: false, + }, }, { // "oneOf" will traverse all following loaders until one will @@ -354,17 +318,21 @@ module.exports = function (webpackEnv) { // 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].[ext]', + test: /\.(bmp|gif|jpe?g|png)$/i, + type: 'asset', + parser: { + dataUrlCondition: { + maxSize: imageInlineSizeLimit, + }, + }, + generator: { + filename: 'static/media/[name].[ext]', }, }, // Process application JS with Babel. // The preset includes JSX, Flow, TypeScript, and some ESnext features. { - test: /\.(js|mjs|jsx|ts|tsx)$/, + test: /\.(js|cjs|mjs|jsx|ts|tsx)$/, include: paths.appSrc, loader: require.resolve('babel-loader'), options: { @@ -413,7 +381,7 @@ module.exports = function (webpackEnv) { // Process any JS outside of the app with Babel. // Unlike the application JS, we only compile the standard ES features. { - test: /\.(js|mjs)$/, + test: /\.(js|cjs|mjs)$/, exclude: /@babel(?:\/|\\{1,2})runtime/, loader: require.resolve('babel-loader'), options: { @@ -503,29 +471,45 @@ module.exports = function (webpackEnv) { 'sass-loader' ), }, - // "file" loader makes sure those assets get served by WebpackDevServer. + // Makes sure assets get served by WebpackDevServer. // When you `import` an asset, you get its (virtual) filename. // In production, they would get copied to the `build` folder. // 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].[ext]', + type: 'asset/resource', + exclude: [ + /^$/, + /\.(js|cjs|mjs|jsx|ts|tsx)$/, + /\.html$/, + /\.json$/, + ], + generator: { + filename: 'static/media/[name].[ext]', }, }, // ** STOP ** Are you adding a new loader? // Make sure to add the new loader(s) before the "file" loader. ], }, - ], + ].filter(Boolean), }, plugins: [ + new ESLintPlugin( { + extensions: [`js`, `jsx`, `cjs`,`mjs`,`ts`,`tsx`], + exclude: [`**/node_modules/**`, ...paths.appExcludeFromBuild], + cache: true, +formatter: require.resolve('react-dev-utils/eslintFormatter'), +eslintPath: require.resolve('eslint'), +resolvePluginsRelativeTo: __dirname, +ignore: true, + useEslintrc: true, + }), + // Generates an `index.html` file with the