diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 3fdefce..0000000 --- a/.babelrc +++ /dev/null @@ -1,22 +0,0 @@ -{ - "presets": [ - [ - "@babel/preset-env", - { - "modules": false, - "useBuiltIns": "usage", - "corejs": 3, - "targets": { - "chrome": 60 - } - } - ], - "@babel/preset-react" - ], - "plugins": [ - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-json-strings", - "@babel/plugin-proposal-nullish-coalescing-operator" - ] -} diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..13eec32 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,28 @@ +const presets = [ + [ + '@babel/preset-env', + { + modules: false, + useBuiltIns: 'usage', + corejs: 3, + targets: { + chrome: 70 + } + } + ], + [ + '@babel/preset-react', + { + runtime: 'automatic' + } + ] +]; +const plugins = [ + process.env.NODE_ENV == 'development' && 'react-refresh/babel', + '@babel/plugin-syntax-dynamic-import', + '@babel/plugin-proposal-class-properties', + '@babel/plugin-proposal-json-strings', + '@babel/plugin-proposal-nullish-coalescing-operator' +].filter(Boolean); + +module.exports = { presets, plugins }; diff --git a/config/env.js b/config/env.js index 80dcccc..3d1411b 100644 --- a/config/env.js +++ b/config/env.js @@ -1,3 +1,5 @@ +'use strict'; + const fs = require('fs'); const path = require('path'); const paths = require('./paths'); @@ -7,18 +9,20 @@ delete require.cache[require.resolve('./paths')]; 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 const dotenvFiles = [ `${paths.dotenv}.${NODE_ENV}.local`, - `${paths.dotenv}.${NODE_ENV}`, // 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`, - paths.dotenv + `${paths.dotenv}.${NODE_ENV}`, + paths.dotenv, ].filter(Boolean); // Load environment variables from .env* files. Suppress warnings using silent @@ -30,7 +34,7 @@ dotenvFiles.forEach(dotenvFile => { if (fs.existsSync(dotenvFile)) { require('dotenv-expand')( require('dotenv').config({ - path: dotenvFile + path: dotenvFile, }) ); } @@ -80,7 +84,12 @@ function getClientEnvironment(publicUrl) { // and `sockPort` options in webpack-dev-server. WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST, WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH, - WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT + 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', } ); // Stringify all values so we can feed into webpack DefinePlugin @@ -88,7 +97,7 @@ function getClientEnvironment(publicUrl) { 'process.env': Object.keys(raw).reduce((env, key) => { env[key] = JSON.stringify(raw[key]); return env; - }, {}) + }, {}), }; return { raw, stringified }; diff --git a/config/getHttpsConfig.js b/config/getHttpsConfig.js index a13b854..013d493 100644 --- a/config/getHttpsConfig.js +++ b/config/getHttpsConfig.js @@ -1,3 +1,5 @@ +'use strict'; + const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); @@ -12,14 +14,20 @@ function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { // publicEncrypt will throw an error with an invalid cert 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); } 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,9 +35,9 @@ 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); @@ -46,7 +54,7 @@ function getHttpsConfig() { const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE); const config = { cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), - key: readEnvFile(keyFile, 'SSL_KEY_FILE') + key: readEnvFile(keyFile, 'SSL_KEY_FILE'), }; validateKeyAndCerts({ ...config, keyFile, crtFile }); diff --git a/config/modules.js b/config/modules.js index c6f0f32..7005858 100644 --- a/config/modules.js +++ b/config/modules.js @@ -1,7 +1,10 @@ -const fs = require('fs'); -const path = require('path'); -const paths = require('./paths'); -const chalk = require('react-dev-utils/chalk'); +"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. @@ -11,27 +14,20 @@ const chalk = require('react-dev-utils/chalk'); function getAdditionalModulePaths(options = {}) { const baseUrl = options.baseUrl; - // We need to explicitly check for null and undefined (and not a falsy value) because - // TypeScript treats an empty string as `.`. - if (baseUrl == null) { - // If there's no baseUrl set we respect NODE_PATH - // Note that NODE_PATH is deprecated and will be removed - // in the next major release of create-react-app. - - const nodePath = process.env.NODE_PATH || ''; - return nodePath.split(path.delimiter).filter(Boolean); + if (!baseUrl) { + return ""; } 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) === '') { + if (path.relative(paths.appNodeModules, baseUrlResolved) === "") { return null; } // Allow the user set the `baseUrl` to `appSrc`. - if (path.relative(paths.appSrc, baseUrlResolved) === '') { + if (path.relative(paths.appSrc, baseUrlResolved) === "") { return [paths.appSrc]; } @@ -40,7 +36,7 @@ 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) === '') { + if (path.relative(paths.appPath, baseUrlResolved) === "") { return null; } @@ -48,7 +44,7 @@ function getAdditionalModulePaths(options = {}) { 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." ) ); } @@ -67,20 +63,37 @@ function getWebpackAliases(options = {}) { const baseUrlResolved = path.resolve(paths.appPath, baseUrl); - if (path.relative(paths.appPath, baseUrlResolved) === '') { + if (path.relative(paths.appPath, baseUrlResolved) === "") { return { - src: paths.appSrc + src: paths.appSrc, }; } } function getModules() { - // Check if jsconfig.json + // Check if TypeScript is setup + 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." + ); + } + let config; - if (hasJsConfig) { + // 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", { + basedir: paths.appNodeModules, + })); + 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); } @@ -91,7 +104,8 @@ function getModules() { return { additionalModulePaths: additionalModulePaths, - webpackAliases: getWebpackAliases(options) + webpackAliases: getWebpackAliases(options), + hasTsConfig, }; } diff --git a/config/paths.js b/config/paths.js index 60e428f..e797679 100644 --- a/config/paths.js +++ b/config/paths.js @@ -1,11 +1,11 @@ -const path = require('path'); -const fs = require('fs'); -const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath'); +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 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,28 +14,28 @@ 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 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 => + const extension = moduleFileExtensions.find((extension) => fs.existsSync(resolveFn(`${filePath}.${extension}`)) ); @@ -48,21 +48,21 @@ const resolveModule = (resolveFn, filePath) => { // config after eject: we're in ./config/ module.exports = { - dotenv: resolveApp('.env'), - appPath: resolveApp('.'), - appBuild: resolveApp('build'), - 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'), - publicUrlOrPath + dotenv: resolveApp(".env"), + appPath: resolveApp("."), + appBuild: resolveApp("build"), + 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"), + proxySetup: resolveApp("src/setupProxy.js"), + appNodeModules: resolveApp("node_modules"), + swSrc: resolveModule(resolveApp, "src/service-worker"), + publicUrlOrPath, }; module.exports.moduleFileExtensions = moduleFileExtensions; diff --git a/config/pnpTs.js b/config/pnpTs.js new file mode 100644 index 0000000..d1b0539 --- /dev/null +++ b/config/pnpTs.js @@ -0,0 +1,35 @@ +'use strict'; + +const { resolveModuleName } = require('ts-pnp'); + +exports.resolveModuleName = ( + typescript, + moduleName, + containingFile, + compilerOptions, + resolutionHost +) => { + return resolveModuleName( + moduleName, + containingFile, + compilerOptions, + resolutionHost, + typescript.resolveModuleName + ); +}; + +exports.resolveTypeReferenceDirective = ( + typescript, + moduleName, + containingFile, + compilerOptions, + resolutionHost +) => { + return resolveModuleName( + moduleName, + containingFile, + compilerOptions, + resolutionHost, + typescript.resolveTypeReferenceDirective + ); +}; diff --git a/config/webpack.config.js b/config/webpack.config.js index 54d2ed5..117ea72 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -6,7 +6,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin'); const TerserPlugin = require('terser-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const ManifestPlugin = require('webpack-manifest-plugin'); +const { WebpackManifestPlugin } = require('webpack-manifest-plugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); @@ -15,20 +15,28 @@ const paths = require('./paths'); const modules = require('./modules'); const getClientEnvironment = require('./env'); const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); +const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); const appPackageJson = require(paths.appPackageJson); // Source maps are resource heavy and can cause out of memory issue for large source files. const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; + +const webpackDevClientEntry = require.resolve('react-dev-utils/webpackHotDevClient'); +const reactRefreshOverlayEntry = require.resolve('react-dev-utils/refreshOverlayInterop'); + // 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 imageInlineSizeLimit = parseInt(process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'); +const imageInlineSizeLimit = parseInt(process.env.IMAGE_INLINE_SIZE_LIMIT || '1000'); // Check if TypeScript is setup const useTypeScript = fs.existsSync(paths.appTsConfig); +// Get the path to the uncompiled service worker (if it exists). +const swSrc = paths.swSrc; + // style files regexes const cssRegex = /\.css$/; const cssModuleRegex = /\.module\.css$/; @@ -49,6 +57,8 @@ module.exports = function (webpackEnv) { // Get environment variables to inject into our app. const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)); + const shouldUseReactRefresh = env.raw.FAST_REFRESH; + // common function to get style loaders const getStyleLoaders = (cssOptions) => { const loaders = [ @@ -78,24 +88,31 @@ 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. - entry: [ - // 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'), - isEnvDevelopment && require.resolve('react-dev-utils/webpackHotDevClient'), - // 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. - ].filter(Boolean), + 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, output: { // The build folder. path: isEnvProduction ? paths.appBuild : undefined, @@ -222,7 +239,7 @@ module.exports = function (webpackEnv) { // 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]) + new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson, reactRefreshOverlayEntry]) ] }, resolveLoader: { @@ -237,7 +254,6 @@ module.exports = function (webpackEnv) { rules: [ // Disable require.ensure as it's not a standard language feature. { parser: { requireEnsure: false } }, - { // "oneOf" will traverse all following loaders until one will // match the requirements. When no loader matches it will fall @@ -259,8 +275,17 @@ module.exports = function (webpackEnv) { { test: /\.(js|mjs|jsx|ts|tsx)$/, include: paths.appSrc, - exclude: /@babel(?:\/|\\{1,2})runtime/, - loader: require.resolve('babel-loader') + loader: require.resolve('babel-loader'), + options: { + babelrc: true, + // 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. + cacheDirectory: true, + // See #6846 for context on why cacheCompression is disabled + cacheCompression: false, + compact: isEnvProduction + } }, // Process any JS outside of the app with Babel. // Unlike the application JS, we only compile the standard ES features. @@ -283,6 +308,7 @@ module.exports = function (webpackEnv) { inputSourceMap: shouldUseSourceMap } }, + // "postcss" loader applies autoprefixer to our CSS. // "css" loader resolves paths in CSS and adds assets as dependencies. // "style" loader turns CSS into JS modules that inject