diff --git a/packages/babel-preset-react-app/create.js b/packages/babel-preset-react-app/create.js index 24b007f69a2..a6e9982c82b 100644 --- a/packages/babel-preset-react-app/create.js +++ b/packages/babel-preset-react-app/create.js @@ -104,6 +104,10 @@ module.exports = function(api, opts, env) { isTypeScriptEnabled && [require('@babel/preset-typescript').default], ].filter(Boolean), plugins: [ + isEnvDevelopment + ? [require('babel-plugin-emotion').default, { sourceMap: true }] + : require('babel-plugin-emotion').default, + // Strip flow types before any other transform, emulating the behavior // order as-if the browser supported all of the succeeding features // https://github.com/facebook/create-react-app/pull/5182 diff --git a/packages/babel-preset-react-app/package.json b/packages/babel-preset-react-app/package.json index 62f509fbb1c..1046f876b62 100644 --- a/packages/babel-preset-react-app/package.json +++ b/packages/babel-preset-react-app/package.json @@ -1,15 +1,15 @@ { - "name": "babel-preset-react-app", - "version": "9.1.0", - "description": "Babel preset used by Create React App", + "name": "xometry-babel-preset-react-app", + "version": "9.2.0", + "description": "Xometry Babel preset used by Xometry Create React App", "repository": { "type": "git", - "url": "https://github.com/facebook/create-react-app.git", + "url": "https://github.com/xometry/create-react-app.git", "directory": "packages/babel-preset-react-app" }, "license": "MIT", "bugs": { - "url": "https://github.com/facebook/create-react-app/issues" + "url": "https://github.com/xometry/create-react-app/issues" }, "files": [ "create.js", @@ -38,6 +38,7 @@ "@babel/preset-typescript": "7.7.4", "@babel/runtime": "7.7.6", "babel-plugin-dynamic-import-node": "2.3.0", + "babel-plugin-emotion": "^10.0.27", "babel-plugin-macros": "2.8.0", "babel-plugin-transform-react-remove-prop-types": "0.4.24" } diff --git a/packages/react-scripts/bin/react-scripts.js b/packages/react-scripts/bin/react-scripts.js index 7e6e290251a..e0e1bf1ab18 100755 --- a/packages/react-scripts/bin/react-scripts.js +++ b/packages/react-scripts/bin/react-scripts.js @@ -24,7 +24,7 @@ const scriptIndex = args.findIndex( const script = scriptIndex === -1 ? args[0] : args[scriptIndex]; const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : []; -if (['build', 'eject', 'start', 'test'].includes(script)) { +if (['build', 'eject', 'start', 'test', 'build-component'].includes(script)) { const result = spawn.sync( 'node', nodeArgs diff --git a/packages/react-scripts/config/jest/babelTransform.js b/packages/react-scripts/config/jest/babelTransform.js index 7feed94c59a..9e34eaf5d71 100644 --- a/packages/react-scripts/config/jest/babelTransform.js +++ b/packages/react-scripts/config/jest/babelTransform.js @@ -10,7 +10,7 @@ const babelJest = require('babel-jest'); module.exports = babelJest.createTransformer({ - presets: [require.resolve('babel-preset-react-app')], + presets: [require.resolve('xometry-babel-preset-react-app')], babelrc: false, configFile: false, }); diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index bad4290061b..3d12ae68fdf 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -38,6 +38,8 @@ const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier'); // @remove-on-eject-end const postcssNormalize = require('postcss-normalize'); +const BUILD_ID = process.env.REACT_APP_BUILD_ID || ''; + const appPackageJson = require(paths.appPackageJson); // Source maps are resource heavy and can cause out of memory issue for large source files. @@ -184,14 +186,14 @@ module.exports = function(webpackEnv) { // 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/bundle.js', + ? `static-${BUILD_ID}/js/[name].[contenthash:8].js` + : isEnvDevelopment && `static-${BUILD_ID}/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].[contenthash:8].chunk.js' - : isEnvDevelopment && 'static/js/[name].chunk.js', + ? `static-${BUILD_ID}/js/[name].[contenthash:8].chunk.js` + : isEnvDevelopment && `static-${BUILD_ID}/js/[name].chunk.js`, // We inferred the "public path" (such as / or /my-project) from homepage. // We use "/" in development. publicPath: publicPath, @@ -270,8 +272,8 @@ module.exports = function(webpackEnv) { : false, }, cssProcessorPluginOptions: { - preset: ['default', { minifyFontValues: { removeQuotes: false } }] - } + preset: ['default', { minifyFontValues: { removeQuotes: false } }], + }, }), ], // Automatically split vendor and commons @@ -396,7 +398,7 @@ module.exports = function(webpackEnv) { loader: require.resolve('url-loader'), options: { limit: imageInlineSizeLimit, - name: 'static/media/[name].[hash:8].[ext]', + name: `static-${BUILD_ID}/media/[name].[hash:8].[ext]`, }, }, // Process application JS with Babel. @@ -407,24 +409,24 @@ module.exports = function(webpackEnv) { loader: require.resolve('babel-loader'), options: { customize: require.resolve( - 'babel-preset-react-app/webpack-overrides' + 'xometry-babel-preset-react-app/webpack-overrides' ), // @remove-on-eject-begin babelrc: false, configFile: false, - presets: [require.resolve('babel-preset-react-app')], + presets: [require.resolve('xometry-babel-preset-react-app')], // Make sure we have a unique cache identifier, erring on the // side of caution. // We remove this when the user ejects because the default // is sane and uses Babel options. Instead of options, we use - // the react-scripts and babel-preset-react-app versions. + // the react-scripts and xometry-babel-preset-react-app versions. cacheIdentifier: getCacheIdentifier( isEnvProduction ? 'production' : isEnvDevelopment && 'development', [ 'babel-plugin-named-asset-import', - 'babel-preset-react-app', + 'xometry-babel-preset-react-app', 'react-dev-utils', 'react-scripts', ] @@ -464,7 +466,9 @@ module.exports = function(webpackEnv) { compact: false, presets: [ [ - require.resolve('babel-preset-react-app/dependencies'), + require.resolve( + 'xometry-babel-preset-react-app/dependencies' + ), { helpers: true }, ], ], @@ -478,7 +482,7 @@ module.exports = function(webpackEnv) { : isEnvDevelopment && 'development', [ 'babel-plugin-named-asset-import', - 'babel-preset-react-app', + 'xometry-babel-preset-react-app', 'react-dev-utils', 'react-scripts', ] @@ -570,7 +574,7 @@ module.exports = function(webpackEnv) { // by webpacks internal loaders. exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/], options: { - name: 'static/media/[name].[hash:8].[ext]', + name: `static-${BUILD_ID}/media/[name].[hash:8].[ext]`, }, }, // ** STOP ** Are you adding a new loader? @@ -644,8 +648,8 @@ module.exports = function(webpackEnv) { new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional - filename: 'static/css/[name].[contenthash:8].css', - chunkFilename: 'static/css/[name].[contenthash:8].chunk.css', + filename: `static-${BUILD_ID}/css/[name].[contenthash:8].css`, + chunkFilename: `static-${BUILD_ID}/css/[name].[contenthash:8].chunk.css`, }), // Generate an asset manifest file with the following content: // - "files" key: Mapping of all asset filenames to their corresponding diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 18c63e4b154..90f8ce47a0d 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -1,10 +1,10 @@ { - "name": "react-scripts", - "version": "3.3.0", - "description": "Configuration and scripts for Create React App.", + "name": "xometry-react-scripts", + "version": "3.5.0", + "description": "Xometry configuration and scripts for Create React App.", "repository": { "type": "git", - "url": "https://github.com/facebook/create-react-app.git", + "url": "https://github.com/xometry/create-react-app.git", "directory": "packages/react-scripts" }, "license": "MIT", @@ -12,7 +12,7 @@ "node": ">=8.10" }, "bugs": { - "url": "https://github.com/facebook/create-react-app/issues" + "url": "https://github.com/xometry/create-react-app/issues" }, "files": [ "bin", @@ -36,7 +36,6 @@ "babel-jest": "^24.9.0", "babel-loader": "8.0.6", "babel-plugin-named-asset-import": "^0.3.5", - "babel-preset-react-app": "^9.1.0", "camelcase": "^5.3.1", "case-sensitive-paths-webpack-plugin": "2.2.0", "css-loader": "3.3.2", @@ -79,7 +78,8 @@ "webpack": "4.41.2", "webpack-dev-server": "3.9.0", "webpack-manifest-plugin": "2.2.0", - "workbox-webpack-plugin": "4.3.1" + "workbox-webpack-plugin": "4.3.1", + "xometry-babel-preset-react-app": "^9.2.0" }, "devDependencies": { "react": "^16.12.0", diff --git a/packages/react-scripts/scripts/build-component.js b/packages/react-scripts/scripts/build-component.js new file mode 100644 index 00000000000..bdc88bf0f48 --- /dev/null +++ b/packages/react-scripts/scripts/build-component.js @@ -0,0 +1,239 @@ +// @remove-on-eject-begin +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +// @remove-on-eject-end +'use strict'; + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'production'; +process.env.NODE_ENV = 'production'; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('../config/env'); +// @remove-on-eject-begin +// Do the preflight checks (only happens before eject). +const verifyPackageTree = require('./utils/verifyPackageTree'); +if (process.env.SKIP_PREFLIGHT_CHECK !== 'true') { + verifyPackageTree(); +} +const verifyTypeScriptSetup = require('./utils/verifyTypeScriptSetup'); +verifyTypeScriptSetup(); +// @remove-on-eject-end + +const path = require('path'); +const chalk = require('react-dev-utils/chalk'); +const fs = require('fs-extra'); +const webpack = require('webpack'); +const configFactory = require('../config/webpack.config'); +const paths = require('../config/paths'); +const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); +const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); +const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); +const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); +const printBuildError = require('react-dev-utils/printBuildError'); + +const measureFileSizesBeforeBuild = + FileSizeReporter.measureFileSizesBeforeBuild; +const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; +const useYarn = fs.existsSync(paths.yarnLockFile); + +// These sizes are pretty large. We'll warn for bundles exceeding them. +const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; +const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; + +const isInteractive = process.stdout.isTTY; + +// Warn and crash if required files are missing +if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { + process.exit(1); +} + +// Generate configuration +const config = configFactory('production'); + +config.optimization.runtimeChunk = false; +config.optimization.splitChunks = { + cacheGroups: { + default: false, + }, +}; +config.output.filename = 'main.js'; + +// We require that you explicitly set browsers and do not fall back to +// browserslist defaults. +const { checkBrowsers } = require('react-dev-utils/browsersHelper'); +checkBrowsers(paths.appPath, isInteractive) + .then(() => { + // First, read the current file sizes in build directory. + // This lets us display how much they changed later. + return measureFileSizesBeforeBuild(paths.appBuild); + }) + .then(previousFileSizes => { + // Remove all content but keep the directory so that + // if you're in it, you don't end up in Trash + fs.emptyDirSync(paths.appBuild); + // Merge with the public folder + copyPublicFolder(); + // Start the webpack build + return build(previousFileSizes); + }) + .then( + ({ stats, previousFileSizes, warnings }) => { + if (warnings.length) { + console.log(chalk.yellow('Compiled with warnings.\n')); + console.log(warnings.join('\n\n')); + console.log( + '\nSearch for the ' + + chalk.underline(chalk.yellow('keywords')) + + ' to learn more about each warning.' + ); + console.log( + 'To ignore, add ' + + chalk.cyan('// eslint-disable-next-line') + + ' to the line before.\n' + ); + } else { + console.log(chalk.green('Compiled successfully.\n')); + } + + console.log('File sizes after gzip:\n'); + printFileSizesAfterBuild( + stats, + previousFileSizes, + paths.appBuild, + WARN_AFTER_BUNDLE_GZIP_SIZE, + WARN_AFTER_CHUNK_GZIP_SIZE + ); + console.log(); + + const appPackage = require(paths.appPackageJson); + const publicUrl = paths.publicUrl; + const publicPath = config.output.publicPath; + const buildFolder = path.relative(process.cwd(), paths.appBuild); + printHostingInstructions( + appPackage, + publicUrl, + publicPath, + buildFolder, + useYarn + ); + }, + err => { + const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; + if (tscCompileOnError) { + console.log( + chalk.yellow( + 'Compiled with the following type errors (you may want to check these before deploying your app):\n' + ) + ); + printBuildError(err); + } else { + console.log(chalk.red('Failed to compile.\n')); + printBuildError(err); + process.exit(1); + } + } + ) + .catch(err => { + if (err && err.message) { + console.log(err.message); + } + process.exit(1); + }); + +// Create the production build and print the deployment instructions. +function build(previousFileSizes) { + // We used to support resolving modules according to `NODE_PATH`. + // This now has been deprecated in favor of jsconfig/tsconfig.json + // This lets you use absolute paths in imports inside large monorepos: + if (process.env.NODE_PATH) { + console.log( + chalk.yellow( + 'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.' + ) + ); + console.log(); + } + + console.log('Creating an optimized production component build...'); + + const compiler = webpack(config); + return new Promise((resolve, reject) => { + compiler.run((err, stats) => { + let messages; + if (err) { + if (!err.message) { + return reject(err); + } + + let errMessage = err.message; + + // Add additional information for postcss errors + if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) { + errMessage += + '\nCompileError: Begins at CSS selector ' + + err['postcssNode'].selector; + } + + messages = formatWebpackMessages({ + errors: [errMessage], + warnings: [], + }); + } else { + messages = formatWebpackMessages( + stats.toJson({ + all: false, + warnings: true, + errors: true, + }) + ); + } + if (messages.errors.length) { + // Only keep the first error. Others are often indicative + // of the same problem, but confuse the reader with noise. + if (messages.errors.length > 1) { + messages.errors.length = 1; + } + return reject(new Error(messages.errors.join('\n\n'))); + } + if ( + process.env.CI && + (typeof process.env.CI !== 'string' || + process.env.CI.toLowerCase() !== 'false') && + messages.warnings.length + ) { + console.log( + chalk.yellow( + '\nTreating warnings as errors because process.env.CI = true.\n' + + 'Most CI servers set it automatically.\n' + ) + ); + return reject(new Error(messages.warnings.join('\n\n'))); + } + + return resolve({ + stats, + previousFileSizes, + warnings: messages.warnings, + }); + }); + }); +} + +function copyPublicFolder() { + fs.copySync(paths.appPublic, paths.appBuild, { + dereference: true, + filter: file => file !== paths.appHtml, + }); +} diff --git a/packages/react-scripts/scripts/init.js b/packages/react-scripts/scripts/init.js index 74d607b105d..2053e8299a4 100644 --- a/packages/react-scripts/scripts/init.js +++ b/packages/react-scripts/scripts/init.js @@ -127,6 +127,7 @@ module.exports = function( { start: 'react-scripts start', build: 'react-scripts build', + 'build-component': 'react-scripts build-component', test: 'react-scripts test', eject: 'react-scripts eject', },