UNPKG

@bhupinderanand/react-redux-saga-boilerplate

Version:
563 lines (535 loc) 22.3 kB
const fs = require('fs'); const path = require('path'); const { createHash } = require('crypto'); const webpack = require('webpack'); const { format } = require('date-fns'); const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); const CircularDependencyPlugin = require('circular-dependency-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin'); const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); const GitRevPlugin = require('git-rev-webpack-plugin'); const HtmlPlugin = require('html-webpack-plugin'); const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const { WebpackManifestPlugin } = require('webpack-manifest-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); const reactRefreshRuntimeEntry = require.resolve('react-refresh/runtime'); const reactRefreshWebpackPluginRuntimeEntry = require.resolve( '@pmmmwh/react-refresh-webpack-plugin', ); const getClientEnvironment = require('./env'); const modules = require('./modules'); const paths = require('./paths'); const NPMPackage = require(paths.packageJson); const gitRevPlugin = new GitRevPlugin(); const GITHASH = gitRevPlugin.hash() || ''; // 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', 10); // Get the path to the un-compiled service worker (if it exists). const { swSrc } = paths; // style files regexes const cssRegex = /\.css$/; const cssModuleRegex = /\.module\.css$/; const sassRegex = /\.(scss|sass)$/; const sassModuleRegex = /\.module\.(scss|sass)$/; function createEnvironmentHash(env) { const hash = createHash('md5'); hash.update(JSON.stringify(env)); return hash.digest('hex'); } module.exports = 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'); // We will provide `paths.publicUrlOrPath` to our app // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. // Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz. // Get environment variables to inject into our app. const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)); const shouldUseReactRefresh = env.raw.FAST_REFRESH; const htmlPluginOptions = { githash: GITHASH, inject: true, template: paths.appHtml, title: NPMPackage.title, }; if (isEnvProduction) { htmlPluginOptions.minify = { collapseWhitespace: true, keepClosingSlash: true, minifyCSS: true, minifyJS: true, minifyURLs: true, removeComments: true, removeEmptyAttributes: true, removeRedundantAttributes: true, removeStyleLinkTypeAttributes: true, useShortDoctype: true, }; } // common function to get style loaders const getStyleLoaders = (cssOptions, preProcessor) => { const loaders = [ isEnvDevelopment && require.resolve('style-loader'), isEnvProduction && { loader: MiniCssExtractPlugin.loader, // css is located in `static/css`, use '../../' to locate index.html folder // in production `paths.publicUrlOrPath` can be a relative path options: paths.publicUrlOrPath.startsWith('.') ? { publicPath: '../../' } : {}, }, { loader: require.resolve('css-loader'), options: cssOptions, }, { // Options for PostCSS as we reference these options twice // Adds vendor prefixing based on your specified browser support in // package.json loader: require.resolve('postcss-loader'), options: { postcssOptions: { // Necessary for external CSS imports to work // https://github.com/facebook/create-react-app/issues/2677 ident: 'postcss', plugins: [ 'postcss-flexbugs-fixes', [ '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. 'postcss-normalize', ], }, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, }, }, ].filter(Boolean); if (preProcessor) { loaders.push( { loader: require.resolve('resolve-url-loader'), options: { sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, root: paths.appSrc, }, }, { loader: require.resolve(preProcessor), options: { sourceMap: true, }, }, ); } return loaders; }; let devtool = 'cheap-module-source-map'; if (isEnvProduction) { devtool = shouldUseSourceMap ? 'source-map' : false; } return { mode: webpackEnv, bail: isEnvProduction, devtool, target: ['browserslist'], cache: { type: 'filesystem', version: createEnvironmentHash(env.raw), cacheDirectory: paths.webpackCache, store: 'pack', buildDependencies: { defaultWebpack: ['webpack/lib/'], config: [__filename], tsconfig: [paths.appTsConfig], }, }, infrastructureLogging: { level: 'none', }, entry: paths.appIndex, output: { chunkFilename: isEnvProduction ? 'static/js/[name].[git-hash].chunk.js' : 'static/js/[name].chunk.js', // Point sourcemap entries to original disk location (format as URL on Windows) devtoolModuleFilenameTemplate: isEnvProduction ? info => path.relative(paths.appSrc, info.absoluteResourcePath).replace(/\\/g, '/') : info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'), // 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].[git-hash].js' : 'static/js/bundle.js', path: paths.appBuild, pathinfo: isEnvDevelopment, publicPath: paths.publicUrlOrPath, }, optimization: { minimize: isEnvProduction, minimizer: [ new TerserPlugin({ terserOptions: { parse: { // 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 ecma: 8, }, compress: { ecma: 5, warnings: false, // Disabled because of an issue with Uglify breaking seemingly valid code: // https://github.com/facebook/create-react-app/issues/2376 // Pending further investigation: // https://github.com/mishoo/UglifyJS2/issues/2011 comparisons: false, // Disabled because of an issue with Terser breaking valid code: // https://github.com/facebook/create-react-app/issues/5250 // Pending further investigation: // https://github.com/terser-js/terser/issues/120 inline: 2, }, mangle: { safari10: true, }, keep_classnames: isEnvProductionProfile, keep_fnames: isEnvProductionProfile, output: { ecma: 5, comments: false, // Turned on because emoji and regex is not minified properly using default // https://github.com/facebook/create-react-app/issues/2488 ascii_only: true, }, }, }), new CssMinimizerPlugin(), ], }, resolve: { alias: { // 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', assets: paths.appAssets, test: paths.test, ...(isEnvProductionProfile && { 'react-dom$': 'react-dom/profiling', 'scheduler/tracing': 'scheduler/tracing-profiling', }), ...modules.webpackAliases, }, extensions: paths.moduleFileExtensions.map(d => `.${d}`), // 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: [paths.nodeModules, paths.appSrc, ...(modules.additionalModulePaths || [])], plugins: [ // Prevents users from importing files from outside of src/ (or node_modules/). // This often causes confusion because we only process files within src/ with babel. // 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.appAssets, paths.packageJson, reactRefreshRuntimeEntry, reactRefreshWebpackPluginRuntimeEntry, ]), ], }, module: { strictExportPresence: true, rules: [ shouldUseSourceMap && { enforce: 'pre', exclude: /@babel(?:\/|\\{1,2})runtime/, test: /\.(js|mjs|jsx|ts|tsx|css)$/, use: 'source-map-loader', }, { oneOf: [ { test: [/\.avif$/], type: 'asset', mimetype: 'image/avif', parser: { dataUrlCondition: { maxSize: imageInlineSizeLimit, }, }, }, { test: /\.(js|mjs|jsx|ts|tsx)$/, loader: require.resolve('babel-loader'), options: { // 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, plugins: [ isEnvDevelopment && shouldUseReactRefresh && require.resolve('react-refresh/babel'), ].filter(Boolean), }, include: paths.appSrc, }, // "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 <style> tags. // In production, we use MiniCSSExtractPlugin to extract that CSS // to a file, but in development "style" loader enables hot editing // of CSS. // By default we support CSS Modules with the extension .module.css { test: cssRegex, exclude: cssModuleRegex, use: getStyleLoaders({ importLoaders: 1, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, modules: { mode: 'icss', }, }), // Don't consider CSS imports dead code even if the // containing package claims to have no side effects. // Remove this when webpack adds a warning or an error for this. // See https://github.com/webpack/webpack/issues/6571 sideEffects: true, }, // Adds support for CSS Modules (https://github.com/css-modules/css-modules) // using the extension .module.css { test: cssModuleRegex, use: getStyleLoaders({ importLoaders: 1, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, modules: { mode: 'local', getLocalIdent: getCSSModuleLocalIdent, }, }), }, // Opt-in support for SASS (using .scss or .sass extensions). // By default we support SASS Modules with the // extensions .module.scss or .module.sass { test: sassRegex, exclude: sassModuleRegex, use: getStyleLoaders( { importLoaders: 3, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, modules: { mode: 'icss', }, }, 'sass-loader', ), // Don't consider CSS imports dead code even if the // containing package claims to have no side effects. // Remove this when webpack adds a warning or an error for this. // See https://github.com/webpack/webpack/issues/6571 sideEffects: true, }, // Adds support for CSS Modules, but using SASS // using the extension .module.scss or .module.sass { test: sassModuleRegex, use: getStyleLoaders( { importLoaders: 3, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, modules: { mode: 'local', getLocalIdent: getCSSModuleLocalIdent, }, }, 'sass-loader', ), }, { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], use: [ { loader: 'file-loader', options: { name: 'static/[name].[ext]' }, }, { loader: 'image-webpack-loader', options: { optipng: { optimizationLevel: 5, }, pngquant: { quality: [0.75, 0.9], }, }, }, ], }, { test: /\.svg$/, use: [ { loader: '@svgr/webpack', options: { icon: true, typescript: true, }, }, { loader: 'file-loader', options: { name: 'static/[name].[git-hash].[ext]', }, }, ], }, { test: /\.md$/, use: ['html-loader', 'markdown-loader'], }, // "file" loader makes sure those 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: '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/[name].[git-hash].[ext]', }, }, // ** STOP ** Are you adding a new loader? // Make sure to add the new loader(s) before the "file" loader. ], }, ], }, plugins: [ new webpack.DefinePlugin({ ...env.stringified, APP__BRANCH: JSON.stringify(gitRevPlugin.branch()), APP__BUILD_DATE: JSON.stringify(format(new Date(), 'dd/MM/yyyy')), APP__GITHASH: JSON.stringify(gitRevPlugin.hash()), APP__VERSION: JSON.stringify(NPMPackage.version), }), new ModuleNotFoundPlugin(paths.appPath), gitRevPlugin, new HtmlPlugin(htmlPluginOptions), new InterpolateHtmlPlugin(HtmlPlugin, env.raw), // This gives some necessary context to module not found errors, such as // the requesting resource. new ModuleNotFoundPlugin(paths.appPath), new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/, }), isEnvProduction && shouldInlineRuntimeChunk && new InlineChunkHtmlPlugin(HtmlPlugin, [/runtime~.+\.js/]), isEnvProduction && new MiniCssExtractPlugin({ filename: 'css/bundle.[git-hash].css', chunkFilename: 'css/bundle.[git-hash].chunk.css', }), isEnvDevelopment && new CircularDependencyPlugin({ exclude: /node_modules/, failOnError: true, cwd: process.cwd(), }), // This is necessary to emit hot updates (currently CSS only): isEnvDevelopment && new webpack.HotModuleReplacementPlugin(), // Experimental hot reloading for React . // https://github.com/facebook/react/tree/master/packages/react-refresh isEnvDevelopment && shouldUseReactRefresh && new ReactRefreshWebpackPlugin({ overlay: false, }), // Watcher doesn't work well if you mistype casing in a path so we use // a plugin that prints an error when you attempt to do this. // See https://github.com/facebook/create-react-app/issues/240 isEnvDevelopment && new CaseSensitivePathsPlugin(), // Generate an asset manifest file with the following content: // - "files" key: Mapping of all asset filenames to their corresponding // output file so that tools can pick it up without having to parse // `index.html` // - "entrypoints" key: Array of files which are included in `index.html`, // can be used to reconstruct the HTML if necessary new WebpackManifestPlugin({ fileName: 'asset-manifest.json', publicPath: paths.publicUrlOrPath, generate: (seed, files, entrypoints) => { const manifestFiles = files.reduce((manifest, file) => { // eslint-disable-next-line no-param-reassign manifest[file.name] = file.path; return manifest; }, seed); const entrypointFiles = entrypoints.main.filter(fileName => !fileName.endsWith('.map')); return { files: manifestFiles, entrypoints: entrypointFiles, }; }, }), isEnvProduction && fs.existsSync(swSrc) && new WorkboxWebpackPlugin.InjectManifest({ swSrc, dontCacheBustURLsMatching: /\.[\da-f]{8}\./, exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/], // Bump up the default maximum size (2mb) that's precached, // to make lazy-loading failure scenarios less likely. // See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270 maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, }), new ForkTsCheckerWebpackPlugin({ async: isEnvDevelopment, typescript: { basedir: paths.nodeModules, context: paths.appPath, diagnosticOptions: { syntactic: true, }, mode: 'write-references', }, }), ].filter(Boolean), // Turn off performance processing because we utilize // our own hints via the FileSizeReporter performance: false, }; };