Webpack 是当下最热门的前端资源模块强大打包工具。它可以将许多松散的模块按照依赖和规则打包成符合生成环境部署的前端资源;还可以将按需加载的模块进行代码分割,等到实际需要再异步加载。在使用 webpack 时,如果不注意性能优化,非常大的可能产生性能问题。性能问题主要分为开发时构建速度慢、开发调试时的重复工作、输出打包文件过大等,因此优化啊方案也主要针对这些方面来分析得出。
# 一、优化构建速度
webpack 打包,首先根据 entry 配置的入口出发,递归遍历解析所依赖的文件。这个过程分为搜索文件和匹配文件进行分析、转化的 2 个过程,因此可以从这两个角度来进行优化配置。
# 1、缩小文件的搜索范围
减小文件搜索的优化配置如下:
# 1)合理配置 resolve,告诉 webpack 如何去搜索文件
- 设置 resolve.modules:[path.resolve (__dirname, 'node_modules')],避免层层查找
resolve.modules 告诉 webpack 解析模块时应该搜索的目录。默认值为 ['node_modules'],会依次查找当前目录及祖先路径(即./node_modules、../node_modules、../../node_modules 等)。
如果你想要添加一个目录到模块搜索目录,此目录优先于 node_modules / 搜索,配置如下:
modules: [path.resolve(__dirname, "src"), "node_modules"] |
设置 resolve.mainFields:['main'],设置尽量少的值,可以减少入口文件的搜索解析
webpack 打包 Node 应用程序默认会从 module 开始解析,resolve.mainFields 默认值为:
mainFields: ["module", "main"] |
第三方模块为了适应不同的使用环境,会定义多个入口文件。我们可以设置 mainFields 统一第三方模块的入口文件 main,减少搜索解析。(大多数第三方模块都使用 main 字段描述入口文件的位置)
- 对庞大的第三方模块设置 resolve.alias,直接引用第三方库的 min 文件,避免库内解析
resolve.alias:{ | |
'react':path.resolve(__dirname, './node_modules/react/dist/react.min.js') | |
} |
这样会影响 Tree-Shaking,适合对整体性比较强的库使用,如果是像 lodash 这类工具类的比较分散的库,比较适合 Tree-Shaking,避免使用这种方式。
- 合理配置 resolve.extensions,减少文件查找
默认值:resolve.extensions: ['.js', '.json'],当引入语句没带文件后缀时,webpack 会根据 extensions 定义的后缀列表进行查找,所以: - 列表值尽量少 - 频率高的文件后缀写在前面 - 代码中引入语句尽可能地带上文件后缀,比如 require ('./data') 改写成 require ('./data.json')。
# 2)module.noParse,告诉 webpack 不必解析哪些文件,可以用来排除对非模块化库文件的解析
比如 JQuery、React,另外如果使用 resolve.alias 配置了 react.min.js,则应该排除解析,因为 react.min.js 经过构建,已经是可以直接运行在浏览器的、非模块化的文件。
module: { | |
noParse: [/jquery|lodash, /react\.min\.js$/] | |
} |
有异曲同工的效果,就是使用 externals 外部扩展,剥离第三方依赖模块(如 jquery、react、echarts 等),不打包到 bundle.js。
# 3)配置 loader 时,通过 test、exclude、include 缩小搜索范围
module: { | |
loaders: [{ | |
test: /\.js$/, | |
loader: 'babel-loader', | |
include: [ | |
path.resolve(__dirname, "app/src"), | |
path.resolve(__dirname, "app/test") | |
], | |
exclude: /node_modules/ | |
}] | |
} |
# 2、使用 DllPlugin 减少基础模块编译次数
使用 DllPlugin 动态链接库插件,大量复用的模块只用编译一次,其原理是把网页依赖的基础模块抽离出来打包到 dll 文件中,当需要导入模块存在于某个 dll 中,这个模块不再打包,直接从 dll 中获取。我认为使用 DllPlugin 链接第三方模块,和配置 resolve.alias 和 module.noParse 的效果有异曲同工之处。
使用方法:
1)使用 DllPlugin 插件,配置 webpack_dll.config.js 来构建 dll 文件:
const path = require('path'); | |
const DllPlugin = require('webpack/lib/DllPlugin'); | |
module.exports = { | |
entry: { | |
// 把 React 相关模块的放到一个单独的动态链接库 | |
react: ['react', 'react-dom'], | |
// 把项目需要所有的 polyfill 放到一个单独的动态链接库 | |
polyfill: ['core-js/fn/promise', 'whatwg-fetch'] | |
}, | |
output: { | |
// 输出的动态链接库的文件名称 | |
filename: '[name].dll.js', | |
// 输出的文件都放到 dist 目录下 | |
path: path.resolve(__dirname, 'dist'), | |
// 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react | |
// 之所以在前面加上 _dll_ 是为了防止全局变量冲突 | |
library: '_dll_[name]' | |
}, | |
plugins: [ | |
new DllPlugin({ | |
// 动态链接库的全局变量名称 | |
name: '_dll_[name]', | |
// 描述动态链接库的 manifest.json 文件输出时的文件名称 | |
path: path.join(__dirname, 'dist', '[name].manifest.json') | |
}) | |
] | |
} |
构建输出的以下这四个文件:
├── polyfill.dll.js
├── polyfill.manifest.json
├── react.dll.js
└── react.manifest.json
2)在主 config 文件,使用 DllReferencePlugin
插件引入 xx.manifest.json
动态链接库文件:
const path = require('path'); | |
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin'); | |
module.exports = { | |
entry: { | |
main: './main.js' | |
}, | |
//... 省略 output 和 loader 配置 | |
plugins: [ | |
new DllReferencePlugin({ | |
// 描述 react 动态链接库的文件内容 | |
manifest: require('./dist/react.manifest.json'), | |
}), | |
new DllReferencePlugin({ | |
// 描述 polyfill 动态链接库的文件内容 | |
manifest: require('./dist/polyfill.manifest.json'), | |
}) | |
] | |
} |
# 3、使用 ParallelUglifyPlugin 开启多进程压缩 JS 文件
ParallelUglifyPlugin
插件可以开启多个子进程,每个子进程使用 uglifyJsPlugin
压缩代码,可以并行执行,能显著缩短压缩代码时间。
使用方法:
1)安装 webpack-parallel-uglify-plugin 插件:
npm install -D webpack-parallel-uglify-plugin |
2)然后在 webpack.config.js 配置代码如下:
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); | |
module.exports = { | |
plugins: [ | |
new ParallelUglifyPlugin({ | |
uglifyJS: { | |
// 这里放uglifyJs的参数 | |
} | |
}) | |
] | |
} |
# 二、优化首屏加载
# 1、使用 loading 配置
html-webpack-plugin 插件,给 html 文件载入时添加 loading 图。使用方法如下:
1)安装 html-webpack-plugin 插件:
npm install -D html-webpack-plugin |
2)webpack.config.js 配置如下:
const HtmlWebpackPlugin = require('html-webpack-plugin'); | |
const loading = require('./render-loading');// 事先设计好的loading图 | |
module.exports = { | |
plugins: [ | |
new HtmlWebpackPlugin({ | |
template: './src/index.html', | |
loading: loading | |
}) | |
] | |
} |
# 2、预渲染
prerender-spa-plugin 插件,预渲染极大地提高了首屏加载速度。其原理是此插件在本地模拟浏览器环境,预先执行我们打包的文件,返回预先解析的首屏 html。使用方法入如下:
1)安装 prerender-spa-plugin 插件:
npm install -D prerender-spa-plugin |
2)webpack.config.js 配置如下:
const PrerenderSPAPlugin = require('prerender-spa-plugin'); | |
module.exports = { | |
plugins: [ | |
new PrerenderSPAPlugin({ | |
// 生成文件的路径,也可以与 webpakc 打包的一致。 | |
staticDir: path.join(__dirname, '../dist'), | |
// 要预渲染的路由 | |
route: [ '/', '/team', '/analyst','/voter','/sponsor'], | |
// 这个很重要,如果没有配置这段,也不会进行预编译 | |
renderer: new Renderer({ | |
headless: false, | |
renderAfterDocumentEvent: 'render-active', | |
// renderAfterTime: 5000 | |
}) | |
}) | |
] | |
} |
3)项目入口文件 main.js 启动预渲染:
/* eslint-disable no-new */ | |
new Vue({ | |
el: '#app', | |
router, | |
store, | |
i18n, | |
components: { App }, | |
template: '<App/>', | |
render: h => h(App), | |
/* 这句非常重要,否则预渲染将不会启动 */ | |
mounted () { | |
document.dispatchEvent(new Event('render-active')) | |
} | |
}) |
# 三、优化输出质量 - 压缩文件体积
# 1、压缩代码 - JS、ES6、CSS
# 1)压缩 JS,使用 webpack 内置 UglifyJSPlugin、ParallelUglifyPlugin
会分析 JS 代码语法树,理解代码的含义,从而去掉无效代码、日志输出代码,缩短变量名,进行压缩等优化。使用 UglifyJSPlug 配置 webpack.config.js 如下:
const UglifyJSPlugin = require('webpack/lib/optimize/UglifyJsPlugin'); | |
//... | |
plugins: [ | |
new UglifyJSPlugin({ | |
compress: { | |
warnings: false, // 删除无用代码时不输出警告 | |
drop_console: true, // 删除所有 console 语句,可以兼容 IE | |
collapse_vars: true, // 内嵌已定义但只使用一次的变量 | |
reduce_vars: true, // 提取使用多次但没定义的静态值到变量 | |
}, | |
output: { | |
beautify: false, // 最紧凑的输出,不保留空格和制表符 | |
comments: false, // 删除所有注释 | |
} | |
}) | |
] |
# 2)压缩 ES6,使用第三方 UglifyJS 插件
如今越来越多的浏览器支持直接执行 ES6 代码了,这样比起转换后的 ES5 代码量更少,且性能更好。直接运行的 ES6 代码,也是需要代码压缩,第三方 uglify-webpack-plugin 提供了压缩 ES6 代码的功能,使用方法如下:
a、安装 uglify-webpack-plugin 插件:
uglify-webpack-plugin |
b、webpack.config.js 配置如下:
const UglifyESPlugin = require('uglify-webpack-plugin'); | |
//... | |
plugins:[ | |
new UglifyESPlugin({ | |
uglifyOptions: { //比UglifyJS多嵌套一层 | |
compress: { | |
warnings: false, | |
drop_console: true, | |
collapse_vars: true, | |
reduce_vars: true | |
}, | |
output: { | |
beautify: false, | |
comments: false | |
} | |
} | |
}) | |
] |
另外要防止 babel-loader 转换 ES6 代码,要在.babelrc 中去掉 babel-preset-env,因为正是 babel-preset-env 负责把 ES6 转换为 ES5。
# 3)压缩 CSS
将 js 里面分离出来的多个 css 合并成一个,然后进行压缩、去重等处理。
a、安装引入 mini-css-extract-plugin、optimize-css-assets-webpack-plugin 插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin') | |
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') |
b、配置 loader
module: { | |
rules: [ | |
{ | |
test: /\.css$/, | |
use: [ | |
MiniCssExtractPlugin.loader, | |
'css-loader', | |
{ | |
loader: 'postcss-loader', | |
options: { | |
plugins: [ | |
require('postcss-import')(), | |
require('autoprefixer')({ | |
browsers: ['last 30 versions', "> 2%", "Firefox >= 10", "ie 6-11"] | |
}) | |
] | |
} | |
} | |
] | |
} | |
] | |
} |
c、将多个 css 文件合并成单一 css 文件
主要是针对多入口,会产生多分样式文件,合并成一个样式文件,减少加载次数 配置如下
- 配置 splitChunks
optimization:{ | |
splitChunks: { | |
chunks: 'all', | |
minSize: 30000, | |
minChunks: 1, | |
maxAsyncRequests: 5, | |
maxInitialRequests: 3, | |
name: true, | |
cacheGroups: { | |
styles: { | |
name: 'style', | |
test: /\.css$/, | |
chunks: 'all', | |
enforce: true | |
} | |
} | |
} | |
} |
- 配置插件
- filename 与 output 中的 filename 命名方式一样
- 这里是将多个 css 合并成单一 css 文件,所以 chunkFilename 不用处理
- 最后产生的样式文件名大概张这样 style.550f4.css ;style 是 splitChunks-> cacheGroups-> name
new MiniCssExtractPlugin({ | |
filename: 'assets/css/[name].[hash:5].css' | |
}) |
d、优化 css 文件,去重压缩等处理
- 主要使用 optimize-css-assets-webpack-plugin 插件和 cssnano 优化器
- cssnano 优化器具体做了哪些优化,可参考 官网
配置方式有两种,效果等同。
方式一:
module.exports = { | |
optimization:{ | |
minimizer: [ | |
new OptimizeCSSAssetsPlugin({ | |
assetNameRegExp: /\.css$/g, | |
cssProcessor: require('cssnano'), | |
// cssProcessorOptions: cssnanoOptions, | |
cssProcessorPluginOptions: { | |
preset: ['default', { | |
// 对注释的处理 | |
discardComments: { | |
removeAll: true, | |
}, | |
// 建议设置为 false, 否则在使用 unicode-range 的时候会产生乱码 | |
normalizeUnicode: false | |
}] | |
}, | |
// 是否打印处理过程中的日志 | |
canPrint: true | |
}) | |
] | |
} | |
} |
方式二:
module.exports = { | |
plugins:[ | |
new OptimizeCSSAssetsPlugin({ | |
assetNameRegExp: /\.css$/g, | |
cssProcessor: require('cssnano'), | |
// cssProcessorOptions: cssnanoOptions, | |
cssProcessorPluginOptions: { | |
preset: ['default', { | |
discardComments: { | |
removeAll: true, | |
}, | |
normalizeUnicode: false | |
}] | |
}, | |
canPrint: true | |
}) | |
] | |
} |
# 2、启用 Tree Shaking 剔除死代码
Tree Shaking 可以剔除用不上的死代码,它依赖 ES6 的 import、export 的模块化语法,最先在 Rollup 中出现,Webpack 2.0 将其引入。适合用于 Lodash、utils.js 等工具类较分散的文件。它正常工作的前提是代码必须采用 ES6 的模块化语法,因为 ES6 模块化语法是静态的(在导入、导出语句中的路径必须是静态字符串,且不能放入其他代码块中)。如果采用了 ES5 中的模块化,例如: module.export = {...}
、 require( x+y )
、 if (x) { require( './util' ) }
,则 Webpack 无法分析出可以剔除哪些代码。
如何启用 Tree Shaking:
# 1)修改.babelrc 以保留 ES6 模块化语句:
{ | |
"presets": [ | |
[ | |
"env", | |
{ "module": false }, // 关闭 Babel 的模块转换功能,保留 ES6 模块化语法 | |
] | |
] | |
} |
# 2)启动 webpack 时带上 --display-used-exports 可以在 shell 打印出关于代码剔除的提示
# 3)使用 UglifyJSPlugin,或者启动时使用 --optimize-minimize
# 4)在使用第三方库时,需要配置 resolve.mainFields: ['jsnext:main', 'main'] 以指明解析第三方库代码时,采用 ES6 模块化的代码入口
# 四、优化输出质量 - 提升代码运行速度
# 1、使用 Prepack 提前求值
prepack-webpack-plugin 插件能提前计算,代码运行时直接获取结果,提升代码运行速度。其原理是,编译代码时提前将计算结果放到编译后的代码中,而不是运行时才去求值计算,运行代码时直接将运算结果输出以提升性能。prepack 的使用方法:
1)安装 prepack-webpack-plugin 插件:
npm install -D prepack-webpack-plugin |
2)webpack.config.js 配置如下:
const PrepackWebpackPlugin = require('prepack-webpack-plugin').default; | |
const configuration = {}; | |
module.exports = { | |
// ... | |
plugins: [ | |
new PrepackWebpackPlugin(configuration) | |
] | |
}; |
# 2、使用 Scope Hoisting(作用域提升)
Scope Hoisting 是 Webpack3.x 内置的功能,它分析模块间的依赖关系,尽可能将被打散的模块合并到一个函数中,但不能造成代码冗余,所以只有被引用一次的模块才能被合并。由于需要分析模块间的依赖关系,所以项目代码中需使用 ES6 模块化,否则 Webpack 会降级处理不采用 Scope Hoisting。Scope Hoisting 的使用配置如下:
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin'); | |
module.exports = { | |
// ... | |
plugins: [ | |
new ModuleConcatenationPlugin() | |
] | |
} |
# 五、优化输出质量 - 加速网络请求
# 1、使用 cdn 加速静态资源加载
# 1)CDN 加速的原理
CDN 通过将资源部署到世界各地,使得用户可以就近访问资源,加快访问速度。要接入 CDN,需要把网页的静态资源上传到 CDN 服务上,在访问这些资源时,使用 CDN 服务提供的 URL。
由于 CDN 会为资源开启长时间的缓存,例如用户从 CDN 获取 index.html,即使之后替换了 index.html,用户那边仍会在使用之前的版本直到缓存时间过期。业界的做法:
- HTML 文件:放在自己的服务器上且关闭缓存,不接入 CDN
- 静态的 JS、CSS、图片等资源:开启 CDN 和缓存,同时文件名带上有内容计算出的 hash 值,这样只要内容变化 hash 就会变化,文件名就会变化,就会被重新下载而不论缓存时间多长。 举个详细的例子,有一个单页应用,构建出的代码结构如下:
lua复制代码dist | |
|-- app_9d89c964.js | |
|-- app_a6976b6d.css | |
|-- arch_ae805d49.png | |
|-- index.html |
另外,HTTP1.x 版本的协议下,浏览器会对于同一个域名并行发起的请求限制在 4-8 个。那么把所有静态资源放在同一域名下的 CDN 服务上就会遇到限制,所以可以把静态资源分散在不同的 CDN 服务上,例如 JS 文件放在 js.cdn.com 域名下,CSS 文件放在 css.cdn.com 域名,图片文件放在 img.cdn.com 域名下。使用了多个域名后又会带来一个新问题:增加域名解析时间。是否采用多域名分散资源需要根据自己的需求去衡量得失。 当然你可以通过在 HTML HEAD 标签中加入 <link rel="dns-prefetch" href="//js.cdn.com">
去预解析域名,以降低域名解析带来的延迟。
# 2)Webpack 实现 CDN 的接入
Webpack 接入 CDN 主要的配置如下:
const path = require('path'); | |
const ExtractTextPlugin = require('extract-text-webpack-plugin'); | |
const {WebPlugin} = require('web-webpack-plugin'); | |
module.exports = { | |
// 省略 entry 配置... | |
output: { | |
// 给输出的 JavaScript 文件名称加上 Hash 值 | |
filename: '[name]_[chunkhash:8].js', | |
path: path.resolve(__dirname, './dist'), | |
// 指定存放 JavaScript 文件的 CDN 目录 URL | |
publicPath: '//js.cdn.com/id/', | |
}, | |
module: { | |
rules: [ | |
{ | |
// 增加对 CSS 文件的支持 | |
test: /\.css$/, | |
// 提取出 Chunk 中的 CSS 代码到单独的文件中 | |
use: ExtractTextPlugin.extract({ | |
// 压缩 CSS 代码 | |
use: ['css-loader?minimize'], | |
// 指定存放 CSS 中导入的资源(例如图片)的 CDN 目录 URL | |
publicPath: '//img.cdn.com/id/' | |
}), | |
}, | |
{ | |
// 增加对 PNG 文件的支持 | |
test: /\.png$/, | |
// 给输出的 PNG 文件名称加上 Hash 值 | |
use: ['file-loader?name=[name]_[hash:8].[ext]'], | |
}, | |
// 省略其它 Loader 配置... | |
] | |
}, | |
plugins: [ | |
// 使用 WebPlugin 自动生成 HTML | |
new WebPlugin({ | |
// HTML 模版文件所在的文件路径 | |
template: './template.html', | |
// 输出的 HTML 的文件名称 | |
filename: 'index.html', | |
// 指定存放 CSS 文件的 CDN 目录 URL | |
stylePublicPath: '//css.cdn.com/id/', | |
}), | |
new ExtractTextPlugin({ | |
// 给输出的 CSS 文件名称加上 Hash 值 | |
filename: `[name]_[contenthash:8].css`, | |
}), | |
// 省略代码压缩插件配置... | |
], | |
}; |
# 2、提取页面间公共代码,以便使用浏览器缓存
# 1)原理
大型网站通常是由多个页面组成,肯定会依赖同样的样式文件、脚本文件等。如果不把这些公共文件提取出来,那么每个单页打包出来的 chunck 中都会包含公共代码,相当于要传输 n 份重复代码。如果把公共代码提取成一个文件,那么当用户访问了一个网页加载了这个公共文件,再访问其他依赖公共文件的网页,就直接使用文件在浏览器的缓存,不用重复加载请求。
# 2)使用方法
a、把多个页面依赖的公共代码提取到 common.js
const CommonsPlugin = require('webpack/lib/optimize/CommonsChunkPlugin'); | |
module.exports = { | |
plugins:[ | |
new CommonsChunkPlugin({ | |
chunks:['a','b'], // 从哪些 chunk 中提取 | |
name:'common', // 提取出的公共部分形成一个新的 chunk | |
}) | |
] | |
} |
b、找出依赖的基础库,写一个 base.js 文件,再与 common.js 提取公共代码到 base 中,common.js 就剔除了基础库代码,而 base.js 保持不变。
//base.js | |
import 'react'; | |
import 'react-dom'; | |
import './base.css'; | |
//webpack.config.json | |
module.exports = { | |
entry:{ | |
base: './base.js' | |
}, | |
plugins:[ | |
new CommonsChunkPlugin({ | |
chunks:['base','common'], | |
name:'base', | |
//minChunks:2, 表示文件要被提取出来需要在指定的 chunks 中出现的最小次数,防止 common.js 中没有代码的情况 | |
}) | |
] | |
} |
c、得到基础库代码 base.js,不含基础库的公共代码 common.js,和页面各自的代码文件 xx.js。
页面引用顺序如下:base.js--> common.js--> xx.js
# 3、限制 chunk 分割数量,减小 HTTP 请求开销
# 1)原理
在 webpack 编译完之后,你可能会注意到有一些很小的 chunk - 这产生了大量 HTTP 请求开销。幸运的是使用 LimitChunkCountPlugin 插件可以通过合并的方式,处理 chunk,以减少 http 请求数。
# 2)使用方法
const LimitChunkCountPlugin = require('webpack/lib/optimize/LimitChunkCountPlugin'); | |
module.exports = { | |
// ... | |
plugins: [ | |
new LimitChunkCountPlugin({ | |
// 限制 chunk 的最大数量,必须大于或等于 1 的值 | |
maxChunks: 10, | |
// 设置 chunk 的最小大小 | |
minChunkSize: 2000 | |
}) | |
] | |
} |
# 六、优化开发体验
# 1、使用自动刷新
# 1)Webpack 监听文件
监听文件有两种方式:
方式一:
在配置文件 webpack.config.js 中设置 watch: true。
// 从配置的 Entry 文件出发,递归解析出 Entry 文件所依赖的文件, | |
// 把这些依赖的文件加入到监听列表 | |
// 而不是直接监听项目目录下的所有文件 | |
module.export = { | |
// 只有在开启监听模式时,watchOptions 才有意义 | |
// 默认为 false,也就是不开启 | |
watch: true, | |
// 监听模式运行时的参数 | |
// 在开启监听模式时,才有意义 | |
watchOptions: { | |
// 不监听的文件或文件夹,支持正则匹配 | |
// 默认为空 | |
ignored: /node_modules/, | |
// 在 Webpack 中监听一个文件发生变化的原理是定时的不停的去获取文件的最后编辑时间, | |
// 每次都存下最新的最后编辑时间,如果发现当前获取的和最后一次保存的最后编辑时间不一致, | |
// 就认为该文件发生了变化。 | |
//poll 就是用于控制定时检查的周期,具体含义是每隔多少毫秒检查一次 | |
// 默认每隔 1000 毫秒询问一次 | |
poll: 1000, | |
// 监听到文件发生变化时,webpack 并不会立刻告诉监听者, | |
// 而是先缓存起来,收集一段时间的变化后,再一次性告诉监听者 | |
//aggregateTimeout 就是用于配置这个等待时间, | |
// 目的是防止文件更新太快导致重新编译频率太高,让程序构建卡死 | |
// 默认为 300ms | |
aggregateTimeout: 300, | |
// 不监听的 node_modules 目录下的文件 | |
ignored: /node_modules/, | |
} | |
} |
方式二:
在执行启动 Webpack 命令时,带上 --watch 参数,完整命令是 webpack --watch。
# 2)控制浏览器自动刷新
方式一:webpack-dev-server
在使用 webpack-dev-server 模块去启动 webpack 模块时,webpack 模块的监听模式默认会被开启。 webpack 模块会在文件发生变化时告诉 webpack-dev-server 模块。
方式二:koa + webpack-dev-middleware + webpack-hot-middleware 前后端同构
# 2、开启模块热更新 HMR
模块热替换 (HMR - Hot Module Replacement) 功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面,所以预览反应更快,等待时间更少。原理是向每个 chunk 中注入代理客户端来连接 DevServer 和网页。开启方式:
# 1)webpack-dev-server -hot
# 2)使用 HotModuleReplacementPlugin 插件
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin'); | |
module.exports = { | |
plugins: [ | |
new HotModuleReplacementPlugin() | |
] | |
} |
开启后,如果修改子模块就可以实现局部刷新,但如果修改的是根 JS 文件,会整个页面刷新。原因在于,子模块更新时,事件一层层向上传递,直到某个层的文件接收了当前变化的模块,然后执行回调函数。如果一层层向外抛到最外层都没有文件接收,就会刷新整页。
永远不要在生产环境 (production) 下启用 HMR。
# 小结:
在使用 webpack 构建前端项目中,逐渐暴露出一些性能问题,其主要有如下几个方面:
- 代码全量构建速度过慢,即使是很小的改动,也要等待长时间才能查看到更新与编译后的结果。(引入 HMR 热更新后有明显改进)
- 随着项目业务的复杂度增加,工程模块的体积也会急剧增大,构建后的模块通常要以 M 为单位计算。
- 多个项目之间共用基础资源存在重复打包,基础库代码复用率不高。
- 首屏加载依赖过多,白屏时间较长等问题。
针对如上问题,上述 webpack 优化方案便派上用场了。作为开发工程师,我们要不断追求项目工程高性能,秉持 “什么方案解决什么问题” 的准则,针对实际开发项目,持续改进优化项目性能,不断提升开发效率、降低资源成本。