diff --git a/config/jest/fileTransform.js b/config/jest/fileTransform.js index 4ed6bdb0..74dc1aa9 100644 --- a/config/jest/fileTransform.js +++ b/config/jest/fileTransform.js @@ -1,6 +1,7 @@ '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 @@ -10,19 +11,27 @@ module.exports = { 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}`; return `const React = require('react'); module.exports = { __esModule: true, default: ${assetFilename}, - ReactComponent: React.forwardRef((props, ref) => ({ - $$typeof: Symbol.for('react.element'), - type: 'svg', - ref: ref, - key: null, - props: Object.assign({}, props, { - children: ${assetFilename} - }) - })), + ReactComponent: React.forwardRef(function ${componentName}(props, ref) { + return { + $$typeof: Symbol.for('react.element'), + type: 'svg', + ref: ref, + key: null, + props: Object.assign({}, props, { + children: ${assetFilename} + }) + }; + }), };`; } diff --git a/config/modules.js b/config/modules.js index 4646eb05..46d9c9ef 100644 --- a/config/modules.js +++ b/config/modules.js @@ -4,6 +4,7 @@ const fs = require('fs'); const path = require('path'); const paths = require('./paths'); const chalk = require('react-dev-utils/chalk'); +const resolve = require('resolve'); /** * Get the baseUrl of a compilerOptions object. @@ -63,7 +64,10 @@ function getModules() { // TypeScript project and set up the config // based on tsconfig.json if (hasTsConfig) { - config = require(paths.appTsConfig); + 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) { diff --git a/config/pnpTs.js b/config/pnpTs.js new file mode 100644 index 00000000..d1b0539f --- /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.demo.js b/config/webpack.config.demo.js index 0a0ef2c3..da2a4965 100644 --- a/config/webpack.config.demo.js +++ b/config/webpack.config.demo.js @@ -25,15 +25,23 @@ const getClientEnvironment = require('./env'); const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin'); const typescriptFormatter = require('react-dev-utils/typescriptFormatter'); +const getPackageJson = require('./getPackageJson'); +const eslint = require('eslint'); 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. const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; // 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' +); + // Check if TypeScript is setup const useTypeScript = fs.existsSync(paths.appTsConfig); @@ -46,6 +54,7 @@ const sassModuleRegex = /\.module\.(scss|sass)$/; // 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'; // Webpack uses `publicPath` to determine where the app is being served from. @@ -66,12 +75,10 @@ module.exports = function(webpackEnv) { // common function to get style loaders const getStyleLoaders = (cssOptions, preProcessor) => { const loaders = [ + isEnvDevelopment && require.resolve('style-loader'), isEnvProduction && { loader: MiniCssExtractPlugin.loader, - options: Object.assign( - {}, - shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined - ), + options: shouldUseRelativeAssetPaths ? { publicPath: '../../' } : {}, }, { loader: require.resolve('css-loader'), @@ -104,12 +111,20 @@ module.exports = function(webpackEnv) { }, ].filter(Boolean); if (preProcessor) { - loaders.push({ - loader: require.resolve(preProcessor), + loaders.push( + { + loader: require.resolve('resolve-url-loader'), options: { sourceMap: isEnvProduction && shouldUseSourceMap, }, - }); + }, + { + loader: require.resolve(preProcessor), + options: { + sourceMap: true, + }, + } + ); } return loaders; }; @@ -118,10 +133,26 @@ module.exports = function(webpackEnv) { mode: 'production', // Stop compilation early in production bail: isEnvProduction, - devtool: shouldUseSourceMap ? 'source-map' : false, + devtool: isEnvProduction + ? shouldUseSourceMap + ? 'source-map' + : false + : 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: isEnvProduction ? [paths.appDemoIndexJs] : [ + // 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.appDemoIndexJs, // We include the app code last so that if there is a runtime error during @@ -132,6 +163,7 @@ module.exports = function(webpackEnv) { // The build folder. path: isEnvProduction ? paths.appDemo : undefined, // 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: 'index.js', @@ -148,7 +180,11 @@ module.exports = function(webpackEnv) { path .relative(paths.appSrcDemo, info.absoluteResourcePath) .replace(/\\/g, '/') - : null, + : isEnvDevelopment && + (info => 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}`, }, optimization: { minimize: isEnvProduction, @@ -157,8 +193,8 @@ module.exports = function(webpackEnv) { new TerserPlugin({ terserOptions: { parse: { - // we want terser to parse ecma 8 code. However, we don't want it - // to apply any minfication steps that turns valid ecma 5 code + // We want terser to parse ecma 8 code. However, we don't want it + // to apply any minification steps that turns valid ecma 5 code // into invalid ecma 5 code. This is why the 'compress' and 'output' // sections only apply transformations that are ecma 5 safe // https://github.com/facebook/create-react-app/pull/4234 @@ -174,7 +210,7 @@ module.exports = function(webpackEnv) { comparisons: false, // Disabled because of an issue with Terser breaking valid code: // https://github.com/facebook/create-react-app/issues/5250 - // Pending futher investigation: + // Pending further investigation: // https://github.com/terser-js/terser/issues/120 inline: 2, }, @@ -236,7 +272,9 @@ module.exports = function(webpackEnv) { // 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 || []), + 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: @@ -286,6 +324,7 @@ module.exports = function(webpackEnv) { options: { formatter: require.resolve('react-dev-utils/eslintFormatter'), eslintPath: require.resolve('eslint'), + resolvePluginsRelativeTo: __dirname, }, loader: require.resolve('eslint-loader'), @@ -305,7 +344,7 @@ module.exports = function(webpackEnv) { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], loader: require.resolve('url-loader'), options: { - limit: 10000, + limit: imageInlineSizeLimit, name: 'static/media/[name].[hash:8].[ext]', }, }, @@ -355,8 +394,6 @@ module.exports = function(webpackEnv) { require.resolve('babel-preset-react-app/dependencies'), { helpers: true }, ], - "@babel/preset-env", - "@babel/preset-react" ], plugins: [ [ @@ -461,24 +498,34 @@ module.exports = function(webpackEnv) { }, plugins: [ // Generates an `index.html` file with the