一、概念
在讲webpack之前,先了解下构建工具的概念:
在正常开发中,我们可能会用到css预处理器来处理页面样式,也可能在js文件中使用最新的一些语法(ES6+),这些语法都是浏览器不能直接识别的,所以都需要借助小工具来进行编译和转换,但是这个多工具管理起来就会相当的麻烦,所以后来就有了构建工具这么一说,所谓的构建工具就可以看成是以上这些小工具的汇总,他包含了这些小工具的所有功能集于一身,后续我们只需要管理好一个工具就可以了,这样对于开发者来说就会简单方便很多;
webpack 就是一个前端资源构建工具,是现代 JavaScript 应用程序的静态模块打包器(module bundler),当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle(捆)。
webpack本身是基于node开发的;像我们打包时要进行各种文件依赖关系的处理,要找到各种文件,这就是通过node中的fs模块来操作的了;
从 webpack v4.0.0 开始,可以不用引入一个配置文件。然而,webpack 仍然还是高度可配置的。
二、安装
为了防止全局安装出现的版本冲突,我们一般都把webpack安装到本地项目中;
cnpm i webpack webpack-cli --save-dev 或者 yard add webpack webpack-cli -D
三、五个核心概念
- Entry
入口指示webpack以哪个文件为入口起点,然后从他开始分析内部的依赖关系 - Output
定义打包输出后bundles放到哪里?如何命名? - Loader
webpack只能识别js代码,对于那些css、img等都识别不了,所以只能借助loader翻译成webpack能看懂的 - Plugins
插件可以用于执行那些范围更广,功能更强大的任务,插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量 - Mode
四、webpack初体验
4.1 运行指令
第一步:新建一个index.js和data.json文件
------------------以下为index.js---------------------------
import data from './data.json' //引入data.json文件
console.log(data)
function add (a,b){
return a+b
}
console.log(add(2,3));
------------------以下为data.json---------------------------
{
name:"haha",
age:12,
}
第二步:执行运行指令:
- 在开发环境下执行命令:
webpack ./index.js -o ./build/built.js --mode=development
上述代码意思就是webpack会把当前路径下的index.js打包到当前路径build文件夹下,生产的新打包后的文件名叫built.js ,整体打包模式是开发模式
- 生产环境下执行命令
webpack ./index.js -o ./build/built.js --mode=production
上述代码意思就是webpack会把当前路径下的index.js打包到当前路径build文件夹下,生产的新打包后的文件名叫built.js ,整体打包模式是生产模式
第三步 index.html 文件 中引入了打包后的built.js文件,浏览器打开页面可以看到输出5和{name:“haha”,age:12}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webpack初体验</title>
</head>
<body>
<script src="./build/built.js"></script>
</body>
</html>
结论:
1、webpack能处理js/json文件,不能处理css/img等其他资源
2、生产环境比开发环境多一个压缩代码
3、生产环境和开发环境将ES6模块编译化成浏览器能识别的模块化(import data from './data.json"这句是es6的语法)
4.2、打包样式资源
1、新建一个less格式的样式文件以及一个引入该样式文件的一个js文件
// index.less
html,body{
margin: 0;
padding: 0;
background-color: pink;
}
----------------------------------------
// index.js
import './index.less'
console.log(12313)
2、然后新建一个webpack.config.js配置文件(因为loader要配合配置文件才能正常使用)
注意:这个配置文件就是用来指示webpack要怎么干活,当你运行webpack指令时,会自动加载里面的配置;
所有构建工具都是基于nodejs平台运行的,所以遵循的都是Commonjs模块规范
const {resolve} =require("path")
module.exports={
/*入口文件*/
entry:'./src/index.js',
/*输出文件*/
output:{
filename:"built.js",
path:resolve(__dirname,"build")
},
/*loader配置*/
module:{
rules:[
{
test:/\.css$/, //匹配css文件
use:[ //use数组中执行顺序是从下到上
/*创建style标签,将index.js中的样式资源插入到style,然后将整个style标签添加到head中生效*/
"style-loader",
/* 将css文件变成commonjs模块,加载到index.js中,里面内容是样式字符串*/
"css-loader"
]
},
{
test:/\.less$/, //匹配less文件
use:[
"style-loader",
"css-loader",
"less-loader", //这里需要注意下要下载less-loader和less两个文件
]
}
]
},
/*插件配置*/
plugins:[],
/*模式配置*/
mode:“development”
}
2、下载上述用到的3个loader和一个less文件(style-loader、css-loader、less-loader、less)
npm i style-loader css-loader less-loader less -D
3、找到index.js的文件路径,执行webpack即可(它会根据配置文件中的入口文件去打包)
4、最终执行webpack打包完,会在build文件下生成一个built.js文件,其中原先的css文件变成commonjs模块,加载到index.js中,在buildt.js文件中能找到这个样式文件模块的字符内容
5、为了看样式效果是否生效,我们再新建一个html文件,将打包完的js文件引入看下效果
4.3、打包html资源
1、创建一个html文件和index.js
// index.html文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>hello webpack</h1>
</body>
</html>
----------------------------------------
// index.js
function add(a,b){
return a+b;
}
console.log(add(2,3));
2、然后写webpack配置文件
const {resolve} =require("path")
const HtmlWebpackPlugin = require('html-webpack-plugin') //插件使用前先引入
module.exports={
entry:"./index.js",
output:{
filename:"built.js",
path:resolve(__dirname,"build")
},
module:{},
plugins:[
new HtmlWebpackPlugin({
template:'./index.html' //复制已有的一个自建html文件格式
})
],
mode:"development" //开发模式
}
注意: loader使用方式是下载、使用,但plugin不同的一点就是使用前还要再加一步引入
3、最终打包完的结果就是再build文件夹下生成一个built.js和一个index.html文件,这个html文件中的结构和src下自建的那个html一样,只不过就多了一个script标签,这个script标签自动引入了打包生成后的built.js文件
4.4、打包图片资源
4.4.1、打包样式文件中引用的图片资源
1、要想打包样式文件中引用的图片资源,则需要用到两个loader(url-loader、file-loader),那么首先就是下载这两个loader文件
npm i url-loader file-loader -D
2、新建一个项目,项目中新建一个index.html、index.js、index.less、以及imgs文件夹(用于放图片资源)以及一个webpack.config.js
//index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>样式图片资源</title>
</head>
<body>
<div id="box"></div>
</body>
</html>
------------------------------------
//index.js
import './index.less'
-------------------------------------
//index.less
#div{
height: 400px;
width: 400px;
background-image: url('./img/2.jpg');
background-repeat: no-repeat;
background-size: 100% 100%;
}
-------------------------------------
//webpack.config.js
const {resolve} =require('path')
const HtmlWebpackPlugin =require('html-webpack-plugin')
module.exports ={
entry:"./index.js",
output:{
filename:'built.js',
path:resolve(__dirname,"build")
},
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader']
},
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader']
},
{
test:/\.jpg$/,
loader:'url-loader', //这里只需要写url-loader即可,但还需要下载一个file-loader,因为url-loader依赖于file-loader
options:{
limit:10*1024 //当 图片大小小于10MB时就会被base64处理,减少请求次数,但增大的文件大小,有利有弊
}
}
],
},
plugins:[
new HtmlWebpackPlugin({
template:'./index.html'
})
],
mode:"development",
}
注意:再module中如果只需要一个loader的话,则test下面就写loader:,如果有多个loader作用,则需要写成use:
4.4.2、打包html文件中的图片资源
1、这里需要用到另外一个loader(html-loader),他专门是负责引入html中的图片资源的,便于能被url-loader进行处理;
npm i html-loader -D
2、这里只写相关代码了:(增加一个loader处理对象)
....
{
test:/\.html$/,
loader:'html-loader'
}
....
3、如果此时执行webpack还显示不了,观察下打包后的html文件中img src的地址是什么?如果是[ object module ],说明还是有问题,这是为什么呢?
注意:因为,url-loader默认使用es6 模块化解析,而html-loader引入图片是CommonJs,所以要想解决这个问题,可以把url-loader中的esModule关闭,直接在loader:'url-loader’下面加上esModule:false即可
补充:如果此时觉得打包后的图片文件名太长,不美观,想要重新定义文件名长度,则可以在url-loader的options中添加name:’[hash:10.[ext]]’,这表示取hash值的前10位作为图片文件名,图片的后缀格式还是保持图片原先的
4.5、打包其他资源
注意:打包其他资源时这里使用排除的方式
4.6、devServer
正常情况下,文件内容发生改变后,需要重新打包生成新的打包后的文件,才能看到最新的效果,这样效率有点低,不便于开发,所以出现了devServer(开发服务器)帮助我们做一些自动编译、自动打开浏览器并自动刷新页面的工作
下载:npm i webpack-dev-server -D
启动devServer:npx webpack-dev-server
五、开发环境的基本配置
const {resolve} = require('path')
const HtmlWebpackPlugin =require('html-webpack-plugin')
module.exports={
entry:"./src/index.js",
output: {
path: resolve(__dirname,"build"),
filename: "built.js"
},
module: {
rules:[
{
test:/\.css$/,
use:[
"style-loader",
"css-loader"
]
},
{
test:/\.jpg$/,
use:[
"url-loader"
]
},
{
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: "file-loader",
options:{
name:'[hash:10].[ext]'
}
}
]
},
plugins:[
new HtmlWebpackPlugin(
{
template:"./src/index.html"
}
)
],
mode:'development',
devServer: {
contentBase:resolve(__dirname,"build"),
compress:true,
port:9000,
open:true,
}
}
六、提取css文件成单独文件
这里要用到一个插件:mini-css-extract-plugin
- 下载:
npm i mini-css-extract-plugin -D
- 配置:
const {resolve} = require('path')
const HtmlWebpackPlugin =require('html-webpack-plugin')
const MiniCssExtractPlugin =require('mini-css-extract-plugin');
module.exports={
entry:"./src/index.js",
output: {
path: resolve(__dirname,"build"),
filename: "built.js"
},
module: {
rules:[
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, //这个对象自带loader取代原先的style-loader,作用就是从js中提取css文件
"css-loader"
]
}
]
},
plugins:[
new HtmlWebpackPlugin(
{
template:"./src/index.html"
}
),
new MiniCssExtractPlugin(
filename:'css/built.css' //对输出文件进行路径和名称自定义
)
],
mode:'development',
devServer: {
contentBase:resolve(__dirname,"build"),
compress:true,
port:9000,
open:true,
}
}
注意:这个插件使用上有所不同:1、插件初始化配置;2、需要用自带loader取代srtle-loader,作用就是从js文件中提取css文件
七、css兼容性处理
需要用到postcss-loader、postcss-preset-env
下载:npm i postcss-loader postcss-preset-env -D
除此之外还需要哎package.json中配置browserslist属性
注意: 这里要注意默认是走生产环境的兼容配置,如果你要走开发环境的兼容配置的话,则还需要在配置文件中设置node的环境变量,即:
process.env.NODE_ENV=“development”
八、压缩css
需要用到optimize-css-assets-webpack-plugin插件
下载:npm i optimize-css-assets-webpack-plugin -D
配置如下:
const optimizeCssAssetsWebpackPlugin =require("optimize-css-assets-webpack-plugin")
plugins:[
new optimizeCssAssetsWebpackPlugin()
],
九、js语法检查
这个需要用到eslint-loader和eslint
- 下载:
npm i eslint-loader eslint -D
- 配置:
rules:[
{
test: /\.js$/,
exclude: /node_modules,
loader:"eslint-loader",
options:{}
}
]
但是这样配置了以后,它还不知道要以什么样的规则去检查代码,所以还需要设置检查规则,这里推荐使用airbnb规则,这个规则需要用到3个库
eslint-config-airbnb-base、eslint-plugin-import 、eslint
所以此时还需要下载2个库(eslint前面已经下过了)
- 下载airbnb相关的库文件:
npm i eslint-config-airbnb-base eslint-plugin-import -D
- 在package.json中配置
//增加一个配置对象"eslintConfig"
"eslintConfig":{
"extends":"ai'rbnb-base"
}
十、js兼容性处理
要用到babel-loader、@babel/core(都需先下载)
方案一:通过@babel/preset-env 实现
//webpack.config.js文件中的loader配置
rules:[
{
test:/\.js$/,
exclude:/node_modules/,
loader:"babel-loader",
options:{
presets:["@babel/preset-env"]
}
}
]
**问题:**它只能转换兼容一些基本的语法,比如箭头函数转成function形式等,不能处理兼容想promise等高级语法
方案二:全部js兼容处理(@babel/polyfill)
首先还是要下载该库npm i @babel/polyfill -D
使用方式:直接挂在js文件中引入即可import "@babel/polyfill"
**问题:**将所有的兼容代码都引入了,代码体积很大
方案三:按需加载(兼容):core-js
下载core-js: npm i core-js -D
然后改造下配置文件,具体如下:
十一、js压缩
这个很简单,只需将开发环境改为生产环境即可:mode:”production“
十二、html压缩
在原先的html插件配置中进行修改配置,增加一个minify属性,具体见下:
十三、性能优化配置
- 开发环境性能优化:①主要是优化构建速度;②优化代码调试
- 生产环境性能优化:①主要是优化构建速度;②优化代码运行性能
13.1、开发环境优化-HMR
概念:hot module replacement 热模块替换/模块热替换
作用:一个模块发生变化,只会重新打包这个模块,极大提高构建速度
如何使用:在devServer配置中添加一个hot:true,加了这句代码意思就是开启了HMR功能,但是不同文件对改功能实现不同,我们分别来看。。
注意:修改了配置文件,一定要重启,不然不生效
- 样式文件: 此时修改样式文件时它能做到样式模块的局部更新,能实现HMR功能,但一定要使用style-loader,这个loader中已经内部实现了
- js文件:默认不能使用HMR功能,需要增加代码来支持
注意:HMR功能对js文件的处理时,只能处理非入口文件,因为入口文件引入了所有资源,它一变,所有文件都会重新加载,这是没办法避免的
- html文件:默认不能使用HMR功能,同时会导致一个问题,html文件不能更新了,此时需要修改入口文件,将html文件引入
entry:[ './src/index' ,'./src/index.html']
,html正常不用做HMR功能,因为它只有一个文件,没有其他文件
13.2、开发环境优化-source-map
概念:一种提供源代码到构建后代码的映射 技术(如果构建后代码出错了,通过映射可以追踪到源代码错误)
总结:根据不同的环境,选择不同的类型,开发环境可以使用eval-source-map,生产环境用source-map
13.3、生产环境优化-oneOf
产生背景:配置文件中的所有loader在匹配的过程中都会从上往下匹配执行一遍,为了提高效率,将所有loader可以用oneOf包裹,这样,这里面的loader对于同一种文件类型只会匹配一个,所以这也是它使用过程中的注意点,不能有两个loader处理同一种类型文件
rules:[
{
oneOf:[
{
loader1....
},
{
loader2....
}
]
}
]
那么有一个问题,如果我确实有两个loader要处理同一种类型文件怎么办?
rules:[
{
loader3....
},
----------------------可以将loader单独提取到oneOf外面---------------------------------
{
oneOf:[
{
loader1....
},
{
loader2....
}
]
}
]
13.4、生产环境优化-缓存
背景:像开发环境基于dev-server,有HMR可以实现单一文件修改后局部更新,但是生产环境不用dev-server,所以遇到这种情况下为了更快更高效的构建,所以需要用到缓存
这里提到的缓存主要是两种:babel缓存、文件资源缓存
- babel缓存
{
test: /\.js$/,
exclude:/.node_modules/,
loader:"babel-loader",
options:{
cacheDirectory:true //开启babel缓存,第二次构建的时候会读取缓存
}
}
- 文件资源缓存
这个文件资源缓存通常是服务器后端设置了缓存的时间,前端第一次请求时会缓存起来,第二次请求页面时如果在强缓存期间就不会发请求,而是直接读取缓存了,但是这有个问题就是如果我此时修改了某个文件,刷新不会生效。。。因为走了缓存,那怎么办?
方案一:可以在配置文件中给打包后文件名添加构建时生产的唯一hash值,这样每次构建时hash值都会变化,文件名也就变了,也就不会走缓存了
output:{
filename:'js/built.[hash:10].js', //去hash值得前10位
}
问题:这样一来,如果某些文件没改动,但也加了这个hash值,就也会跟着重新请求,其他没变的文件缓存也失效了,那要怎么办呢?
方案二:不用hash值,用chunkhash,这个chunkhash会根据chunk生产的hash,如果打包来源于同一个chunk,那么hash值一样,这个方案貌似还是不行,因为有些像css文件,构建时会被引入到js文件中,他们属于同一个chunk,所以还是会同时变化。。。
方案三:contenthash:根据文件内容生产的hash值,不同文件,hash值一定不一样,这个就可以了
13.5、生产环境优化-tree shaking(树摇)
作用:为了去除无用代码,减少代码体积
前提:1、必须使用es6模块化;2、必须开启production生产模式,只要满足上述两个条件就会自动帮你树摇了
问题:但有些不同的版本中,可能会把你引入的css文件当成无用文件删除了,所以最好你在package.json中配置下'sideEffects':["*.css","*.less"]
,写了这个,这个数组中的文件就不会被tree shaking
13.6、生产环境优化-code-split(代码分割)
背景:像之前一个入口js引入了另外的js,那么构建后会自动耦合成一个js文件,但如果我想要让其单独打包,便于我后续按需加载或者其他操作,那要如何操作呢?
- 方式一:将单一入口文件改为多入口文件方式
entry:{ //这样就会输出两个bundle文件
main:'./src/js/index.js',
test:'./src/js/test.js'
}
- 方式二:在配置文件中增加一个optimization属性,具体如下:
plugins:[....],
// 和plugins同级的属性配置
optimization:{
splitChunks:{
chunks:"all"
}
}
这个配置的作用:
1、它会把node_modules中的第三方包文件单独打包成一个chunk输出 ;
2、遇到多入口时,它会自动分析多入口chunk中有没有公共的文件,如果有会打包成单一chunk,比如上面的多入口例子main.js引入了jquery,test.js中也引入了jquery,那么最终只会输出3个文件(main.js 、test.js、jquery.js),而不是4个
13.7、生产环境优化-js文件的懒加载和预加载
13.8、生产环境优化-PWA
概念:渐进式网络开发应用程序 ,可以让你在离线状态下访问页面
如何使用:
- 先在webpack.config.js文件中下载一个插件(workbox-webpack-plugin),然后配置
const WorkWebpackPlugin =require('workbox-webpack-plugin')
new WorkWebpackPlugin (
clientsClaim:true,
skipWaiting:true
)
//这两个配置有什么用呢?
1、帮助serviceworker快速启动
2、删除旧的serviceworker,生成一个serviceworker的配置文件
- 在入口js文件中注册serviceworker
3、 这样写后会有问题,如果你在配置文件中配了eslint的话,此时eslint会报错,因为eslint不认识window、navigator等全局变量,所以还需要在packsge.json中修改eslintConfig配置
"env":{
"browser":true //支持浏览器端全局变量
}
4、由于serviceworker代码必须运行在服务器端,所以必须新建一个服务器,这里我们用serve库来创建服务器,先下载npm i serve -g
,然后执行serve -s build
启动服务器,这样它就会把build文件下的所有资源作为静态资源暴露出去,然后打开服务启动后的地址就可以看到效果了
5、如果成功了,可以在页面的Application中看到注册的service-worker资源,以及Cache中缓存的东西
13.9、生产环境优化-多进程打包
下载一个包:npm i thread-loader -D
一般和babel-loader一同使用,因为项目中一般是js代码最多,所以在处理js代码时会比较慢
使用方式:
{
test: /\.js$/,
exclude:/node_modules/,
use:[
"thread-loader", //就是和babel-loader放一起,这里就要改为[]
{
loader:'babel-loader',
options{
.....
}
}
]
}
//注意:这个开启多线程打包也是需要消耗时间的,只有真的打包时间很久时采用多线程打包,不然比以前没用时还慢下·
13.10、生产环境优化-externals
作用:是防止一些包被打包输出,也就是你可以自定义哪些包你不要打包的,但是要注意,这些包因为没被打包,所以需要你在html中手动引入cdn的一些链接
13.11、生产环境优化-dll
- 新建一个webpack.dll.js文件,下面是文件内容,就是把需要单独打包的文件在这里声明输出,下面的例子就是单独打包jquery
2、新建完这个文件后,执行webpack --config webpack.dll.js
这句代码的意思就是执行.dll这个配置文件(默认是webpack.config.js)执行后输出两个文件,一个jquery.js,还有一个manifest.json,这是单独打包的文件的一种映射关系
3、然后再webpack.package.js配置文件中告诉webpack哪些包不用打包了,通过manifest告诉webpack,因为它存有映射关系
注意:dll与externals的区别:dll是需要打包的,只不过打包一次,后续就不需要打包了,但externals是不打包的,通过cdn链接引入的
十四、webpack详细配置
14.1、entry
14.2、output
常用的配置如下:
14.3、module
14.4、resolve
14.5、devServer
devServer:{
//运行代码的目录
contentBase:resolve(__dirname,"build"),
//监视contentbase目录下的所有文件,一旦改变,就会reload
watchContentBase:resolve(__dirname,"build"),
//忽略文件
watchOptions:{
ignored:/node_modules/
}
//启动压缩
compress:true,
//端口号
port:5000,
//域名
host: "localhost",
//自动打开浏览器
open:true,
//开启HMR功能
hot:true
//不要显示启动服务器日志信息
clientLogLevel:"none"
//除了一些基本信息外,其他都不要显示
quiet:true,
//如果出错了,不要全屏提示
overlay:false,
//代理
proxy:{
"/api":{
"target":"http://localhost:3000", //一旦devServer(5000)服务器接收到/api请求时就会把请求转发给另外一个服务器(3000)
"pathRewrite":{ //发送请求时,请求路径重写,将/api/xxx=>/xxx,去掉前面的前缀
'^/api':''
}
}
}
}