react-cli 环境配置

293 阅读7分钟

开发环境

package.json
{
  "name": "react-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "npm run dev",
    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "browserslist": [
    "last 2 version",
    "> 1%",
    "not dead"
  ],
  "devDependencies": {
    "@babel/core": "^7.20.2",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
    "babel-loader": "^9.1.0",
    "babel-preset-react-app": "^10.0.1",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.2",
    "eslint-config-react-app": "^7.0.1",
    "eslint-webpack-plugin": "^3.2.0",
    "html-webpack-plugin": "^5.5.0",
    "less-loader": "^11.1.0",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.8.3",
    "react-app": "^1.1.2",
    "react-refresh": "^0.14.0",
    "sass": "^1.56.1",
    "sass-loader": "^13.2.0",
    "style-loader": "^3.3.1",
    "stylus-loader": "^7.1.0",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.0",
    "webpack-dev-server": "^4.11.1"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.4.3"
  }
}

.eslintrc.js
module.exports = {
  extends: ["react-app"], // 继承react官方规则
  // 解析选项
  parserOptions: {
    babelOptions: {
      presets: [
        // 解决页面报错问题
        ["babel-preset-react-app", false],
        "babel-preset-react-app/prod"
      ]
    }
  },
  // 具体检查规则
  rules: {},
  // 其它规则详见: http://eslint.bootcss.com/docs/user-guide/configuring
}
babel.config.js
module.exports = {
  // 预设
  presets: [
    // https://github.com/facebook/create-react-app
    // babel-preset-react-app
    ["react-app"]
  ]
}
config/webpack.dev.js
const path = require('path');
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); // react提供的js HMR插件

// 返回处理样式loader的函数
const getStyleLoaders = (pre) => {
  return [
    "style-loader",
    "css-loader",
    {
      // 处理浏览器兼容性问题
      // 需要配合package.json中的browserslist来指定兼容性处理到什么程度
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins:['postcss-preset-env']
        }
      }
    },
    pre
  ].filter(Boolean);
}
module.exports = {
  entry: './src/main.js',
  output: {
    path: undefined,
    filename: 'static/js/[name].js',
    chunkFilename: 'static/js/[name].chunk.js',
    assetModuleFilename: 'static/media/[hash:10][ext][query]',
  },
  module: {
    rules: [
      // 处理css
      {
        test: /\.css$/i,
        use: getStyleLoaders()
      },
      {
        test: /\.less$/i,
        use: getStyleLoaders('less-loader')
      },
      {
        test: /\.s[ac]ss$/i,
        use: getStyleLoaders('sass-loader')
      },
      {
        test: /\.styl$/i,
        use: getStyleLoaders('stylus-loader')
      },
      // 处理图片
      {
        test: /.(png|jpe?g|gif|webp|svg)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            // 小于10kb的图片会转base64
            maxSize: 10 * 1024 // 10kb
          }
        },
      },
      // 处理其他资源
      {
        test: /.(ttf|woff2?|mp3|mp4|avi)$/, 
        type: "asset/resource"
      },
      // 处理js
      // 配置babel
      {
        test: /\.jsx?$/,
        include: path.resolve(__dirname, '../src'), // 只处理src
        loader: 'babel-loader',
        options: {
          cacheDirectory: true, // 开启babel缓存
          cacheCompression: false, // 关闭缓存文件压缩
          plugins: ['react-refresh/babel'], // 激活js的HMR
        }
      }
    ]
  },
  plugins: [
    new ESLintPlugin({
      context: path.resolve(__dirname, '../src'),
      exclude: ['node_modules'],
      cache: true, // 开启eslint缓存
      cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'), // eslint缓存目录
    }),
    // 处理html
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html')
    }),
    new ReactRefreshWebpackPlugin(), // 激活js的HMR
  ],
  // 模式
  mode: 'development',
  // SourceMap
  devtool: 'cheap-module-source-map',
  optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all",
      // 其它的都用默认值即可
    },
    // runtime文件来保存文件的hash值
    runtimeChunk: {
      name: entrypoint => `runtime-${entrypoint.name}.js`
    }
  },
  // webpack解析模块加载选项
  resolve: {
    extensions: ['.jsx', '.js', '.json']
  },
  devServer: {
    host: 'localhost', // 启动服务器的域名
    port: 4000, // 启动服务器端口号
    open: true,
    hot: true, // 默认开启
    historyApiFallback: true, // 解决前端路由刷新404问题
  },
}
src/main.ts
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'

const root = ReactDOM.createRoot(document.getElementById("app"));
root.render(
<BrowserRouter>
  <App />
</BrowserRouter>
);
src/App.jsx
import React, { Suspense, lazy } from 'react'
import { Link, Routes, Route } from 'react-router-dom'
// import Home from './pages/Home'
// import About from './pages/About'
// 路由懒加载
const Home = lazy(() => import(/* webpackChunkName: 'home' */ './pages/Home'));
const About = lazy(() => import(/* webpackChunkName: 'about' */ './pages/About'));

function App() {
  return (
    <div>
      <h1>app</h1>
      <ul>
        <li><Link to="/home">home</Link></li>
        <li><Link to="/about">about</Link></li>
      </ul>
      <Suspense fallback={<div>loading...</div>}>
        <Routes>
          <Route path="/home" element={<Home/>}></Route>
          <Route path="/about" element={<About/>}></Route>
        </Routes>
      </Suspense>
      
    </div>
  ) 
}

export default App;

生产环境配置

package.json
{
  "name": "react-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "npm run dev",
    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js",
    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "browserslist": [
    "last 2 version",
    "> 1%",
    "not dead"
  ],
  "devDependencies": {
    "@babel/core": "^7.20.2",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
    "babel-loader": "^9.1.0",
    "babel-preset-react-app": "^10.0.1",
    "copy-webpack-plugin": "^11.0.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.2",
    "css-minimizer-webpack-plugin": "^4.2.2",
    "eslint-config-react-app": "^7.0.1",
    "eslint-webpack-plugin": "^3.2.0",
    "html-webpack-plugin": "^5.5.0",
    "image-minimizer-webpack-plugin": "^3.8.1",
    "imagemin-gifsicle": "^7.0.0",
    "imagemin-jpegtran": "^7.0.0",
    "imagemin-optipng": "^8.0.0",
    "imagemin-svgo": "^10.0.1",
    "less-loader": "^11.1.0",
    "mini-css-extract-plugin": "^2.7.0",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.8.3",
    "react-app": "^1.1.2",
    "react-refresh": "^0.14.0",
    "sass": "^1.56.1",
    "sass-loader": "^13.2.0",
    "style-loader": "^3.3.1",
    "stylus-loader": "^7.1.0",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.0",
    "webpack-dev-server": "^4.11.1"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.4.3"
  }
}

config/webpack.prod.js
const path = require('path');
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); // react提供的js HMR插件
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 提取css为单独文件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // css压缩
const TerserWebpackPlugin = require('terser-webpack-plugin'); // 内置(压缩js)
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); // 图片压缩
const CopyPlugin = require("copy-webpack-plugin"); // 复制插件

// 返回处理样式loader的函数
const getStyleLoaders = (pre) => {
  return [
    MiniCssExtractPlugin.loader, // 提取css为单独文件
    "css-loader",
    {
      // 处理浏览器兼容性问题
      // 需要配合package.json中的browserslist来指定兼容性处理到什么程度
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins:['postcss-preset-env']
        }
      }
    },
    pre
  ].filter(Boolean);
}
module.exports = {
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: 'static/js/[name].[contenthash:10].js',
    chunkFilename: 'static/js/[name].[contenthash:10].chunk.js',
    assetModuleFilename: 'static/media/[hash:10][ext][query]',
    clean: true,
  },
  module: {
    rules: [
      // 处理css
      {
        test: /\.css$/i,
        use: getStyleLoaders()
      },
      {
        test: /\.less$/i,
        use: getStyleLoaders('less-loader')
      },
      {
        test: /\.s[ac]ss$/i,
        use: getStyleLoaders('sass-loader')
      },
      {
        test: /\.styl$/i,
        use: getStyleLoaders('stylus-loader')
      },
      // 处理图片
      {
        test: /.(png|jpe?g|gif|webp|svg)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            // 小于10kb的图片会转base64
            maxSize: 10 * 1024 // 10kb
          }
        },
      },
      // 处理其他资源
      {
        test: /.(ttf|woff2?|mp3|mp4|avi)$/, 
        type: "asset/resource"
      },
      // 处理js
      // 配置babel
      {
        test: /\.jsx?$/,
        include: path.resolve(__dirname, '../src'), // 只处理src
        loader: 'babel-loader',
        options: {
          cacheDirectory: true, // 开启babel缓存
          cacheCompression: false, // 关闭缓存文件压缩
          // plugins: ['react-refresh/babel'], // 激活js的HMR 生产模式没HMR功能
        }
      }
    ]
  },
  plugins: [
    new ESLintPlugin({
      context: path.resolve(__dirname, '../src'),
      exclude: ['node_modules'],
      cache: true, // 开启eslint缓存
      cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'), // eslint缓存目录
    }),
    // 处理html
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html')
    }),
    // new ReactRefreshWebpackPlugin(), // 激活js的HMR 生产环境无HMR功能
    new MiniCssExtractPlugin({ // 提取css为单独文件
      filename: 'static/css/[name].[contenthash:10].css',
      chunkFilename: 'static/css/[name].[contenthash:10].chunk.css',
    }),
    new CopyPlugin({
      patterns: [
        { // 把public下的文件复制到dist目录
          from: path.resolve(__dirname, '../public'),
          to: path.resolve(__dirname, '../dist'),
          globOptions: {
            ignore: ["**/index.html"], // 忽略public中的index.html
          },
        },
      ],
    }),
  ],
  // 模式
  mode: 'production',
  // SourceMap
  devtool: 'source-map',
  optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all",
      // 其它的都用默认值即可
    },
    // runtime文件来保存文件的hash值
    runtimeChunk: {
      name: entrypoint => `runtime-${entrypoint.name}.js`
    },
    minimizer: [
      new CssMinimizerPlugin(), // css压缩
      new TerserWebpackPlugin(), // 压缩js
      // 压缩图片(也可以放到plugin配置中)
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
            plugins: [
              ["gifsicle", { interlaced: true }],
              ["jpegtran", { progressive: true }],
              ["optipng", { optimizationLevel: 5 }],
              [
                "svgo",
                {
                  plugins: [
                    "preset-default",
                    "prefixIds",
                    {
                      name: "sortAttrs",
                      params: {
                        xmlnsOrder: "alphabetical"
                      }
                    }
                  ]
                }
              ]
            ]
          }
        }
      }),
    ]
  },
  // webpack解析模块加载选项
  resolve: {
    extensions: ['.jsx', '.js', '.json']
  },
}

合并开发配置和生产配置

package.json
{
  "name": "react-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "npm run dev",
    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js",
    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "browserslist": [
    "last 2 version",
    "> 1%",
    "not dead"
  ],
  "devDependencies": {
    "@babel/core": "^7.20.2",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
    "babel-loader": "^9.1.0",
    "babel-preset-react-app": "^10.0.1",
    "copy-webpack-plugin": "^11.0.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.2",
    "css-minimizer-webpack-plugin": "^4.2.2",
    "eslint-config-react-app": "^7.0.1",
    "eslint-webpack-plugin": "^3.2.0",
    "html-webpack-plugin": "^5.5.0",
    "image-minimizer-webpack-plugin": "^3.8.1",
    "imagemin-gifsicle": "^7.0.0",
    "imagemin-jpegtran": "^7.0.0",
    "imagemin-optipng": "^8.0.0",
    "imagemin-svgo": "^10.0.1",
    "less-loader": "^11.1.0",
    "mini-css-extract-plugin": "^2.7.0",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.8.3",
    "react-app": "^1.1.2",
    "react-refresh": "^0.14.0",
    "sass": "^1.56.1",
    "sass-loader": "^13.2.0",
    "style-loader": "^3.3.1",
    "stylus-loader": "^7.1.0",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.0",
    "webpack-dev-server": "^4.11.1"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.4.3"
  }
}

config/webpack.config.js
const path = require('path');
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); // react提供的js HMR插件
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 提取css为单独文件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // css压缩
const TerserWebpackPlugin = require('terser-webpack-plugin'); // 内置(压缩js)
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); // 图片压缩
const CopyPlugin = require("copy-webpack-plugin"); // 复制插件

// 获取cross-env定义的环境变量
const isProduction = process.env.NODE_ENV === 'production';
// 返回处理样式loader的函数
const getStyleLoaders = (pre) => {
  return [
    isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
    "css-loader",
    {
      // 处理浏览器兼容性问题
      // 需要配合package.json中的browserslist来指定兼容性处理到什么程度
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins:['postcss-preset-env']
        }
      }
    },
    pre
  ].filter(Boolean);
}
module.exports = {
  entry: './src/main.js',
  output: {
    path: isProduction ? path.resolve(__dirname, '../dist') : undefined,
    filename: isProduction ? 'static/js/[name].[contenthash:10].js' : 'static/js/[name].js',
    chunkFilename: isProduction ? 'static/js/[name].[contenthash:10].chunk.js' : 'static/js/[name].chunk.js',
    assetModuleFilename: 'static/media/[hash:10][ext][query]',
    clean: true,
  },
  module: {
    rules: [
      // 处理css
      {
        test: /\.css$/i,
        use: getStyleLoaders()
      },
      {
        test: /\.less$/i,
        use: getStyleLoaders('less-loader')
      },
      {
        test: /\.s[ac]ss$/i,
        use: getStyleLoaders('sass-loader')
      },
      {
        test: /\.styl$/i,
        use: getStyleLoaders('stylus-loader')
      },
      // 处理图片
      {
        test: /.(png|jpe?g|gif|webp|svg)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            // 小于10kb的图片会转base64
            maxSize: 10 * 1024 // 10kb
          }
        },
      },
      // 处理其他资源
      {
        test: /.(ttf|woff2?|mp3|mp4|avi)$/, 
        type: "asset/resource"
      },
      // 处理js
      // 配置babel
      {
        test: /\.jsx?$/,
        include: path.resolve(__dirname, '../src'), // 只处理src
        loader: 'babel-loader',
        options: {
          cacheDirectory: true, // 开启babel缓存
          cacheCompression: false, // 关闭缓存文件压缩
          plugins: [ !isProduction && 'react-refresh/babel'].filter(Boolean), // 激活js的HMR 生产模式没HMR功能
        }
      }
    ]
  },
  plugins: [
    new ESLintPlugin({
      context: path.resolve(__dirname, '../src'),
      exclude: ['node_modules'],
      cache: true, // 开启eslint缓存
      cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'), // eslint缓存目录
    }),
    // 处理html
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html')
    }),
    !isProduction && new ReactRefreshWebpackPlugin(), // 激活js的HMR 生产环境无HMR功能
    isProduction && new MiniCssExtractPlugin({ // 提取css为单独文件
      filename: 'static/css/[name].[contenthash:10].css',
      chunkFilename: 'static/css/[name].[contenthash:10].chunk.css',
    }),
    isProduction && new CopyPlugin({
      patterns: [
        { // 把public下的文件复制到dist目录
          from: path.resolve(__dirname, '../public'),
          to: path.resolve(__dirname, '../dist'),
          globOptions: {
            ignore: ["**/index.html"], // 忽略public中的index.html
          },
        },
      ],
    }),
  ].filter(Boolean),
  // 模式
  mode: isProduction ? 'production' : 'development',
  // SourceMap
  devtool: isProduction ? 'source-map' : 'cheap-module-source-map',
  optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all",
      // 其它的都用默认值即可
    },
    // runtime文件来保存文件的hash值
    runtimeChunk: {
      name: entrypoint => `runtime-${entrypoint.name}.js`
    },
    minimize: isProduction, // 是否需要压缩(生产环境下开启)
    minimizer: [
      new CssMinimizerPlugin(), // css压缩
      new TerserWebpackPlugin(), // 压缩js
      // 压缩图片(也可以放到plugin配置中)
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
            plugins: [
              ["gifsicle", { interlaced: true }],
              ["jpegtran", { progressive: true }],
              ["optipng", { optimizationLevel: 5 }],
              [
                "svgo",
                {
                  plugins: [
                    "preset-default",
                    "prefixIds",
                    {
                      name: "sortAttrs",
                      params: {
                        xmlnsOrder: "alphabetical"
                      }
                    }
                  ]
                }
              ]
            ]
          }
        }
      }),
    ]
  },
  // webpack解析模块加载选项
  resolve: {
    extensions: ['.jsx', '.js', '.json']
  },
  devServer: {
    host: 'localhost', // 启动服务器的域名
    port: 4000, // 启动服务器端口号
    open: true,
    hot: true, // 默认开启
    historyApiFallback: true, // 解决前端路由刷新404问题
  },
}

优化配置

1、引入antd配置antd的自定义主题色

antd的样式是用less写的,要改antd中的某一个颜色变量
自定义antd样式:4x.ant.design/docs/react/… main.js引入andt样式

import 'antd/dist/antd.less'
2、将react、andt、node_modules单独打包

package.json

{
  "name": "react-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "npm run dev",
    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js",
    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "browserslist": [
    "last 2 version",
    "> 1%",
    "not dead"
  ],
  "devDependencies": {
    "@babel/core": "^7.20.2",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
    "babel-loader": "^9.1.0",
    "babel-preset-react-app": "^10.0.1",
    "copy-webpack-plugin": "^11.0.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.2",
    "css-minimizer-webpack-plugin": "^4.2.2",
    "eslint-config-react-app": "^7.0.1",
    "eslint-webpack-plugin": "^3.2.0",
    "html-webpack-plugin": "^5.5.0",
    "image-minimizer-webpack-plugin": "^3.8.1",
    "imagemin-gifsicle": "^7.0.0",
    "imagemin-jpegtran": "^7.0.0",
    "imagemin-optipng": "^8.0.0",
    "imagemin-svgo": "^10.0.1",
    "less-loader": "^11.1.0",
    "mini-css-extract-plugin": "^2.7.0",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.8.3",
    "react-app": "^1.1.2",
    "react-refresh": "^0.14.0",
    "sass": "^1.56.1",
    "sass-loader": "^13.2.0",
    "style-loader": "^3.3.1",
    "stylus-loader": "^7.1.0",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.0",
    "webpack-dev-server": "^4.11.1"
  },
  "dependencies": {
    "antd": "^4.24.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.4.3"
  }
}

webpack.config.js

const path = require('path');
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); // react提供的js HMR插件
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 提取css为单独文件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // css压缩
const TerserWebpackPlugin = require('terser-webpack-plugin'); // 内置(压缩js)
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); // 图片压缩
const CopyPlugin = require("copy-webpack-plugin"); // 复制插件

// 获取cross-env定义的环境变量
const isProduction = process.env.NODE_ENV === 'production';
// 返回处理样式loader的函数
const getStyleLoaders = (pre) => {
  return [
    isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
    "css-loader",
    {
      // 处理浏览器兼容性问题
      // 需要配合package.json中的browserslist来指定兼容性处理到什么程度
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins:['postcss-preset-env']
        }
      }
    },
    pre && {
      loader: pre,
      options: pre === 'less-loader' ? {
        // antd自定义主体配置
        lessOptions: {
          modifyVars: { '@primary-color': '#1DA57A' },
          javascriptEnabled: true,
        },
      } : {}
    }
  ].filter(Boolean);
}
module.exports = {
  entry: './src/main.js',
  output: {
    path: isProduction ? path.resolve(__dirname, '../dist') : undefined,
    filename: isProduction ? 'static/js/[name].[contenthash:10].js' : 'static/js/[name].js',
    chunkFilename: isProduction ? 'static/js/[name].[contenthash:10].chunk.js' : 'static/js/[name].chunk.js',
    assetModuleFilename: 'static/media/[hash:10][ext][query]',
    clean: true,
  },
  module: {
    rules: [
      // 处理css
      {
        test: /\.css$/i,
        use: getStyleLoaders()
      },
      {
        test: /\.less$/i,
        use: getStyleLoaders('less-loader')
      },
      {
        test: /\.s[ac]ss$/i,
        use: getStyleLoaders('sass-loader')
      },
      {
        test: /\.styl$/i,
        use: getStyleLoaders('stylus-loader')
      },
      // 处理图片
      {
        test: /.(png|jpe?g|gif|webp|svg)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            // 小于10kb的图片会转base64
            maxSize: 10 * 1024 // 10kb
          }
        },
      },
      // 处理其他资源
      {
        test: /.(ttf|woff2?|mp3|mp4|avi)$/, 
        type: "asset/resource"
      },
      // 处理js
      // 配置babel
      {
        test: /\.jsx?$/,
        include: path.resolve(__dirname, '../src'), // 只处理src
        loader: 'babel-loader',
        options: {
          cacheDirectory: true, // 开启babel缓存
          cacheCompression: false, // 关闭缓存文件压缩
          plugins: [ !isProduction && 'react-refresh/babel'].filter(Boolean), // 激活js的HMR 生产模式没HMR功能
        }
      }
    ]
  },
  plugins: [
    new ESLintPlugin({
      context: path.resolve(__dirname, '../src'),
      exclude: ['node_modules'],
      cache: true, // 开启eslint缓存
      cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'), // eslint缓存目录
    }),
    // 处理html
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html')
    }),
    !isProduction && new ReactRefreshWebpackPlugin(), // 激活js的HMR 生产环境无HMR功能
    isProduction && new MiniCssExtractPlugin({ // 提取css为单独文件
      filename: 'static/css/[name].[contenthash:10].css',
      chunkFilename: 'static/css/[name].[contenthash:10].chunk.css',
    }),
    isProduction && new CopyPlugin({
      patterns: [
        { // 把public下的文件复制到dist目录
          from: path.resolve(__dirname, '../public'),
          to: path.resolve(__dirname, '../dist'),
          globOptions: {
            ignore: ["**/index.html"], // 忽略public中的index.html
          },
        },
      ],
    }),
  ].filter(Boolean),
  // 模式
  mode: isProduction ? 'production' : 'development',
  // SourceMap
  devtool: isProduction ? 'source-map' : 'cheap-module-source-map',
  optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all",
      // 其它的都用默认值即可
      cacheGroups: {
        // react react-dom react-router-dom 一起打包成一个js文件
        react: {
          test: /[\\/]node_modules[\\/]react(.*)?/,
          name: 'chunk-react', // 包名
          priority: 40, // 权重要比node_modules权重高,否则就打包到node_modules中了
        },
        // antd单独打包
        antd: {
          test: /[\\/]node_modules[\\/]antd[\\/]/,
          name: 'chunk-antd', // 包名
          priority: 30,
        },
        // 剩下node_modules单独打包
        libs: {
          test: /[\\/]node_modules[\\/]/,
          name: 'chunk-libs', // 包名
          priority: 20,
        },
      }
    },
    // runtime文件来保存文件的hash值
    runtimeChunk: {
      name: entrypoint => `runtime-${entrypoint.name}.js`
    },
    minimize: isProduction, // 是否需要压缩(生产环境下开启)
    minimizer: [
      new CssMinimizerPlugin(), // css压缩
      new TerserWebpackPlugin(), // 压缩js
      // 压缩图片(也可以放到plugin配置中)
      // new ImageMinimizerPlugin({
      //   minimizer: {
      //     implementation: ImageMinimizerPlugin.imageminGenerate,
      //     options: {
      //       plugins: [
      //         ["gifsicle", { interlaced: true }],
      //         ["jpegtran", { progressive: true }],
      //         ["optipng", { optimizationLevel: 5 }],
      //         [
      //           "svgo",
      //           {
      //             plugins: [
      //               "preset-default",
      //               "prefixIds",
      //               {
      //                 name: "sortAttrs",
      //                 params: {
      //                   xmlnsOrder: "alphabetical"
      //                 }
      //               }
      //             ]
      //           }
      //         ]
      //       ]
      //     }
      //   }
      // }),
    ]
  },
  // webpack解析模块加载选项
  resolve: {
    extensions: ['.jsx', '.js', '.json']
  },
  devServer: {
    host: 'localhost', // 启动服务器的域名
    port: 4000, // 启动服务器端口号
    open: true,
    hot: true, // 默认开启
    historyApiFallback: true, // 解决前端路由刷新404问题
  },
  performance: false, // 关闭性能分析,提升打包速度
}