webpack相关文章
HMR
hot module replacement 热模块替换/模块热替换 作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块) 极大提升构建速度
module.exports = {
entry: './src/js/index.html',
...
devServer: {
...
// 开启HMR功能
// 当修改了webpack配置,新配置要想生效,必须重新webpack服务
hot: true
...
}
...
}
-
样式文件: 可以使用HMR功能,因为style-loader内部实现了
-
js文件: 默认不能使用HMR功能
注意:HMR功能对js的处理,只能处理非入口js文件的其他文件。
解决:
if (module.hot){
// 一旦module.hot为true,说明开启了HMR功能。 -->让HMR功能代码生效
module.hot.accept('./print.js', function(){
// 方法会监听print.js文件的变化,一旦发生变化,其他模块不会重新打包构建。
//会执行后面的回调函数
print( );
})
-
html文件: 默认不能使用HMR功能,同时会导致问题: html 文件不能热更新了
解决: 修改entry入口,将html文件引入
module.exports = { entry: ['./src/js/index.js', './src/index.html'], ... }
source-map
一种提供源代码到构建后代码映射技术(如果构建后代码出错了,通过映射可以追踪源代码错误)
不同类型的source-map的区别
- source-map
- 外部
- 能提示错误代码准确信息和源代码的错误位置
- inline-source-map
- 内联
- 只生成一个内联source-map
- 能提示错误代码准确信息和源代码的错误位置
- hidden-source-map
- 外部
- 能提示错误代码错误原因,但是没有错误位置
- 不能追踪源代码错误,只能提示到构建后代码的错误位置
- eval-source-map
- 内联
- 每一个文件都生成对应的source-map, 都在eval
- 能提示错误代码准确信息和源代码的错误位置
- nosources-source-map
- 外部
- 错误代码准确信息,但是没有任何源代码信息
- 防止源代码泄漏
- cheap-source-map
- 外部
- 能提示错误代码准确信息和源代码的错误位置
- 只能精确到行,而source-map能精确到列
- cheap-module-source-map
- 外部
- 能提示错误代码准确信息和源代码的错误位置
- module会将loader的source map加入
使用方法
module.exports = {
...
//devtool: 'inline-source-map'
//devtool: 'hidden-source-map'
//devtool: 'eval-cheap-source-map'
//...
devtool: 'source-map'
...
}
内联和外部的区别
1.外部生成了文件,内联没有
2.内联构建速度更快
如何选择
开发环境需求: 速度快,调试更友好
速度快慢(eval> inline> cheap>...)
1. eval-cheap-source-map
2.eval-source-map
调试友好程度(是否更方便调试)
1.souce-map
2.cheap-module-souce-map
3.cheap-souce-map
综合速度与友好程度后,一般选择:
* **eval-source-map (vue,react脚手架默认选择就是这种)**
* eval-cheap-module-souce-map
生产环境:源代码要不要隐藏?调试要不要更友好
内联会让代码体积变大,所以在生产环境不用内联 nosources- source-map 全部隐藏 hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
--> source-map / cheap- module - souce -map
oneOf
为什么需要oneOf
因为如果没有oneOf的话,每个文件都会去匹配一次,效率很低
使用
用oneOf包裹rules中的对象
module.exports = {
...
module: {
rules: [
// 以下loader只会匹配一一个
// 注意:不能有两个配置处理同一种类型文件
// 比如有一个eslint处理js文件,babel也处理js文件,必须把eslint的loader提到oneOf的外面来
oneOf: [
{
test: '',
...
},
...
]
]
}
}
缓存
目的是为了让未被修改过的文件使用缓存,这样子运行效率更高
babel缓存
方法
- cacheDirectory: true
目的
让第二次打包构建速度更快
文件资源缓存
方法
-
hash:每次wepack构建时会生成一个唯一的hash值。
问题:因为js和css同时使用一个hash值。 如果重新打包,会导致所有缓存失效。
如果我只改动一个文件,导致两个的文件名的改变,则都会使用新的文件,相当于没有使用缓存
-
chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
问题: js 和css的hash值还是一样的 因为css是在js中被引入的, 所以同属于一个chunk
-
contenthash:根据文件的内容生成hash值。不同文件hash值一定不一样
目的
让代码上线运行缓存更好使用
module.exports = {
...
output: {
//js文件名加上hash值后,文件更改时,文件名更改,就不会一直采用缓存的文件了
//css文件同理
filename: 'js/built.[hash:10].js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_ _modules/ ,
loader: ' babel-loader',
options: {
presets:[ ...
], I
// 开启babel缓存
// 第二次构建时,会读取之前的缓存
cacheDirectory: true
}
]
}
...
}
tree shaking
树摇是为了去除我们在应用程序中没有使用的代码
前提
-
必须使用ES6模块化
-
开启production环境
作用
减少代码体积
问题
可能会把在js中引入的css文件干掉
解决办法:在package. json中配置
// 所有代码都没有副作用( 都可以进行tree shaking )
"sideEffects": false
//问题:可能会把css / @babel/polyfill (副作用) 文件干掉
//解决:
"sideEffects": ["*.css", "*.less" ]
code split 代码分割
分割成多个js文件,能实现并行加载,速度更快
打包多入口文件
module.exports = {
// 单入口,单页面配置
// entry: './src/js/index.js'
entry:{
// 多入口: 有几个入口,最终输出就有几个bundle
// 多页面配置
main: './src/js/index.js',
test: './src/js/test.js'
},
output:{
//[name]:取文件名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, "build')
},
}
打包node_modules中的文件
module.exports = {
...
entry:{
// 多入口: 有几个入口,最终输出就有几个bundle
// 多页面配置
main: './src/js/index.js',
test: './src/js/test.js'
},
...
/*
1.可以将node_modules中代码单独打包一个chunk最终输出
2.自动分析多入口chunk中,有没有公共的文件(依赖),如果有会打包成单独一个chunk
*/
optimization:{
splitChunks:{
chunks: 'all'
}
},
...
}
单入口单独打包某个js文件
在js中操作
/*
通过js代码,让某个文件被单独打包成一个chunk
import动态导入语法:能将某个文件单独打包
*/
import(/* webpackChunkName: 'test' */'./test' )
.then(({ mul, count }) => {
//文件加载成功~
// eslint-disable-next- line
console.1og(mu1(2, 5));
})
.catch(() => {
// eslint-disable-next- lineconsole.1og('文件加载失败~');
});
懒加载和预加载
设置点击按钮后,才去加载对应的js文件
懒加载的前提是需要进行代码分割,分割成一个单独的js文件,再对这个单独的文件进行懒加载
document.getElementById('btn').onclick = function() {
// 懒加载:当文件需要使用时才加载
// 预加载prefetch:会在使用之前,提前加载js文件
// 正常加载可以认为是并行加载(同一时间加载多个文件)
// 预加载prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(( { mul} ) => {
console.log(mul(4, 5));
});
};
PWA
pwa:渐进式网络开发应用程序(离线可访问)
workbox --> workbox -webpack-plugin
安装
npm i workbox-webpack-plugin -D
使用
webpack.config.js
module.exports = {
...
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
/*
1.帮助serviceworker快速启动
2.删除旧的serviceworker
生成一个serviceworker 配置文件
*/
clientsClaim: true,
skipWaiting: true
})
]
...
}
入口js文件index.js
/*
1. eslint不认识window、navigator 全局变量
解决:需要修改package . json中eslintConfig配置
"env": {
"browser": true //支持浏览器端全局变量
}
2. sw代码必须运行在服务器上
--> nodejs
-->
npm i serve -g
serve -s build启动服务器,将build目录下所有资源作为静态资源暴露出去
*/
//注册serviceworker
//处理兼容性问题
if ('serviceWorker' in navigator){
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js')
.then(()=>{
console.log('sw注册成功了~');
})
.catch(() =>{
console.log('sw注册失败了~');
});
})
}
多进程打包
安装
npm_ i thread- loader -D
使用
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
//开启多进程打包
//进程启动大概为600ms,进程通信也有开销
//只有工作消耗时间比较长,才需要多进程打包
//'thread- loader',
{
loader: 'thread-loader',
options: {
workers: 2 //进程2个
}
},
{
loader: ' babel-loader',
options:{
presets :[
[
'@babel/ preset-env',
{
useBuiltIns: ' usage',
corejs:{ version: 3 } ,
targets:{
chrome: ' 60',
firefox: '50'
}
}
]
]
}
]
}
}
]
}
...
}
externals
module.exports = {
...
externals: {
//拒绝jQuery被打包进来
jquery: 'jQuery'
}
}
dll
使用dll技术,对某些库(第三方库: jquery、react. vue...)进行单独打包
webpack.dll.js(和webpack.config.js不一样,这里取什么名字都可以)
const{ resolve } = require('path');
const webpack = require('webpack');
module. exports = {
entry: {
// 最终打包生成的[name] --> jquery
// ['jquery'] -->要打包的库是jquery
jquery: ['jquery']
},
output: {
filename: '[name].js',
path: resolve(__dirname, 'dll'),
library: '[name]_[hash]', //打包的库里面向外暴露出去的内容叫什么名字
},
plugins:[
// 打包生成一个manifest.json --> 提供和jquery映射
// 告诉webpack jquery不需要再打包
new webpack.DllP1ugin({
name: '[name]_[hash]', //映射库的暴露的内容名称
path: resolve(_ dirname, 'dl1/manifest.json') //输出文件路径
})
],
mode: 'production'
};
当你运行webpack时,默认查找webpack. config.js配置文件
需求:需要运行webpack.dll.js文件
--> webpack --config webpack.dll.js
webpack.config.js
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
module. exports = {
...
plugins: [
//告诉webpack哪些库不参与打包,同时使用时的名称也得变
//问题:不打包之后,不会引入jquery,所以需要用到下面的插件AddAssetHtmlWebpackPlugin自动引入该资源
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
//将某个文件打包输出去,并在html中自动引入该资源
new AddAssetHtmlWebpackPlugin(
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
...
}
性能优化总结
开发环境性能优化
- 优化打包构建速度
- HMR
- 优化代码调试
- source-map
生产环境性能优化
- 优化打包构建速度
- oneOf
- babel缓存
- 多进程打包
- 优化代码运行性能
- 缓存(hash-chunkhash-contenthash)
- tree shaking
- code split
- 单入口项目
- 直接打包只会打包成一个bundle,所以我们需要对bundle代码进行分割,分割成多个js文件,这样子多个文件能并行加载,比加载一个文件更快。
- 可以使用配置optimization对node_modules进行打包,如果想对node_modules其中的某个文件打包,可以使用dll技术
- 单入口项目
- 懒加载/预加载
- 需要在代码分割的基础上,才能进行懒加载
- pwa
- externals
- dll