webpack实用配置dev--react(一)

一直以来都是使用脚手架创建应用,现在有空认真研究了一下webpack5,从零开始搭建项目,受益颇多,记下心得,以供参考,生产环境请参考生产环境配置

准备工作

  1. 新建项目空白目录 ,并运行npm init -y 命令,初始化包管理配置文件 package.json
  2. 新建config目录 -> webpack.dev.js,webpack开发环境配置
  3. 新建public目录 -> index.html
  4. 新建src目录 -> main.js、app.jsx
<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link href="favicon.ico" ref="shortcut" type="image/x-iocn" />
  <title>Webpack React Cli</title>
</head>
<body>
  <div id="app"></div>
</body>
<!-- 通过插件自动引入 html-webpack-plugin-->
<!-- <script src="../dist/static/js/main.js"></script> -->
</html>
// main.js

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>
);
// App.jsx
import React, { lazy, Suspense } from "react";
import { Link, Route, Routes } from "react-router-dom";
// 路由懒加载
const Home = lazy(() => import(/* webpackChunkName: 'home' */ "./pages/Home"));
const About = lazy(() => import(/* webpackChunkName: 'about' */"./pages/About"));
function App() {
  return (
    <div>
      <ul>
        <li>
          <Link to="/home">home</Link>
        </li>
        <li>
          <Link to="/about">about</Link>
        </li>
      </ul>
      <Suspense>
        <Routes fallback={<div>loading</div>}>
          <Route path="/home" element={<Home />}></Route>
          <Route path="/about" element={<About />}></Route>
        </Routes>
      </Suspense>
    </div>
  );
}
export default App;

配置 package.json

在这里把所有需要的依赖全部安装一下

{
  "name": "webpack_react_cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "npm run dev",
    "dev": "cross-env NODE_ENV=development webpack server --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.21.5",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
    "babel-loader": "^9.1.2",
    "babel-preset-react-app": "^10.0.1",
    "copy-webpack-plugin": "^11.0.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.3",
    "css-minimizer-webpack-plugin": "^5.0.0",
    "eslint": "^8.39.0",
    "eslint-config-react-app": "^7.0.1",
    "eslint-webpack-plugin": "^4.0.1",
    "html-webpack-plugin": "^5.5.1",
    "image-minimizer-webpack-plugin": "^3.8.2",
    "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.5",
    "postcss-loader": "^7.3.0",
    "postcss-preset-env": "^8.3.2",
    "progress-bar-webpack-plugin": "^2.1.0",
    "react-refresh": "^0.14.0",
    "sass-loader": "^13.2.2",
    "style-loader": "^3.3.2",
    "terser-webpack-plugin": "^5.3.7",
    "thread-loader": "^4.0.1",
    "webpack": "^5.81.0",
    "webpack-cli": "^5.0.2",
    "webpack-dev-server": "^4.13.3",
    "webpackbar": "^5.0.2",
    "workbox-webpack-plugin": "^6.5.4"
  },
  "dependencies": {
    "antd": "^5.4.6",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.11.0"
  },
  "resolutions": {
    "//": "Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it",
    "bin-wrapper": "npm:bin-wrapper-china",
    "rollup": "^2.72.0"
  }
}

webpack 配置

基础配置

通过entry节点指定打包的入口,通过output节点指定打包的出口,配置模式mode,自动解析文件类型等。

// webpack.dev.js

module.exports = {
  entry: "./src/main.js",
  // 开发服务器不会输出资源,在内存中编译打包
  output: {
    filename: "static/js/[name].js",
    chunkFilename: "static/js/[name].chunk.js",
    assetModuleFilename: "static/media/[hash:10][ext][query]",
  },
  mode: "development",
  devServer: {
    open: true, // 是否自动打开浏览器
    port: 3000, // 启动服务器端口
    host: "localhost",  // 启动服务器域名
    // 模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:
    // 保留在完全重新加载页面期间丢失的应用程序状态。
    // 只更新变更内容,以节省宝贵的开发时间。
    // 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
    hot: true,
    // 解决开发时刷新404
    // 当使用HTML5 History API时,可能必须使用index.html页面来代替任何404响应。通过将devServer.historyPiFallback设置为true来启用它
    historyApiFallback: true,
  },
  devtool: "source-map",
  resolve: {
    // 自动解析文件扩展名
    extensions: [
      ".web.mjs",".mjs",".web.js",".js",".web.ts",".ts"  ,".web.tsx",".tsx",".json",".web.jsx",".jsx"
    ],
  },
}

配置 loader

loader 从右到左(或从下到上)的解析执行,所以写法要谨慎。

  1. style-loader,把 CSS以style标签的形式 插入到 head 中
  2. css-loader 会对 @import 和 url() 进行处理,就像 js 解析 import/require() 一样
  3. postcss-loader,处理css兼容性问题,配合package.json中browserslist来指定兼容性
  4. less-loader,加载 Less 文件并将他们编译为 CSS
  5. sass-loader,加载 Sass/SCSS 文件并将他们编译为 CSS
  6. babel-loader,在旧的浏览器或环境中将ECMAScript 2015+ 代码转换为向后兼容的 js 代码,可以配合根目录babel.config.js使用
// babel.config.js

module.exports = {
  // 智能预设,能够编译es6语法
  // https://github.com/facebook/create-react-app/blob/main/packages/babel-preset-react-app/create.js
  presets: [
    "react-app"
  ],
  "compact": true
};
// webpack.dev.js

const path = require("path");
function getStyLoader(pre) {
  return [
    "style-loader",
    {
      loader: "css-loader",
      options: {
        // 开启模块化
        modules: {
          mode: "local",
          // 自动匹配module.css开启模块化
          auto: true,
          localIdentName: "[path][name]__[local]--[hash:5]",
          exportLocalsConvention: "camelCase",
        },
      },
    },
    // 处理兼容性,配合package.json中的browserslist使用
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            [
              "postcss-preset-env",
              {
                // 其他选项
              },
            ],
          ],
        },
      },
    },
    pre,
  ].filter(Boolean); // 过滤掉underfind
}
module.exports = {
  module: {
    rules: [
      // 处理css
      {
        test: /\.css$/i,
        use: getStyLoader(),
      },
      {
        test: /\.less$/i,
        use: getStyLoader("less-loader"),
      },
      {
        test: /\.s[ac]ss$/i,
        use: getStyLoader("sass-loader"),
      },
      // 处理图片
      {
        test: /\.(png|jpe?g|gif|webp)$/i,
        // asset可以转换base64
        type: "asset",
        parser: {
          dataUrlCondition: {
            // 小于4kb转化成base64.减少请求,资源会变大一些
            maxSize: 4 * 1024, // 4kb
          },
        },
        generator: {
          // :8,hash钱8位
          filename: "static/images/[hash:8][ext]",
        },
      },
      {
        test: /\.(ttf|mp4)$/i,
        // asset/resource 原封不动输出
        type: "asset/resource",
        // generator: {
        //   // :8,hash钱8位
        //   filename: "static/fonts/[hash:8][ext]",
        // },
      },
      // 处理js
      {
        test: /\.jsx?$/,
        // exclude: /(node_modules)/, // 排除的文件
        include: [path.resolve(__dirname, "../src")],
        use: [
          {
            loader: "babel-loader",
            options: {
              cacheDirectory: true, // 开启缓存
              cacheCompression: false, // 关闭缓存压缩
              plugins: ["react-refresh/babel"], // HMR js
            },
          },
        ],
      },
    ],
  },
};

配置 plugin

  1. eslint-webpack-plugin,eslint检验,配合根目录.eslintrc.js使用
// .eslintrc.js

module.exports = {
  extends: ["react-app"], // 继承 react 官方规则
  parserOptions: {
    babelOptions: {
      presets: [
        // 解决页面报错问题
        ["babel-preset-react-app", false],
        "babel-preset-react-app/prod",
      ],
    },
  },
};
  1. html-webpack-plugin,配置html模板,将js自动添加到html中
  2. @pmmmwh/react-refresh-webpack-plugin,模块热替换,自动更新js模块,而不是刷新页面,css热替换在style-loader中实现
// webpack.dev.js

const path = require("path");
// eslint检验
const ESLintPlugin = require("eslint-webpack-plugin");
// html模板
const HtmlWebpackPlugin = require("html-webpack-plugin");
// HMR
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
module.exports = {
  // 配置plugins
  plugins: [
    new ESLintPlugin({
      // 检查哪些文件
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules",
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(__dirname, "../node_modules/.cache/eslint"),
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    // HMR js
    new ReactRefreshWebpackPlugin(),
  ],
  optimization: {
    // 代码分割
    splitChunks: {
      // include all types of chunks
      chunks: "all",
      cacheGroups: {
        // react react-dom react-router-dom 一起打包成一个js文件
        react: {
          test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
          name: "chunk-react",
          priority: 40,
        },
        // antd 单独打包
        antd: {
          test: /[\\/]node_modules[\\/]antd[\\/]/,
          name: "chunk-antd",
          priority: 30,
        },
        // 剩下node_modules单独打包
        libs: {
          test: /[\\/]node_modules[\\/]/,
          name: "chunk-libs",
          priority: 20,
        },
      },
    },
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}`,
    },
  },
};

完整的 webpack.dev.js 配置

// webpack.dev.js

const path = require("path");
// eslint检验
const ESLintPlugin = require("eslint-webpack-plugin");
// html模板
const HtmlWebpackPlugin = require("html-webpack-plugin");
// HMR
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
function getStyLoader(pre) {
  return [
    "style-loader",
    {
      loader: "css-loader",
      options: {
        modules: {
          mode: "local",
          auto: true,
          localIdentName: "[path][name]__[local]--[hash:5]",
          exportLocalsConvention: "camelCase",
        },
      },
    },
    // 处理兼容性,配合package.json中的browserslist使用
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            [
              "postcss-preset-env",
              {
                // 其他选项
              },
            ],
          ],
        },
      },
    },
    pre,
  ].filter(Boolean); // 过滤掉underfind
}
module.exports = {
  entry: "./src/main.js",
  // 开发服务器不会输出资源,在内存中编译打包
  output: {
    filename: "static/js/[name].js",
    chunkFilename: "static/js/[name].chunk.js",
    assetModuleFilename: "static/media/[hash:10][ext][query]",
  },
  mode: "development",
  devServer: {
    open: true, // 是否自动打开浏览器
    port: 3000, // 启动服务器端口
    host: "localhost", // 启动服务器域名
    // 模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:
    // 保留在完全重新加载页面期间丢失的应用程序状态。
    // 只更新变更内容,以节省宝贵的开发时间。
    // 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
    hot: true,
    // 解决开发时刷新404
    // 当使用HTML5 History API时,可能必须使用index.html页面来代替任何404响应。通过将devServer.historyPiFallback设置为true来启用它
    historyApiFallback: true,
  },
  devtool: "source-map",
  resolve: {
    // 自动解析文件扩展名
    extensions: [
      ".web.mjs",
      ".mjs",
      ".web.js",
      ".js",
      ".web.ts",
      ".ts",
      ".web.tsx",
      ".tsx",
      ".json",
      ".web.jsx",
      ".jsx",
    ],
  },
  module: {
    rules: [
      // 处理css
      {
        test: /\.css$/i,
        use: getStyLoader(),
      },
      {
        test: /\.less$/i,
        use: getStyLoader("less-loader"),
      },
      {
        test: /\.s[ac]ss$/i,
        use: getStyLoader("sass-loader"),
      },
      // 处理图片
      {
        test: /\.(png|jpe?g|gif|webp)$/i,
        // asset可以转换base64
        type: "asset",
        parser: {
          dataUrlCondition: {
            // 小于4kb转化成base64.减少请求,资源会变大一些
            maxSize: 4 * 1024, // 4kb
          },
        },
        generator: {
          // :8,hash钱8位
          filename: "static/images/[hash:8][ext]",
        },
      },
      {
        test: /\.(ttf|mp4)$/i,
        // asset/resource 原封不动输出
        type: "asset/resource",
        // generator: {
        //   // :8,hash钱8位
        //   filename: "static/fonts/[hash:8][ext]",
        // },
      },
      // 处理js
      {
        test: /\.jsx?$/,
        // exclude: /(node_modules)/, // 排除的文件
        include: [path.resolve(__dirname, "../src")],
        use: [
          {
            loader: "babel-loader",
            options: {
              cacheDirectory: true, // 开启缓存
              cacheCompression: false, // 关闭缓存压缩
              plugins: ["react-refresh/babel"], // HMR js
            },
          },
        ],
      },
    ],
  },
  //
  plugins: [
    new ESLintPlugin({
      // 检查哪些文件
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules",
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(__dirname, "../node_modules/.cache/eslint"),
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    // HMR js
    new ReactRefreshWebpackPlugin(),
  ],
  optimization: {
    // 代码分割
    splitChunks: {
      // include all types of chunks
      chunks: "all",
      cacheGroups: {
        // react react-dom react-router-dom 一起打包成一个js文件
        react: {
          test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
          name: "chunk-react",
          priority: 40,
        },
        // antd 单独打包
        antd: {
          test: /[\\/]node_modules[\\/]antd[\\/]/,
          name: "chunk-antd",
          priority: 30,
        },
        // 剩下node_modules单独打包
        libs: {
          test: /[\\/]node_modules[\\/]/,
          name: "chunk-libs",
          priority: 20,
        },
      },
    },
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}`,
    },
  },
};

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小•愿望

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值