弄懂webpack,只要看这一片就够了(文末有福利)

我们知道一个打包资源中会有多个budle文件,如果我们修改其中一个,那和项目相关的hash值就会统一改变,而那些没有修改过的、已打包好的、并且已经缓存的budle文件也会因此失效,需要重新打包,为了解决这个问题,就出现了chunkhash的概念,一个budle文件就对应一个chunkhash。

  1. contenthash:和chunkhash相比,contenthash的粒度就更细了,它主要是针对一个bundle中的内容,如果内容不变,contenthash就不变,所以我们通常会把项目中的js和css分离开来,对于css的打包文件命名我们就采用contenthash。

为什么要用webpack

==========================================================================

  • 前后端分离开发模式是催化剂

随着开发模式的转变,现在的前端开发已经不是想以往那些php、jsp那些,但凡懂点html/css的后端程序员都可以上手做的,现在的前端已经开始专业化,而我们的代码也是以工程的形式存在,你可以使用任何框架和技术,但是最后跑在用户浏览器上的东西还是和以前一样,就是静态资源和html,而从框架代码到浏览器识别的资源之间的转化就是由webpack来完成的。

  • 工程化、模块化的产物

工程化、模块化是我们研发代码的一种重要思想,同时也正是这种思想的存在,才会在前端领域中衍生出那么多框架和技术,比如:Vue、React、Angular等工程化框架,TS、less、Scss等各种语言的扩展,这些都是提高我们开发效率的利器,但是同时这些语言和框架是不能被浏览器所识别的,怎样让我们的代码更优雅的被浏览器识别并运行,这就是webpack的主要职责。

webpack的安装

========================================================================

环境准备


​ webpack是基于nodek开发的工程化工具,所以开发环境支持node,并且node版本和webpack的匹配也很重要,具体参见webpack官网的发布说明,尽量使用最新版本以提高打包速度。

全局安装 不推荐


安装webpack V4+版本时,需要额外安装webpack-cli

npm install webpack webpack-cli -g

检查版本

webpack -v

卸载

npm uninstall webpack webpack-cli -g

项目安装 推荐


安装最新的稳定版本

npm i -D webpack

安装指定版本

npm i -D webpack@

安装最新的体验版本 可能包含bug,不不要⽤用于⽣生产环境

npm i -D webpack@beta

安装webpack V4+版本时,需要额外安装webpack-cli

npm i -D webpack-cli

webpack的构建

========================================================================

默认配置


​ 首先我们先看一下webpack的默认配置代码,如下:

const path = require(‘path’)

module.exports = {

// 必填,webpack的执行入口

entry: ‘./src/main.js’,

output: {

// 合并输出的文件名

filename: ‘main.js’,

// 合并输出的绝对存放地址

path: path.resolve(__dirname, ‘./dist’)

}

}

​ 其中,我们需要注意的就是

  • webpack默认只支持JS模块和JSON模块

  • 支持CommonJS Es moudule AMD等模块类型

  • webpack4⽀持零配置使⽤,但是可用性很差,一般都需要额外扩展配置

执行构建


可以通过这两种方式进行项目构建

方式1:npx方式

npx webpack

方式2:npm script

首先需要在package.json中添加test指令对应的执行脚本


“scripts”:{

“test”: “webpack” // 原理就是通过shell脚本在node_modules/.bin⽬录下创建⼀个软链接

},


然后在执行如下脚本

npm run test

​ 构建成功后会发现⽬录下多出⼀个 dist ⽬录,⾥⾯有个 main.js ,这个⽂件是⼀个可执行的JavaScript文件,里面包含webpackBootstrap启动函数。

SourceMap


源代码与打包后的代码的映射关系,通过sourceMap定位到源代码。

在dev模式中,默认开启,关闭的话可以在配置文件⾥ devtool:"none"

// devtool的介绍 https://webpack.js.org/configuration/devtool#devtool

eval: 速度最快,使用eval包裹模块代码,

source-map: 产生.map文件

cheap: 较快,不包含列信息

Module: 第三⽅模块,包含loader的sourcemap(比如jsx to js, babel的sourcemap)

inline: 将.map作为DataURI嵌入,不单独生成.map⽂件

配置推荐:

devtool:“cheap-module-eval-source-map”,// 开发环境配置

// 线上不推荐开启

devtool:“cheap-module-source-map”, // 线上生成配置

WebpackDevServer


webpack-dev-server提升开发效率的利器,可以解决重复打包生成dist目录,这是因为devServer把打包后的模块不会放在dist目录下,二是放到内存中,从而提升速度。

安装

npm install webpack-dev-server -D

修改package.json配置

“scripts”: {

“server”: “webpack-dev-server”

},

修改webpack.config.js配置

devServer: {

contentBase: “./dist”,

open: true,

port: 8081

}

启动

npm run server

跨域问题的解决


  • 设置服务器代理

devServer: {

proxy: {

“/api”: {

target: “http://localhost:9092”

}

}

}

  • 本地mock server

devServer: {

// before()是devServer的钩子函数,除了before之外还有after,本质上只有执行时间顺序的问题

// app是一个本地的node服务,可以是express,也可以是koa,这里是express

before(app, server) {

app.get(‘/api/mock.json’, (req, res) => {

res.json({

hello: ‘world’

})

})

}

}

Hot Module Replacement (HMR:热模块替换)


  1. 启动hmr

devServer: {

contentBase: “./dist”,

open: true,

hot: true, // 开启HMR

hotOnly: true // 即便HMR不生效,浏览器也不自动刷新,就开启hotOnly

},

  1. 配置webpack.config.js

const webpack = require(“webpack”);

// 在插件配置处添加

plugins: [

new CleanWebpackPlugin(),

new webpack.HotModuleReplacementPlugin()

]

  • 注意启动HMR后,css抽离会不生效,还有不支持contenthash,chunkhash, 建议使用style-loader将css处理到html中

  • 处理js模块HMR需要使用module.hot.accept来观察模块更新,从⽽更新

if (module.hot) {

// path为需要监听变化的js的相对路径

module.hot.accept(path, function() {

// 执行操作,重新渲染修改后的js效果

});

}

webpack的基本概念

==========================================================================

entry


指定webpack打包⼊口文件,webpack 执⾏构建的第一步将从Entry开始,可理解成输入,有三种不同的输入方式

// 字符串: 单入口

entry: “./src/index.js”,

// 数组: 单入口,最终将数组文件合并成一个

entry: [“./src/index.js”, “./src/other.js”],

// 对象: 多入口

entry: {

index: “./src/index.js”,

other: “./src/other.js”

}

output


打包转换后的⽂件输出到磁盘位置:输出结果,在webpack经过⼀系列处理并得出最终想要的代码后输出结果。

// 默认处理(单入口)

output: {

filename: “bundle.js”,// 输出⽂文件的名称

path: path.resolve(__dirname, “dist”)// 输出⽂文件到磁盘的⽬目录,必须是绝对路路径

},

// 多⼊口的处理

output: {

filename: “[name][chunkhash:8].js”,// 利用占位符,⽂件名称不要重复

path: path.resolve(__dirname, “dist”)// 输出⽂件到磁盘的目录,必须是绝对路径

},

mode


mode用来指定当前的构建环境,主要取值有:

  • production // 生产环境的开启会有帮助模块压缩,处理副作用等一些功能

  • development // 开发环境的开启会有利于热更新的处理,识别哪个模块变化

  • none // 5+取消了

设置mode可以自动触发webpack内置的函数,达到优化的效果

在这里插入图片描述

none的赋值在5+版本中取消了,包括production时启动的函数中TerserPlugin改成为UglifyJsPlugin

module


模块,在webpack里一切皆模块,⼀个模块对应着一个文件。

webpack会从配置的 Entry 开始递归找出所有依赖的模块。当webpack处理到不认识的模块时,需要在webpack中的module 处进⾏配置,当检测到是什么格式的模块,使⽤什么loader来处理。

module:{

rules:[

{

test:/.xxx$/, //指定匹配规则

use:{

loader: ‘xxx-load’//指定使⽤用的loader

}

}

]

}

loader


webpack 默认只支持.json 和 .js模块,不支持不认识其他格式的模块,而loader就是用来转换这些不认识模块的模块转换器,可以把模块原内容按照需求转换成新内容。

常⻅的loader

style-loader

css-loader

less-loader

sass-loader

ts-loader // 将Ts转换成js

babel-loader // 转换ES6、7等js新特性语法

file-loader // 处理理图⽚片⼦子图

eslint-loader

Plugins


plugins 选项用于以各种方式自定义 webpack 构建过程。

也就是说,webpack的打包过程是有生命周期的概念的,每个阶段都有对应的钩子函数,而plugin就可以在webpack运行到某个阶段的时候进行一些操作,类似生命周期钩子函数的扩展插件,最终完成在webpack构建过程中的特定时机注入扩展逻辑来生成自己想要的结果。

Loader详解

======================================================================

上面我们了解到loader是webpack构建过程中的模块转换器,下面我们着重看几个常用的loader

静态资源处理类


  1. file-loader

file-loader是把打包⼊口中识别出的资源模块,移动到输出目录,并且返回⼀个地址名称,所以file-loader通常用来处理那些只需要从源代码移到打包目录的静态资源,比如:txt、svg、csv、excel以及各种图片、字体文件。

module: {

rules: [

// test 表示需要识别文件的后缀名,即是这样的后缀名的文件需要由该loader处理

test: /.(png|jpe?g|gif)$/,

// use 表示处理上述匹配文件的loader,如果是一个loader,需要用对象表示,如果是多个loader,则需要用数组

use: {

loader: ‘file-loader’,

options: {

// [name]老资源模块的名称 [hash]hash值 [ext]老资源的后缀名

name: ‘[name]_[hash].[ext]’

// 打包后的存放位置

outputPath: ‘images/’

}

}

]

}

  1. url-loader

​ url-loader是file-loader的加强版本,它的内部实现也是基于file-loader,所以可以处理file-loader可以处理的资源。

​ 同时url-loader会根据默认的(或者配置的)limit参数,将满足条件的图片转换成base64格式,并打包到js里,从而减小http请求,提高页面加载速度。但是这种操作只针对小体积的图片,不适用大图片。

module: {

rules: [

{

test: /.(png|jpe?g|gif)$/,

use: {

loader: ‘url-loader’,

options: {

name: ‘[name]_[hash].[ext]’,

outputPath: ‘images/’,

// 小于2048Byte(2KB),才转换成base64

limit: 2048

}

}

}

]

}

样式处理类


  1. css-loader

分析css模块之间的关系,并合成⼀个css

  1. style-loader

style-loader可以会把css-loader生成的内容,以style挂载到页面的head部分

  1. less-loader

less-loader会把less语法转成css

在多loader转换时,执行顺序为从右到左,从下到上

module: {

rules: [

{

test: /.css$/,

use: [‘style-loader’, ‘css-loader’]

},

{

test: /.scss$/,

use: [‘style-loader’, ‘css-loader’, ‘less-loader’]

}

]

}

  1. postcss-loader

postcss-loader也比较常见,通过用来批量转化css,比如:自动添加前缀以适配不同版本的浏览器,具体的浏览器适配规则可以参见https://caniuse.com/

// webpack.config.js中的配置

module: {

rules: [

{

test: /.css$/,

use: [‘style-loader’, ‘css-loader’, ‘postcss-loader’]

}

]

}

// 此外,需要新建postcss.config.js

module.exports = {

plugins: [

require(‘autoprefixer’)({

// last 2 versions: 最近的两个大版本

// >1%: 全球市场份额大于1%的浏览器

overrideBrowerslist: [‘last 2 versions’, ‘>1%’]

})

]

}

JS脚本处理类


babel-loader

在使用babel-loader,我们需要了解几个概念

  1. Babel是JavaScript编译器器,能将ES6代码转换成ES5代码,让我们开发过程中放⼼使用JS新特性⽽不用担⼼心兼容性问题。并且还可以通过插件机制根据需求灵活的扩展。

  2. babel-loader是webpack与babel的通信桥梁,不做把es6转成es5的⼯作,这部分⼯作需要用到@babel/preset-env来做,@babel/preset-env里包含了es6,7,8转es5的转换规则

  • 在使用preset-env时,需要注意的就是按需引入,由配置参数useBuiltIns决定

  • useBuiltIns选项是babel7的新功能,这个选项告诉babel如何配置@babel/polyfill。 它有三个参数可以使⽤:

  • entry:需要在webpack的⼊口文件里import "@babel/polyfill"⼀次。 babel会根据你的使⽤情况导⼊垫片,没有使⽤的功能不会被导入相应的垫⽚。

  • usage:不需要import,全⾃动检测,但是要安装@babel/polyfill。(试验阶段)

  • false:如果你import "@babel/polyfill" ,它不会排除掉没有使用的垫⽚,程序体积会庞⼤。(不推荐)

  • 请注意: usage的⾏为类似 babel-transform-runtime,不会造成全局污染,因此也不会对类似 Array.prototype.includes() 进行 polyfill。

[

“@babel/preset-env”,

{

targets: { // 需要指定代码运行的浏览器环境

edge: “17”,

firefox: “60”,

chrome: “67”,

safari: “11.1”

},

corejs: 2,// 新版本需要指定核心库版本

useBuiltIns: “usage”// 按需注入

}

]

  1. 默认的Babel只⽀持let等⼀些基础的特性转换,Promise等⼀些还有转换过来,这时候需要借助垫片 @babel/polyfill,把es的新特性都装进来,来弥补低版本浏览器中缺失的特性,其原理是语法转换,也就是说在转换后的文件里定义一个promise,并挂载到window对象上。

  2. Babel在执⾏编译的过程中,会从项⽬根⽬录下的 .babelrc JSON 文件中读取配置。没有该⽂件会从loader的options地⽅读取配置

//.babelrc

{

presets: [

[

“@babel/preset-env”,

{

targets: { // 需要指定代码运行的浏览器环境

edge: “17”,

firefox: “60”,

chrome: “67”,

safari: “11.1”

},

corejs: 2,// 新版本需要指定核心库版本

useBuiltIns: “usage”// 按需注入

}

]

]

}

//webpack.config.js

{

test: /.js$/,

exclude: /node_modules/,

loader: “babel-loader”

}

这是最后的全部配置

module: {

rules: [

test: /.js$/,

exclude: /node_modules/,

use: {

loader: “babel-loader”,

options: {

presets: [

[

“@babel/preset-env”,

{

targets: { // 需要指定代码运行的浏览器环境

edge: “17”,

firefox: “60”,

chrome: “67”,

safari: “11.1”

},

corejs: 2,// 新版本需要指定核心库版本

useBuiltIns: “usage”// 按需注入

}

]

]

}

}

]

}

Plugin详解

======================================================================

HtmlWebpackPlugin


HtmlWebpackPlugin简化了HTML文件的创建,以便为你的webpack包提供服务。这对于在文件名中包含每次会随着编译而发生变化哈希的 webpack bundle 尤其有用。 你可以让插件为你生成一个HTML文件,使用lodash模板提供你自己的模板,或使用你自己的loader

// 配置参数

title: ⽤来⽣成⻚面的 title 元素

filename: 输出的 HTML ⽂件名,默认是 index.html, 也可以直接配置带有子目录。

template: 模板⽂件路路径,⽀持加载器,⽐如 html!./index.html

inject: true | ‘head’ | ‘body’ | false ,注⼊所有的资源到特定的 template 或者 templateContent 中,如果设置为 true 或者 body,所有的 javascript 资源将被放置到 body 元 素的底部,‘head’ 将放置到 head 元素中。

favicon: 添加特定的 favicon 路径到输出的 HTML ⽂文件中。 minify: {} | false , 传递 html-minifier 选项给 minify 输出

hash: true | false, 如果为 true, 将添加⼀个唯一的 webpack 编译 hash 到所有包含的脚本和 CSS 文件,对于解除 cache 很有用。

cache: true | false,如果为 true, 这是默认值,仅仅在⽂件修改之后才会发布⽂件。

showErrors: true | false, 如果为 true, 这是默认值,错误信息会写入到 HTML ⻚面中。

chunks: 允许只添加某些块 (⽐如,仅 unit test 块) chunksSortMode: 允许控制块在添加到⻚面之前的排序方式,支持的值:‘none’ | ‘default’ | {function}-default:‘auto’ excludeChunks: 允许跳过某些块,(比如,跳过单元测试的块)

// 使用案例

const path = require(‘path’)

const htmlWebpackPlugin = require(‘html-webpack-plugin’)

module.exports = {

plugins: [

new htmlWebpackPlugin(

// 插件参数传递,使用对象

{

title: ‘my App’,

filename: ‘index.html’,

template: ‘./src/index.html’

}

)

]

}

// index.html 中获取插件参数

<%= htmlWebpackPlugin.options.title %>

CleanWebpackPlugin


其实 clean-webpack-plugin 很容易知道它的作用,就是来清除文件的。

一般这个插件是配合 webpack -p 这条命令来使用,就是说在为生产环境编译文件的时候,先把 build或dist (就是放生产环境用的文件) 目录里的文件先清除干净,再生成新的。

// 使用案例

const cleanWebpackPlugin = require(‘clean-webpack-plugin’)

module.exports = {

plugins: [

new cleanWebpackPlugin()

]

}

MiniCssExtractPlugin


一般我们的 css 是直接打包进 js⾥面的,我们希望能单独⽣成 css文件。 因为单独⽣成css,css可以和js并行下载,提高⻚面加载效率

借助MiniCssExtractPlugin 完成抽离css

// webpack.config.js

const MiniCssExtractPlugin = require(“mini-css-extract-plugin”);

module: {

rules: [

{

test: /.scss$/,

use: [

// “style-loader”,

// 不再需要style-loader,⽤MiniCssExtractPlugin.loader

MiniCssExtractPlugin.loader,

“css-loader”, // 编译css

“postcss-loader”,

“sass-loader” // 编译scss

]

}

]

},

plugins: [

new MiniCssExtractPlugin({

filename: “css/[name]_[contenthash:6].css”,

chunkFilename: “[id].css”

})

]

性能优化

==================================================================

提升检索速度


1. 缩小loader处理范围

优化loader配置

  • test include exclude三个配置项来缩小loader的处理范围

  • 推荐include

include: path.resolve(__dirname, “./src”),

2. resolve.modules

  • resolve.modules⽤于配置webpack去哪些目录下寻找第三⽅模块,默认是[‘node_modules’]

  • 寻找第三方模块,默认是在当前项⽬录下的node_modules⾥⾯去找,如果没有找到,就会去上⼀级目录…/node_modules找,再没有会去…/…/node_modules中找,以此类推,和Node.js的模块寻找机制很类似。

  • 如果我们的第三方模块都安装在了项目根⽬录下,就可以直接指明这个路径。

module.exports = {

resolve:{

modules: [path.resolve(__dirname, “./node_modules”)]

}

}

3. resolve.alias

resolve.alias配置通过别名来将原导⼊路径映射成⼀个新的导⼊路径

拿react为例,我们引⼊的react库,⼀般存在两套代码

  • cjs

  • 采⽤用commonJS规范的模块化代码

  • umd

  • 已经打包好的完整代码,没有采用模块化,可以直接执⾏

默认情况下,webpack会从⼊口文件./node_modules/bin/react/index开始递归解析和处理依赖的⽂件。我们可以直接指定文件,避免这处的耗时。

alias: {

“@”: path.join(__dirname, “./pages”),

react: path.resolve(

__dirname, “./node_modules/react/umd/react.production.min.js”

),

“react-dom”: path.resolve(

__dirname, “./node_modules/react-dom/umd/react-dom.production.min.js”

)

}

4. resolve.extensions

resolve.extensions在导入语句没带文件后缀时,webpack会⾃动带上后缀后,去尝试查找⽂件是否存在。

默认值:

extensions:[‘.js’,‘.json’,‘.jsx’,‘.ts’]

  • 后缀尝试列表尽量的⼩

  • 导⼊语句尽量的带上后缀

5. externals

我们可以将⼀一些JS⽂文件存储在 CDN 上(减少 Webpack 打包出来的 js 体积),在 index.html 中通过 标签引⼊入,如:

Document
root

我们希望在使用时,仍然可以通过 import 的⽅式去引⽤(如 import $ from ‘jquery’ ),并且希望 webpack 不不会对其进⾏打包,此时就可以配置 externals 。

//webpack.config.js

module.exports = {

//…

externals: {

//jquery通过script引⼊入之后,全局中即有了了 jQuery 变量量

‘jquery’: ‘jQuery’

}

}

6. CDN

CDN通过将资源部署到世界各地,使得⽤户可以就近访问资源,加快访问速度。要接⼊CDN,需要把⽹

⻚的静态资源上传到CDN服务上,在访问这些资源时,使⽤CDN服务提供的URL。

// webpack.config.js

output:{

publicPath: ‘//cdnURL.com’, //指定存放JS⽂文件的CDN地址

}

  • 有cdn服务器地址

  • 确保静态资源⽂件的上传与否

提升加载速度


1. css压缩

  • 借助 optimize-css-assets-webpack-plugin

  • 借助 cssnano

安装

npm install cssnano -D

npm i optimize-css-assets-webpack-plugin -D

// webpack.config.js

const OptimizeCSSAssetsPlugin = require(“optimize-css-assets-webpack-plugin”);

new OptimizeCSSAssetsPlugin({

cssProcessor: require(“cssnano”), //引⼊cssnano配置压缩选项

cssProcessorOptions: {

discardComments: { removeAll: true }

}

})

2. html压缩

  • 借助html-webpack-plugin

new htmlWebpackPlugin({

title: “my app”,

template: “./index.html”,

filename: “index.html”,

minify: {

// 压缩HTML⽂文件

removeComments: true, // 移除HTML中的注释

collapseWhitespace: true, // 删除空⽩符与换行符

minifyCSS: true // 压缩内联css

}

}),

3. 摇树(tree Shaking)

最后

javascript是前端必要掌握的真正算得上是编程语言的语言,学会灵活运用javascript,将对以后学习工作有非常大的帮助。掌握它最重要的首先是学习好基础知识,而后通过不断的实战来提升我们的编程技巧和逻辑思维。这一块学习是持续的,直到我们真正掌握它并且能够灵活运用它。如果最开始学习一两遍之后,发现暂时没有提升的空间,我们可以暂时放一放。继续下面的学习,javascript贯穿我们前端工作中,在之后的学习实现里也会遇到和锻炼到。真正学习起来并不难理解,关键是灵活运用。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

css源码pdf

JavaScript知识点
‘jquery’: ‘jQuery’

}

}

6. CDN

CDN通过将资源部署到世界各地,使得⽤户可以就近访问资源,加快访问速度。要接⼊CDN,需要把⽹

⻚的静态资源上传到CDN服务上,在访问这些资源时,使⽤CDN服务提供的URL。

// webpack.config.js

output:{

publicPath: ‘//cdnURL.com’, //指定存放JS⽂文件的CDN地址

}

  • 有cdn服务器地址

  • 确保静态资源⽂件的上传与否

提升加载速度


1. css压缩

  • 借助 optimize-css-assets-webpack-plugin

  • 借助 cssnano

安装

npm install cssnano -D

npm i optimize-css-assets-webpack-plugin -D

// webpack.config.js

const OptimizeCSSAssetsPlugin = require(“optimize-css-assets-webpack-plugin”);

new OptimizeCSSAssetsPlugin({

cssProcessor: require(“cssnano”), //引⼊cssnano配置压缩选项

cssProcessorOptions: {

discardComments: { removeAll: true }

}

})

2. html压缩

  • 借助html-webpack-plugin

new htmlWebpackPlugin({

title: “my app”,

template: “./index.html”,

filename: “index.html”,

minify: {

// 压缩HTML⽂文件

removeComments: true, // 移除HTML中的注释

collapseWhitespace: true, // 删除空⽩符与换行符

minifyCSS: true // 压缩内联css

}

}),

3. 摇树(tree Shaking)

最后

javascript是前端必要掌握的真正算得上是编程语言的语言,学会灵活运用javascript,将对以后学习工作有非常大的帮助。掌握它最重要的首先是学习好基础知识,而后通过不断的实战来提升我们的编程技巧和逻辑思维。这一块学习是持续的,直到我们真正掌握它并且能够灵活运用它。如果最开始学习一两遍之后,发现暂时没有提升的空间,我们可以暂时放一放。继续下面的学习,javascript贯穿我们前端工作中,在之后的学习实现里也会遇到和锻炼到。真正学习起来并不难理解,关键是灵活运用。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

[外链图片转存中…(img-Cm9EwWft-1714192836371)]

[外链图片转存中…(img-80xGWxf7-1714192836372)]

  • 14
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值