/* eslint-disable import/no-extraneous-dependencies */ const webpack = require('webpack'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // installed via npm const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); // const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin'); const ImageminPlugin = require('imagemin-webpack-plugin').default; const CompressionPlugin = require('compression-webpack-plugin'); const zlib = require('zlib'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const merge = require('webpack-merge'); // local const paths = require('../paths'); /* Need to load the configs here so they can be applied to the external folders as wells */ const babelConfig = require('../../babel.config'); // Need required in the file for parent files const postcssConfig = require('../../postcss.config'); /** * Resolve paths */ exports.modulePathResolve = (srcList, modulesList) => ({ resolve: { modules: [...srcList, ...modulesList], extensions: ['*', '.js', '.jsx', '.ts', '.tsx', '.json', '.mjs', '.gql', '.graphql'], }, resolveLoader: { modules: modulesList, }, }); /** * Clean */ exports.clean = (path, root) => ({ plugins: [new CleanWebpackPlugin()], }); /** * JS (see also .babelrc) */ exports.loadJavaScript = ({ include, exclude, prod } = {}) => ({ module: { rules: [ { test: /\.(js|jsx|ts|mjs|tsx)$/, include, // node_modules(?!([\\/](attr-accept|moment))) exclude: exclude || /node_modules/, use: [ { loader: 'babel-loader', options: { presets: babelConfig.presets, plugins: prod ? babelConfig.plugins.concat('transform-remove-console') : babelConfig.plugins, }, // */ }, ], }, ], }, }); /** * Default output path */ exports.outputPath = ({ path = paths.defaultBuild, filename = '[name].js', chunkFilename = '[name].[contenthash].js', publicPath = paths.publicPath, } = {}) => ({ output: { path, filename, chunkFilename, // This option specifies the public URL of the output directory when referenced in a browser. A relative URL is resolved relative to the HTML page (or tag). publicPath, // https://github.com/webpack/webpack/issues/11660 // addition for webpack 5 // chunkLoading: false, // wasmLoading: false, }, }); /** * (S)CSS */ exports.loadCSS = ({ include, exclude, prod = false, styleLoader = false, server = false } = {}) => { const fileName = prod ? '[name].[contenthash].css' : '[name].css'; // use style loader in dev (no ssr) const useStyleLoader = styleLoader; const pluginOptions = { filename: fileName, ignoreOrder: !prod, // Enable to remove warnings about conflicting order }; const plugin = new MiniCssExtractPlugin(pluginOptions); const loaderOptions = { // disabling esModule prevent from having warning for empty css files => should not be disabled in prod esModule: prod, }; const usages = [ { loader: 'css-loader', options: { sourceMap: true, // The option importLoaders allows you to configure how many loaders before css-loader should be applied to @imported resources. importLoaders: 2, // to use with localIdentName modules: { localIdentName: '[local]--[hash:base64:5]', }, }, }, { loader: 'postcss-loader', options: { postcssOptions: postcssConfig, }, }, { loader: 'sass-loader', options: {}, }, ]; if (!server) { // if not loaded on server part, module styleIdentifiers will not be usable in the server usages.unshift({ loader: useStyleLoader ? 'style-loader' : MiniCssExtractPlugin.loader, options: loaderOptions, }); } return { module: { rules: [ { test: /\.(css|sass|scss)$/, include, exclude, use: usages, }, ], }, plugins: [plugin], }; }; exports.setVariables = obj => { const env = {}; const keys = Object.keys(obj); for (let i = 0; i < keys.length; i += 1) { const key = keys[i]; const value = obj[key]; env[key] = JSON.stringify(value); } return { plugins: [new webpack.DefinePlugin(env)], }; }; /** * FONTS */ const FONTLIST = [ ['woff', 'application/font-woff'], ['woff2', 'application/font-woff2'], ['otf', 'font/opentype'], ['ttf', 'application/octet-stream'], ['eot', 'application/vnd.ms-fontobject'], ['svg', 'image/svg+xml'], ]; exports.loadFonts = () => { let rsltConf = { module: { rules: [], }, }; FONTLIST.forEach(font => { const extension = font[0]; const mimetype = font[1]; const rule = { module: { rules: [ { test: new RegExp(`\\.${extension}$`), loader: 'file-loader', include: [/fonts?/], options: { name: 'fonts/[name].[ext]', // limit: 10000, mimetype, }, }, ], }, }; rsltConf = merge(rsltConf, rule); }); return rsltConf; }; /** * Images */ exports.loadImages = () => ({ module: { rules: [ { test: /\.(png|jpe?g|gif|svg|ico)$/, loader: 'file-loader', exclude: [/fonts?/], options: { name: 'images/[name].[contenthash].[ext]', // for url loader // limit: 8192, }, }, ], }, }); /** * Images */ exports.loadVideos = () => ({ module: { rules: [ { test: /\.(mp4|mov)$/, loader: 'file-loader', options: { name: 'videos/[name].[contenthash].[ext]', }, }, ], }, }); // Idea of splitting vendors exports.optimize = () => ({ // https://webpack.js.org/plugins/compression-webpack-plugin/ plugins: [ // new CompressionPlugin({ // filename: '[path][base].br', // algorithm: 'brotliCompress', // test: /\.(js|css|html|svg)$/, // compressionOptions: { // params: { // [zlib.constants.BROTLI_PARAM_QUALITY]: 11, // }, // }, // threshold: 10240, // minRatio: 0.8, // deleteOriginalAssets: false, // }), ], optimization: { minimizer: [ new TerserPlugin({ terserOptions: { safari10: true, }, }), new OptimizeCSSAssetsPlugin({}), ], // use default splitChunks config with chnks all // https://webpack.js.org/plugins/split-chunks-plugin/#optimizationsplitchunks splitChunks: { chunks: 'all', // cacheGroups: { // vendor: { // // test with exclusion : /[\\/]node_modules[\\/](?!(attr-accept|moment))/ // test: /[\\/]node_modules[\\/]/, // name: 'vendor', // }, // }, }, // Source : create-react-app // Keep the runtime chunk separated to enable long term caching // https://twitter.com/wSokra/status/969679223278505985 // https://github.com/facebook/create-react-app/issues/5358 runtimeChunk: { name: entrypoint => `runtime-${entrypoint.name}`, }, }, }); // Text loader exports.loadTxts = () => ({ module: { rules: [ { test: /\.txt$/i, use: 'raw-loader', }, ], }, }); // GraphQL loader exports.loadGQL = () => ({ module: { rules: [ // fixes https://github.com/graphql/graphql-js/issues/1272 { test: /\.mjs$/, include: /node_modules/, type: 'javascript/auto', }, ], }, }); exports.imageOptimization = () => ({ plugins: [new ImageminPlugin({ test: /\.(jpe?g|png|gif)$/i })], }); // Add stats exports.stats = () => ({ // To discover stats: { cached: false, cachedAssets: false, chunks: false, chunkModules: false, colors: true, hash: false, modules: false, reasons: false, timings: true, version: false, }, });