安装开发服务器webpack-dev-server
避免开发过程中重复的打包操作,自动编译打包、自动刷新浏览器,开发者只需写好代码就可以。
特点:将编译好的文件保存在内存中。
npm i webpack-dev-server -D
webpack.config.js中的配置:
devServer:{
//为哪个文件夹提供此服务
contentBase:'rsolve(__dirname,'dist')',
//页面实时刷新
inline:true,
//启动gzip压缩
compress:true
//自动打开浏览器
//open:true
}
启动:本地下载的话可以使用npx webpack-dev-serve命令启动这个服务或者在package.json文件中进行配置,然后直接使用npm run dev命令启动服务。成功启动后,直接点击http://localhost:8080进行调试。
"scripts":{
"dev":"webpack-dev-server"
}
npm run dev——>执行这个命令,优先从本地中查找有没有安装webpack-dev-server包。
有了这个命令后,在开发中就不用频繁的去执行npm run build命令打包了。npm run dev会自动更新改动的地方,等开发结束后,执行npm run build最终打包即可,打包结束后,把dist文件夹放到服务器进行部署。
提取css文件为单独文件(生产环境下配置)
使用css-loader能将css文件变成commonJS模块内嵌到JS文件中,这样操作会使得js文件体积变大,加载js文件的速度降低,为了优化速度,可以将内嵌在js中的css提取出来,提取出来的css文件就可以和js文件并行加载。提取需要安装mini-css-extract-plugin
插件。提取出来的css文件会以<link>标签的形式添加到document中。
使用这个插件就不需要使用style-loader
包了。
use:[
MiniCssExtractPlugin.loader,
'css-loader'
]
plugins:[
new MiniCssExtractPlugin({
//对提起的css文件重命名
filename:'css/index.css'
})
]
css文件的兼容性处理(生产环境下配置)
需要用到postcss库---->此库要在webpack中使用,需要安装postcss-loader以及插件postcss-preset-env。
压缩css文件(生产环境下配置)
使用optimize-css-access-webpack-plugin
插件
压缩js文件(生产环境下配置)
只要把webpack.config.js中的mode改为production即可,在生产环境下,会自动启动很多插件,其中UglyfyJsPlugin插件会直接对js文件压缩,不用下载压缩包。
压缩html文件(生产环境下配置)
也不需要下载什么压缩包,在plugins配置即可。
new HtmlWebpackPlugin({
template:'./src/index.html',
//压缩html,不需要下载插件
minify:{
//清除空格
collapseWhitespace:true,
//清除注释
removeComments:true
}
})
es6转为es5的处理(生产环境下配置)
部分低版本浏览器不认识es6语法,比如let/const关键字,箭头函数等,需要转为浏览器能识别的es5。
需要下载babel-loader @babel/core @babel-preset-env
//第一种配置方法
//这种配置只能做基本的转换,es6中的promise对象还是不能转换
{
test:/\.js$/,
exclude:/node_modules/,//不对node_modules里的js文件匹配处理
use:{
loader:'babel-loader',
options:{
//preset:指示babel做什么样的转换处理
preset:['@babel/preset-env']
}
}
}
//第二种配置方法
//安装@babel/polyfill,能把全部的es6语法转为es5语法
//安装好后,在index.js文件中引入'import @babel/polyfill'
//缺点:转换后的bundle.js文件变得很大,因为里面包含了所有的方法,有些方法根本没用到,也一起打包进去。
//第三种配置方法(最好)
//需要转换的就去转换,不需要的就不做转换,按需转换。
//安装core-js npm i core-js -D
use:{
loader:'babel-loader',
options:{
//preset:指示babel做什么样的转换处理
preset:[
[
'@babel/preset-env',
{
useBuiltIns:'usage',//按需加载
corejs:{ //指定corejs版本
version:3
},
targets:{ //指定应用到哪些版本的浏览器
chrome:'60',
firefox:'60',
ie:'9',
safari:'10',
edge:'17'
}
}
]
]
}
}
webpack开发环境下的性能优化(使用HMR、source map)
HMR(hot module replacement,热模块替换)
在开发阶段,当对某个模块作出修改后,在控制台会看到其它模块的结果也会跟着一起刷新,虽然其它模块并没做任何改动。这显然不是我们想要的结果,我们希望的是哪个模块修改了,只刷新这个模块的值,其它模块不做处理。所以就有了HMR。
HMR是加快开发阶段开发速度,节省开发时间的一种方式。
HMR能实现只对单个模块的刷新,而不用重新刷新页面中的所有模块。(有点类似ajax技术,只刷新局部页面)
配置:
devServer:{
//webpack.config.js中的配置修改后,需要重启webpack-dev-server
hot:true
}
配置好HMR后,分别对css文件、js文件、HTML文件进行测试。在webpack-dev-server下,发现当单独对css文件修改时,控制台提示Nothing hot updated.
。无论修改什么样式,这些样式都不会生效。目前还找不到原因。
单独对js文件修改时,发现HMR功能无效;单独修改HTML文件时,也发现HMR功能无效,并且HTML文件无法更新。
//js文件默认不能使用HMR功能,要使用必须添加支持HMR的代码
//且HMR只能处理非index.js的文件,对于入口文件index.js无法处理
//在index.js文件中写上下面的配置代码:
if(module.hot){ //
module.hot.accept('./print.js',function(){
//监听print.js文件的变化,如若变化,打包此文件,并执行回调函数
print()
})
}
//对于HTML文件,需要修改entry
entry:['./src/index.js','./src/index.html'],
//修改entry后测试,HTML页面能得到更新,但还是无法使用HMR功能。不过由于HTML文件只有一个,所以,没有HMR功能也无妨。
source map
当使用webpack打包源代码时,可能很难发现出错误的地方在源代码中的位置。为了能更好地追踪错误,提供了source map功能,将构建后出错的代码映射到源代码的位置。
配置很简单:一行代码。但是source map有很多种不同的选项供选择。
devtool:'source-map' //外部,直接使用source-map,在dist/js文件夹下会生成一个.map文件
- 生成的source-map能映射到源代码出错的具体位置
devtool:'inline-source-map' //生成的sourcemap文件嵌套在bundle.js文件中,所以也称为内联sourcemap。
- bundle.js文件中只有一个sourcemap
- 映射功能和上面的source-map一致
devtool:'hidden-source-map'//外部,在dist/js文件夹内会生成一个外部的.map文件
- 能提示错误的原因,但是不会映射到源代码的位置,而是映射到构建后代码的位置。
devtool:'eval-source-map'//内联,没有生成外部文件,生成的sourcemap文件嵌套在bundle.js文件中。
- 每一个文件(css、js、less...)都会生成对应的source-map在bundle.js文件中
- 和source-map、inline-source-map一样,能映射到源代码的具体位置
devtool:'nosource-source-map'//外部
- 能提示错误原因,以及源代码哪行代码出了问题,但是不会显示源代码信息
devtool:'cheap-source-map'//外部
- 能提示错误原因,也能映射到源代码出错位置。不同的是,它只有行映射,没有列映射,即你知道这行代码出了问题,但是不清楚这行代码的哪个位置出了问题。
devtool:'cheap-module-source-map'//外部
- 同cheap-source-map
内联和外部的区别:
1.外部生成的文件,内联没有
2.内联构建速度比外部快
开发环境的选择:选择速度快,调试友好的
速度快(eval>inline>cheap>...)
- eval-source-map
- eval-cheap-source-map
调试更友好
- source-map(能精确到行、列)
- cheap-module-source-map
- cheap-source-map(只能精确到行)
综合选择:eval-source-map
生产环境的选择:源代码要不要隐藏?调试要不要更友好?
生产环境不考虑内联的source-map,因为会让bundle.js文件体积变大。所以,inline-source-map、eval-source-map这两种方式排除。
代码若要隐藏:
hidden-source-map、nosource-source-map
调试更友好:
source-map(能精确到行、列)、cheap-module-source-map
webpack生产环境下的性能优化
1、oneOf
在module中配置了很多的loader,某文件在处理过程中会去寻找指定的loader处理,但处理完后,并不会停止,还会去匹配其它的loader,直到全部匹配完,很显然,后续的操作都是无意义的,都是在浪费性能。
所以,oneOf的作用就是:根据文件类型匹配对应的loader转换即可。
配置:
把所有的loader配置放在oneOf中即可。注意:在oneOf中不能有2个loader处理同一个文件类型,必须分离开。
module:{
rules:[
{
test:/\.js$/,
exclude:/node_modules/,
// //优先执行
enforce:'pre',
loader:'eslint-loader',
options:{
fix:true
}
},
{
//下面的loader只会匹配一个,处理性能更好
//注意:不能有两个配置处理同一种类型文件
oneOf:[
{
test:/\.css$/,
use:[...commenCssLoader]
},
{
test:/\.less$/,
use:[...commenCssLoader,'less-loader']
},
]
}
]
},
2、缓存
对资源做缓存处理,等用户下次请求的时候就可以直接从缓存中拿取,速度会快很多。浏览器缓存可以降低网络流量,加快网站的访问速度。
但是,缓存也有弊端。当把文件全部部署到服务器上,之后如果没有更改文件名,浏览器会认为此文件没更新,就会使用此资源的缓存版本。由于缓存的存在,可能获取不到最新的文件。
babel缓存:
babel能对js代码进行处理,编译成浏览器能够识别的语法。babel也能对众多的js文件进行处理,将它们缓存起来,如果修改了哪份js文件,打包后,只会请求这一份js文件,其它文件依然是从缓存中获取。
配置:https://www.bilibili.com/video/BV1e7411j7T5?p=23
文件资源缓存:
/*在强制缓存期间,服务器是不会向浏览器返回请求资源的,而是让浏览器直接从缓存中拿取资源。这样有个问题,假如在强制缓存期间,某些文件代码发生了变化,那就无法获取到最新的页面数据。
可以给请求的资源,比如css文件、js文件的名称加个版本号,如果文件更新了,会有个更新的版本号。
这个版本号怎么加上去?用hash表示。
output:{
filename:'js/[name].[hash:8].js',
path:resolve(__dirname,'dist')
}
new MiniCssExtractPlugin({
//对提取的css文件重命名
filename:'css/index.[hash:8].css'
}),
在输出的css文件、js文件名称上就能看到hash结尾,在控制台也能看到这两种文件就不再是memory cache了
css文件、js文件每次修改打包后,都会生成新的hash值
但是又带来个问题,因为css和js文件都使用了hash,即使我只修改了其中一个文件,打包后,另一个文件的hash值也会跟着变化,因为它们共用一个hash,所以导致另一个文件也重新发送了请求,可事实是另一个文件根本没有做任何修改。
换成chunkhash:如果打包的两个文件来自同一个chunk,那么它们的hash就一样,否则不一样。
试试。
实际结果不如人意,css文件和js文件的hash值仍一样,因为webpack打包过程中将css以commonJS规范编译引入到了js文件中,所以它俩来自同一个chunk。
换成contenthash:根据文件内容生成的hash,文件内容不一样,hash肯定不一样
试试。
两种文件的hash不一样了,当修改css文件的内容,再打包后,css文件重新发送请求,js文件的hash不变,不会重新发送请求,而是使用上次的缓存了*/
new MiniCssExtractPlugin({
//对提取的css文件重命名
filename:'css/index.[contenthash:8].css'
}),
tree shaking:树摇
你可以将应用程序想象成一棵树。绿色表示实际用到的源码和 library,是树上活的树叶。灰色表示无用的代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。
具体实践:
//创建了一个print.js,文件中包含两个方法
export function sum(x,y){
console.log(x+y)
}
export function mul(x,y){
console.log(x*y)
}
//在index.js文件中引入其中的sum()方法,不引入mul()方法
//既然mul()没被导入,它就是“未引用代码(dead code)”,这种代码应该被删除的
import {sum} from './print.js'
sum(3,3)
//在package.json中配置:
"sideEffects":false //标记所有的文件都是无副作用的,可以删除那些未被使用的代码
//如果有些代码有副作用,就以数组的形式把代码加进去。
"sideEffects":[
./src/some-side-effectful-file.js
]
//配置了sideEffects后,发现css文件在tree shaking后无意被删除了,因为css文件都是直接引入,没有执行什么操作。
//所以,为了避免被删除,需要把css文件添加到sideEffects列表中
"sideEffects":[
./src/some-side-effectful-file.js,
*.css,
*.less
]
tree shaking要能生效,必须是在mode:'production'
环境下。
4、代码分割(只针对js文件)
未使用代码分割的情况下,所有的js文件都是打包到了同一个文件中,导致代码体积很大。代码分割将一个打包后的大文件,分成几个独立的小文件。可以提高文件加载速度、实现文件按需加载功能。
分两种情况:
第一种:将单入口拆分成多入口,一个入口形成一个chunk代码块,然后输出对应的文件
//未拆分前是单入口
entry:['./src/index.js'],
//拆分成多入口---->打包后,就会输出两个对应的bundle.js
//index.js中导入test.js测试没问题
entry:{
index:'./src/index.js',
test:'./src/test.js'
},
output:{
filename:'js/[name].[contenthash:8].js',
path:resolve(__dirname,'dist')
}
执行npm run build命令后,查看dist/js下的js文件,出现了两个js文件,说明两个文件是分开打包的。
但是修改单入口为多入口这种方式并不方便,所以有了第二种途径。
//在webpack.config.js中加上如下配置(单页面情况)。
entry:'./src/index.js',
output:{
filename:'js/[name].[contenthash:8].js',
path:resolve(__dirname,'dist')
},
optimization:{
splitChunks:{
chunks:'all'
}
}
optimization的作用:
如果是单页面应用,只有一个entry,即都是从index.js作为入口,如果index.js中引入了第三方库,optimization可以把这个第三方包单独打包成一个chunk输出,就不会打包到bundle.js中去。
如果是多页面应用,即entry中有多个入口(index.js、test.js),它会分析这些文件有没有使用相同的依赖,比如有没有都用到jQuery,如果都用到了jQuery,就会把jQuery提取成单独的一个chunk,避免jQuery被打包两次。
注意:optimization只是对第三方包起作用。如果不同的js文件还是打包到一个bundle.js文件中,想要把不同的js文件分开打包,此方式不可行
如果我想把index.js和test.js文件分开打包,该怎么实现呢?
import()
在index.js文件中使用import()动态导入语法,可以对指定的js文件单独打包。
/*webpackChunkName:'test'*/:给打包后的js文件加上test名称
import(/*webpackChunkName:'test'*/'./test.js')
.then((result)=>{ //result:是个module对象
console.log(result)//如下图
result.mul(2,3)//6
})
.catch(()=>{
console.log('打包失败')
})
5、懒加载和预加载
懒加载:文件需要使用的时候才加载。比如执行点击事件,再去异步加载代码,可以提高代码利用率。
预加载:等其它资源加载完毕了,浏览器空闲的时候再去偷偷加载。
使用index.js和test.js示例。
//index.js
import { mul } from './test.js'
console.log('我是index.js文件')
//文件懒加载+预加载
document.getElementById('btn').addEventListener('click',()=>{ //点击按钮的时候才去加载test.js文件,不要一打开页面就加载test.js
import(/*webpackChunkName:'test',webpackPrefetch:true*/'./test.js')
.then((result)=>{ //result:是个module对象
result.mul(2,3)
})
.catch(()=>{
console.log('打包失败')
})
})
//引入css文件
import './index.css'
//引入less文件
import './index.less'
//js文件默认不能使用HMR功能,要使用必须添加支持HMR的代码
//且HMR只能处理非index.js的文件,对于入口文件index.js无法处理
if(module.hot){ //
module.hot.accept('./print.js',function(){
//监听print.js文件的变化,如若变化,打包此文件,并执行回调函数
print()
})
}
//test.js
console.log('我是test.js文件')
export function sum(x,y){
console.log(x+y)
}
export function mul(x,y){
console.log(x*y)
}
6、externals
作用:拒绝某些包被打包,比如通过import $ from 'jQuery'
,这些包的体积很大,在js文件打包的时候如果将它们也打包进去,到时候加载速度肯定会降低。
externals:{
jquery:'jQuery'
}
7.dll(配置、理解上有点难)
dll将引入的第三方库(jQuery、React、Vue)单独拆开来,打包成不同的文件,有利于性能优化。
7.1 创建一个webpack.dll.js文件
先打包jQuery,打包后的文件名称为jquery.js,这个文件中以[name][hash:8]的名称向外暴露了jQuery第三方库的内容。
plugin的插件会生成manifest.json文件,文件中以"name":"[name][hash:8]"建立了和jQuery的映射关系。使用dll打包后,将来就不要再打包了,直接引入打包后的jQuery文件即可。
const {resolve} = require('path')
const webpack = require('webpack')
module.exports = {
entry:{
//jquery:表示打包后的文件名称是jquery
//['jQuery']:表示要打包的是jQuery这个库
jquery:['jQuery']
},
output:{
filename:'[name].js',
path:resolve(__dirname,'dll'),
library:'[name]_[hash:8]'//打包后的库向外暴露的内容名称
},
plugins:[
new webpack.DllPlugin({
name:'[name].[hash:8]',//打包后的库向外暴露的内容名称
path:resolve(__dirname,'dll/manifest.json')//输出文件路径
//manifest.json的作用:提供和jQuery的映射关系,通过
})
],
mode:'production'
}
注意:运行webpack的时候,默认查找webpack.config.js文件,为了能执行webpack.dll.js文件,需要执行webpack --config webpack.dll.js
7.2 dll将第三方库打包好后,回到webpack.config.js配置文件
const webpack = require('webpack')
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
}),
//告诉webpack不用打包jQuery了
new webpack.DllReferencePlugin({
manifest:resolve(__dirname,'dll/manifest.json')
}),
//将jquery.js文件自动引入到html文件中去,这样就能使用jQuery了
new AddAssetHtmlWebpackPlugin ({
filepath:resolve(__dirname,'dll/jquery.js')
})
],
mode:'production'