使用webpack来构建项目

 
1、在webpack的官网可以看到,webpack是一个 文件打包工具,它将复杂的的文件依赖打包成独立的资源文件。换句话说, 在webpack里一切文件都是模块,通过loader加载文件,通过plugin注入钩子,最后输出由多个模块组合的文件。那么loader是什么呢?loader用来读取各类资源,比如css、js等。模块loader可以链式调用,链汇总的每个loader都将对资源进行转换,然后将结果传递给下一个loader。
 
也就是说webpack使用loader来管理各类的资源。
 
2、使用webpack来管理资源 webpack.config.js
 
const path = require('path');


module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },
};

 

 
需要注意的是需要保证 loader 的先后顺序: 'style-loader' 在前,而 'css-loader' 在后。如果不遵守此约定,webpack 可能会抛出错误
 
根据上面的配置中,webpack执行的时候就会把对应的文件当做是一个模块进行处理,你可以import对应的资源进行使用了。
 
 
2、 在安装一个 package,而此 package 要打包到生产环境 bundle 中时,你应该使用 npm install --save。如果你在安装一个用于开发环境的 package 时(例如,linter, 测试库等),你应该使用 npm install --save-dev
 
3、我们知道的是,webpack会生成bundle.js文件,那么,当某一个源文件中出现了一个错误,我们在开发的时候就无法定位到具体是哪一个源文件出错了。这个时候我们在开发环境下面使用 source map来定位问题。配置如下
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
    print: './src/print.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
 
  module: {
    
  },
};

 

 
4、我们在使用vue搭建项目的时候,是不是每次修改了一个文件,vue就会自动帮我们重新编译构建并刷新浏览器来着?这是因为vue内置了一个webpack的配置。那如果我们不是使用vue来开发项目又想着可以自动编译和刷新呢?这里我们就可以使用webpack-dev-server来进行配置和搭建实现这个需求。
 
首先我们安装这个插件
npm install --save-dev webpack-dev-server

 

 
然后在配置文件中加入下面的代码
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
    print: './src/print.js',
  },
  plugins: [
     new HtmlWebpackPlugin({
       title: 'Development',
     }),
   ],
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
  devServer: {
    contentBase: './dist',
    hot:true
  },
  module: {
    
  },
};

在package.json中加入下面的srcipt
{
  "name": "wepack-demo",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack serve --open"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "css-loader": "^5.2.4",
    "html-webpack-plugin": "^5.3.1",
    "style-loader": "^2.0.0",
    "webpack": "^5.4.0",
    "webpack-cli": "^4.2.0",
    "webpack-dev-server": "^3.11.2"
  },
  "dependencies": {
    "loadash": "^1.0.0",
    "lodash": "^4.17.21"
  }
}

 

 
 
 
 
执行npm run start,可以看到自动编译和打开了浏览器,这个时候,修改任意文件都会重新刷新文件
 
这是因为配置中告诉了webpack-dev-server这个插件,将dist目录下面的文件可以通过localhost:8080这个地址可以访问到,webpack-dev-server在编译之后不会写入到任何输出文件。而是将bundle文件保留在内存里面,然后将它们当做是可以被访问的文件。
 
 
5、从上面的例子中,我们可以知道,webpack是可以将你的js文件全部打包到一个bundle文件里面,可是有的情况下面,我们不想要将所有的文件一次性引入进来,有些插件在需要的时候再引入。这个时候我们想到的是不是就是将这个bundle文件分离到不同的bundle中,然后再按需加载进来。这个时候我们就需要用到webpack的代码分离的特性了。
 
在entry里写入多个接口,然后使用 SplitChunksPlugin插件来防止重复加载共同的模块
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
    print: './src/print.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },
  plugins: [
    new HtmlWebpackPlugin({//用来管理新的构建的html文件
      title: '管理输出',
    }),
  ],

  devServer: {
    contentBase: './dist',
  },
};

 

然后执行npm run build
可以看到loadash文件只构建了一次
 
 
6、浏览器访问网站的资源的时候,使用缓存技术来加快加载资源的速度。但这又会出现一个问题,就好像我们在项目中修改了很多东西,但是我们文件名没有发生改变。这个时候,浏览器就会以为这个文件没有发生改变,使用缓存的版本。所以我们可以使用webpack的substitution方式来输出文件的名称。这个方式会根据资源的内容创建出唯一的hash,然后再文件后面加上这部分hash。而且这个方式超级简单,我们只需要在webpack的配置文件中修改下面代码:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
    print: './src/print.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },
  plugins: [
    new HtmlWebpackPlugin({//用来管理新的构建的html文件
      title: '管理输出',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },
  devServer: {
    contentBase: './dist',
  },
};

 

 
现在我们不改变文件内容,再build一下看看
 
可以看到,此时是使用了cache,文件是没有发生变化的
 
此外,我们希望一些引用外部插件的bundle在构建的时候不发生变化,这样每次项目发生变化的时候,重新构建就不要再去构建这些不经常改变的外部插件了。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
    print: './src/print.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
  plugins: [
    new HtmlWebpackPlugin({//用来管理新的构建的html文件
      title: '管理输出',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },
  devServer: {
    contentBase: './dist',
  },
};

 

 
这个时候我们可以看到,构建出来的vendor和runtime模块独立出来了,其他自己写的代码模块体积就变小了,这样的资源在二次浏览的时候,就大大的提升了加载速度!牛逼格拉斯!~
 
 
 
6、假设我们自己写了一个插件,想要发布到npm上面去,我们怎么样使用webpack来打包构建这一个插件呢?
其他部分和上面都一样,此外还要申明一下引入方式:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: {
    index: './src/index.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,//每次新构建之前都会清理一下输出的文件夹
    library: {
      name: 'webpackNumbers',
      type: 'umd',
    },
  },
};

 

 
 
 
7、webpack 4中开始引入了tree-shaking,它主要的作用就是消除项目中无用的代码。这样依赖,项目的体积就会大大提升了。也许你会问,项目中还会有无用的代码吗?那不是有用才会写!
那不是这样的,在项目开发的过程中,需求是会不断的改变,一个页面里面引入的组件可能在这一个阶段有用,但是在下一个阶段里,这个功能有可能就会被废弃了。维护的时间越长,废弃的代码可能会越多。
这样,tree-shaking就大大压缩了打包后的体积了。也许你又会问,这个东西内部是怎么识别代码有没有用的,tree-shaking依赖于模块的特性,也就是import和export。。Webpack 跟踪整个应用程序的 import/export 语句,因此,如果它看到导入的东西最终没有被使用,它会认为那是“死代码”,并会对其进行 tree-shaking
 
那么什么样的代码会认为是未被使用的代码呢?我们先来看一下例子
import _ form  'lodash';//这样的导入,webpack会认为这整个lodash的库你都有使用到,就不会动这整个库的代码,所以不建议引入整个库。

import {join} from 'lodash';//这样具名引入的时候,如果后续的代码没有使用这个引入的方法,就会被当做是废弃的代码,然后tree-shaking

 

 
接下来我们在webpack的配置中开启tree-shaking
需要注意的是,webpack官网中指定,必须要在生产模式下面才可以开启tree-shaking,这也可以理解,因为只有在压缩打包代码的时候才会tree-shaking,所以只有在生产模式下才会,所以最后打包的时候记得要将mode 改成production
const path = require('path');


module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
 mode: 'development',
 optimization: {
   usedExports: true,//Webpack 将识别出它认为没有被使用的代码,并在最初的打包步骤中给它做标记
 },
};

 

 
 
接下来我们先理解一个概念:sideEffects,副作用
在上面的配置里面,我们已经让webpack去筛选那些没有被使用的代码,但有些代码引入没有被使用不代表它是一个废弃的代码。比如我们引入样式,使用全局样式表,或者是一个引入全局配置的配置文件。 这样被引入没有被使用但依然起起作用的文件, webpack认为这样的文件有“ sideEffects ”,这样的文件不应该被tree-shaking。为了避免整个项目有副作用的文件,webpack默认将所有的文件视为有副作用,这样就是整个项目都不能tree-shaking,所以我们先配置一下sideEffects,告诉webpack你可以进行tree-shaking。
在package.json中配置
// 所有文件都有副作用,全都不可 tree-shaking
 { 
    "sideEffects": true
 } 

// 没有文件有副作用,全都可以 tree-shaking 
{ 
    "sideEffects": false 
} 


// 只有这些文件有副作用,所有其他文件都可以 tree-shaking,但会保留这些文件 

{ 
    "sideEffects": [ 
        "./src/file1.js", 
        "./src/file2.js" 
    ] 
}

 

 
此外,还可以在loader中配置sideEffects
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    publicPath:'/',//指定资源的目录
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
  plugins: [
    new HtmlWebpackPlugin({//用来管理新的构建的html文件
      title: '管理输出',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
        sideEffects:true,
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },
  devServer: {
    contentBase: './dist',
    hot: true,
  },
};


 

然后执行npm run build的时候,废弃代码就被删除掉了。下面我们来看一下前后代码的对比
 
这是我引入lodash之后没有使用的然后打包的代码(配置之前)
 
可以很清晰的看到最后打包是有吧lodash引进来的,接下来我们按照上面的配置然后打包
记住!这一步很重要,将mode改成production 删除optimization
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'production',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    publicPath:'/',//指定资源的目录
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
  plugins: [
    new HtmlWebpackPlugin({//用来管理新的构建的html文件
      title: '管理输出',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
        sideEffects:true,
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },
  devServer: {
    contentBase: './dist',
    hot: true,
  },
};

 

 
执行npm run build看下
可以看到此时那些无用的代码就被删掉啦!
 
好了,配置完了我们来总结一下
  • 需要使用模块的语法(import、export)
  • 没有被babel转换成commonJs(Webpack 不支持使用 commonjs 模块来完成 tree-shaking。)
  • package.json中配置sideEffects
  • mode设置为producrion再打包
8、环境配置
刚才我们先配置了开发环境的webpack.config.js,然后打包前又要修改,要是每一次都这样是不是很麻烦?):
首先我们在webpack.common.js里面写公有的配置,在webpack.dev.js中写开发环境的配置,在webpack.pro.js中写生产环境的配置,然后使用webpack-merge的工具来进行合并共有项。
 
然后再package.json中写入下面的script,只对你对应的配置文件
 
 "build": "webpack --config webpack.prod.js",
 "start": "webpack serve --open --config webpack.dev.js"
这样就可以啦
 
9、概念理解
接下来要理解一些webpack里面的概念
 
(1)entry
entry指明 webpack应该使用哪个作为入口,然后webpack会找出那些模块和库是这个入口直接或间接的依赖。 默认值是./src/index.js,可以是一个或多个入口。
 
//像这样写的时候输出的时候就会index.bundle.js,print.bundle.js
entry: {//entry可以有多个入口文件
    index: './src/index.js',
    print:'./src/print.js'
  },

//如果想这多个入口文件被绑在一个chunk中的时候,使用下面的方法
entry: [
'./src/index.js', 
'./src/print.js'
],


//单个入口的时候
entry: './src/index.js'

//当一个入口依赖另一个依赖的时候,
entry: {
   index: './src/index.js',
   print: {
      dependOn: 'index',//比如等index被加载完成再加载这个入口
      import: './src/app.js',//启动的时候需要加载的模块
    },
  },

//将app和vendor入口分离(上面说的代码分离)
//在vendor中存入未做修改的必要库或文件,然后将这些打包在一起成为单独的chunk,内容哈希保持不变,这就使浏览器可以独立缓存,减少了加载的时间
entry: {
    main: './src/app.js',
    vendor: './src/vendor.js',
  },
output: {
    filename: '[name].[contenthash].bundle.js',
  },

 

 
 
(2)output
output是告诉webpack,在 哪里输出这些文件还有如何命名这些文件。我们在上面已经可以知道,这里可以配置哈希文件名,并且可以再每次输出前清空输出文件。
output.path指定bundle输出到哪里,filename来配置输出的文件名
 
output: {
    filename: '[name].[contenthash].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },

 

 
(3)loader(类似于gulp中的task)
因为webpack只能理解js和json文件格式,loader是为了让webpack去处理其他类型的文件,将这些文件转换成模块,然后被添加到依赖中,
loader对应的顶级配置是module.rules中,其中每个数组项是一个loader。
test属性是识别哪些文件需要被转换,use属性是定义使用哪个loader来转换这些文件
还可以在这里指定哪些文件有副作用sideEffects
 
module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
        sideEffects:true,
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },

 

 
(4)插件
插件是可以解决loader无法解决的事,webpack的插件是一个有apply方法的对象,这个apply会被webpack的编译器调用。
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
plugins: [
    new webpack.ProgressPlugin(),
    new HtmlWebpackPlugin({ template: './src/index.html' }),
  ],

 

(5) target 指明配置使用的环境,可以在这里指明是服务端node还是浏览器
target: 'node',

 

target可选值如下:
async-node
编译为类 Node.js 环境可用(使用 fs 和 vm 异步加载分块)
electron-main
编译为 Electron 主进程。
electron-renderer
编译为 Electron 渲染进程,使用 JsonpTemplatePlugin,
FunctionModulePlugin 来为浏览器环境提供目标,使用 NodeTargetPlugin 和 ExternalsPlugin
 
为 CommonJS 和 Electron 内置模块提供目标。
 
electron-preload
编译为 Electron 渲染进程,
使用 NodeTemplatePlugin 且 asyncChunkLoading 设置为 true ,FunctionModulePlugin 来为浏览器提供目标,使用 NodeTargetPlugin 和 ExternalsPlugin 为 CommonJS 和 Electron 内置模块提供目标。
 
node
编译为类 Node.js 环境可用(使用 Node.js require 加载 chunks)
node-webkit
编译为 Webkit 可用,并且使用 jsonp 去加载分块。支持 Node.js 内置模块和 nw.gui
导入(实验性质)
 
nwjs[[X].Y]
等价于 node-webkit
web
编译为类浏览器环境里可用 (默认)
webworker
编译成一个 WebWorker
esX
编译为指定版本的 ECMAScript。例如,es5,es2020
browserslist
从 browserslist-config 中推断出平台和 ES 特性 (如果 browserslist 可用,其值则为默认)
 
 
那么我们这个项目要是要在服务端和客户端同时运行的咋办呢?
多target配置!
const path = require('path');
const serverConfig = {
  target: 'node',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'lib.node.js',
  },
  //…
};


const clientConfig = {
  target: 'web', // <=== 默认为 'web',可省略
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'lib.js',
  },
  //…};


module.exports = [serverConfig, clientConfig];

 

 
 
(6)runtime与 manifest
这两个东西是用来管理项目中所有模块的交互。是 在浏览器运行过程中,webpack 用来 连接模块化应用程序所需的所有代码 。它包含:在模块交互时, 连接模块所需的加载和解析逻辑 。包括:已经加载到浏览器中的连接模块逻辑,以及尚未加载模块的延迟加载逻辑。
 
当 webpack 的compiler 开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "manifest",当完成打包并发送到浏览器时,runtime 会通过 manifest 来解析和加载模块。无论你选择哪种 模块语法,那些 import 或 require 语句现在都已经转换为 __webpack_require__ 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够检索这些标识符,找出每个标识符背后对应的模块
 
也就是说runtime是用来管理模块的主要逻辑代码,manifest是用来记录所有模块之间的关系。
 
runtime和manifest在每次构建的时候都会发生变化,这也就是为什么即使内容没有发生变化,hash还会会改变的原因。
 
 
 
 
 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值