diff --git a/.env b/.env deleted file mode 100644 index 38aee11b..00000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -HTTPS=true \ No newline at end of file diff --git a/.env.development b/.env.development new file mode 100644 index 00000000..c370e8e7 --- /dev/null +++ b/.env.development @@ -0,0 +1,7 @@ +PUBLIC_URL=/ + +REACT_APP_CLIENT_V3_HOST=http://localhost:3001 +REACT_APP_API_HOST=http://localhost:5002/ + +REACT_APP_GRAPHQL_HOST=http://localhost:5002/ +REACT_APP_GRAPHQL_HOST_NOCDN=https://v2.velog.io/ \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 00000000..034cf768 --- /dev/null +++ b/.env.production @@ -0,0 +1,7 @@ +PUBLIC_URL=/ + +REACT_APP_CLIENT_V3_HOST=http://localhost:3001 +REACT_APP_API_HOST=http://localhost:5002/ + +REACT_APP_GRAPHQL_HOST=https://v2cdn.velog.io/ +REACT_APP_GRAPHQL_HOST_NOCDN=https://v2.velog.io/ \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 98db1db6..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,22 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - extends: ['plugin:@typescript-eslint/recommended', 'react-app', 'prettier'], - plugins: ['@typescript-eslint', 'react'], - rules: { - 'react/jsx-filename-extension': [ - 1, - { extensions: ['.js', '.jsx', 'tsx', 'ts'] }, - ], - '@typescript-eslint/indent': 0, - '@typescript-eslint/no-empty-interface': 0, - 'import/first': 0, - '@typescript-eslint/explicit-function-return-type': 0, - '@typescript-eslint/explicit-member-accessibility': 0, - '@typescript-eslint/prefer-interface': 0, - '@typescript-eslint/no-use-before-define': 0, - '@typescript-eslint/no-non-null-assertion': 0, - '@typescript-eslint/no-explicit-any': 0, - 'no-use-before-define': 0, - '@typescript-eslint/camelcase': 0, - }, -}; diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 00000000..2a95e2f9 --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,62 @@ +name: Deploy Serverless SSR (Dev) + +on: + push: + branches: + - develop + +jobs: + deploy: + name: deploy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Get yarn cache + id: yarn-cache + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v1 + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: yarn install + uses: borales/actions-yarn@v2.0.0 + with: + cmd: install + - name: npm run build:ci + run: npm run build:ci + env: + REACT_APP_API_HOST: 'https://v2.velog.io/' + PUBLIC_URL: 'https://d3v0gm8v6v8olv.cloudfront.net/' + STAGE: true + - name: s3 sync + uses: jakejarvis/s3-sync-action@master + with: + args: --follow-symlinks --delete + env: + AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET_DEV }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY }} + AWS_REGION: 'ap-northeast-2' + SOURCE_DIR: 'build' + + - name: serverless deploy + uses: serverless/github-action@master + with: + args: deploy + env: + # or if using AWS creds directly + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY }} + REACT_APP_API_HOST: 'https://v2.velog.io/' + PUBLIC_URL: 'https://d3v0gm8v6v8olv.cloudfront.net/' + STAGE: true + - name: Slack Notification + uses: rtCamp/action-slack-notify@v2.0.0 + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + SLACK_USERNAME: 'Github Actions' + SLACK_ICON: 'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png' + SLACK_MESSAGE: '*stage: dev* - 벨로그 웹 클라이언트 배포가 끝났어요! :rocket:' \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..660d8f68 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,52 @@ +name: Deploy + +on: + push: + branches: + - master + +jobs: + deploy: + name: deploy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Setup Node.js 16.x + uses: actions/setup-node@v2 + with: + node-version: '16.x' + - name: Get yarn cache + id: yarn-cache + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v3 + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-v1-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: yarn install + uses: borales/actions-yarn@v4.0.0 + with: + cmd: install + - name: npm run build:ci + run: npm run build:ci + env: + REACT_APP_API_HOST: 'https://v2.velog.io/' + REACT_APP_GRAPHQL_HOST: 'https://v2.velog.io/' + REACT_APP_GRAPHQL_HOST_NOCDN: 'https://v2.velog.io/' + PUBLIC_URL: 'https://static.velog.io/' + REACT_APP_REDIS_HOST: ${{ secrets.REDIS_HOST }} + REACT_APP_CLIENT_V3_HOST: 'https://velog.io/' + REACT_APP_WHITELIST_IPS: ${{ secrets.REACT_APP_WHITELIST_IPS }} + CI: false + - name: upload + env: + S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} + S3_BUCKET_SSR: ${{ secrets.AWS_S3_BUCKET_SSR }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY }} + AWS_REGION: 'ap-northeast-2' + run: | + yarn upload + yarn upload:ssr \ No newline at end of file diff --git a/.gitignore b/.gitignore index 34663306..0516f767 100755 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,15 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -/dist \ No newline at end of file +/dist + +# package directories +jspm_packages + +# Serverless directories +.serverless +.webpack + +# ignore setting +.idea +.vscode diff --git a/README.md b/README.md index 590dae5a..76a9a43c 100755 --- a/README.md +++ b/README.md @@ -1,3 +1,20 @@ ## velog-client -NEXT CLIENT FOR VELOG \ No newline at end of file +> Velog is a blog platform for developers. It provides compfy markdown editor with syntax highlighter enabled. Currently, this service only supports Korean language. + +Website link: https://velog.io/ + +Backend project of service is at another Repo - [velog-backend](https://github.com/velopert/velog-server) + +### Project Stack + +- React +- React Router +- TypeScript +- Redux +- Apollo GraphQL +- Styled Components +- Remark +- Codemirror +- Serverless Framework +- AWS Lambda diff --git a/asset-license.md b/asset-license.md index 89cef52a..db7d2bd0 100644 --- a/asset-license.md +++ b/asset-license.md @@ -3,4 +3,9 @@ Icons made by [Smashicons](https://www.flaticon.com/authors/smashicons) from [ww
Icons made by SimpleIcon from www.flaticon.com is licensed by CC 3.0 BY
-
Icons made by SimpleIcon from www.flaticon.com is licensed by CC 3.0 BY
\ No newline at end of file +
Icons made by SimpleIcon from www.flaticon.com is licensed by CC 3.0 BY
+ + +Heart Icon: https://iconmonstr.com/favorite-8-svg/ +Clip Icon: https://iconmonstr.com/paperclip-2-svg/ +Check Icon: https://iconmonstr.com/check-mark-1-svg/ \ No newline at end of file diff --git a/config/env.js b/config/env.js index 3e22182c..3ec40919 100644 --- a/config/env.js +++ b/config/env.js @@ -1,5 +1,3 @@ - - const fs = require('fs'); const path = require('path'); const paths = require('./paths'); @@ -15,7 +13,7 @@ if (!NODE_ENV) { } // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use -var dotenvFiles = [ +const dotenvFiles = [ `${paths.dotenv}.${NODE_ENV}.local`, `${paths.dotenv}.${NODE_ENV}`, // Don't include `.env.local` for `test` environment @@ -77,7 +75,6 @@ function getClientEnvironment(publicUrl) { // This should only be used as an escape hatch. Normally you would put // images into the `src` and `import` them in code to get their paths. PUBLIC_URL: publicUrl, - APP_ENV: process.env.APP_ENV || 'browser', }, ); // Stringify all values so we can feed into Webpack DefinePlugin @@ -88,7 +85,12 @@ function getClientEnvironment(publicUrl) { }, {}), }; - return { raw, stringified }; + const stringifiedForServerless = Object.keys(raw).reduce((env, key) => { + env[`process.env.${key}`] = JSON.stringify(raw[key]); + return env; + }, {}); + + return { raw, stringified, stringifiedForServerless }; } module.exports = getClientEnvironment; diff --git a/config/jest/fileTransform.js b/config/jest/fileTransform.js index 07010e33..aab67618 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,17 +11,26 @@ module.exports = { const assetFilename = JSON.stringify(path.basename(filename)); if (filename.match(/\.svg$/)) { - return `module.exports = { + // 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: (props) => ({ - $$typeof: Symbol.for('react.element'), - type: 'svg', - ref: null, - 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 new file mode 100644 index 00000000..c8efd0dd --- /dev/null +++ b/config/modules.js @@ -0,0 +1,141 @@ +'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. + * + * @param {Object} options + */ +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); + } + + 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; + } + + // Allow the user set the `baseUrl` to `appSrc`. + if (path.relative(paths.appSrc, baseUrlResolved) === '') { + return [paths.appSrc]; + } + + // If the path is equal to the root directory we ignore it here. + // We don't want to allow importing from the root directly as source files are + // 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; + } + + // 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.' + ) + ); +} + +/** + * Get webpack aliases based on the baseUrl of a compilerOptions object. + * + * @param {*} options + */ +function getWebpackAliases(options = {}) { + const baseUrl = options.baseUrl; + + if (!baseUrl) { + return {}; + } + + const baseUrlResolved = path.resolve(paths.appPath, baseUrl); + + if (path.relative(paths.appPath, baseUrlResolved) === '') { + return { + src: paths.appSrc, + }; + } +} + +/** + * Get jest aliases based on the baseUrl of a compilerOptions object. + * + * @param {*} options + */ +function getJestAliases(options = {}) { + const baseUrl = options.baseUrl; + + if (!baseUrl) { + return {}; + } + + const baseUrlResolved = path.resolve(paths.appPath, baseUrl); + + if (path.relative(paths.appPath, baseUrlResolved) === '') { + return { + '^src/(.*)$': '/src/$1', + }; + } +} + +function getModules() { + // 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 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); + } + + config = config || {}; + const options = config.compilerOptions || {}; + + const additionalModulePaths = getAdditionalModulePaths(options); + + return { + additionalModulePaths: additionalModulePaths, + webpackAliases: getWebpackAliases(options), + jestAliases: getJestAliases(options), + hasTsConfig, + }; +} + +module.exports = getModules(); diff --git a/config/paths.js b/config/paths.js index 62e134c8..98742e73 100644 --- a/config/paths.js +++ b/config/paths.js @@ -74,14 +74,17 @@ module.exports = { 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'), publicUrl: getPublicUrl(resolveApp('package.json')), servedPath: getServedPath(resolveApp('package.json')), - ssrIndexJs: resolveApp('src/server'), + ssrIndexJs: resolveApp('src/index.server.ts'), + ssrServerlessEntry: resolveApp('src/serverless.ts'), ssrBuild: resolveApp('dist'), + ssrServerlessBuild: resolveApp('.webpack'), }; module.exports.moduleFileExtensions = moduleFileExtensions; 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.js b/config/webpack.config.js index 6d6b34ed..37d9638b 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -1,4 +1,3 @@ -/* eslint-disable */ const fs = require('fs'); const path = require('path'); const webpack = require('webpack'); @@ -18,13 +17,16 @@ const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeM const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); const paths = require('./paths'); +const modules = require('./modules'); const getClientEnvironment = require('./env'); const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); -const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin'); const typescriptFormatter = require('react-dev-utils/typescriptFormatter'); const LoadablePlugin = require('@loadable/webpack-plugin'); -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') - .BundleAnalyzerPlugin; + +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'; @@ -32,6 +34,10 @@ const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; // 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); @@ -47,6 +53,11 @@ module.exports = function(webpackEnv) { 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'); + // 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. // In development, we always serve from the root. This makes config easier. @@ -72,10 +83,7 @@ module.exports = function(webpackEnv) { isEnvDevelopment && require.resolve('style-loader'), isEnvProduction && { loader: MiniCssExtractPlugin.loader, - options: Object.assign( - {}, - shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined, - ), + options: shouldUseRelativeAssetPaths ? { publicPath: '../../' } : {}, }, { loader: require.resolve('css-loader'), @@ -98,18 +106,30 @@ module.exports = function(webpackEnv) { }, 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 : isEnvDevelopment, + sourceMap: isEnvProduction && shouldUseSourceMap, }, }, ].filter(Boolean); if (preProcessor) { - loaders.push({ - loader: require.resolve(preProcessor), - options: { - sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, + loaders.push( + { + loader: require.resolve('resolve-url-loader'), + options: { + sourceMap: isEnvProduction && shouldUseSourceMap, + }, + }, + { + loader: require.resolve(preProcessor), + options: { + sourceMap: true, + }, }, - }); + ); } return loaders; }; @@ -122,7 +142,7 @@ module.exports = function(webpackEnv) { ? shouldUseSourceMap ? 'source-map' : false - : isEnvDevelopment && 'eval-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: [ @@ -152,11 +172,13 @@ 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].[chunkhash:8].js' + ? 'static/js/[name].[contenthash:8].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].[chunkhash:8].chunk.js' + ? 'static/js/[name].[contenthash:8].chunk.js' : isEnvDevelopment && 'static/js/[name].chunk.js', // We inferred the "public path" (such as / or /my-project) from homepage. // We use "/" in development. @@ -169,6 +191,12 @@ module.exports = function(webpackEnv) { .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. + jsonpFunction: `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', }, optimization: { minimize: isEnvProduction, @@ -177,8 +205,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 @@ -194,13 +222,16 @@ 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, }, mangle: { safari10: true, }, + // Added for profiling in devtools + keep_classnames: isEnvProductionProfile, + keep_fnames: isEnvProductionProfile, output: { ecma: 5, comments: false, @@ -209,11 +240,6 @@ module.exports = function(webpackEnv) { ascii_only: true, }, }, - // Use multi-process parallel running to improve the build speed - // Default number of concurrent runs: os.cpus().length - 1 - parallel: true, - // Enable file caching - cache: true, sourceMap: shouldUseSourceMap, }), // This is only used in production mode @@ -242,16 +268,18 @@ module.exports = function(webpackEnv) { }, // Keep the runtime chunk separated to enable long term caching // https://twitter.com/wSokra/status/969679223278505985 - runtimeChunk: true, + // 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'].concat( - // It is guaranteed to exist because we tweak it in `env.js` - process.env.NODE_PATH.split(path.delimiter).filter(Boolean), + 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 @@ -266,6 +294,12 @@ module.exports = function(webpackEnv) { // 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', + // Allows for better profiling with ReactDevTools + ...(isEnvProductionProfile && { + '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 @@ -300,8 +334,10 @@ module.exports = function(webpackEnv) { use: [ { options: { + cache: true, formatter: require.resolve('react-dev-utils/eslintFormatter'), eslintPath: require.resolve('eslint'), + resolvePluginsRelativeTo: __dirname, }, loader: require.resolve('eslint-loader'), }, @@ -320,7 +356,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]', }, }, @@ -341,7 +377,8 @@ module.exports = function(webpackEnv) { { loaderMap: { svg: { - ReactComponent: '@svgr/webpack?-svgo![path]', + ReactComponent: + '@svgr/webpack?-svgo,+titleProp,+ref![path]', }, }, }, @@ -351,7 +388,8 @@ module.exports = function(webpackEnv) { // It enables caching results in ./node_modules/.cache/babel-loader/ // directory for faster rebuilds. cacheDirectory: true, - cacheCompression: isEnvProduction, + // See #6846 for context on why cacheCompression is disabled + cacheCompression: false, compact: isEnvProduction, }, }, @@ -372,21 +410,16 @@ module.exports = function(webpackEnv) { ], ], cacheDirectory: true, - cacheCompression: isEnvProduction, + // See #6846 for context on why cacheCompression is disabled + cacheCompression: false, - // If an error happens in a package, it's possible to be - // because it was compiled. Thus, we don't want the browser - // debugger to show the original code. Instead, the code - // being evaluated would be much more helpful. - sourceMaps: false, + // Babel sourcemaps are needed for debugging into node_modules + // code. Without the options below, debuggers like VSCode + // show incorrect code and set breakpoints on the wrong lines. + sourceMaps: shouldUseSourceMap, + inputSourceMap: shouldUseSourceMap, }, }, - // handles .graphql files - { - test: /\.(graphql|gql)$/, - exclude: /node_modules/, - loader: require('graphql-tag/loader'), - }, // "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