实战一

准备本篇的环境

虽然可以仅展示核心代码,但笔者认为在一个完整的环境中边看边做,举一反三,效果更佳。

这里的环境其实就是初步认识 webpack一文完整的示例,包含 webpack、devServer、处理css、生成 html。

项目结构如下:

webpack-example2
- src // 项目源码
- a.css
- b.js
- c.js
- index.html // 页面模板
- index.js // 入口
- package.json // 存放了项目依赖的包
- webpack.config.js // webpack配置文件

src中的代码如下:

// a.css
body{color:blue;} // b.js
import './c.js'
console.log('moduleB')
console.log('b2') // c.js
console.log('moduleC') // index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=`, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p>请查看控制台</p>
</body>
</html> // index.js
import './b.js'
import './a.css'
console.log('moduleA')

package.json:

{
"name": "webpack-example2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"dev": "webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"css-loader": "^5.2.4",
"html-webpack-plugin": "^4.5.2",
"style-loader": "^2.0.0",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2"
}
}

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
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"]
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
})
],
mode: 'development',
devServer: {
open: true,
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000,
},
};

在 webpack-example2 目录下运行项目:

// 安装项目依赖的包
> npm i
// 启动服务
> npm run dev

启动服务器后,浏览器会自动打开页面,如果看到蓝色文字”请查看控制台“,说明环境已准备就绪。

打包样式

处理 css 和 less

less 是一种 css 预处理语言,在 webpack 中要处理 less 需要使用 less-loader,用于将 less 转为 css。

首先安装依赖,然后修改配置文件:

// 安装包。版本8安装失败,所以降了一个版本
> npm i -D less-loader@7 // webpack.config.js
// 增加对 less 文件处理的loader
rules: [
// 需要保留,否则识别不了 css 文件
{
test: /\.css$/i,
use: ["style-loader", "css-loader"]
},
// +
{
test: /\.less$/i,
loader: [
// compiles Less to CSS
"style-loader",
"css-loader",
"less-loader",
],
},
],

然后增加 a.less 文件,在 index.js 中引入 a.less,重新启动服务进行测试:

// src/a.less
body{
p{
color:pink;
}
} // index.js
import './b.js'
import './a.css'
// +
import './a.less'
console.log('moduleA') // 启动服务
> npm run dev

在新开的页面中,看到粉色文字”请查看控制台“,说明 less 处理成功。

提取 css 成单独文件

通过浏览器我们发现现在 css 是嵌在页面内的,就像这样:

<head>
...
<style>body{color:blue;}</style>
<style>body p {
color: pink;
}
</style>
</head>

通常我们会通过 link 来引入 css 文件,所以接下来就将 css 取成单独的文件。这里需要使用 mini-css-extract-plugin 这个包。

我们只需要安装依赖包,修改配置文件即可:

// 安装依赖包
> npm i -D mini-css-extract-plugin@1
// 不在需要 style-loader,卸载
> npm r -D style-loader // webpack.config.js
// +
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
...
module: {
rules: [
// 修改规则
{
test: /\.css$/i,
// 将 style-loader 改为 MiniCssExtractPlugin.loader
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.less$/i,
loader: [
// 将 style-loader 改为 MiniCssExtractPlugin.loader
MiniCssExtractPlugin.loader,
"css-loader",
"less-loader",
],
},
],
},
plugins: [
// +
new MiniCssExtractPlugin(),
...
], };

启动服务(npm run dev),在打开的页面中可以看到 css 已经改为 link 的方式引入,就是这样:

// 从 style 改为 link 方式
<link href="main.css" rel="stylesheet"> // 通过网络查看 main.css 的内容是:
body{color:blue;}
body p {
color: pink;
}

由于我们对 css 和 less 都使用了 MiniCssExtractPlugin.loader,所以 a.css 和 a.less 都被提取到 main.css 中。

Tip:如果通过npm run build打包,则可以看到 dist/main.css 文件。

使用 PostCSS

PostCSS - 使用JavaScript转换CSS的工具。

可以将 postcss 当作一个平台,下面我们通过 postcss 做两件事:

  • 增加代码可读性(或增加前缀)
:fullscreen {
} // 转为 :-webkit-full-screen {
}
:-ms-fullscreen {
}
:fullscreen {
}
  • 立即使用明天的CSS
body {
color: lch(53 105 40);
} // 转为 body {
color: rgb(250, 0, 4);
}

webpack 可以通过 postcss-loader 来使用 postcss。

由于 postcss 只是一个平台,具体功能需要通过插件来实现,这里我们使用 postcss-preset-env

postcss-preset-env 可以将现代CSS转换为大多数浏览器可以理解的内容,并根据目标浏览器或运行时环境确定所需的polyfill。而且它包含自动前缀。

首先安装相关依赖,并修改配置文件:

> npm i -D postcss-loader@4 postcss-preset-env@6

// webpack.config.js
// +
const postcssPresetEnv = require('postcss-preset-env');
// +
const postcssLoader = {
loader: 'postcss-loader',
options: {
// postcss 只是个平台,具体功能需要使用插件
// Set PostCSS options and plugins
postcssOptions:{
plugins:[
// 配置插件 postcss-preset-env
[
"postcss-preset-env",
{
// 自动前缀。默认是true
// autoprefixer: true,
// 根据您所支持的浏览器来确定需要哪些polyfill。这里仅做演示
browsers: 'ie >= 8, chrome > 10',
// stage 默认是 2
// stage:2
},
],
]
}
}
} module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
// + 放在css-loader后面
postcssLoader
]
},
{
test: /\.less$/i,
loader: [
MiniCssExtractPlugin.loader,
"css-loader",
// +
postcssLoader,
"less-loader",
],
},
]
},
};

接着修改 a.css 和 a.less,重新启动服务器:

// a.css
body{
color: lch(53 105 40);
} // a.less
body{
p{
transform: scale(1, 2);
}
} // 启动服务
> npm run dev

在新开的页面中,我们看到红色文字”请查看控制台“,而且文字纵向拉长了一倍。通过浏览器查看 main.css 源码如下:

body{
color: rgb(250, 0, 4);
}
body p {
-webkit-transform: scale(1, 2);
-ms-transform: scale(1, 2);
transform: scale(1, 2);
}

至此,增加前缀以及立即使用明天的CSS都已经完成。

Tip:stage(阶段)可以是0(实验)到4(稳定),默认是2,如果我们改为3或4,重新打包,lch(53 105 40);则不会转为 rgb(250, 0, 4);将plugins换成下面的写法效果相同。

plugins:[
postcssPresetEnv({
browsers: 'ie >= 8, chrome > 10',
})
]

postcss-preset-env 支持任何标准的 browserslist 配置,可以是 .browserslistrc 文件,package.json 中的browserslist 键或 browserslist 环境变量。

如果将 browsers: 'ie >= 8, chrome > 10', 注释,browsers 将使用默认的 browserslist 查询(即> 0.5%, last 2 versions, Firefox ESR, not dead),重新构建,则不会添加前缀。

如果不想在 browsers 中写,在 package.json 中的 browserslist 中配置也是可以的:

// package.json
{
...
"browserslist": [
"ie >= 8",
"chrome > 10"
]
}

:package.json 不能写注释,本文在 package.json 中的注释仅作说明。

如果觉得 package.json 写的内容太多,我们甚至可以将这部分提取到一个单独的文件中来写:

// .browserslistrc
// from github browserslist
# Browsers that we support ie >= 8
chrome > 10

最后,如果我们针对开发环境和生成环境做不同的处理,比如开发环境支持 ie8+,而生产环境支持 chrom10+,我们可以这么写:

// .browserslistrc
# Browsers that we support [production]
chrome > 10 [development]
ie >= 8

然后在配置文件中通过 process.env 来指定环境:

// +
// process.env属性返回一个包含用户环境的对象
process.env.NODE_ENV = 'development' // or production

Browserslist将根据BROWSERSLIST_ENV或NODE_ENV变量选择,所以设置 process.env.BROWSERSLIST_ENV 也是可以的。

再次打包 npm run build,则只会针对 ie,生成的 main.css 内容如下:

body{
color: rgb(250, 0, 4);
}
body p {
-ms-transform: scale(1, 2);
transform: scale(1, 2);
}

压缩 css

如果我们需要压缩 css 代码,可以使用 optimize-css-assets-webpack-plugin,用于优化或最小化 css。

首先安装依赖,然后修改配置:

> npm i -D optimize-css-assets-webpack-plugin@5

// webpack.config.js
// +
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new MiniCssExtractPlugin(),
// +
new OptimizeCssAssetsPlugin()
],

重新打包,原来的 main.css 则变成一行,请看:

> npm run build

// main.css(优化前)
body{
/* 注释 */
color: rgb(250, 0, 4);
}
body p {
-ms-transform: scale(1, 2);
transform: scale(1, 2);
} // main.css(优化后)
body{color:#fa0004}body p{-ms-transform:scaleY(2);transform:scaleY(2)}

优化后,css 变成了一行,注释也删除了。

打包图片

前端资源通常有图片,由于 webpack 只识别 javascript,所以需要 loader 来帮们识别图片。

我们使用 url-loader,能将图片转为 base64。

首先安装依赖,并修改配置文件:

> npm i -D url-loader@4

// webpack.config.js
module: {
rules: [
...
// +
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
// 指定文件的最大大小(以字节为单位)
limit: 1024*7,
},
},
],
},
]
},

接着引入图片,启动服务:

// 引入图片。src/6.68kb.png

// a.less
body{
p{
transform: scale(1, 2);
}
.m-box{display:block;width:100px;height:100px;}
.img-from-less{background:url(./6.68kb.png) no-repeat;background-size:100% 100%;}
} // index.html
...
<body>
<p>请查看控制台</p>
<span class='m-box img-from-less'></span>
</body>
... // 启动服务
> npm run dev

Tip: 笔者的图片大小为 6.68kb,上面的 limit 只需要大于6.68kb即可

在新开的页面中,我们在”请查看控制台“文字下面看见了我们设置的图片。通过检查元素会发现这张图片是 base64。

body .img-from-less{
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA... no-repeat;
...
}

如果将 limit: 1024*7 修改为 limit: 1024*6(也就是将 limit 设置的比图片的 size 更小),再次运行 npm run dev,会发现报错了。还会提示找不到 file-loader。这是因为这张图片(6.68kb.png)大于 1024*6,所以就不会被打包成 base64,所以需要 file-loader 来处理。

安装依赖包 npm i -D file-loader@6,再次启动服务器,页面上又看到我们的图片,而且这次不再是 base64,而是直接生成了一张图片。

body .img-from-less{
background: url(26bd867dd65e26dbc77d1e151ffd36e0.png) no-repeat;
...
}

图片除了在 css 中使用,我们也会通过 img 元素引用,于是我们在 index.html 中新增 <img class='m-box' src="./6.68kb.png" alt=""> 再次启动服务,在打开的浏览器页面中发现 img 引用的图片没生效,而且源码也没变化。

这里需要使用 html-loader 这个包,它能让每个被加载的属性(例如:<img src="data:image.png")能被引入(imported)。

安装依赖包,修改配置:

> npm i -D html-loader@1

// webpack.config.js
module: {
rules: [
...
// +
{
test: /\.html$/i,
loader: 'html-loader',
},
]
},

再次启动服务,就能看到两张一样的图片了。img 的代码变为 <img class="m-box" src="26bd867dd65e26dbc77d1e151ffd36e0.png" alt="">

如果再次将 limit: 1024*6 修改为 limit: 1024*7,启动服务你会发现这两处图片都变为 base64。

打包 javascript

js 语法检查

有时我们希望团队成员写的 javascript 代码风格一致。

我们可以使用 eslint,它能查找并修复JavaScript代码中的问题;可以自定义 eslint,使其完全按照项目所需的方式工作。代码风格,笔者选用 airbnb,一个流行的 javascript 风格指南(此刻是第 6 名(topics javascript))。

在 webpack 中使用 eslint,需要使用 eslint-webpack-plugin( eslint-loader废弃了),而 eslint-webpack-plugin 依赖于 eslint

eslint-config-airbnb 默认导出包含我们所有的ESLint规则,包括ECMAScript 6+和React,而 我们不需要使用 react,所以使用 eslint-config-airbnb-base 即可。

首先安装依赖包,修改配置:

// 没有引入 eslint-plugin-import
> npm i -D eslint@7 eslint-webpack-plugin@2 eslint-config-airbnb-base@14 // webpack.config.js
// +
const ESLintPlugin = require('eslint-webpack-plugin'); module.exports = {
// ...
plugins: [
new ESLintPlugin({
// 将启用ESLint自动修复功能。此选项将更改源文件
fix: true
}) ],
// ...
}; // package.json
{
// +
"eslintConfig": {
"extends": "airbnb-base"
}
}

重新打包 npm run build,出现了一些警告和错误,核心信息如下:

WARNING in 

webpack-example2\src\index.js
6:1 warning Unexpected console statement no-console 6 problems (2 errors, 4 warnings) ERROR in webpack-example2\src\index.js
1:8 error Unexpected use of file extension "js" for "./b.js" import/extensions 5 problems (2 errors, 3 warnings)

错误(import/extensions)是不希望使用 js 扩展名,将 ./b.js 改为 ./b 就好了,可参考issues:import/extensions

警告(no-console)是因为不能出现 console.log。可以通过配置将这个告警关闭:

// package.json
{
"eslintConfig": {
"extends": "airbnb-base",
// +
"rules": {
"no-console": "off",
}
}
}

import/extensions修复,并将警告关闭,重新打包 npm run build则不会出现警告和错误。

Tip:打包后,源码也会自动修复,比如 src/index.js 中的 sum() 方法,a, 后面是多个空格,打包后会合并成一个空格:

function sum(a,    b) {
return a + b;
}
// 需要调用 sum() 方法
// 否则报错:error 'sum' is defined but never used no-unused-vars
console.log(sum(1, 100)); // 修复后 function sum(a, b) {
return a + b;
}

如果在 js 文件中使用 window ,再次打包会报错,就像这样:

// index.js
// +
setTimeout(() => {
window.location = 'https://www.baidu.com/';
}, 1000); // 打包
> npm run build
...
error 'window' is not defined no-undef

可以在配置文件中指定环境来解决这个问题。就像这样:

// package.json
"eslintConfig": {
// +
"env": {
"browser": true
}
}

如果不想写到 package.json,也可以配置到单独的文件(.eslintrc.js)中:

// .eslintrc.js
module.exports = {
"extends": "airbnb-base",
"rules": {
"no-console": "off"
},
"env": {
"browser": true
}
}

js 兼容性处理

我们想使用 es6 来编写代码,但有的浏览器支持的不够全面,所以我们会将 es6 转成 es5。

接着上面的例子进行,重写 index.js,放入一个箭头函数,再次打包,你会发现 webpack 不会对 es6 语法做处理,const 还是 const,而不是 var:

// src/index.js
const sum = (a, b) => (a + b);
console.log(sum(1, 10)); // sum 还是我们的箭头函数
eval("const sum = (a, b) => (a + b);\nconsole.log(sum(1, 10));\n\n\n//# sourceURL=webpack:///./src/index.js?");

Babel 是一个 JavaScript 编译器。通过它可以让我们使用下一代的 JavaScript 语法编程。

在 webpack 中要使用 babel 就得用 label-loader。用法(Usage)如下:

// 安装依赖包。没有使用 @babel/core
> npm i -D babel-loader@8 @babel/preset-env@7 // webpack.config.js
module: {
rules: [
// +
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
]
}
}
}
]
}

重新打包,箭头函数就变成普通函数:

eval("var sum = function sum(a, b) {\n  return a + b;\n};\n\nconsole.log(sum(1, 10));\n\n//# sourceURL=webpack:///./src/index.js?");

如果我们在 js 中使用 Promise,重新打包后 Promise 还是 Promise,而且在不识别 Promise 语法的浏览器中(比如 ie11)运行会报错。

// index.js
Promise.resolve('aaron').then((v) => {
console.log(v);
}); // 重新打包,Promise 还是 Promise
eval("Promise.resolve('aaron').then(function (v) {\n console.log(v);\n});\n\n//# sourceURL=webpack:///./src/index.js?");

babel 官网提到,使用@babel/polyfill,就可以使用新的内置函数(例如Promise或WeakMap),静态方法(例如Array.from或Object.assign),实例方法(例如Array.prototype.includes)等等。所以这个 polyfill 是我们的解决方案。

但是 @babel/polyfill 废弃了。而 @babel/polyfill 包含 regenerator runtime 和 core-js。

core-js,包括适用于2021年前ECMAScript的polyfill,而且仅加载必需的功能。

在 useBuiltIns 参数中也提到:由于在7.4.0中已弃用@ babel/polyfill,因此我们建议直接添加core-js并通过corejs选项设置版本。

于是我们知道 core-js 能解决 Promise 这类问题。

如何使用 core-js ?我们先来介绍一下插件预设

babel 通过将插件(或预设)应用于配置文件来启用Babel的代码转换。比如插件列表中的es2015,这是一个集合,包含了箭头函数(arrow-functions)、类(classes)等插件;

而预设(presets)其实是多个插件(plugin)的集合。比如 @babel/preset-env 这种预设则包含了 es2015、es2016、es2017等最新的插件。

最后根据babel-preset-env中的介绍,我们将 core-js 应用上:

// 安装依赖
> npm i -D core-js@3.11 // webpack.config.js
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
// +
{
// 配置处理polyfill的方式
useBuiltIns: "usage",
// 版本与我们下载的版本保持一致
corejs: { version: "3.11"},
"targets": "> 0.25%, not dead"
}
]
]
}
}

重新打包,dist/main.js 的Size变成 109 Kib,而之前还不到 4kiB。

启动服务,在不支持 Promise 语法的浏览器中,比如 ie11,也能在控制台输入 aaron。

至此 javascript 的兼容处理完毕。

webpack 快速入门 系列 —— 实战一的更多相关文章

  1. webpack 快速入门 系列 - 自定义 wepack 上

    其他章节请看: webpack 快速入门 系列 自定义 wepack 上 通过"初步认识webpack"和"实战一"这 2 篇文章,我们已经学习了 webpac ...

  2. webpack 快速入门 系列 —— 性能

    其他章节请看: webpack 快速入门 系列 性能 本篇主要介绍 webpack 中的一些常用性能,包括热模块替换.source map.oneOf.缓存.tree shaking.代码分割.懒加载 ...

  3. webpack 快速入门 系列 —— 初步认识 webpack

    初步认识 webpack webpack 是一种构建工具 webpack 是构建工具中的一种. 所谓构建,就是将资源转成浏览器可以识别的.比如我们用 less.es6 写代码,浏览器不能识别 less ...

  4. vue 快速入门 系列 —— vue loader 上

    其他章节请看: vue 快速入门 系列 vue loader 上 通过前面"webpack 系列"的学习,我们知道如何用 webpack 实现一个不成熟的脚手架,比如提供开发环境和 ...

  5. vue 快速入门 系列 —— vue loader 下

    其他章节请看: vue 快速入门 系列 vue loader 下 CSS Modules CSS Modules 是一个流行的,用于模块化和组合 CSS 的系统.vue-loader 提供了与 CSS ...

  6. vue 快速入门 系列 —— vue-cli 下

    其他章节请看: vue 快速入门 系列 Vue CLI 4.x 下 在 vue loader 一文中我们已经学会从零搭建一个简单的,用于单文件组件开发的脚手架:本篇,我们将全面学习 vue-cli 这 ...

  7. 快速入门系列--WebAPI--03框架你值得拥有

    接下来进入的是俺在ASP.NET学习中最重要的WebAPI部分,在现在流行的互联网场景下,WebAPI可以和HTML5.单页应用程序SPA等技术和理念很好的结合在一起.所谓ASP.NET WebAPI ...

  8. 前端学习 node 快速入门 系列 —— 初步认识 node

    其他章节请看: 前端学习 node 快速入门 系列 初步认识 node node 是什么 node(或者称node.js)是 javaScript(以下简称js) 运行时的一个环境.不是一门语言. 以 ...

  9. 前端学习 node 快速入门 系列 —— npm

    其他章节请看: 前端学习 node 快速入门 系列 npm npm 是什么 npm 是 node 的包管理器,绝大多数 javascript 相关的包都放在 npm 上. 所谓包,就是别人提供出来供他 ...

随机推荐

  1. 如何对shell脚本中斜杠进行转义?

    1.在编写shell脚本时,经常会遇到对某个路径进行替换,而路径中包含斜杠(/),此时我们就需要对路径中涉及的斜杠进行转义,否则执行失败.具体示例如下: 需求描述: 将sjk目录下的test文件中的p ...

  2. pwnable.kr第三题bof

    Running at : nc pwnable.kr 9000 IDA查看 1 unsigned int __cdecl func(int a1) 2 { 3 char s; // [esp+1Ch] ...

  3. springboot源码解析-管中窥豹系列之BeanPostProcessor(十二)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  4. python多版本与虚拟环境

    这篇纯python技术文章,我自己平时也会用到,在此记录一下. 为什么会用到多个Python版本? 用macOS和Ubutntu的同学都知道系统默认安装的Python2.7.x,然后,我们平时pyth ...

  5. Springboot进行Http接口交互实现邮件告警

    本项目采用idea编辑器,依赖maven环境,相关搭建请自行百度一.引入相关依赖    本文Http接口交互使用hutool工具类与阿里FastJson解析报文. <dependencies&g ...

  6. 配置Java环境变量时的一个常见错误

    我们在把JDK路径配置为环境变量时,有一个常用的配置方法,就是把JDK根路径配置为"JAVA_HOME"值,然后在Path中添加一条"%JAVA_HOME%\bin&qu ...

  7. OxyPlot.SkiaSharp显示中文乱码的问题

    oxyplot 图表控件功能强大,使用很广泛.最近考虑到性能使用OxyPlot.SkiaSharp替代OxyPlot.WPF,曲线图表初步测试,性能提升近10倍左右.基于SkiaSharp图形引擎的一 ...

  8. vs2019新建数据库后插入中文变问号

    在使用VS创建了数据库后如果直接给字符类型插入中文内容的话查询结果插入的中文会以"?"的格式展现. 原因是因为默认创建的数据库的排序类型为拉丁文不支持中文. 所以需要讲这个排序的字 ...

  9. 设计原则:接口隔离原则(ISP)

    接口隔离原则的英文是Interface Segregation Principle,缩写就是ISP.与里氏替换原则一样其定义同样有两种 定义1: Clients should not be force ...

  10. MinIO分布式集群的扩展方案及实现

    目录 一.命令行方式扩展 1. MinIO扩展集群支持的命令语法 2. 扩容示例 二.etcd扩展方案 1. 环境变量 2. 运行多个集群 3. 示例 相关链接 MinIO 支持两种扩展方式: 通过修 ...