vue-cli的webpack模板项目配置文件说明

如果没有vue-cli,那么进行vue项目的开发环境配置将是很费力的一件事情,现在脚手架可以快速构建一个开发环境,是很不错的。不过对于脚手架构建的配置,还是要大概了解一下,方便自己调试配置。

初始化一个vue项目操作命令如下:

# 全局安装 vue-cli
$ npm install –global vue-cli

# 创建一个基于 webpack 模板的新项目
$ vue init webpack my-project

# 安装依赖
$ cd my-project
$ npm install
$ npm run dev

一般来说,vue-cli构建项目时候回生成类似下面的项目文件结构:

复制代码
├─build
│ ├─build.js
│ ├─check-versions.js
│ ├─dev-client.js
│ ├─dev-server.js
│ ├─utils.js
│ ├─vue-loader.conf.js
│ ├─webpack.base.conf.js
│ ├─webpack.dev.conf.js
│ ├─webpack.prod.conf.js
├─config
│ ├─dev.env.js
│ ├─index.js
│ ├─prod.env.js
├─…
└─package.json
复制代码

一、项目指令

我们可以在package.json里面的看到scripts字段是这样:

这里包含开发运行,项目打包,单元测试等命令,测试的东西先放一边,看dev和build命令。运行”npm run dev”的时候执行的是build/dev-server.js文件,运行”npm run build”的时候执行的是build/build.js文件,我们可以从这两个文件开始进行代码阅读分析。

二、build文件夹配置

1、build/dev-server.js

这个文件主要是完成下面操作:

复制代码
1 检查node和npm的版本
2 引入相关插件和配置
3 创建express服务器和webpack编译器
4 配置开发中间件(webpack-dev-middleware)和热重载中间件(webpack-hot-middleware)
5 挂载代理服务和中间件
6 配置静态资源
7 启动服务器监听特定端口(8080)
8 自动打开浏览器并打开特定网址(localhost:8080)
复制代码
Ps:express服务器提供静态文件服务,不过它还使用了http-proxy-middleware,一个http请求代理的中间件。前端开发过程中需要使用到后台的API的话,可以通过配置proxyTable来将相应的后台请求代理到专用的API服务器。

代码配置注释:

复制代码
1 // 检查NodeJS和npm的版本
2 require(‘./check-versions’)()
3
4 // 获取配置
5 var config = require(‘../config’)
6 // 如果Node的环境变量中没有设置当前的环境(NODE_ENV),则使用config中的配置作为当前的环境
7 if (!process.env.NODE_ENV) {
8 process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
9 }
10
11 // 一个可以调用默认软件打开网址、图片、文件等内容的插件
12 // 这里用它来调用默认浏览器打开dev-server监听的端口,例如:localhost:8080
13 var opn = require(‘opn’)
14 var path = require(‘path’)
15 var express = require(‘express’)
16 var webpack = require(‘webpack’)
17
18 // 一个express中间件,用于将http请求代理到其他服务器
19 // 例:localhost:8080/api/xxx –> localhost:3000/api/xxx
20 // 这里使用该插件可以将前端开发中涉及到的请求代理到API服务器上,方便与服务器对接
21 var proxyMiddleware = require(‘http-proxy-middleware’)
22
23 // 根据 Node 环境来引入相应的 webpack 配置
24 var webpackConfig = process.env.NODE_ENV === ‘testing’
25 ? require(‘./webpack.prod.conf’)
26 : require(‘./webpack.dev.conf’)
27
28 // dev-server 监听的端口,默认为config.dev.port设置的端口,即8080
29 var port = process.env.PORT || config.dev.port
30
31 // 用于判断是否要自动打开浏览器的布尔变量,当配置文件中没有设置自动打开浏览器的时候其值为 false
32 var autoOpenBrowser = !!config.dev.autoOpenBrowser
33
34 // 定义 HTTP 代理表,代理到 API 服务器
35 var proxyTable = config.dev.proxyTable
36
37 // 创建1个 express 实例
38 var app = express()
39
40 // 根据webpack配置文件创建Compiler对象
41 var compiler = webpack(webpackConfig)
42
43 // webpack-dev-middleware使用compiler对象来对相应的文件进行编译和绑定
44 // 编译绑定后将得到的产物存放在内存中而没有写进磁盘
45 // 将这个中间件交给express使用之后即可访问这些编译后的产品文件
46 var devMiddleware = require(‘webpack-dev-middleware’)(compiler, {
47 publicPath: webpackConfig.output.publicPath,
48 quiet: true
49 })
50
51 // webpack-hot-middleware,用于实现热重载功能的中间件
52 var hotMiddleware = require(‘webpack-hot-middleware’)(compiler, {
53 log: () => {}
54 })
55
56 // 当html-webpack-plugin提交之后通过热重载中间件发布重载动作使得页面重载
57 compiler.plugin(‘compilation’, function (compilation) {
58 compilation.plugin(‘html-webpack-plugin-after-emit’, function (data, cb) {
59 hotMiddleware.publish({ action: ‘reload’ })
60 cb()
61 })
62 })
63
64 // 将 proxyTable 中的代理请求配置挂在到express服务器上
65 Object.keys(proxyTable).forEach(function (context) {
66 var options = proxyTable[context]
67 // 格式化options,例如将’www.example.com’变成{ target: ‘www.example.com’ }
68 if (typeof options === ‘string’) {
69 options = { target: options }
70 }
71 app.use(proxyMiddleware(options.filter || context, options))
72 })
73
74 // handle fallback for HTML5 history API
75 // 重定向不存在的URL,常用于SPA
76 app.use(require(‘connect-history-api-fallback’)())
77
78 // serve webpack bundle output
79 // 使用webpack开发中间件
80 // 即将webpack编译后输出到内存中的文件资源挂到express服务器上
81 app.use(devMiddleware)
82
83 // enable hot-reload and state-preserving
84 // compilation error display
85 // 将热重载中间件挂在到express服务器上
86 app.use(hotMiddleware)
87
88 // serve pure static assets
89 // 静态资源的路径
90 var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
91
92 // 将静态资源挂到express服务器上
93 app.use(staticPath, express.static(‘./static’))
94
95 // 应用的地址信息,例如:http://localhost:8080
96 var uri = ‘http://localhost:’ + port
97
98 // webpack开发中间件合法(valid)之后输出提示语到控制台,表明服务器已启动
99 devMiddleware.waitUntilValid(function () {
100 console.log(‘> Listening at ‘ + uri + ‘\n’)
101 })
102
103 // 启动express服务器并监听相应的端口(8080)
104 module.exports = app.listen(port, function (err) {
105 if (err) {
106 console.log(err)
107 return
108 }
109
110 // when env is testing, don’t need open it
111 // 如果符合自动打开浏览器的条件,则通过opn插件调用系统默认浏览器打开对应的地址uri
112 if (autoOpenBrowser && process.env.NODE_ENV !== ‘testing’) {
113 opn(uri)
114 }
115 })
复制代码
2、build/webpack.base.conf.js

从代码中看到,dev-server使用的webpack配置来自build/webpack.dev.conf.js文件(测试环境下使用的是build/webpack.prod.conf.js,这里暂时不考虑测试环境)。而build/webpack.dev.conf.js中又引用了webpack.base.conf.js,所以这里先看webpack.base.conf.js。

webpack.base.conf.js主要完成下面的操作:

1 配置webpack编译入口
2 配置webpack输出路径和命名规则
3 配置模块resolve规则
4 配置不同类型模块的处理规则
代码配置注释:

复制代码
1 var path = require(‘path’)
2 var utils = require(‘./utils’)
3 var config = require(‘../config’)
4 var vueLoaderConfig = require(‘./vue-loader.conf’)
5
6 // 给出正确的绝对路径
7 function resolve (dir) {
8 return path.join(__dirname, ‘..’, dir)
9 }
10
11 module.exports = {
12 // 配置webpack编译入口
13 entry: {
14 app: ‘./src/main.js’
15 },
16
17 // 配置webpack输出路径和命名规则
18 output: {
19 // webpack输出的目标文件夹路径(例如:/dist)
20 path: config.build.assetsRoot,
21 // webpack输出bundle文件命名格式
22 filename: ‘[name].js’,
23 // webpack编译输出的发布路径
24 publicPath: process.env.NODE_ENV === ‘production’
25 ? config.build.assetsPublicPath
26 : config.dev.assetsPublicPath
27 },
28
29 // 配置模块resolve的规则
30 resolve: {
31 // 自动resolve的扩展名
32 extensions: [‘.js’, ‘.vue’, ‘.json’],
33 // resolve模块的时候要搜索的文件夹
34 modules: [
35 resolve(‘src’),
36 resolve(‘node_modules’)
37 ],
38 // 创建路径别名,有了别名之后引用模块更方便,例如
39 // import Vue from ‘vue/dist/vue.common.js’可以写成 import Vue from ‘vue’
40 alias: {
41 ‘vue$’: ‘vue/dist/vue.common.js’,
42 ‘src’: resolve(‘src’),
43 ‘assets’: resolve(‘src/assets’),
44 ‘components’: resolve(‘src/components’)
45 }
46 },
47
48 // 配置不同类型模块的处理规则
49 module: {
50 rules: [
51 {// 对src和test文件夹下的.js和.vue文件使用eslint-loader
52 test: /\.(js|vue)$/,
53 loader: ‘eslint-loader’,
54 enforce: “pre”,
55 include: [resolve(‘src’), resolve(‘test’)],
56 options: {
57 formatter: require(‘eslint-friendly-formatter’)
58 }
59 },
60 {// 对所有.vue文件使用vue-loader
61 test: /\.vue$/,
62 loader: ‘vue-loader’,
63 options: vueLoaderConfig
64 },
65 {// 对src和test文件夹下的.js文件使用babel-loader
66 test: /\.js$/,
67 loader: ‘babel-loader’,
68 include: [resolve(‘src’), resolve(‘test’)]
69 },
70 {// 对图片资源文件使用url-loader,query.name指明了输出的命名规则
71 test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
72 loader: ‘url-loader’,
73 query: {
74 limit: 10000,
75 name: utils.assetsPath(‘img/[name].[hash:7].[ext]’)
76 }
77 },
78 {// 对字体资源文件使用url-loader,query.name指明了输出的命名规则
79 test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
80 loader: ‘url-loader’,
81 query: {
82 limit: 10000,
83 name: utils.assetsPath(‘fonts/[name].[hash:7].[ext]’)
84 }
85 }
86 ]
87 }
88 }
复制代码
3、build/webpack.dev.conf.js

接下来看webpack.dev.conf.js,这里面在webpack.base.conf的基础上增加完善了开发环境下面的配置,主要完成下面操作:

1 将hot-reload相关的代码添加到entry chunks
2 合并基础的webpack配置
3 使用styleLoaders
4 配置Source Maps
5 配置webpack插件
代码配置注释:

复制代码
1 var utils = require(‘./utils’)
2 var webpack = require(‘webpack’)
3 var config = require(‘../config’)
4
5 // 一个可以合并数组和对象的插件
6 var merge = require(‘webpack-merge’)
7 var baseWebpackConfig = require(‘./webpack.base.conf’)
8
9 // 一个用于生成HTML文件并自动注入依赖文件(link/script)的webpack插件
10 var HtmlWebpackPlugin = require(‘html-webpack-plugin’)
11
12 // 用于更友好地输出webpack的警告、错误等信息
13 var FriendlyErrorsPlugin = require(‘friendly-errors-webpack-plugin’)
14
15 // add hot-reload related code to entry chunks
16 Object.keys(baseWebpackConfig.entry).forEach(function (name) {
17 baseWebpackConfig.entry[name] = [‘./build/dev-client’].concat(baseWebpackConfig.entry[name])
18 })
19
20 // 合并基础的webpack配置
21 module.exports = merge(baseWebpackConfig, {
22 // 配置样式文件的处理规则,使用styleLoaders
23 module: {
24 rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
25 },
26
27 // 配置Source Maps。在开发中使用cheap-module-eval-source-map更快
28 devtool: ‘#cheap-module-eval-source-map’,
29
30 // 配置webpack插件
31 plugins: [
32 new webpack.DefinePlugin({
33 ‘process.env’: config.dev.env
34 }),
35 // https://github.com/glenjamin/webpack-hot-middleware#installation–usage
36 new webpack.HotModuleReplacementPlugin(),
37 // 后页面中的报错不会阻塞,但是会在编译结束后报错
38 new webpack.NoEmitOnErrorsPlugin(),
39 // https://github.com/ampedandwired/html-webpack-plugin
40 new HtmlWebpackPlugin({
41 filename: ‘index.html’,
42 template: ‘index.html’,
43 inject: true
44 }),
45 new FriendlyErrorsPlugin()
46 ]
47 })
复制代码
4、build/utils.js

utils主要完成下面3个操作:

1 配置静态资源路径
2 生成cssLoaders用于加载.vue文件中的样式
3 生成styleLoaders用于加载不在.vue文件中的单独存在的样式文件
5、build/vue-loader.conf.js

vue-loader.conf则只配置了css加载器以及编译css之后自动添加前缀。

代码配置注释:

复制代码
var utils = require(‘./utils’)
var config = require(‘../config’)
var isProduction = process.env.NODE_ENV === ‘production’

module.exports = {
// css加载器
loaders: utils.cssLoaders({
sourceMap: isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap,
extract: isProduction
}),

// 让 vue-loader 知道需要对 audio 的 src 属性的内容转换为模块
transformToRequire: {
video: ‘src‘,
source: ‘src‘,
img: ‘src‘,
image: ‘xlink:href‘
}
}
复制代码
6、build/build.js

build.js主要完成下面操作:

1 loading动画
2 删除创建目标文件夹
3 webpack编译
4 输出信息
Ps: webpack编译之后会输出到配置里面指定的目标文件夹;删除目标文件夹之后再创建是为了去除旧的内容,以免产生不可预测的影响。

代码配置注释:

复制代码
1 // 检查NodeJS和npm的版本
2 require(‘./check-versions’)()
3
4 process.env.NODE_ENV = ‘production’
5
6 //ora插件,实现node.js 命令行环境的 loading效果, 和显示各种状态的图标等
7 var ora = require(‘ora’)
8 var rm = require(‘rimraf’)
9 var path = require(‘path’)
10
11 // 用于在控制台输出带颜色字体的插件
12 var chalk = require(‘chalk’)
13
14 var webpack = require(‘webpack’)
15 var config = require(‘../config’)
16 var webpackConfig = require(‘./webpack.prod.conf’)
17
18 var spinner = ora(‘building for production…’)
19 spinner.start()
20
21 //rimraf插件,每次启动编译或者打包之前,先把整个dist文件夹删除,然后再重新生成dist
22 rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
23 if (err) throw err
24 webpack(webpackConfig, function (err, stats) {
25 spinner.stop()
26 if (err) throw err
27 process.stdout.write(stats.toString({
28 colors: true,
29 modules: false,
30 children: false,
31 chunks: false,
32 chunkModules: false
33 }) + ‘\n\n’)
34
35 console.log(chalk.cyan(‘ Build complete.\n’))
36 console.log(chalk.yellow(
37 ‘ Tip: built files are meant to be served over an HTTP server.\n’ +
38 ‘ Opening index.html over file:// won\’t work.\n’
39 ))
40 })
41 })
复制代码
7、build/webpack.prod.conf.js

构建的时候用到的webpack配置来自webpack.prod.conf.js,该配置同样是在webpack.base.conf基础上的进一步完善。主要完成下面操作:

1 合并基础的webpack配置
2 使用styleLoaders
3 配置webpack的输出
4 配置webpack插件
5 gzip模式下的webpack插件配置
6 webpack-bundle分析
代码配置注释:

复制代码
1 var path = require(‘path’)
2 var utils = require(‘./utils’)
3 var webpack = require(‘webpack’)
4 var config = require(‘../config’)
5 var merge = require(‘webpack-merge’)
6 var baseWebpackConfig = require(‘./webpack.base.conf’)
7 var HtmlWebpackPlugin = require(‘html-webpack-plugin’)
8
9 // 用于从webpack生成的bundle中提取文本到特定文件中的插件
10 // 可以抽取出css,js文件将其与webpack输出的bundle分离
11 var ExtractTextPlugin = require(‘extract-text-webpack-plugin’)
12
13 var env = process.env.NODE_ENV === ‘testing’
14 ? require(‘../config/test.env’)
15 : config.build.env
16
17 // 合并基础的webpack配置
18 var webpackConfig = merge(baseWebpackConfig, {
19 module: {
20 rules: utils.styleLoaders({
21 sourceMap: config.build.productionSourceMap,
22 extract: true
23 })
24 },
25 devtool: config.build.productionSourceMap ? ‘#source-map’ : false,
26 // 配置webpack的输出
27 output: {
28 // 编译输出目录
29 path: config.build.assetsRoot,
30 // 编译输出文件名格式
31 filename: utils.assetsPath(‘js/[name].[chunkhash].js’),
32 // 没有指定输出名的文件输出的文件名格式
33 chunkFilename: utils.assetsPath(‘js/[id].[chunkhash].js’)
34 },
35
36 // 配置webpack插件
37 plugins: [
38 // http://vuejs.github.io/vue-loader/en/workflow/production.html
39 new webpack.DefinePlugin({
40 ‘process.env’: env
41 }),
42
43 // 丑化压缩代码
44 new webpack.optimize.UglifyJsPlugin({
45 compress: {
46 warnings: false
47 },
48 sourceMap: true
49 }),
50
51 // 抽离css文件
52 new ExtractTextPlugin({
53 filename: utils.assetsPath(‘css/[name].[contenthash].css’)
54 }),
55
56 // generate dist index.html with correct asset hash for caching.
57 // you can customize output by editing /index.html
58 // see https://github.com/ampedandwired/html-webpack-plugin
59 new HtmlWebpackPlugin({
60 filename: process.env.NODE_ENV === ‘testing’
61 ? ‘index.html’
62 : config.build.index,
63 template: ‘index.html’,
64 inject: true,
65 minify: {
66 removeComments: true,
67 collapseWhitespace: true,
68 removeAttributeQuotes: true
69 // more options:
70 // https://github.com/kangax/html-minifier#options-quick-reference
71 },
72 // necessary to consistently work with multiple chunks via CommonsChunkPlugin
73 chunksSortMode: ‘dependency’
74 }),
75
76 // split vendor js into its own file
77 new webpack.optimize.CommonsChunkPlugin({
78 name: ‘vendor’,
79 minChunks: function (module, count) {
80 // any required modules inside node_modules are extracted to vendor
81 return (
82 module.resource &&
83 /\.js$/.test(module.resource) &&
84 module.resource.indexOf(
85 path.join(__dirname, ‘../node_modules’)
86 ) === 0
87 )
88 }
89 }),
90 // extract webpack runtime and module manifest to its own file in order to
91 // prevent vendor hash from being updated whenever app bundle is updated
92 new webpack.optimize.CommonsChunkPlugin({
93 name: ‘manifest’,
94 chunks: [‘vendor’]
95 })
96 ]
97 })
98
99 // gzip模式下需要引入compression插件进行压缩
100 if (config.build.productionGzip) {
101 var CompressionWebpackPlugin = require(‘compression-webpack-plugin’)
102 webpackConfig.plugins.push(
103 new CompressionWebpackPlugin({
104 asset: ‘[path].gz[query]’,
105 algorithm: ‘gzip’,
106 test: new RegExp(
107 ‘\\.(‘ +
108 config.build.productionGzipExtensions.join(‘|’) +
109 ‘)$’
110 ),
111 threshold: 10240,
112 minRatio: 0.8
113 })
114 )
115 }
116
117 if (config.build.bundleAnalyzerReport) {
118 var BundleAnalyzerPlugin = require(‘webpack-bundle-analyzer’).BundleAnalyzerPlugin
119 webpackConfig.plugins.push(new BundleAnalyzerPlugin())
120 }
121
122 module.exports = webpackConfig
复制代码
8、build/check-versions.js

check-version.js完成对node和npm的版本检测。

代码配置注释:

复制代码
1 // 用于在控制台输出带颜色字体的插件
2 var chalk = require(‘chalk’)
3
4 // 语义化版本检查插件(The semantic version parser used by npm)
5 var semver = require(‘semver’)
6
7 // 引入package.json
8 var packageConfig = require(‘../package.json’)
9
10 // 开辟子进程执行指令cmd并返回结果
11 function exec (cmd) {
12 return require(‘child_process’).execSync(cmd).toString().trim()
13 }
14
15 // node和npm版本需求
16 var versionRequirements = [
17 {
18 name: ‘node’,
19 currentVersion: semver.clean(process.version),
20 versionRequirement: packageConfig.engines.node
21 },
22 {
23 name: ‘npm’,
24 currentVersion: exec(‘npm –version’),
25 versionRequirement: packageConfig.engines.npm
26 }
27 ]
28
29 module.exports = function () {
30 var warnings = []
31 // 依次判断版本是否符合要求
32 for (var i = 0; i < versionRequirements.length; i++) {
33 var mod = versionRequirements[i]
34 if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
35 warnings.push(mod.name + ‘: ‘ +
36 chalk.red(mod.currentVersion) + ‘ should be ‘ +
37 chalk.green(mod.versionRequirement)
38 )
39 }
40 }
41
42 // 如果有警告则将其输出到控制台
43 if (warnings.length) {
44 console.log(”)
45 console.log(chalk.yellow(‘To use this template, you must update following to modules:’))
46 console.log()
47 for (var i = 0; i < warnings.length; i++) {
48 var warning = warnings[i]
49 console.log(‘ ‘ + warning)
50 }
51 console.log()
52 process.exit(1)
53 }
54 }
复制代码

三、config文件夹

1、config/index.js

config文件夹下最主要的文件就是index.js了,在这里面描述了开发和构建两种环境下的配置,前面的build文件夹下也有不少文件引用了index.js里面的配置。

代码配置注释:

复制代码
1 // see http://vuejs-templates.github.io/webpack for documentation.
2 var path = require(‘path’)
3
4 module.exports = {
5 // 构建产品时使用的配置
6 build: {
7 // webpack的编译环境
8 env: require(‘./prod.env’),
9 // 编译输入的index.html文件
10 index: path.resolve(__dirname, ‘../dist/index.html’),
11 // webpack输出的目标文件夹路径
12 assetsRoot: path.resolve(__dirname, ‘../dist’),
13 // webpack编译输出的二级文件夹
14 assetsSubDirectory: ‘static’,
15 // webpack编译输出的发布路径
16 assetsPublicPath: ‘/’,
17 // 使用SourceMap
18 productionSourceMap: true,
19 // Gzip off by default as many popular static hosts such as
20 // Surge or Netlify already gzip all static assets for you.
21 // Before setting to true, make sure to:
22 // npm install –save-dev compression-webpack-plugin
23 // 默认不打开开启gzip模式
24 productionGzip: false,
25 // gzip模式下需要压缩的文件的扩展名
26 productionGzipExtensions: [‘js’, ‘css’],
27 // Run the build command with an extra argument to
28 // View the bundle analyzer report after build finishes:
29 // npm run build --report
30 // Set to true or false to always turn it on or off
31 bundleAnalyzerReport: process.env.npm_config_report
32 },
33 // 开发过程中使用的配置
34 dev: {
35 // webpack的编译环境
36 env: require(‘./dev.env’),
37 // dev-server监听的端口
38 port: 8080,
39 // 启动dev-server之后自动打开浏览器
40 autoOpenBrowser: true,
41 // webpack编译输出的二级文件夹
42 assetsSubDirectory: ‘static’,
43 // webpack编译输出的发布路径
44 assetsPublicPath: ‘/’,
45 // 请求代理表,在这里可以配置特定的请求代理到对应的API接口
46 // 例如将’/api/xxx’代理到’www.example.com/api/xxx’
47 proxyTable: {},
48 // CSS Sourcemaps off by default because relative paths are “buggy”
49 // with this option, according to the CSS-Loader README
50 // (https://github.com/webpack/css-loader#sourcemaps)
51 // In our experience, they generally work as expected,
52 // just be aware of this issue when enabling this option.
53 // 是否开启 cssSourceMap
54 cssSourceMap: false
55 }
56 }
复制代码
Ps:config/dev.env.js、config/prod.env.js,这两个个文件就简单设置了环境变量而已,没什么特别。

VB中正则表达式的SubMatches、matches和MatchCollection的使用

这两天在写一个excel VBA的东西,记录一下。