(基础篇)手把手教你从零配置webpack4(含性能优化)
前言
本篇是基于webpack4
搭建vue
脚手架常用的知识点。并非从0开始搭建,因为网上此类教程非常多,仅将作者这些年搭建脚手架配置的知识点一一罗列,排名不分先后,可作为采坑文档查询,欢迎评论补充。
一、如何配置启动服务以及实现开发环境下热更新
修改开发目录下任意文件后,无需手动刷新而页面直接渲染展现出来,是对开发者最大的尊重。原理没啥好说的,直接上配置:
const webpack = require('webpack');
module.exports = {
//其他忽略
plugins:[
new webpack.HotModuleReplacementPlugin()
],
devServer:{
historyApiFallback:{
index:'/index.html'
},
progress:true, //进度条
inline:true, //打包后加入一个websocket客户端
hot:true, //热加载
contentBase: path.resolve(__dirname,'../src'), //开发服务运行时的文件根目录
host: 'localhost', //主机地址
port: 5000, //端口号
}
//其他忽略
}
二、引入html
模板
html-webpack-plugin
是创建html入口文件的webpack插件。简易版配置方法如下:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
//其他忽略
plugins:[
//...
new HtmlWebpackPlugin({
title:'Home',
filename:'index.html',
template:resolve('../public/index.html')
})
//...
]
//其他忽略
}
html-webpack-plugin
插件也可以配置多页面应用,感兴趣的同学可查阅更多资料。更多options
参数配置参考如下:
title : 用于生成的HTML文件的标题。
filename : 用于生成的HTML文件的名称,默认是index.html。你可以在这里指定子目录(例如:assets/admin.html)
template : 模板的路径。支持加载器,例如 html!./index.html。
inject :true | ‘head’ | ‘body’ | false 。把所有产出文件注入到给定的 template 或templateContent。当传入 true或者 ‘body’时所有javascript资源将被放置在body元素的底部,“head”则会放在head元素内。
favicon : 给定的图标路径,可将其添加到输出html中。
minify : {…} | false 。传一个html-minifier 配置object来压缩输出。
hash : true | false。如果是true,会给所有包含的script和css添加一个唯一的webpack编译hash值。这对于缓存清除非常有用。
cache : true | false 。如果传入true(默认),只有在文件变化时才 发送(emit)文件。
showErrors : true | false 。如果传入true(默认),错误信息将写入html页面。
chunks : 只允许你添加chunks 。(例如:只有单元测试块 )
chunksSortMode : 在chunk被插入到html之前,你可以控制它们的排序。允许的值 ‘none’ | ‘auto’ | ‘dependency’ | {function} 默认为‘auto’.
excludeChunks : 允许你跳过一些chunks(例如,不要单元测试的 chunk).
xhtml : 用于生成的HTML文件的标题。
title : true | false。如果是true,把link标签渲染为自闭合标签,XHTML要这么干的。默认false。
三、清除dist
文件夹
现在通常单页面应用为了防止缓存,每次build
打包后都会改变变动的文件名称,那么这种情况下每次打包后dist
文件夹下都会产生多个相同功能的相似文件,久而久之文件会越来越多。那么这个时候我们就需要在每次打包前先清空目录,这里推荐clean-webpack-plugin
插件
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
//其他忽略
plugins:[
new CleanWebpackPlugin()
]
//其他忽略
}
每次在打包的时候,都会先清空dist
文件夹。那么这个时候就会有疑问了:为什么在new CleanWebpackPlugin
的时候,没有配置呢?它怎么知道需要清空的是dist
文件夹呢?我项目build之后的文件目录如果是build
文件夹呢?其实也是有配置的,文档地址,只是一般用不到,我们的目的就是清除打包后的目录而已。clean-webpack-plugin
会在我们每次build
的时候动态读取输出目录,清除里面的文件。
四、CDN分包加载方式
一般常见的类库都会发布到CDN
上,我们在webpack
中打包的时候其实无需将公共类库打包到自己项目中,因为这样不仅会增加文件体积,影响客户端到服务器这条网络线路的加载速度,还影响webpack
打包效率。以CDN
方式加载资源需要使用到add-asset-html-cdn-webpack-plugin
插件,我们以vue
为例:
const AddAssetHtmlCdnPlugin = require("add-asset-html-cdn-webpack-plugin");
module.exports = {
//其他忽略
plugins:[
new AddAssetHtmlCdnPlugin(true, {
'vue': "https://lib.baomitu.com/vue/2.6.12/vue.min.js",
}),
],
externals: {
'vue': 'Vue'
},
//其他忽略
}
按照以上方法配置后,我们在项目中执行如下引入的时候,webpack
将会忽略vue
的引入,转而从CDN
引入。
import Vue from 'vue';
注意:引入的CDN
库中一定要在全局范围内暴露Vue
,否则build后会因找不到Vue
而报错。
五、vue-router 使用history模式报错Cannot GET /xxx
报错原因:找不到资源。在搭建本地开发服务的时候我们通常使用webpack-dev-server
搭建服务,启动服务后访问首页正常,例如访问http://localhost:5000/
页面展示正常,点击路由跳转到页面demohttp://localhost:5000/demo
访问也正常,这个时候若刷新页面恐怕会提示Cannot GET /demo
。为什么会这样呢?因为服务器实际上并不存在demo
目录,我们需要将不存在的目录重定向到index.html
即可,关键配置是这样的:
module.exports = {
//其他忽略
devServer:{
//...
historyApiFallback:{
index:'/index.html'
}
//...
}
//其他忽略
}
六、拷贝文件夹内容至打包目录
webpack
编译打包时代难免会存在一些需要直接引入而无需打包的文件,比如icon Font
。这个时候只需要在build
的时候将指定文件内容copy
一份到dist
目录即可。copy-webpack-plugin
插件就是用来解决这一问题的,不仅可以拷贝内容,还可以动态转换内容。
如下配置的意思是:将public
文件夹中的内容拷贝到dist
文件夹中,但是test
文件夹下以png
为后缀的图片不拷贝,也不拷贝以html
为后缀的内容
const CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = {
//其他忽略
plugins:[
new CopyWebpackPlugin(
[
{
from:'./public',
to:'./',
}
],
{
ignore:['test/*.png','*.html']
}
)
]
//其他忽略
}
七、设置Loader
搜索范围
webpack
打包,主要依赖各种Loader
加载器将代码转换为字符串生成AST
,然后对AST
继续进行转变最后再生成新的代码,项目越大,需要转换的代码就越多,所以效率就越低。所以我们在项目中要指定哪些位置需要转换,哪些位置可以忽略的,很显然node_modules
文件夹下依赖的库非常多,且都是经过编译过的,没必要再去处理一遍,只需要将关心的重点放到业务代码src
文件夹中即可:只处理src
文件夹,忽略node_modules
,我们以Babel
为例
module.exports = {
//其他忽略
module:{
rules:[
//...
{
test: /\.js$/,
include: path.resolve('src'), //只处理src文件夹
exclude: /node_modules/, //忽略node_modules文件夹
use: ['babel-loader']
}
//...
]
}
//其他忽略
}
八、支持SASS
,查询原样式具体位置sourceMap
注意:此配置常见的问题是相关loader
版本问题。若确保配置无误,而报错问题又无法解决的话,就要检查一下style-loader
、css-loader
、sass-loader
的版本是否过高,适当降低版本即可。
module.exports = {
//其他忽略
module:{
rules:[
//...
{
test:/\.scss$/,
include:resolve('../src'),
exclude:/node_modules/,
use:[
{ loader:'style-loader',},
{ loader:'css-loader', options:{ sourceMap:true } },
{ loader:'sass-loader', options:{ sourceMap:true } },
]
}
//...
]
}
//其他忽略
}
注:sourceMap
主要用于调试代码,出于性能考虑,生产环境下sourceMap
要设置为false
。
九、定位js
打印具体位置sourceMap
有的时候页面报错、打印警告,或者console.log
的时候,我们需要知道具体源代码是哪个文件以及具体位置,这个时候就需要开启js
的sourceMap
定位
module.exports = {
//其他忽略
devtool: 'eval-source-map'
//其他忽略
}
注:sourceMap
主要用于调试代码,出于性能考虑,生产环境下devtool
要设置为none
。
十、图片pic、字体fonts引入配置
很简单,没啥好说的,直接上配置。低于10KB的图片直接打包成base64,超过10KB的按照路径的方式引入图片。
module.exports = {
module:{
rules:[
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use:[
{
loader: 'url-loader',
options:{
esModule:false,
limit:10*1024,
name:'static/images/[name]-[hash:15].[ext]'
}
}
]
},
{
test: /\.(eot|woff|woff2?|ttf|svg)$/,
use: [
{
loader: "url-loader",
options: {
name: "static/fonts/[name]-[hash:15].[ext]",
limit: 5*1024,
}
}
]
}
]
}
}
十一、webpack4的js压缩以及异步加载方法分包策略
webpack4
中对js文件的压缩配置非常方便,生产环境下,也就是mode
值为production
的时候,package.json中执行压缩的时候webpack -p
即可实现文件压缩。
1.require.ensure
方式
比如这种场景:首页index.js
中引入了testA.js
以及testB.js
,而恰好这两个js的功能对首屏加载无影响的,也就是说需要做某些操作的时候才会使用到,而恰好这俩体积又比较大的时候,就比较适合异步加载了,毕竟可以减少首屏加载文件体积。废话不多说,直接上代码
{
methods:{
alertA(){
require.ensure([],() => {
//异步加载
let testA = require('./testA.js');
console.log(testA)
})
},
alertB(){
require.ensure([],() => {
//异步加载
let testB = require('./testB.js');
console.log(testB)
})
},
}
}
采用这种写法打包后就会发现多了两个文件。原因是webpack
将testA.js
、testB.js
分别独立打包,只有在需要的时候才会用到,这么做显然体验更好。
2.import(xxx).then()
方式
import()
是webpack4
才启用的分包切割方法,语法相对简单,只接受一个包的引用地址作为参数,使用了Promise
式回调,在加载成功后回调执行逻辑。
{
methods:{
alertA(){
//异步加载
import('./testA.js').then(res => {
console.log(res)
})
},
alertB(){
//异步加载
import('./testB.js').then(res => {
console.log(res)
})
},
}
}
那么问题来了:该如何控制这些异步加载js打包后的位置以及命名方式?其实无论require.ensure
还是import(xxx)
方式分包,最终都是放在chunk
存储的目录下。例如将异步加载的js存放在static/js/
目录下:
output:{
chunkFilename:'static/js/[name].[chunkhash:10].chunk.min.js'
}
3.vue-router
分包方式
其实这并不算一种新的分包方式,顶多算以上两种方式的经典应用,也是常见应用案例之一。主要修改vue-router
路由配置component
的引入方式而已。两种方式分别实例如下:
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Index',
component: () => import('@/pages/index/index.vue')
},
{
path: '/list',
name: 'List',
component: resolve => require(['@/pages/list/list.vue'],resolve)
},
]
})
4.webpack4
分包策略
以上两种异步分包方式常用于业务类,方便随时分包处理,且一般单个文件较小。但除此之外项目打包引入的公共库,例如vue
、vue-router
、lodash
、echart
等往往会被打包进一个公共js中,这样就会出现文件过大,首屏加载时间较长,导致白屏时间也过长的情况。这个时候就十分需要将这些依赖库分包加载。首先CDN
分包加载的方式就是一个很好的办法,上文已讲过,此处忽略。其次就是可以指定哪些库合到一起,真正实现随意分包
的个性化优化方式。
4.1 CommonsChunkPlugin
分包策略
在webpack4.x版本之前采用的是这种方式,由于本篇是基于webpack4配置,所以这种方式简单略过
module.exports = {
//其他忽略
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
return (
module.resource && /\.js$/.test(module.resource) && module.resource.indexOf(path.join(__dirname, './node_modules')) === 0
)
},
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
chunks: 'initial',
minChunks: 2,
}),
]
//其他忽略
}
4.2 splitChunks
分包策略
webpack4
移除了CommonsChunkPlugin
分包方式,改用splitChunksPlugin
分包策略。
此内容算是webpack4
分包核心内容了,接下来会用一整篇文章分析,此处暂时略过,敬请期待。
十二、sass
引入全局注册变量文件
sass
、less
这些预处理器极大的方便了我们书写css
变量,我们可以定义一个文件,将变量定义在里面,其他文件引入。以sass
为例,定义通用样式文件mixins.scss
,然后每个sass
文件通过@import
引入即可。如果有一天通用样式名称变成了base.scss
,然后我们项目里有几十个页面要去一一手动修改,痛苦!!!那么是否可以全局引入sass
文件呢?可以,尝试一下sass-resources-loader
插件。将其配置在use
最后面即可,毕竟loader
解析是从后往前依次工作的,基础依赖毕竟需要跑在第一位
module.exports = {
module:{
rules:[
{
test:/\.scss$/,
include:resolve('../src'),
exclude:/node_modules/,
use:[
//其他暂时忽略
{
loader: 'sass-resources-loader',
options: {
sourceMap:process.env.NODE_ENV==='production'?false:true,
resources: [resolve('../src/static/css/mixins.scss')]
}
}
]
}
]
}
}
我们注意到resources
是一个数组(也可以是一个字符串,就是直接引入的通用样式路径),之所以示例用数组,意思就是可以配置多个通用样式。
以上是sass
示例,less
配置也是同样原理,感兴趣的同学可以尝试style-resources-loader
十三、css分包、压缩、去除注释
注意:
- 1、此方法只能用于生产环境
- 2、注意引入插件版本号问题
用到的插件mini-css-extract-plugin
、optimize-css-assets-webpack-plugin
、cssnano
,配置方式如下
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
module:{
rules:[
{
test:/\.scss$/,
include:resolve('../src'),
exclude:/node_modules/,
use:[
MiniCssExtractPlugin.loader
]
},
{
test:/\.less$/,
include:resolve('../src'),
exclude:/node_modules/,
use:[
MiniCssExtractPlugin.loader
]
},
{
test:/\.css$/,
include: resolve('../src'),
exclude: /node_modules/,
use:[
MiniCssExtractPlugin.loader
]
}
]
},
plugins:[
new OptimizeCSSAssetsPlugin({
assetNameRegExp:/\.css$/g,
cssProcessor:require("cssnano"), //引入cssnano配置压缩选项
cssProcessorPluginOptions:{
preset:['default',{discardComments:{removeAll:true}}]
},
canPrint:true, //是否将插件信息打印到控制台
}),
new MiniCssExtractPlugin({
filename: "static/css/[name].[chunkhash:10].css",
}),
]
}
版本号详见最后源码彩蛋。
十四、解决不能解析async … await
等高级语法问题
当你的Chrome浏览器报这种错误的时候,那么一定代表你的业务代码中使用了一些高级版本的js导致的,例如这种错误
ReferenceError: regeneratorRuntime is not defined
那么这个时候你就需要babel-polyfill
的帮忙,安装并引入再重启服务即可
npm install babel-polyfill -save-dev
//webpack入口文件,例如`main.js`中引入即可
import "babel-polyfill"
十五、开启 Tree Shaking
Tree-shaking
,字面意思翻译过来就是:摇晃树
。顾名思义就是秋天的时候,当我们摇晃小树的时候,没用的树叶就会掉到地上,就像我们代码中util.js
中会写几十个方法,在打包的时候没必要将用不到的方法也打包进来,除去多余代码的过程译做Tree-shaking
。webpack4
以前时代就不讲了,webpack4
当mode
模式设置为production
的时候,默认就会开启Tree-shaking
。例如:
main.js
import { A } from './util';
console.log(A())
util.js
export const A = () => 'aaa';
export const B = () => 'bbb';
这个时候打包的时候以下代码就不会被打包进去
export const B = () => 'bbb';
Tree-Shaking
原理:主要得益于ES6 Module
引入了静态分析,它的模块是在编译时输出。静态分析流可以判断哪些模块或者变量未被使用或者输出。
十六、开启 Scope Hoisting
另一个在webpack4
中当mode
设置为production
的时候也会默认开启的是Scope Hoisting
。就是可以让webpack
打包出来的js体积更小,运行更快的一种方式。最初webpack
在打包的时候,会在打包后的模块外部包裹一层函数,import
引入会被转换成require
,进而产生大量的作用域,模块越多内存占用的就会越大。这个时候若开启了Scope Hoisting
,webpack
会动态分析模块之间的依赖关系,将一些模块合并到一个函数中去,从而减少被包裹
的数量,同时也会重命名一些变量防止冲突。
十七、安装打包分析工具
在没分析工具之前,要想分析打包后的文件大小以及文件内引入内容,往往都会人工去查看dist
目录,看看哪些文件较大,以及可能引入的哪些js可以分离出去。费时又费力,且准确性还不高。
这个时候推荐webpack-bundle-analyzer
插件,可以帮助分析网站质量以及性能。使用方式较简单
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
//其他忽略
plugins:[
new BundleAnalyzerPlugin({
generateStatsFile: true, // 是否生成stats.json文件
})
]
//其他忽略
}
这里是最简洁的配置方式,更多配置可查看webpack-bundle-analyzer
文档。
执行打包后默认会打开另外一个服务,页面大概样子如下:
分析一目了然,哪些文件较大,以及文件内都打包了依赖了哪些库,或者打包了不改引入的库,都可以看的清清楚楚。接着就是针对具体项目具体分析了,至于如何分包,可查看上面
webpack4
分包策略,或者CDN分包加载方式。不同项目分包细节不同,此处只如授人以渔
。
十八、打包速度测量分析
一些较大的项目,虽然我们做了大量打包优化,但总归还是会占用一定时间,至于哪个地方占用时间较多,单凭经验很难分析出来,这个时候我们就需要一款插件能够分析打包速度 ———— speed-measure-webpack-plugin
。
使用方式较简单:
const SpeedMeasureWebpackPlugin = require("speed-measure-webpack-plugin");
const seepM = new SpeedMeasureWebpackPlugin();
module.exports = seepM.wrap({
entry:'',
output:'',
module:{},
plugins:[]
})
使用很简单:引入并执行打包速度插件,并将配置内容包在seemP.wrap
中即可。
十九、DllPlugin
提前打包特定类库成动态链接
DllPlugin
可以将特定的类库提前打包成动态链接库,这适用于那些不常更新的类库或者函数模块。因为每次更新完业务逻辑代码,打包的时候都要顺带上他们,十分影响打包速度。如果能将这些类库提前打包到一边,在用到的时候拿过来用即可。只有当代码库更新的时候再手动去更新即可。
提前在package.json
中配置文件生成依赖:
{
"scripts":{
"dll": "webpack --config ./config/webpack.dll.js --mode production"
}
}
对应的webpack.dll.js
配置:
const path = require("path");
const DllPlugin = require("webpack/lib/DllPlugin");
module.exports = {
// 想统一打包的类库
entry: ["vue-router","bignumber.js","axios","vant","js-md5"],
output: {
filename: "[name].dll.js", //输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称
path: path.resolve(__dirname, "dll"), // 输出的文件都放到 dll 目录下
library: "_dll_[name]", //存放动态链接库的全局变量名称,例如对应 vant 来说就是 _dll_vant
},
plugins: [
new DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
// 例如 vant.manifest.json 中就有 "name": "_dll_vant"
name: "_dll_[name]",
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(__dirname, "dll", "[name].manifest.json"),
}),
],
};
最后将依赖文件引入到项目中即可:
const DllReferencePlugin = require("webpack/lib/DllReferencePlugin");
module.exports = {
//其他忽略
plugins:[
//...
new DllReferencePlugin({
// manifest 就是之前打包出来的 json 文件
manifest: path.join(__dirname, "../dll", "main.manifest.json"),
}),
//...
]
//其他忽略
}
后期备注:DllPlugin
已成鸡肋,不再建议使用,推荐hard-source-webpack-plugin
插件。
二十、noParse
为了提高打包速度,生产环境可以设置noParse
告诉webpack
过滤指定文件,不做解析。那么什么条件的库可以noParse
呢?就是无需特殊处理,直接可以运行在浏览器端的,或者已经被编译过的插件或框架,可以直接使用的。例如一般jquery
、lodash
都可以直接过滤掉
module.exports = {
//其他忽略
module:{
noParse:/jquery|lodash/,
//或者支持函数类型(例如忽略所有lib文件夹下的解析)
noParse:function(fullPath){
return /lib/.test(fullPath)
}
}
//其他忽略
}
二十一、IgnorePlugin
IgnorePlugin 防止在import
或require
调用时,忽略符合正则表达式条件的内容。例如moment
库中支持多种语言,但是我们只用到中文语言包,其他的就可以忽略掉,这样可以减小包的体积。
module.exports = {
//其他忽略
plugins: [
new webpack.IgnorePlugin(/^\.\/locale/, /moment$/)
],
//其他忽略
};
二十二、其他小配置优化点
- 1、可以使用别名,手动指定映射位置,减少
webpack
搜索范围; - 2、在引入组件或者js工具库的时候,我们往往习惯不携带后缀,可以设置文件搜索后缀列表,将常用的放在前面。
module.exports = {
//其他忽略
resolve:{
extensions:['.js','.vue','.scss','.css','.json'],
alias:{
'vue': 'vue/dist/vue.esm.js',
'@':path.resolve(__dirname,'../src')
}
}
//其他忽略
}
二十三、缓存loader
处理结果
对一些性能开销比较大的loader
最后,也就是use
数组的第一位增加cache-loader
,可以缓存处理结果,这样下次在打包的时候只要文件内容没发生变动,就可以使用上次缓存的结果,大大节省时间。
当然,babel-loader
编译的结果也是可以缓存下来的,也可以大幅提升打包时间。
module.exports = {
//其他忽略
module:{
rules:[
{
test: /\.js$/,
use: ['cache-loader','babel-loader?cacheDirectory=true']
},
]
}
//其他忽略
}
注意:对于一些编译相对轻松的内容,不推荐使用cache-loader
缓存结果。因为编译本身耗费时间较少,没必要浪费保存和读取缓存的时间。
二十四、开启多线程打包处理
由于webpack
在打包的过程是单线程的,每次打包有都较多的loader
任务排队,当某个编译占用较长时间时,其他的只能排队等候,这样导致打包时间更长。如果多个loader
能够齐头并进,势必会大大缩短打包过程。
二十五、webpack-dashboard
仪表盘
在本地开发环境下,控制台打印的信息都是以列表的形式展示,往往显得不够直观,也不美观。webpack-dashboard
翻译过来大意就是“仪表盘”,可以让控制台分多个区域显示内容。左上角为webpack
编译日志,日志右侧是状态、编译时间、编译进度条,下面是引入模块以及文件大小、报错信息等,截图信息如下:
使用方式:
//第一步:安装
npm install webpack-dashboard -D
//第二步:引入并使用
const DashboardPlugin = require('webpack-dashboard/plugin');
{
//暂时忽略
plugins:[
new DashboardPlugin()
]
//暂时忽略
}
//第三步:修改package.json中启动方式,在之前启动方式前追加 “webpack-dashboard --”
{
"scripts":{
"serve":"webpack-dashboard -- webpack-dev-server"
}
}
总结:常见棘手问题汇总
- 1、版本问题。
webpack
配置项目中往往会引入大量第三方插件,大升级后的版本往往会修改配置API,在确认配置API问题无误后可以试着不断卸载相应插件,不断降低版本重试。 - 2、路径问题导致引入失败。读取引入文件的时候建议用
path.resolve(__dirname,"../src")
模式,避免相对路径导致引入失败。 - 3、js、css压缩仅适用于生产环境。
- 4、开发环境下加载
css
的时候一定要用style-loader
,切忌压缩或link
方式引入,否则会导致热更新失败,且不利于开发体验。 - 5、css清除多余样式,一个非常好的想法,但作者目前还没搞清楚
purifycss-webpack
插件清除多余css原理。比如动态添加的css样式,是如何做到不误杀的,目前已知是会被误杀的,懂的欢迎留言讨论。
转载自:https://juejin.cn/post/7025594027118428191