Webpack 5 完整文档与实践指南

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Webpack 5 是一款前端模块打包工具,负责处理 JavaScript 应用的依赖关系和静态资源。通过模块化、Loader 转换和 Plugin 扩展,它将各类资源打包为优化后的静态文件。配置文件 webpack.config.js 是其核心,包含入口(entry)、输出(output)、模块规则(module rules)等。Webpack 5 还引入了缓存机制、性能优化、TypeScript 支持等特性。文档资料详细解释了这些知识点,包括环境变量、Asset Modules、HTTP/2 Push 等高级功能,以及如何使用 source maps 和管理 package.json .gitignore 等文件。同时提供了 README.md docs 目录等重要文档资源,以帮助开发者深入理解并高效使用 Webpack 5。 webpack5 文档资料

1. Webpack 5 模块打包工具概述

随着前端工程化的发展,Webpack 已成为现代 Web 应用构建的基石。它不仅是一个静态模块打包器,还是一个功能强大的资源加载器,支持各种模块类型,如 JavaScript、TypeScript、CSS、Sass、图像文件等。

Webpack 的核心概念是“入口(entry)”和“输出(output)”。入口是应用开始的地方,Webpack 从这里开始递归地构建一个依赖关系图,最终输出为打包后的资源。Webpack 5 引入了对模块联邦的原生支持,允许在多个构建之间共享和消费模块,极大的提升了模块复用和微前端架构的能力。

掌握 Webpack 是每一个前端开发者必备的技能。本章我们将从基础出发,逐步深入了解 Webpack 的模块打包机制,以及如何配置和优化它以满足不同项目需求。

graph LR
A[开始] --> B[理解 Webpack 核心概念]
B --> C[安装和初始化 Webpack]
C --> D[配置 Entry 和 Output]
D --> E[添加 Loaders 和 Plugins]
E --> F[优化打包性能]
F --> G[实践与最佳实践]
G --> H[结束]

本章小结

Webpack 5 不仅仅是一个打包工具,它更像一个生态系统,通过众多插件和 Loaders 与开发者形成互动,从而构建出更加复杂且高效的应用。了解这些基础概念,是驾驭 Webpack 并将其应用于实际项目的起点。接下来的章节中,我们将逐一深入探讨模块规范、Loader 转换机制、插件开发和配置高级特性,为你的 Webpack 知识库添砖加瓦。

2. 深入理解模块规范

2.1 CommonJS 和 ES6 模块规范对比

2.1.1 CommonJS 模块规范解析

CommonJS 是服务器端JavaScript的一个标准模块定义规范,它最初是为了解决Node.js的模块化问题而制定的。CommonJS模块规范定义了模块的行为,包括如何导出和引入模块。在CommonJS中,每个文件就是一个模块,有自己的作用域。通过module.exports可以将变量、函数和对象导出为模块的一部分,而require方法则用于引入模块。

以下是一个CommonJS模块的典型例子:

// someModule.js
var name = 'CommonJS Module';
function sayHello() {
  console.log('Hello from ' + name);
}
module.exports = sayHello;

然后在另一个文件中引入这个模块:

// main.js
var sayHello = require('./someModule.js');
sayHello(); // 输出 "Hello from CommonJS Module"

CommonJS模块在同步加载模块时很有效,这对于服务器端的应用程序来说通常是可以接受的。然而,这种同步加载方式并不适合浏览器环境,因为网络延迟会导致加载时间过长。

2.1.2 ES6 模块规范解析

随着ECMAScript 2015(ES6)的发布,JavaScript引入了一套全新的模块系统。ES6模块规范通过import和export语句实现模块的导入和导出。与CommonJS不同,ES6模块是异步加载的,这使得它们更适合于浏览器环境。

下面是ES6模块的一个例子:

// someModule.js
const name = 'ES6 Module';
export function sayHello() {
  console.log(`Hello from ${name}`);
}

然后在另一个模块中引入这个模块:

// main.js
import { sayHello } from './someModule.js';
sayHello(); // 输出 "Hello from ES6 Module"

ES6模块支持静态分析,这意味着加载模块时,导入和导出可以在编译时进行优化,这有助于tree shaking,即移除未使用的代码,进一步优化打包体积。

2.2 模块规范在 Webpack 中的应用

2.2.1 模块解析机制

Webpack 作为一种现代前端模块打包器,支持CommonJS和ES6等多种模块规范。Webpack处理模块时,会将它们解析为依赖图,然后打包成静态资源。Webpack使用了加载器(loaders)来解析不同类型的文件。

Webpack的解析机制允许开发者定义alias、extensions等配置项,这些配置项帮助Webpack更准确地解析模块引用。例如,我们可以通过配置resolve.alias来指定模块的别名,这样在引用模块时可以使用更简短的名称:

// webpack.config.js
module.exports = {
  // ...
  resolve: {
    alias: {
      'utils': path.resolve(__dirname, 'src/utils/'),
    },
    extensions: ['.js', '.jsx', '.json'],
  },
};

有了这样的配置后,在代码中就可以这样引用模块:

import utilsFn from 'utils/someFn';
2.2.2 模块依赖管理

Webpack内部通过依赖图来管理模块依赖,它会根据模块间的依赖关系来构建最终的打包文件。当Webpack解析到一个import语句时,它会递归地解析这个import语句所依赖的模块,然后将所有这些模块打包到一起。

为了更好地管理这些依赖,Webpack提供了一些优化手段,比如SplitChunksPlugin,它可以将公共的依赖提取到一个单独的bundle中。还有Tree Shaking的功能,它可以在编译时消除那些无用代码,仅保留实际被使用的部分。这样不仅减少了打包文件的体积,也提高了加载效率。

// webpack.config.js
module.exports = {
  // ...
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
    usedExports: true,
  },
};

以上配置会让Webpack在构建时分析出哪些模块是被实际使用的,并且在最后的打包文件中排除掉未使用的导出部分。这样,最终打包的文件中只包含实际用到的代码,减少了输出文件的大小,提升了性能。

3. Webpack 的 Loader 转换机制

3.1 Loader 的基本概念和作用

3.1.1 Loader 的定义和分类

Loader 是 Webpack 中用于处理不同文件类型的转换器,它们可以在构建过程中将特定类型的文件转换成 Webpack 能够处理的有效模块。Loader 使得 Webpack 不仅限于 JavaScript,也能够打包静态资源,比如 CSS、图片、字体等。

Loader 可以分为以下几种类型:

  1. 预处理 loader :这类 loader 通常在其他 loader 执行前运行,负责处理文件的前置转换,例如 style-loader 在其他 CSS 相关 loader 前运行,将 CSS 内嵌到 HTML 中。
  2. 编译 loader :将资源文件转换成 JavaScript 模块,例如 babel-loader 转换 ES6 代码为浏览器可执行的代码。
  3. 加载器 loader :将资源文件直接转换为 JavaScript 模块,例如 url-loader file-loader 可以将文件转为 Data URLs 或生成文件路径。
  4. 后处理 loader :这类 loader 在其他 loader 执行后运行,处理最终的模块代码,例如 eslint-loader 可在代码加载到 JavaScript 模块之前检查代码规范。

3.1.2 Loader 的执行流程

Webpack 中的 Loader 执行流程遵循如下步骤:

  1. 识别文件 :Webpack 通过配置的 module.rules 中的正则表达式或条件匹配到需要经过 loader 处理的文件。
  2. 逆序执行 :Webpack 从右到左,从下到上顺序执行匹配到的 loader,保证加载器(loaders)从右到左通过链式调用。如果从左到右执行,会导致执行顺序错误,因为第一个 loader 需要处理最终的文件内容。
  3. 资源转换 :每个 loader 会对资源文件做相应的转换操作,将其转换成 JavaScript 代码。
  4. 结果传递 :转换后的结果会被传递回 Webpack,Webpack 再将它们转换为模块依赖。

Loader 的结果可以是一个 JavaScript 字符串、一个 Buffer 对象或者生成一个或多个文件,也可以通过返回 null 不生成任何模块。

3.2 实践中的 Loader 应用

3.2.1 常用 Loader 的介绍和配置

在现代的前端工程化实践中,常用到的 Loader 包括但不限于 babel-loader style-loader css-loader less-loader ts-loader 等。

babel-loader 为例,它通常用于转译 ES6+ 代码。配置示例如下:

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
};

上面的配置会应用 babel-loader 到所有 .js 文件上,但排除 node_modules 目录下的文件。 options 中可以指定预设(presets),用于定义转译的具体规则。

3.2.2 Loader 的链式调用和组合使用

Loader 链式调用使得我们可以按顺序执行多个 loader,以处理多种类型的文件。链式调用的顺序是逆序的,最后定义的 loader 会最先执行。

例如,处理 Sass 文件可能需要一系列的 loader,包括 style-loader css-loader sass-loader

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          'css-loader',
          'sass-loader'
        ]
      }
    ]
  }
};

在上面的配置中,从左到右, sass-loader 首先将 Sass 转换为 CSS, css-loader 接着处理 CSS 模块,最后 style-loader 将 CSS 插入到 HTML 文件中。

注意 :在书写 loader 队列时,每个 loader 必须以数组的形式书写,即使只有一个 loader。

Loader 的链式调用和组合使用是 Webpack 功能强大的原因之一,通过这种方式可以灵活地处理各种资源文件,使其最终都能成为 Webpack 可处理的模块。

4. Webpack 插件功能详解

4.1 插件系统的工作原理

4.1.1 插件的生命周期和钩子

Webpack 的插件系统是其扩展性的核心,允许开发者在 Webpack 的生命周期内执行各种自定义操作。一个插件可以在 Webpack 运行的任何阶段挂钩到它的内部事件系统中,从而实现各种功能。

Webpack 插件的生命周期主要由以下几个阶段组成:

  • apply :插件被实例化之后, apply 方法被调用,插件可以在这个方法中挂载其钩子函数到 Webpack 的事件流中。
  • compilation :每次构建编译都会创建一个新的 compilation 对象,插件可以监听 compilation 钩子来执行特定的编译操作。
  • make :开始读取记录中的入口文件,进行模块图的构建,插件可以在这个阶段进行模块处理。
  • emit :在将编译后的文件写入磁盘之前,插件可以对最终生成的资源进行修改。

此外,Webpack 也提供了一系列的钩子(hooks)供插件开发者使用,比如 compiler.hooks.run ***pile compilation.hooks.seal 等。每一个钩子都可能携带特定的数据参数,供插件处理。

4.1.2 插件的扩展点分析

Webpack 通过 Tapable 提供的钩子系统来实现插件的扩展点。每个钩子都定义了注册插件和触发回调的方法。当 Webpack 触发这些钩子时,所有注册到钩子上的插件都将依次执行。

插件可以通过调用 tap tapAsync tapPromise 方法注册到钩子上,取决于钩子是否支持异步操作。例如,一个插件可能会监听 compilation 钩子来添加自定义的资源处理逻辑:

``` pilation.tap('MyPlugin', (compilation) => { compilation.hooks.processAssets.tap( { name: 'MyPlugin', stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL }, () => { // 自定义资源处理逻辑 } ); });


在这个示例中,`processAssets` 钩子被用于在资源处理阶段添加额外的步骤。`stage` 属性用于指示钩子的执行时机。

## 4.2 插件的开发与应用实例

### 4.2.1 开发自定义插件的步骤

开发自定义插件的步骤通常包括以下几点:

1. 创建一个新的 JavaScript 文件作为插件。
2. 引入必要的 Webpack 模块和类,比如 `Tapable` 和 `Compiler`。
3. 定义一个类,该类包含一个或多个 `apply` 方法。
4. 在 `apply` 方法内部,使用 `compiler` 实例访问 Webpack 的内部数据和钩子。
5. 在对应的钩子上注册事件处理函数,这些函数会根据需要修改 Webpack 的行为。
6. 编译项目以测试插件功能,确保无误后发布插件。

一个基本的插件结构如下所示:

```javascript
class MyPlugin {
  apply(compiler) {
    // 使用 Tapable 注册钩子
    compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
      // 这里可以访问到 compilation 对象,从而操作构建好的资源

      // 异步操作
      process.nextTick(() => {
        console.log('This is an example plugin.');
        callback();
      });
    });
  }
}

module.exports = MyPlugin;

4.2.2 实践案例:优化构建流程

假设我们需要开发一个插件来优化构建流程,例如清除无用的 CSS 类。下面是一个简单的例子,演示了如何实现一个这样的插件:

class PurgeCssPlugin {
  constructor(options) {
    this.options = options;
  }

  apply(compiler) {
    compiler.hooks.emit.tapAsync('PurgeCssPlugin', (compilation, callback) => {
      const { output } = compilation;
      // 获取所有编译完成的资源
      const assets = output.getAssets();

      const cssFiles = assets.filter(asset => asset.endsWith('.css'));
      cssFiles.forEach(cssFile => {
        const content = compilation.assets[cssFile].source();
        const usedClasses = content.match(/[^{]+(?=\{)/g); // 这里是一个简单的正则表达式来获取类名
        // 这里可以进一步将 usedClasses 与你的项目源码进行比对来确定哪些是未使用的类名
        // ...
      });

      callback();
    });
  }
}

module.exports = PurgeCssPlugin;

此插件将在每次构建结束时尝试分析 CSS 文件,查找未使用的类名。在实际项目中,这个过程会更加复杂,可能需要借助外部工具或库来准确地识别未使用的 CSS。

为了在 Webpack 中使用这个插件,我们需要在配置文件中添加插件的实例:

const PurgeCssPlugin = require('./PurgeCssPlugin');

module.exports = {
  //...
  plugins: [
    new PurgeCssPlugin({
      // 选项...
    })
  ],
};

通过这样的一个流程,我们就能够有效地减少最终打包文件的大小,提升加载性能。

4.3 插件的高级应用

4.3.1 分析和优化打包体积

除了之前提及的 PurgeCssPlugin ,Webpack 插件还可以用于分析打包后的文件,找出体积过大的模块或者对资源进行优化。例如使用 webpack-bundle-analyzer 插件,它可以帮助开发者可视化地分析打包后的模块构成,了解哪些模块占用了较多的空间。

npm install --save-dev webpack-bundle-analyzer

在 Webpack 配置中应用:

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

module.exports = {
  //...
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'server',
      analyzerPort: 8888,
      openAnalyzer: true,
    })
  ],
};

运行构建后,它会在浏览器中打开一个新的标签页,展示打包结果的树状结构,让你一目了然看到每个模块的大小及依赖关系。

4.3.2 提升构建过程的可维护性

构建过程的可维护性是项目可持续发展的一个关键因素。一些插件可以帮助我们维护构建配置,例如 webpack-merge 插件,可以用来合并多个 Webpack 配置文件,从而将通用配置和环境特定配置分离,提高配置的可读性和可维护性。

npm install --save-dev webpack-merge
const { merge } = require('webpack-merge');
const commonConfig = require('./***mon.config');
const developmentConfig = require('./webpack.development.config');

module.exports = merge(commonConfig, developmentConfig);

通过这种方式,你可以将 webpack.config.js 分割成多个文件,比如一个 ***mon.config.js 包含通用配置,一个 webpack.development.config.js 和一个 webpack.production.config.js 分别包含开发和生产环境的特定配置。 webpack-merge 负责在构建时合并这些配置文件,从而简化了配置管理。

通过上述示例,我们可以看到 Webpack 插件不仅能够优化构建流程和提升性能,还能够在维护和管理构建配置方面发挥重要作用。熟练掌握这些插件的使用和开发,可以极大地提高项目的构建效率和质量。

5. Webpack 配置文件详解

5.1 webpack.config.js 配置基础

5.1.1 配置文件结构和字段解析

Webpack 配置文件 webpack.config.js 是一个 Node.js 环境下运行的 JavaScript 文件,它导出一个配置对象,该对象由 Webpack 在构建过程中读取和使用。配置对象中可以包含许多字段,它们影响着构建过程的各个方面。以下是一些核心字段:

const path = require('path');

module.exports = {
  entry: './src/index.js', // 指定项目的入口文件
  output: {
    path: path.resolve(__dirname, 'dist'), // 指定输出目录的绝对路径
    filename: 'bundle.js', // 指定输出文件名
  },
  mode: 'development', // 指定构建模式,常用的有 'development' 和 'production'
  module: {
    // 模块配置,用于配置 Loader 等
  },
  plugins: [
    // 插件数组,每个插件都是一个实例,用于执行额外任务
  ],
  resolve: {
    // 配置模块解析选项,例如 extensions 和 alias
  },
  devtool: 'source-map', // 控制是否生成以及如何生成 source map
};

5.1.2 环境区分和配置复用

在大型项目中,通常需要区分开发环境和生产环境。为了简化配置,Webpack 提供了一种方式来根据环境变量加载不同的配置文件。

// ***mon.js
const commonConfig = {...};

// webpack.dev.js
const { merge } = require('webpack-merge');
const commonConfig = require('./***mon');

module.exports = merge(commonConfig, {
  mode: 'development',
});

// webpack.prod.js
const { merge } = require('webpack-merge');
const commonConfig = require('./***mon');

module.exports = merge(commonConfig, {
  mode: 'production',
});

使用 webpack-merge 这个库,可以轻松合并共用配置和环境特定配置。在运行 Webpack 时,可以通过 -c 参数指定要使用的配置文件。

5.2 配置高级特性

5.2.1 使用 DefinePlugin 定义环境变量

DefinePlugin 是 Webpack 内置的插件,允许你在编译时创建全局常量,这对于环境配置和代码优化非常有用。

const { DefinePlugin } = require('webpack');

module.exports = {
  // ...
  plugins: [
    new DefinePlugin({
      // 'process.env.NODE_ENV': JSON.stringify('development'),
      // 定义全局常量
      'API_URL': JSON.stringify('***'),
    }),
  ],
};

将环境变量设置在 Webpack 配置中,可以覆盖代码中的 process.env.NODE_ENV ,在构建时将它们替换为对应的值。

5.2.2 配置分离与环境特定配置

为了更好地管理不同环境下的配置,可以将配置分离成多个文件,并使用 Webpack 的配置合并功能。这使得配置更清晰,也便于维护。

// 使用 webpack-merge 进行配置合并
// webpack.config.js
const { merge } = require('webpack-merge');
const commonConfig = require('./***mon');

module.exports = merge(commonConfig, {
  // development-specific settings
});

// webpack.config.prod.js
const { merge } = require('webpack-merge');
const commonConfig = require('./***mon');

module.exports = merge(commonConfig, {
  mode: 'production',
  // production-specific settings
});

通过这种方式,可以确保生产环境和开发环境共用基础配置,同时各自拥有特定的配置项,便于维护和更新。

6. Webpack 优化与最佳实践

在当前的前端开发中,Webpack 作为一个强大的模块打包器,对性能优化和最佳实践有着举足轻重的作用。本章节将深入探讨如何通过Webpack进行优化和配置最佳实践,从而提升项目的构建效率和运行性能。

6.1 入口(entry)与输出(output)优化

Webpack 的构建过程始于入口(entry)配置,结束于输出(output)配置。合理优化这两部分配置,可以显著减少构建时间并提升项目的加载性能。

6.1.1 精细化控制入口点

通过将应用程序分成多个入口点可以更有效地利用缓存和并行构建。例如,将第三方库和项目源码分离为两个不同的入口可以有效减少不必要的重新构建。下面是一个简单的多入口配置示例:

module.exports = {
  entry: {
    app: './src/index.js',
    vendor: ['lodash', 'react', 'react-dom']
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

6.1.2 输出配置与文件命名策略

在输出配置中,可以利用占位符来创建一个更加灵活的文件命名策略。例如,使用hash值可以确保文件名在更改时具有唯一性,从而避免缓存问题:

output: {
  path: path.resolve(__dirname, 'dist'),
  filename: '[name].[contenthash].js'
}

6.2 缓存机制与性能提升

缓存策略在优化构建时间和运行性能方面也扮演着关键角色。合理地应用缓存可以减少重复的工作,加快开发速度。

6.2.1 缓存策略的应用

Webpack 5 通过 cache 选项引入了持久化缓存,以提升构建速度。通过设置 cache: true,Webpack 将缓存其编译结果,从而在随后的构建中重用它们:

module.exports = {
  // ...
  cache: true,
  // ...
};

6.2.2 分析和优化构建时间

Webpack 提供了官方插件 WebpackBar speed-measure-webpack-plugin ,帮助开发者分析构建时间并识别瓶颈:

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap({
  // ...
});

6.3 TypeScript 与非 JS 资源的处理

随着 TypeScript 在前端开发中的流行,Webpack 配置支持 TypeScript 成为了开发者的日常任务。

6.3.1 TypeScript 支持配置

在 Webpack 中支持 TypeScript,需要配置一个专门的 ts-loader 来处理 TypeScript 文件:

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      }
    ]
  },
  resolve: {
    extensions: ['.ts', '.js']
  },
  // ...
};

6.3.2 Asset Modules 和 HTTP/2 Push 配置

为了优化非 JavaScript 文件的处理,Webpack 5 引入了 Asset Modules。这允许你以不同的方式处理字体、图片等资源:

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|gif)$/,
        type: 'asset/resource',
      }
    ]
  },
  // ...
};

6.4 源码映射与项目文档化

良好的源码映射和项目文档对于维护和扩展项目至关重要。

6.4.1 Source Maps 的配置和使用

Source Maps 允许你将编译、打包后的代码映射回源代码,便于调试。在 Webpack 中配置 Source Maps 如下:

module.exports = {
  // ...
  devtool: 'source-map',
  // ...
};

6.4.2 生成和维护文档文件

为了生成项目文档,可以使用 compodoc 这样的工具,它可以基于注释和 TypeScript 类型信息自动生成文档。结合 Webpack 配置,可以将文档打包输出:

const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.config.base');

module.exports = merge(baseConfig, {
  // 配置 Webpack 以输出文档
  // ...
});

6.5 项目文件管理与维护

在项目的生命周期内,文件的管理和维护也是一项重要的工作。

6.5.1 package.json 和 package-lock.json 的重要性

package.json package-lock.json 文件是管理项目依赖的关键。确保这些文件保持最新和准确,可以避免因版本冲突而带来的问题。

6.5.2 Gitignore 文件和 Readme.md 的编写

为了提高项目管理效率,编写合适的 .gitignore 文件以忽略不必要的文件,同时,编写清晰的 Readme.md 文件,可以帮助其他开发者理解和使用你的项目。

通过上述章节的探讨,我们逐步深入理解了 Webpack 的优化与最佳实践。在后续开发中,根据项目需求和团队约定灵活配置和使用这些策略,可以极大地提升开发和部署效率,优化用户的使用体验。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Webpack 5 是一款前端模块打包工具,负责处理 JavaScript 应用的依赖关系和静态资源。通过模块化、Loader 转换和 Plugin 扩展,它将各类资源打包为优化后的静态文件。配置文件 webpack.config.js 是其核心,包含入口(entry)、输出(output)、模块规则(module rules)等。Webpack 5 还引入了缓存机制、性能优化、TypeScript 支持等特性。文档资料详细解释了这些知识点,包括环境变量、Asset Modules、HTTP/2 Push 等高级功能,以及如何使用 source maps 和管理 package.json .gitignore 等文件。同时提供了 README.md docs 目录等重要文档资源,以帮助开发者深入理解并高效使用 Webpack 5。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值