Appearance
Loader工作原理
Loader是webpack的核心特性,借助于Loader就可以加载任何类型的资源。
webpack内部的loader只能处理js文件,如果去处理css等其他类型的文件会报错,我们需要使用其他的Loader加载器会处理其他类型的文件。
Loader原理
- Loader负责资源文件从输入到输出的转换
- Loader有管道的概念,对于同一个资源可以依次使用多个Loader处理
webpack常用加载器分类
webpack加载器有点像工厂车间,用来处理加工打包过程中遇到的资源文件,常用的加载器有:
加载器分类 | 描述 |
---|---|
编译转换类 | 将加载到的资源模块转换成JavaScript代码,例如:css-loader(将css代码 -> bundle.js文件中以JS形式工作的CSS模块) |
文件操作类 | 文件加载的资源模块拷贝到输出目录,又将访问路径向外导出,例如:file-loader |
代码检查类 | 对加载到的资源文件进行代码校验,目的是为了统一代码风格,提高代码质量,这种加载器不会去修改生产环境的代码,例如:eslint-loader |
常用Loader
css-loader
css-loader的作用是将css文件转化成一个js模块
- 安装css-loader,
npm i css-loader --save-dev
- 在webpack.config.js里面添加配置
js
const path = require('path')
module.exports = {
entry: './src/main.css',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'output')
},
mode: 'none',
module: {
// 其他模块加载规则的配置,数组
rules: [{
// 匹配文件打包路径
test: /\.css$/,
// 匹配到的文件所使用的loader,如果是单个的直接写字符
use: 'css-loader'
}]
}
}
- 命令行执行
npm run build
这个时候虽然编译不报错,但是使用的时候样式并没有生效,是因为css-loader只是转化了代码,但是并没有追加到style标签里,这里还需要用到style-loader
style-loader
style-loader的作用是将已经成为js模块的css代码通过<style>
标签的形式加载到页面上
- 安装style-loader,
npm i style-loader --save-dev
- 在webpack.config.js里面这样写
js
const path = require('path')
module.exports = {
entry: './src/main.css',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'output')
},
mode: 'none',
module: {
rules: [{
test: /\.css$/,
// 匹配到的文件所使用的loader,如果是单个的直接写字符串,如果是多个的,需要使用到数组,而且使用顺序从后往前,css-loader要写在style-loader的后面
use: [
'style-loader',
'css-loader'
]
}]
}
}
- 命令行执行
npm run build
,打开页面可以看到样式生效
PS: webpack导入资源模块
上面我们可以加载css模块资源,入口文件entry写的是main.css,但是目前而言,前端的业务是由JavaScript整体驱动,但是正确 的做法都是webpack的打包入口设为js文件(打包入口就是运行入口),然后在入口文件中import引用css文件。
- 在webpack.config.js的entry改为
./main.js
- 在main.js里面引入
mian.css
js
import createHeading from './heading.js'
// 直接执行
import './main.css'
const heading = createHeading()
document.body.append(heading)
- 在同名文件中添加
heading.css
文件,编写样式
css
.heading {
padding: 20px;
background-color: #fff;
color: #333;
}
- 在heading.js中引入文件并给标签添加类名
js
// 引入heading的css文件
import './heading.css'
export default () => {
const element = document.createElement('h2')
element.textContent = 'hello world'
// 给标签添加类名
element.classList.add('heading')
element.addEventListener('click', () => {
alert('hello webpack')
})
return element
}
- 命令行运行
npm run build
,可以看到样式还可以正常运行。
传统我们要把css和js文件分离开,单独维护单独引入,但是webpack要求我们将css文件引入到js文件中。 webpack不仅仅要求我们引入css文件,它要求我们在js文件中引入所有我们需要的动态资源。
真正需要资源的不是应用,而是我们目前正在编写的代码 代码要正常工作就必须要加载对应的资源,这是webpack的思维,这个对于js和其他资源文件建立依赖关系是有很明显的优势的。
javascript代码本身负责整个业务的功能,放大就是驱动了整个前端应用。在实现功能的时候可能会用到样式图片之类的资源文件,如果建立这种依赖关系,就会觉得逻辑比较合理。
- 逻辑合理,js完成功能本身需要这些资源
- 确保线上资源不缺失,且都是必要的
file-loader
文件资源加载器
- 安装
npm i file-loader --save-dev
- 在文件中使用
js
//main.js
import createHeading from './heading.js'
import './main.css'
// 这里需要接收一下这个资源的默认导出,这个导出是打包过后这个图片的资源路径
import icon from './icon.png'
const heading = createHeading()
document.body.append(heading)
// 创建一个image对象,然后将路径赋值给这个图片的src
const img = new Image()
img.src = icon
document.body.append(img)
- 在webpack中配置loader
js
module: {
// 其他模块加载规则的配置,数组
rules: [{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},{
// 匹配图片文件使用file-loader文件加载器
test:/\.png$/,
use: 'file-loader'
}]
}
- 运行如果加载不出来可以指定文件资源的根目录
js
// webpack.config.js
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'output'),
// 默认空字符串表示当前根目录,这里可以指定,output后面的 / 不能省略
publicPath: 'output/'
}
- 再次尝试运行成功就可以看到图片被加载出来了
总结工作过程
- webpack在打包的时候遇到了文件资源,然后在配置文件中的配置匹配到对应的文件加载器
- 文件加载器先把导入的文件拷贝到输出目录,然后将文件拷贝到输出目录的路径作为当前模块的返回值返回
- js所需要的文件资源就发布出来了,我们还可以通过返回的模块成员也拿到了返回值路径,引用之后图片正常显示
URL Loader
Data URLs
Data URLs是一种特殊URL协议,可以直接用来表示一个文件。
- 传统的URL:要求服务器上有一个对应的文件,我们请求服务器地址得到这个对应的文件
- Data URLs:当前URL就可以直接表示这个文件内容,我们在使用的时候就不会发送任何的HTTP请求
举个例子:
这是一个html的内容,编码是UTF-8,内容是一个包含h1标签的代码
data:text/html;charset=UTF-8,<h1>html content</h1>
如果是图片或者文字这种无法用文本表示的二进制类型的文件,我们可以将文件的内容进行base64编码,以base64编码之后的字符串表示这个文件的内容。
举个例子: 这是一个png类型的文件,编码是base64 data:img/png;base64,iVBPR ... SuQmCC
url-loader
- 安装
npm i url-loader --save-dev
- 在webpack.config.js中修改
js
module: {
rules: [{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},{
// 匹配图片文件使用url-loader加载器
test:/\.png$/,
use: 'url-loader'
}]
}
这个时候webpack在打包的时候,就会将png图片以url-loader加载成为Data URLs的形式。
这种加载方式很适合体积比较小的资源,因为体积比较大的资源会造成打的包大小过大,从未影响运行次数
最佳实践
- 小文件使用Data URLs,减少请求次数
- 大文件单独提取存放,提高加载速度
在webpack里面配置大于10kb的文件仍然使用file-loader加载单独存放,小于10kb的使用url-loader加载转换为Data URLs嵌入代码中
js
module: {
rules: [{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},{
test:/\.png$/,
// 这里要传一个对象,options里面要配置最大限定大小,超过的仍然使用file-loader处理
use: {
loader:'url-loader',
options: {
limit: 10 * 1024 // 这里显示字节,10KB
}
}
}]
}
ps: 对于使用url-loader,如果这样使用必须安装file-loader
babel-loader
因为webpack中可以用export和import,所以很多人认为webpack可以使用es6代码,其实不是。
webpack可以使用export和import是因为模块打包需要,所以会对这两个进行转换,除此之外,并不能转换其他的ES6特性。
如果要对ES6进行转换,需要添加一个编译型loader,最常见的就是babel-loader
- 安装
npm i babel-loader @babel/core @babel/preset-env --save-dev
- 在配置文件
webpack.config.js
中指定加载器
js
module: {
rules: [{
test: /\.js/,
// babel-loader只是一个平台,单独写是不会进行转化的,需要使用插件
// preset-env是一个插件集合,用来处理新特性的转化
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},{
test:/\.png$/,
use: {
loader:'url-loader',
options: {
limit: 10 * 1024 // 10KB
}
}
}]
}
- 在命令行运行
npm run build
可以看到生成的bundle.js文件已经成功转化
总结
- webpack只是打包工具,不对代码进行编译转化
- 加载器可以用来编译转换代码
html-loader
- 安装
npm i html-loader --save-dev
- 编写html文件
html
<!--footer.html-->
<footer>
<img src="icon.png" alt="footer" width="256"/>
</footer>
- 在配置文件中
webpack.config.js
中配置
js
module: {
rules: [{
test: /\.html$/,
use: {
loader: 'html-loader'
}
}]
}
4.在main.js中引用
js
import footer from './footer.html'
document.write(footer)
- 命令行运行
npm run build
可以看到图片正常加载 - 如果添加a标签的href
html
<footer>
<img src="icon.png" alt="footer" width="256"/>
<a href="icon.png">click me~</a>
</footer>
点击按钮,跳转资源会404,因为html-loader只会默认处理img的src标签的打包,如果我们要处理其他标签的属性也进行打包处理,需要在配置文件中额外配置
js
module: {
// 其他模块加载规则的配置,数组
rules: [{
//加载html资源
test: /\.html$/,
use: {
loader: 'html-loader',
options: {
// 默认只有img:src,如果要对其他属性进行处理这里需要添加
// attrs: ['img:src', 'a:href']这是旧版本的写法,新版本这么写
attributes: {
list: [
{
tag: 'img',
attribute: 'src',
type: 'src'
},
{
tag: 'a',
attribute: 'href',
type: 'src'
}
]
}
}
}
}]
}
之后a链接跳转可以正常显示。
动手实践:开发一个自己的Loader:markdown-loader
目的:可以在代码中直接导入markdown文件,导入的结果就是转换过后的HTML字符串
js
import about from './about.md'
console.log(about) // 转换过后的HTML字符串
- 安装
npm i marked --save-dev
- 在webpack.config.js目录下添加一个
markdown-loader.js
js
const marked = require('marked')
// 导出一个函数,输入是资源文件的内容
module.exports = source => {
console.log(source)
// 这个值就是html字符串,也就是转换过后的结果
const html = marked(source)
return html
}
- 在webpack.config.js中配置
js
{
test: /\.md$/,
// 这里除了可以写npm包名称,也可以写相对路径
use: './markdown-loader'
}
- 准备
about.md
md
# 目录
## 目标
自己做一个markdown-loader
- 在main.js中引用
js
import about from './about.md'
document.write(about)
尝试运行会正常输出,但是会报错,原因是,webpack内部像一个管道,可以在过程中依次使用过个loader,但是要求结果必须是一个JavaScript代码,这里返回的html结构不是一个JavaScript代码,所以解决办法有两种:
html结构换成JavaScript代码
- 导入模块,并使用
js
const marked = require('marked')
module.exports = source => {
const html = marked(source)
// 不能直接导出html变量,因为必须要转换成JavaScript代码
// 这里使用stringify转换成一个标准的JSON字符串,其换行符和引号都会正确转义
// 然后再对其进行拼接,就不会有问题了
return `module.exports = ${JSON.stringify(html)}`
// or ES Modules的形式也可以
return `export default ${JSON.stringify(html)}`
}
这里在网页中显示正常
使用其他的loader对代码进行处理
- 这里将html直接导出,安装一个
npm i html-loader --save-dev
,在webpack.config.js中使用
js
{
test: /\.md$/,
//先执行markdown-loader,再执行html-loader
use: [
'html-loader',
'./markdown-loader'
]
}