webpack5 搭建脚手架 js版

5 大核心概念

1、entry(入口)
指示 Webpack 从哪个文件开始打包
2、output(输出)
指示 Webpack 打包完的文件输出到哪里去,如何命名等
3、loader(加载器)
webpack 本身只能处理 js、json 等资源,其他资源需要借助 loader,Webpack 才能解析
4、plugins(插件)
扩展 Webpack 的功能
5、mode(模式)
主要由两种模式:
开发模式:development
生产模式:production

webpack默认开启tree shaking功能
开发服务器安装
// 运行指定目录 serve dist
npm i serve -g

// 运行当前项目目录
npm i live-server -g
安装webpack
npm install webpack@5.72.0 webpack-cli@4.9.2 -D
// 命令行打包
// npx webpack ./src/main.js --mode=development
// npx webpack ./src/main.js --mode=production
加载css
npm install css-loader@6.7.1 style-loader@3.3.1 -D
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },

兼容css
npm install postcss@8.4.13 postcss-loader@6.2.1 postcss-preset-env@7.5.0 -D
    {
      // 处理css兼容性问题
      // 配合package.json中browserslist来指定兼容性
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: ["postcss-preset-env"],
        },
      },
    },
编译less
npm install less@4.1.2 less-loader@10.2.0 -D
      {
        test: /\.less$/i,
        use: [
          // 使用多个loader
          "style-loader",
          "css-loader",
          "less-loader", // 将less编译成css文件
        ],
      },
编译scss
npm install sass@1.51.0 sass-loader@12.6.0 -D
      {
        test: /\.s[ac]ss$/,
        use: [
          "style-loader",
          "css-loader",
          "sass-loader", // 将sass编译成css文件
        ],
      },
整理图片
// 图片整理 webpack内置
      {
        test: /\.(png|jpe?g|gif|webp|svg)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            // 小于10kb的图片转base64
            // 优点:减少请求数量  缺点:体积会更大
            maxSize: 10 * 1024, // 10kb
          },
        },
        generator: {
          // 输出图片名称
          // [hash:10] hash值取前10位
          filename: "static/images/[hash:10][ext][query]",
        },
      },
验证js和jsx的代码
npm install eslint@8.14.0 eslint-webpack-plugin@3.1.1 -D
npm install thread-loader@3.0.4 -D
// 检查js和jsx的代码,需要配置 .eslintrc.js   也可以直接在package.json中配置eslintConfig
const os = require("os");
const ESLintPlugin = require("eslint-webpack-plugin");
const threads = os.cpus().length;
    new ESLintPlugin({
      // 检测哪些文件
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值
      cache: true, // 开启缓存
      cacheLocation: path.resolve(
        __dirname,
        "../node_modules/.cache/eslintcache"
      ),
      threads, // 开启多进程和设置进程数量
    }),
加载js,默认包含css
npm install babel-loader@8.2.5 @babel/core@7.17.10 @babel/preset-env@7.17.10 -D
npm install thread-loader@3.0.4 -D
// 处理es代码 转成js,把高级语法转成低级语法来兼容低级环境,可设置预设 babel.config.js
// @babel/preset-env  允许使用最新的js
// @babel/preset-react react jsx的预设
// @babel/preset-typescript typescript的预设
npm install thread-loader@3.0.4 -D
npm install @babel/plugin-transform-runtime@7.17.10 -D
// 禁用了babel自动对每个文件的runtime注入,而是引入
// 并且使所有辅助代码从这里引用
// 生产开发环境都可以用
const os = require("os");
const threads = os.cpus().length;
// 默认情况下
      {
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
        include: path.resolve(__dirname, "../src"), // 只处理src下的文件,其他文件不处理
        use: [
          {
            loader: "thread-loader", // 开启多进程
            options: {
              works: threads, // 进程数量
            },
          },
          {
            loader: "babel-loader",
            options: {
              // presets: ["@babel/preset-env"],
              cacheDirectory: true, // 开启babel缓存
              cacheCompression: false, // 关闭缓存文件压缩
              plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
            },
          },
        ],
      },
// react情况下
      {
        test: /\.jsx?$/,
        include: path.resolve(__dirname, "../src"),
        loader: "babel-loader",
        options: {
          cacheDirectory: true,
          cacheCompression: false,
          plugins: [
            "react-refresh/babel", // 激活js的HMR
          ],
        },
      },
// vue情况下
      {
        test: /\.js$/,
        include: path.resolve(__dirname, "../src"),
        loader: "babel-loader",
        options: {
          cacheDirectory: true,
          cacheCompression: false,
        },
      },
      {
        test: /\.vue$/,
        loader: "vue-loader",
        options: {
          // 开启缓存
          cacheDirectory: path.resolve(__dirname, "../node_modules/.cache/vue-loader"),
        },
      },
打包html
npm install html-webpack-plugin@5.5.0 -D
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 自动打包 自动引入
    new HtmlWebpackPlugin({
      // 模板:以public/index.html文件创建新的html文件
      // 新的html文件特点:1. 结构和原来一致 2. 自动引入打包输出的资源
      template: path.resolve(__dirname, "public/index.html"),
    }),

搭起服务,自动化刷新前端
npm install webpack-dev-server@4.8.1 -D
// 自动化  npx webpack serve
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
    hot: true, // 开启HMR(默认值)
  },
把css单独抽离
npm install mini-css-extract-plugin@2.6.0 -D
npm install postcss-loader@6.2.1 postcss@8.4.13 postcss-preset-env@7.5.0 -D
// MiniCssExtractPlugin.loader, // 使用loader,提取css成单独文件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
function getStyleLoader(pre) {
  return [
    MiniCssExtractPlugin.loader, // 提取css成单独文件
    "css-loader", // 将css资源编译成commonjs的模块到js中
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
      },
    },
    pre,
  ].filter(Boolean);// 过滤undefined null的值
}
    new MiniCssExtractPlugin({
      filename: "static/css/main.css",
    }),
// 兼容什么浏览器
  "browserslist": [
    "last 2 version",
    "> 1%",
    "not dead"
  ]
压缩css
npm install css-minimizer-webpack-plugin@3.4.1 -D
    // 压缩css
    new CssMinimizerPlugin(),
多线程
// npm install terser-webpack-plugin -D   // 不需安装
npm install thread-loader@3.0.4 -D
// webpack自带,正式环境打包 自动开启压缩html js
const os = require("os"); 
const TerserWebpackPlugin = require("terser-webpack-plugin");
const threads = os.cpus().length;
      new TerserWebpackPlugin({
        parallel: threads, // 开启多进程和设置进程数量
      }),
压缩图片
npm install image-minimizer-webpack-plugin@3.2.3 imagemin@8.0.1 -D
// 无损压缩
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
// 有损压缩
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
const ImageMinimizerPlugin = require("image-minimizer-webpack-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",
                      },
                    },
                  ],
                },
              ],
            ],
          },
        },
      }),
代码分割
  optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all", // 对所有模块都进行分割
      // 以下是默认值
      // minSize: 20000, // 分割代码最小的大小
      // minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
      // minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
      // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
      // maxInitialRequests: 30, // 入口js文件最大并行请求数量
      // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
      // cacheGroups: { // 组,哪些模块要打包到一个组
      //   defaultVendors: { // 组名
      //     test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
      //     priority: -10, // 权重(越大越高)
      //     reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
      //   },
      //   default: { // 其他没有写的配置会使用上面的默认值
      //     minChunks: 2, // 这里的minChunks权重更大
      //     priority: -20,
      //     reuseExistingChunk: true,
      //   },
      // },
    },
  },
runtime
    // 用于保留没修改的js文件
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}.js`,
    },
加载顺序
npm install @vue/preload-webpack-plugin@2.0.0 -D
// 加载顺序,
    new PreloadWebpackPlugin({
      // rel: "preload", // 推荐
      // as: "script",
      rel: "prefetch", // 让浏览器空闲加载,增加用户体验
    }),
离线可访问
npm install workbox-webpack-plugin@6.5.3 -D
const WorkboxPlugin = require("workbox-webpack-plugin");
    new WorkboxPlugin.GenerateSW({
      // 这些选项帮助快速启用 ServiceWorkers
      // 不允许遗留任何“旧的” ServiceWorkers
      clientsClaim: true,
      skipWaiting: true,
    }),
// 代码中
if ("serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    navigator.serviceWorker
      .register("/service-worker.js")
      .then((registration) => {
        console.log("SW registered: ", registration);
      })
      .catch((registrationError) => {
        console.log("SW registration failed: ", registrationError);
      });
  });
}
设置命令变量
npm i cross-env -D
  "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"
  },
兼容es6+
npm i core-js
  "dependencies": {
    "core-js": "^3.22.5"
  }
// 代码中
// 完整引入
// import 'core-js'
// 手动按需加载
// import "core-js/es/promise";
// 自动按需加载 babel.config.js
module.exports = {
  // 智能预设:能够编译ES6语法
  presets: [
    [
      "@babel/preset-env",
      {
        useBuiltIns: "usage", // 按需加载自动引入
        corejs: 3,
      },
    ],
  ],
};
react的js热更新 HMR
npm i @pmmmwh/react-refresh-webpack-plugin@0.5.5 react-refresh@0.13.0 -D
// 已经包含 plugin-transform-runtime
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
      {
        test: /\.jsx?$/,
        include: path.resolve(__dirname, "../src"),
        loader: "babel-loader",
        options: {
          cacheDirectory: true,
          cacheCompression: false,
          plugins: [
            "react-refresh/babel", // 激活js的HMR
          ],
        },
      },
    new ReactRefreshWebpackPlugin(), // 激活js的HMR
  devServer: {
    host: "localhost",
    port: 3000,
    open: true,
    hot: true, // 开启HMR 但没有针对react的js进行热更新,需要自己处理
    historyApiFallback: true, // 解决前端路由刷新404问题
  },
复制文件
npm i copy-webpack-plugin@10.2.4 -D
const CopyPlugin = require("copy-webpack-plugin");
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, "../public"),
          to: path.resolve(__dirname, "../dist"),
          globOptions: {
            // 忽略index.html文件
            ignore: ["**/index.html"],
          },
        },
      ],
    }),
使用vue来加载css
npm install css-loader@6.7.1 vue-style-loader@4.1.3 -D
      {
        test: /\.css$/i,
        use: ["vue-style-loader", "css-loader"],
      },
编译vue文件
npm install vue-loader vue-template-compiler -D
const { VueLoaderPlugin } = require("vue-loader");
      {
        test: /\.vue$/,
        loader: "vue-loader",
      },
    new VueLoaderPlugin(),
定义环境变量
const { DefinePlugin } = require("webpack");
    // cross-env定义的环境变量给打包工具使用 webpack
    // DefinePlugin定义环境变量给源代码使用,从而解决vue3页面警告的问题
    new DefinePlugin({
      __VUE_OPTIONS_API__: true,
      __VUE_PROD_DEVTOOLS__: false,
    }),
自动按需引用vue组件
npm i unplugin-auto-import@0.7.1 -D 
const AutoImport = require("unplugin-auto-import/webpack");
const Components = require("unplugin-vue-components/webpack");
const { ElementPlusResolver } = require("unplugin-vue-components/resolvers");
    // 按需加载element-plus
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [
        ElementPlusResolver({
          // 自定义主题,引入sass
          importStyle: "sass",
        }),
      ],
    }),
配置路径别名
  // webpack解析模块加载选项
  resolve: {
    // 路径别名
    alias: {
      "@": path.resolve(__dirname, "../src"),
    },
  },
热加载 适用于(开发模式)
if (module.hot) {
  // 判断是否支持热模块替换功能
  module.hot.accept("./js/count", function(){
      // 代码已修改回调
  });
  module.hot.accept("./js/sum");
}
// devServer中添加hot: true
// 实际项目开发会使用其他loader进行解决
// vue-loader react-hot-loader
oneOf 适用于(开发模式 正式模式)
// 每个文件只能被其中一个loader配置处理
onrOf:[{test:...}]
解析模块加载配置
  // webpack解析模块加载选项
  resolve: {
    // 自动补全文件扩展名
    extensions: [".jsx", ".js", ".json"],
  },
.eslintrc.js
module.exports = {
  // 继承 Eslint 规则
  extends: ["eslint:recommended"],
  env: {
    node: true, // 启用node中全局变量
    browser: true, // 启用浏览器中全局变量
  },
  parserOptions: {
    ecmaVersion: 6, // es6
    sourceType: "module", // es module
  },
  rules: {
    "no-var": 2, // 不能使用 var 定义变量
  },
  plugins: ["import"], // 解决动态导入语法报错
};
babel.config.js
module.exports = {
  // 智能预设:能够编译ES6语法
  presets: ["@babel/preset-env"],
};
// devtool: "cheap-module-source-map",
// 编译的时候生成源代码映射,浏览器自动通过映射查找源代码的位置,开发环境的时候使用
// cheap-module-source-map 只映射行
// source-map 映射行和列
“资源模块”类型有四种
  1. asset/resource: 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
  2. asset/inline: 导出一个资源的 data URI。之前通过使用 url-loader 实现。
  3. asset/source: 导出资源的源代码。之前通过使用 raw-loader 实现。
  4. asset: 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。
4 个角度对 webpack 和代码进行了优化
  1. 提升开发体验
  • 使用 Source Map 让开发或上线时代码报错能有更加准确的错误提示。
  1. 提升 webpack 提升打包构建速度
  • 使用 HotModuleReplacement 让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。
  • 使用 OneOf 让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。
  • 使用 Include/Exclude 排除或只检测某些文件,处理的文件更少,速度更快。
  • 使用 Cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。
  • 使用 Thead 多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)
  1. 减少代码体积
  • 使用 Tree Shaking 剔除了没有使用的多余代码,让代码体积更小。
  • 使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。
  • 使用 Image Minimizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)
  1. 优化代码运行性能
  • 使用 Code Split 对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。
  • 使用 Preload / Prefetch 对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。
  • 使用 Network Cache 能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。
  • 使用 Core-js 对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。
  • 使用 PWA 能让代码离线也能访问,从而提升用户体验。
webpack.prod.js

const path = require("path"); // nodejs核心模块,专门用来处理路径问题
const ESLintPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

// 用来获取处理样式的loader
function getStyleLoader(pre) {
  return [
    MiniCssExtractPlugin.loader, // 提取css成单独文件
    "css-loader", // 将css资源编译成commonjs的模块到js中
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
      },
    },
    pre,
  ].filter(Boolean);
}

module.exports = {
  // 入口
  entry: "./src/main.js", // 相对路径
  // 输出
  output: {
    // 所有文件的输出路径
    // __dirname nodejs的变量,代表当前文件的文件夹目录
    path: path.resolve(__dirname, "../dist"), // 绝对路径
    // 入口文件打包输出文件名
    filename: "static/js/main.js",
    clean: true,
  },
  // 加载器
  module: {
    rules: [
      // loader的配置
      {
        oneOf: [
          {
            test: /\.css$/, // 只检测.css文件
            use: getStyleLoader(), // 执行顺序:从右到左(从下到上)
          },
          {
            test: /\.less$/,
            // loader: 'xxx', // 只能使用1个loader
            use: getStyleLoader("less-loader"),
          },
          {
            test: /\.s[ac]ss$/,
            use: getStyleLoader("sass-loader"),
          },
          {
            test: /\.styl$/,
            use: getStyleLoader("stylus-loader"),
          },
          {
            test: /\.(png|jpe?g|gif|webp|svg)$/,
            type: "asset",
            parser: {
              dataUrlCondition: {
                // 小于10kb的图片转base64
                // 优点:减少请求数量  缺点:体积会更大
                maxSize: 10 * 1024, // 10kb
              },
            },
            generator: {
              // 输出图片名称
              // [hash:10] hash值取前10位
              filename: "static/images/[hash:10][ext][query]",
            },
          },
          {
            test: /\.(ttf|woff2?|map3|map4|avi)$/,
            type: "asset/resource",
            generator: {
              // 输出名称
              filename: "static/media/[hash:10][ext][query]",
            },
          },
          {
            test: /\.js$/,
            exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
            loader: "babel-loader",
          },
        ],
      },
    ],
  },
  // 插件
  plugins: [
    // plugin的配置
    new ESLintPlugin({
      // 检测哪些文件
      context: path.resolve(__dirname, "../src"),
    }),
    new HtmlWebpackPlugin({
      // 模板:以public/index.html文件创建新的html文件
      // 新的html文件特点:1. 结构和原来一致 2. 自动引入打包输出的资源
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    new MiniCssExtractPlugin({
      filename: "static/css/main.css",
    }),
    new CssMinimizerPlugin(),
  ],
  // 模式
  mode: "production",
  devtool: "source-map",
};
webpack.dev.js

const path = require("path") // nodejs核心模块,专门用来处理路径问题
const ESLintPlugin = require("eslint-webpack-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
  // 入口
  entry: "./src/main.js", // 相对路径
  // 输出
  output: {
    // 所有文件的输出路径
    // 开发模式没有输出
    // path: undefined,
    path: path.resolve(__dirname, "../dist"), // 绝对路径
    // 入口文件打包输出文件名
    filename: "static/js/main.js",
  },
  // 加载器
  module: {
    rules: [
      // loader的配置
      {
        // 每个文件只能被其中一个loader配置处理
        oneOf: [
          {
            test: /\.css$/, // 只检测.css文件
            use: [
              // 执行顺序:从右到左(从下到上)
              "style-loader", // 将js中css通过创建style标签添加html文件中生效
              "css-loader", // 将css资源编译成commonjs的模块到js中
            ],
          },
          {
            test: /\.less$/,
            // loader: 'xxx', // 只能使用1个loader
            use: [
              // 使用多个loader
              "style-loader",
              "css-loader",
              "less-loader", // 将less编译成css文件
            ],
          },
          {
            test: /\.s[ac]ss$/,
            use: [
              "style-loader",
              "css-loader",
              "sass-loader", // 将sass编译成css文件
            ],
          },
          {
            test: /\.styl$/,
            use: [
              "style-loader",
              "css-loader",
              "stylus-loader", // 将stylus编译成css文件
            ],
          },
          {
            test: /\.(png|jpe?g|gif|webp|svg)$/,
            type: "asset",
            parser: {
              dataUrlCondition: {
                // 小于10kb的图片转base64
                // 优点:减少请求数量  缺点:体积会更大
                maxSize: 10 * 1024, // 10kb
              },
            },
            generator: {
              // 输出图片名称
              // [hash:10] hash值取前10位
              filename: "static/images/[hash:10][ext][query]",
            },
          },
          {
            test: /\.(ttf|woff2?|map3|map4|avi)$/,
            type: "asset/resource",
            generator: {
              // 输出名称
              filename: "static/media/[hash:10][ext][query]",
            },
          },
          {
            test: /\.js$/,
            exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
            loader: "babel-loader",
          },
        ],
      },
    ],
  },
  // 插件
  plugins: [
    // plugin的配置
    new ESLintPlugin({
      // 检测哪些文件
      context: path.resolve(__dirname, "../src"),
    }),
    new HtmlWebpackPlugin({
      // 模板:以public/index.html文件创建新的html文件
      // 新的html文件特点:1. 结构和原来一致 2. 自动引入打包输出的资源
      template: path.resolve(__dirname, "../public/index.html"),
    }),
  ],
  // 开发服务器: 不会输出资源,在内存中编译打包的
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
  },
  // 模式
  mode: "development",
  // devtool: "cheap-module-source-map",
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值