webpack是前端资源构建工具,一个静态模块打包器。
在webpack看来,前端所有的资源文件(js/css/img/less...)都会作为模块处理,它根据模块的依赖关系进行静态分析,打包生成对应的静态资源bundle。
webpack的五个核心概念
Entry
entry入口指示webpack以哪个文件为入口起点开始打包,分析构建内部依赖图。
单入口:
entry:'./src/index.js'
多入口:
entry:['./src/index.js','./src/add.js']
以上两种方式都是将文件打包形成一个chunk文件,最终输出为一个bundle文件。
对象形式:有几个文件就形成几个chunk,输出几个bundle.
entry:{
index:'./src/index.js',
add:'./src/add.js'
}
Output
output输出指示打包后的资源bundle输出到哪里去,以及如何命名。
output:{
filename:'js/[name].js',
path:resolve(__dirname,'build')
publicPath:'/',
chunkFilename:'js/[name]_chunk.js'//非入口文件chunk名称,
library:'[name]',//整个库向外暴露的变量名
libraryTarget:'window',//变量名添加到哪个上
}
Loader
loader让webpack能够去处理那些非JavaScript的文件。
通常,一个Loader只能被一个Loader处理,当一个文件要被多个Loader处理,那么一定要指定loader执行的先后顺序。enforce:pre
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader']
},{
test:/\.js$/,
loader:'eslint-loader',
exclude:/node-modules/,
include:resolve(__dirname,'src'),//只检查该目录下的文件
enforce:'pre',//延后为post
}
]
}
Plugins
Plugins插件可以用于执行范围更广的任务,插件的范围包括,从打包优化和压缩,一致到重新定义环境中的变量等。
Mode
模式指示webpack使用相应模式的配置。
- development:会将process.env.NODE_ENV的值设置为development,能让代码本地调试运行的环境。
- production:会将process.env.NODE_ENV的值设置为production,能让代码优化上线运行的环境。
运行指令:
- 开发环境:webpack ./test.js -o ./build/bundle.js --mode=development
webpack会以./test.js为入口文件开始打包,打包后输出到/build/bundle.js,整体打包环境是开发环境。
- 生产环境:webpack ./test.js -o ./build/bundle.js --mode=production
修改代码后需要重新打包
- webpack能处理js/json资源,不能处理css/img等其他资源。
- 生产环境和开发环境将es6模块化编译成浏览器能识别的模块化。
- 生产环境比开发环境多一个压缩js代码。
webpack.config.js是webpack的配置文件,作用:当运行webpack指令时,会加载里面的配置。
所有构建工具都是基于node.js平台运行的,模块化默认采用commonjs。
const {resolve} = require('path')
module.exports = {
//webpack配置
//入口起点
entry:'./src/index.js,
//输出
output:{
//输出文件名
filename:'built.js',
//输出路径 __dirname是nodejs的变量,代表当前文件的目录的绝对路径
path:resolve(__dirname,'build')
},
module:{
rules:[
//详细loader配置
{
//匹配哪些文件
test:/\.css$/,
//前者是创建style标签,将js中的样式资源插入到js中,添加到head中生效,后者是将css文件编程commonjs模块加载js中,里面内容是样式字符串。执行顺序是从右到左。
use:['style-loader','css-loader']
},{
test:/\.less$/,
use:['style-loader','css-loader','less-loader']
}
]
},
plugins:[
],
mode:'development'
//mode:'production'
}
以上的做法可以将css样式一起输出到js文件中,但是如果需要将css单独输出为一个文件,就可以用另一个插件:mini-css-extract-plugin,这个插件可以从css中提取css代码到单独的文件中,对css代码进行压缩。
css兼容性处理
js兼容性处理
bable-loader @bable/core 按需加载
resolve
解析模块的规则
resolve:{
alias:{//配置路径别名:@符号就相当于src的绝对路径
@:resolve(__dirname,'./src')
},
extensions:['.js','.json'],//配置路径的后缀名
modules:[resolve(__dirname,'../../node_modules'),'node_modules'],//告诉webpack解析模块是去哪个目录,前者是告诉具体的路径,加快速度
}
devServer
devServer:{
//运行代码的目录
contentBase:resolve(__dirname,''build),
watchContentBase:true,//监视该目录下的所有文件,一旦文件变化就会reload
watchOptions:{
//忽略文件
ignore:true
}
//启动gzip压缩,加快速度
compress:true,
port:8080,
host:'localhost',
open:true,//自动打开浏览器
hot:true,//开启HRM功能
clientLogLevel:'none',//不要显示启动服务器日志信息
//除了一些启动基本信息以外,其他内容都不要显示
quiet:true,
overlay:false,//如果出错了,不要全屏提示
proxy:{
//解决开发环境跨域问题,一旦服务器接受到/api/xxx的请求,就会把请求转发到另外一个服务器3000。原理是服务器与服务器之间不存在跨域问题,进行网络请求的时候,proxy会帮助我们将我们请求远端服务器改为我们请求本地服务器,本地服务器帮助我们请求远端服务器,这样,开发环境跨域问题就解决了。
'api':{
target:'http://localhost:3000',
pathRewhite:{
'^/api':''
}
}
}
}
optimazation
以下全部都是默认值,可以不写。
optimazation:{
splitChunks:'all',
minSize:30*1024,//分割的chunk最小为30kb
maxSize:0,//最大没有限制
minChunks:1,//要提取的chunk最少被引用1次
maxAsyncRequests:5,//按需加载时并行加载的文件的最大数量
maxInitialRequest:3,//入口js文件最大并行请求数量
automaticNameDelimiter:'~',//名称连接符
name:true,//可以使用命名规则
cacheGroup:{
//node_modules文件会被打包到vendors组的chunk中,并且满足上面的公共规则。
vendors:{
test:/[\\]node_modules[\\/]/,
priority:-10,//打包优先级为-10
},
default:{
minChunks:2,//要提取的chunk最少被引用2次,
priority:-20,
reuseExistingChunk:true,//如果当前要打包的模块,和之前以前被提取的模块是同一个,就会复用,而不是重新打包模块。
}
},
//将当前模块的记录其他模块的hash单独打包为一个文件runtime
//解决:修改a文件导致b文件的contenthash变化
runtimeChunk: {
name: entrypoint => `runtime~${entrypoint.name}`
},
minimizer:[
//配置生产环境的压缩方案:js和css
new TerserWebpackPlugin({
cache:true,//开启缓存
parallel:true,//开启多进程打包
sourceMap:true,//启动source-map
})
]
}
打包HTML资源
const resolve = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output:{
filename:'built.js',
path:resolve(__dirname,'build')
},
module:{
rules:[
//loader的配置
]
},
plugins:[
//plugins的配置
//功能:默认会创建一个空的html,自动引入打包输出的所有资源(js/css)
//需求:需要有结构的HTML文件
new HtmlWebpackPlugin({
//复制下面的文件,并自动引入打包输出的所有资源
template:'./src/index.html'
})
],
mode:'development'
}
打包图片资源
const resolve = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output:{
filename:'built.js',
path:resolve(__dirname,'build')
},
module:{
rules:[
//loader的配置
{
test:/\.less$/,
//使用多个loader使用use关键字
use:['style-loader','css-loader','less-loader']
},{
//处理图片资源
test:/\.(jpg|png|gif)/,
//使用一个loader只用loader关键字就可以
//使用url-loader需要下载url-loader和file-loader
loader:'url-loader',
option:{
//图片小于8kb,就会被base64处理
//优点:减少请求数量,减轻服务器压力;
//缺点:图片体积可能会更大,使文件请求速度更慢
limit: 8*1024,
//取Hash值的前10位和文件的原扩展名。
name:'[hash:10].[ext]
}
},{
test:/\.html$/,
//处理html文件的img图片(负责引入img,从而能被url-loader进行处理)
loader:'html-loader'
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
})
],
mode:'development'
}
以上配置中,由于url-loader不能处理html文件中引入的文件,所以我们又使用了一个html-loader.
打包其他资源
const resolve = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output:{
filename:'built.js',
path:resolve(__dirname,'build')
},
module:{
rules:[
//loader的配置
{
test:/\.less$/,
use:['style-loader','css-loader']
},{
//打包除了html/js/css资源以外的资源
exclude:/\.(css|js|html)$/,
//排除以上资源
loader:'file-loader',
options:{
name:'[hash:10].[ext]'
}
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
})
],
mode:'development'
}
devServer
作用:用来自动化(自动编译、自动打开浏览器、自动刷新浏览器)
const resolve = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output:{
filename:'built.js',
path:resolve(__dirname,'build')
},
module:{
rules:[
//loader的配置
{
test:/\.less$/,
use:['style-loader','css-loader']
},{
exclude:/\.(css|js|html)$/,
//排除以上资源
loader:'file-loader',
options:{
name:'[hash:10].[ext]'
}
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
})
],
mode:'development',
devServer:{
//特点:只会在内存中编译打包,不会有任何输出
//启动devServer指令为:npx webpack serve
//项目构建后的路径
contentBase:resolve(__dirname,'built'),
//启动gzip压缩
compress:true,
//端口号
port:8080,
//自动打开默认浏览器
open:true
}
}
完整配置
const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry:'./src/index.js',
output:{
filename:'js/built.js',
path:resolve(__dirname,'build')
},
module:{
rules:[
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader']
},{
test:/\.css$/,
use:['style-loader','css-loader']
},{
//处理图片资源
test:/\.(jpg|png|gif)$/,
loader:'url-loader',
option:{
limit:8*1024,
name:'[hash:10].[ext]',
//关闭es6模块化
esModule:false,
outputPath:'imgs'
}
},{
//处理html中的img资源
test:/\.html$/,
loader:'html-loader'
},{
//处理其他资源
exclude:/\.(html|js|css|jpg|png|gif)$/,
loader:'file-loader',
option:{
name:'[hash:10].[ext]',
outputPath:'assets'
}
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
})
],
devServer:{
contentBase:resolve(__dirname,'build'),
compress:true,
port:3000,
open:true
}
}
webpack优化配置
性能优化
开发环境性能优化:
- 优化打包构建速度
- HMR热模块替换
- 优化代码调试
- source-map:提供源代码到构建后代码映射技术
生产环境性能优化:
- 优化打包构建速度
- oneOf:文件只被处理一次,不进行多次匹配
- babel缓存:优化打包速度
- 多进程打包
- externals让某些库不打包,通过cdn引入
- dll让某些库只单独打包一次
- 优化代码运行的性能
- 缓存(hash-chunkhash-contenthash)
- tree shaking
- code split
- 懒加载/预加载
- pwa离线可访问技术
HMR:hot module replacement热模块替换
作用:一个模块发生变化,只会重新打包这一个模块,而不是打包所有模块,极大提升构建速度。
devServer:{
contentBase:resolve(__dirname,'build'),
compress:true,
port:3000,
open:true,
//开启HMR功能
hot:true
}
- 样式文件:可以使用HMR功能,因为style-loader内部实现了。
- js文件:默认不能使用HMR功能
- 解决方法:非入口js文件
if(module.hot){
module.hot.accept('./print.js',function(){
//do something
})
}
- html文件:默认不能使用HMR功能,同时会导致问题:html文件不能使用热更新了。(由于html文件只有一个,所以不需要做hmr功能)
- 解决方法:修改entry入口,将html文件引入
entry:['./src/index.js','./src/index.html'],
source-map:提供源代码到构建后代码映射技术(如果构建后代码出错了,通过映射可以追踪源代码错误)
module.exports = {
devtool:'source-map'
//提示错误代码的准确信息;源代码错误位置
//source-map:外部
//inline-source-map:内联:只生成一个内联的source-map
//eval-source-map:每一个文件都生成对应的source-map文件,都在eval函数中
//cheap-module-source-map:外部,会将loader的source-map加入
//错误代码错误原因,构建后代码的错误位置
//hidden-source-map:外部
//错误代码准确信息,无任何源代码
//nosource-source-map:外部
//错误代码位置精确到行
//cheap-source-map:外部
//内部和外部的区别:外部会单独生成文件,内联没有;内联构建速度更快
}
- 生产环境:源代码要不要隐藏(nosource>hidden),调试要不要更友好(source>cheap-module),由于内联的代码体积会变得很大,所以生产环境一般不会用内联的方案)
- 开发环境:速度快(eval>inline>cheap),调试更友好(source>cheap-module>cheap)
最优选择:eval-source-map
oneOf:优化生产环境配置
由于rules里的规则对每一个文件都会挨个匹配一遍,但是很多文件可能只需要用一个Loader匹配,这时候就可以使用oneOf,意思是只匹配一次。
缓存
- babel缓存:让第二次打包构建速度更快
module.exports = {
rules:[{
test:/\.js$/,
exclude:/node-modules/,
loader:'babel-loader',
options:{
presets:[],
//开启babel缓存。第二次构建时,会读取之前的缓存。
cacheDirectory:true
}
}]
}
-
文件资源缓存:让代码上线运行缓存更好使用
- hash:文件名+hash值,每次构建打包都会生成唯一的hash值。
- chunkhash:根据chunk生成hash值,如果打包来源于同一个chunk,那么hash值就一样。
- contenthashs:根据文件的内容生成hash值,不同的文件Hash值一定不一样。
- tree shaking:让代码体积更小,去掉无用代码
- 前提:使用es6模块化;开启production
- 副作用:可能会把css等文件干掉。解决方法:package.json中添加
'sideEffects':["*.css"]//值为false时意味着所有代码都没有副作用
文件拆分code split
按需加载、并行加载提高速度
module.exports = {
entry:{
//多入口:有一个入口,最终输出就有一个Bundle
main:'./src/js/index.js',
test:'./src/js/test.js'
},
output:{
filename:'js/[name].[contenthash:10].js',
path:resolve(__dirname,'build')
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
minify:{
collapseWhitespace:true,
removeComments:true
}
})
],
mode:'production'
}
module.exports = {
entry:{
//多入口:有一个入口,最终输出就有一个Bundle
main:'./src/js/index.js',
test:'./src/js/test.js'
},
output:{
filename:'js/[name].[contenthash:10].js',
path:resolve(__dirname,'build')
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
minify:{
collapseWhitespace:true,
removeComments:true
}
})
],
//1.可以将node_modules中的代码单独打包一个chunk最终输出
//2.自动分析多入口文件中,是否有公共文件,如果有,会单独打包一个chunk
optimization:{
splitChunks:{
chunks:'all'
}
},
mode:'production'
}
通过js代码让某个文件被单独打包成一个chunk
import动态导入语法:能将某个文件单独打包
//注释用来给打包出来的文件命名
import(/*webpackChunkName:'test'*/'./index')
.then(res=>{
console.log(res)
}).catch((err)=>{
console.log(err)
})
module.exports = {
entry:'./src/js/index.js',
output:{
filename:'js/[name].[contenthash:10].js',
path:resolve(__dirname,'build')
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
minify:{
collapseWhitespace:true,
removeComments:true
}
})
],
//1.可以将node_modules中的代码单独打包一个chunk最终输出
//2.自动分析多入口文件中,是否有公共文件,如果有,会单独打包一个chunk
optimization:{
splitChunks:{
chunks:'all'
}
},
mode:'production'
}
懒加载
比如按钮点击时,才开始加载对应的文件里的方法
import(/*webpackChunkName:'test' */'./index')
.then((add)=>{
add(1,2)
}).catch((err)=>{
console.log(err)
})
预加载
会在使用前就加载完成文件。
正常加载可以认为是并行加载,同一时间加载多个文件,且没有先后顺序。
预加载是等其他资源加载完毕,浏览器空闲了,再偷偷加载资源。(兼容性较差)
import(/*webpackChunkName:'test',webpackPrefetch:true */'./index')
.then((add)=>{
add(1,2)
}).catch((err)=>{
console.log(err)
})
PWA渐进式网络开发应用程序(离线可访问)
使用workbox-webpack-plugin
plugins:[
new WorkboxWebpackPlugin.GenerationSW({
/*
1.帮助serviceworker快速启动
2.删除旧的serviceworker
*/
clientsClaim:true,
skipWaiting:true
})
],
js文件中,处理兼容性问题,注册service-worker
if('serviceWorker' in navigator){
window.addEventListener('load',()=>{
navigator.serviceWorker.register('/service-worker.js')
.then(()=>{
console.log('sw注册成功了')
})
.catch(()=>{
console.log('注册失败')
})
})
}
多进程打包
tread-loader开启多进程打包。
由于进程启动的时间开销和进程通信的开销,只有工作消耗时间长,才需要多进程打包。
externals
防止将某些包打包进去,比如有些包我们希望它使用cdn链接,就可以把这些包排除出去。
module.exports = {
mode:'production',
externals:{
//拒绝Jquery被打包进来
jquery:'Jquery'
}
}
动态打包库DLL
对一些三方库进行单独打包,以下文件命名为webpack.dll.js,用于将jquery进行单独打包,这样之后jquery就不需要重复打包,从而提升打包速度。
module.exports = {
entry:{
jquery:['Jquery']
},
output:{
filename:'[name].js',
path:resolve(__dirname,'dll'),
library:'[name]_[hash]',//打包的库里向外暴露出去的内容叫什么名字
},
plugins:[
new webpack.Dllplugin({
name:'[name]_[hash]',//映射库的暴露的内容名称
path:resolve(__dirname,'dll/mainfest.json'),//输出文件路径
})
],
mode:'production'
}
$webpack --config webpack.dll.js
运行上面的指令,单独打包jquery,然后在webpack.config.js文件中加入以下插件:
plugins: [
//高速webpack哪些库不需要参与打包,同时使用时名称也得变
new webpack.DllReferencePlugin({
mainfest: resolve(__dirname, 'dll/mainfest.json')
}),
//将某个文件打包输出去,并在html中自动引入该资源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
]