「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。
一.webpack的使用
-
初始化项目:yarn init -yes
-
安装webpack核心模块和cli模块
yarn add webpack webpack-cli --dev -
安装完成后,node_modules下的bin目录就有一个webpack-cli
-
那么就可以通过yarn这个命令快速去找到并运行它
-
yarn webpack --version
-
yarn webpack 这样webpack就会从src目录下的index开始打包
-
会生成一个dist的目录文件,这个时候,就可以改一下src中的html文件script
-
可以在package.json中添加一个script
"scripts":{
"build":"webpack"
}
- 这样就能直接进行yarn build了
二. webpack配置文件
- webpack4.0之后,支持0配置直接打包
- 但如果需要具体的配置,需要建立一个webpack.config.js文件
const path = require('path')
//这个文件会导出一个对象,属性就是配置选项
module.exports = {
// 输入文件的位置
entry:'./src/main.js',//如果是相对路径,那么./不能省略
// 输出文件的位置
output:{
filename:'bundle.js',//指定打包后的文件名字
path:path.join(__dirname, 'output')//指定输出的路径
},
mode:'development'//指定打包的模式,一共有三种模式
}
-
webpack默认会使用prod的模式去工作,会压缩代码
-
yarn webpack --mode development//启用开发模式
开发模式会优化打包的速度和一些代码调试 -
yarn webpack --node none//none模式只会进行原始的打包,不会进行任何的处理
三. webpack打包结果运行原理
-
先使用yarn webpack --mode none进行原始打包
-
查看dist目录下的main.js代码
- 这是一个立即执行函数,这个函数是webpack的工作入口
- 接受一个参数modules
- 调用的时候传入一个数组,指最后两行。
-
展开参数数组
数组里面是两个参数列表相同的函数
展开函数,对应源代码里面的模块,也就是说,每个模块都会被包裹到这个函数当中,从而去实现模块的私有作用域 -
再展开webpakc工作入口的函数
- 里面的内容并不复杂,且都有注释
- 首先第一个是一个对象,用来缓存加载过的模块
- 然后是一个require function,顾名思义,就是用来加载模块的
- 再往后,就是在require 函数上挂载一些其他的数据和一些工具函数,这些并不重要
-
- 拉到最后
这个函数调用了webapck_require函数,并传入了一个参数,0。开始去加载我们的模块,
这里的模块id就是加载模块数组中的元素下标。其实在这里才开始加载源代码中的所谓入口模块。 - 为了更好的理解这个过程,可以先运行起来,然后通过浏览器的单目调试一下
- 拉到最后
-
- 先找到source面板下的bundle.js文件
- 在开始的地方打上断点(第三行),然后刷新页面,开始调试
- 先找到source面板下的bundle.js文件
-
- 那个这个函数参数对应的就是加载的两个模块
- 对于不重要的环节直接往下点,直到return
- 那个这个函数参数对应的就是加载的两个模块
-
- 进入require函数内部,先判断有没有加载过,加载过就从缓存里面读,没有的话就创建一个新的对象。
- 创建完成之后,去调用了这个模块所创建的函数。把我们刚刚创建的模块对象,还有导出成员对象,还有require函数,给他传入进去。这样的话就可以在模块内部去使用module.export去导出成员,通过webpakc require去载入模块了
- 进入require函数内部,先判断有没有加载过,加载过就从缓存里面读,没有的话就创建一个新的对象。
-
- 点进去,可以看到首先调用了一个r函数。它的内部作用十分简单,就是给导出对象添加一个标记,进去看一下
- 进去之后呢,其实就是通过obejct.defineProperty定义了一个标记__esModule,定义完了之后呢。导出对象就有一个这样的标记。用来对外界表明这是一个esmodule
- 点进去,可以看到首先调用了一个r函数。它的内部作用十分简单,就是给导出对象添加一个标记,进去看一下
-
-
从这函数里出来,继续往下走,就会发现,又调用了require函数,但此时的id是1,说明用来去加载第一个模块
-
这个模块实际上就是我们定义的第一个模块,head。然后就以相同的步骤去执行head模块,最后把head模块导出的整体对象,通过require函数,把它return回去。module.export通常moduel里面的export是一个对象,默认的导出是放在default上面。然后我们把这个对象的访问函数拿到,去访问里面的default.
-
然后去调用default函数,然后就去调用内部模块的代码
-
总结:webpack打包后的代码并不会特别复杂,它只是把所有的模块都放在一个文件里面。除了放置基础的代码,它还可以保持模块间原本的相互依赖关系
-
四. webpack资源加载
- webpack内部默认只会去处理JavaScript文件\
- 那么就需要为其他类型的文件,添加不同的loader\
- 安装css-loader,yarn add css-loader --dev
- 安装完成后,需要添加对应的配置
//修改入口地址为css
entry:'./src/main.css
// 具体配置是在module属性当中,去添加一个rules数组
module:{
// rules就是针对其他加载资源模块的一个规则配置
// 每个规则对象都有两个属性
rules:[
{
// 一个是test属性,是一个正则表达式
// 用来匹配打包过程中的文件路径
test:/.css$/,
// use属性,用来指定匹配到的文件使用的loader
use:'css-loader'
}
]
}
- css-loader就是将我们的css文件,转换成一个模块。具体实现就是将这样一个css代码,push到数组当中。这个数组是由css-loader内部定义的。但整个文件并没有地址去用到这个数组,所以还需要一个style-loader
- yarn add style-loader --dev//把css-loader转换的结果,通过style标签追加到页面上
module:{
rules:[
{
test:/.css$/,
// 这里需要把use改成一个数组
// 如果配置了多个loader,它是从后往前去执行
// 所以一定要将css-loader放在最后
use:[
'style-loader',
'css-loader'
]
}
]
}
-
打包编译完成后,可以看到多了两个模块
-
其中在最后一个模块的insert函数当中,通过style的形式,把样式代码,挂载到了页面当中。
-
loader是webpack前端模块化的核心,通过loader就可以去加载任何雷子的资源
1.导入资源模块
- 通常还是把js文件作为打包的入口,通过在js文件中import的方式,这样的话,css-loader 仍然可以工作
- 将配置文件中的入口地址改回main.js
const path = require('path')
module.exports = {
entry:'./src/main.js',
output:{
filename:'bundle.js',
path:path.join(__dirname,'dist')
},
module:{
rules:[
test:/.css$/,
use:[
'style-loader',
'css-loader'
]
]
}
}
- 然后在main.js中去导入css
// 因为只需要去执行,所以直接导入即可
import './main.css'
- 完成后去运行打包命令
- webpack建议我们去动态的引入当前代码需要的任何文件,因为真正需要资源的不是应用,而是现在正编写的代码。是这里的代码,需要正常工作,就必须加载对应的资源。这就是webpack的哲学。
- 小的来看,是完成了业务需求,大的来看,驱动了整个前端应用
- 学习一个新事物,并不是学会它的用法就结束。学会它的思想,这才是重点和突破点。能够搞明白它为什么要这样设计,那么就算出道了。
2.文件资源加载器
-
大多数loader都是将资源文件转化为js的方式去工作
-
但还有一些经常用到但文件,比如图片或者字体。这些文件是没有办法通过js去表示的
-
对于这一类的文件,就需要用到文件资源加载器,file-loader
-
先添加一个图片文件,在main.js中导入
import icon from './icon.png'
- 这里需要接受一个这个资源文件的默认导出,因为导出成员就是打包过后的资源路径,有了这个路径之后,我们就可以创建一个image对象
const img = new Image()
img.src = icon
// 最后再将这个元素append到body当中
document.body.append(img)
- 安装对于的loader,yarn add file-loader --dev
- 在为png文件添加相应的配置,那么webpack在打包时就会以file-loader去处理png文件了
{
test:/.png$/,
use:'file-loader'
}
-
运行打包命令,可以看到dist目录下会多一个图片文件,不过文件名称发现了改变
-
看看图片文件在bundle.js是如何体现的,打开bundle文件,找到最后一个模块,因为处理图片是最后加载的,所以一般就是最后一个。代码非常简单,就是把刚刚生成的名称,导出出去了
-
在第一个加载模块当中,就是去使用了这个文件路径,并没有复杂的地方
-
添加一个publicPath路径,去表示网站资源加载的路径
publicPath:''//表示资源放在根目录下
publicPath:'dist/'//表示资源放置dist目录下,/不能省略
- 总结:根据配置文件中的配置,匹配到文件加载器。先是将导出的文件,拷贝到输出的目录。再将文件拷贝到输出目录的路径,作为当前模块的返回值返回。
3.URL加载器
-
除了可以用file-loader去解析文件,还能用dataURLs去表示文件
-
dataURLs是一种特殊的url,可以表示文件
-
传统的url需要服务器上有一个这样的文件,然后请求这个地址,获得这个文件
-
然而dataURLs是当前url就可以表示文件内容的方式。这种url的文本,就已经包含文件的内容。我们在使用这种url的时候,就不会去发送任何的http请求。
- 比如这一段url,浏览器就能解析出这是html的内容,类型是UTF-8,内容是一串文本。
data:text/html;charset=UTF-8,<h1>html content</h1>
- 所以,如果是图片或者字体这种无法通过文本表示的二进制文件。可以将文件的内容进行base64编码。一base64编码后的结果,就是一串字符串,来表示文件的内容。\
data:image/png;base64,iVBORw0KGg0AAAAANSUhE...SuQmCC
-
base64编码可能会比较长,但浏览器同样能解析出文件
-
所以,我们就能以dataURLs的方式表示任何类型的文件了
-
安装url-loader,yarn add url-loader --dev
-
打开配置文件,找到之前的file-loader,进行替换
{
test:/.png$/,
use:'url-loader'
}
-
进行打包编译,这个时候,dist目录下就不会有图片文件了,同时bundle文件内也不是文件路径了,而是文件内容
-
这种比较适合体积比较小的资源,如果体积比较大,那么打包出来的文件也会比较大。那么会影响到我们的速度。
-
最佳实践:小文件直接转换成dataURLs,减少http请求的次数。大文件单独提取存放,提高加载速度。url-loader支持通过配置的方式去实现这种情况。
{
test:/.png$/,
use:{
loader:'url-loader',
options:{
// 因为这里的单位是字节,所以需要乘1024
// 这里就只会将小于10KB的文件进行dataURLs
// 大文件继续单独存放
// 注意的是,如果使用这种方式,还是要安装file-loader
// 因为use-loader会调用file-loader
limit:10 * 1024 //10kB
}
}
}
4.常用的加载器分类
-
编译转换类:把使用到的资源,转换成js代码
- css-loader
-
文件操作类:将资源模块拷贝到资源目录,同时输出访问路径
- file-loader
-
代码质量检查类:统一代码风格,提高代码质量
- eslint-loader
5.webpack与ES2015
-
虽然可以在webpack种使用import和export,但webpack并不是支持es6的新特性
-
因为模块需要,所以处理import和export
-
如果需要处理,那么就需要一个额外的编译型loader
-
yarn add babel-loader @babel/core @babel/preset-env --dev
-
修改配置文件
{
test:/.js$/,
use:{
loader:'bable-laoder',
options:{
presets:['@babel/preset-env']
}
}
}
6.webpack加载资源的方式
-
遵循ES6标准的import声明
-
遵循CommonJS标准的require函数
- 但CommonJS需要通过导出结果的default属性去获取
const creatHeading = require('./heading.js').default
- 遵循AMD标准的define函数和require函数也是同样支持的
- 总结:尽量不要去混合使用这些标准,只需要统一使用一个标准。
-
样式代码中的@import指令和url函数
- css文件会用css-loader去处理它,处理的时候会发现有url载入图片,那么就会将这个图片作为资源模块加入到打包过程
- 然后webpack会根据相应的配置,找到处理这个问题,图片的loader。
-
HTML代码中图片标签的src属性,a标签的href等
- HTML导出默认会导出字符串,所以需要导出和write
import footerHtml from './footer.html'
document.write(footerHtml)
- 然后还得去配置对于的laoder,否则webpack是不认识html模块的
- yarn add html-loader --dev
- 配置webpack.config.js
test:/.html$/,
use:{
loader:'html-loader',
// html-loader默认只会处理img:src的模块引入
// 其他的情况比如a标签的href,需要进行配置
options:{
attrs:['img:src','a:href']
}
}
总结:几乎所有的资源加载方式,都会被webpack找出来,并有对应的loader去处理,进行输出。webpack就是基于这样的一个特点,去实现项目的模块化。