loader资源模块加载器
webpack 资源模块加载
webpack内部(内部loader)默认只会处理Javascript文件,也就是说它会把打包过程中所有遇到的文件当作Javascript文件去解析。
可以为其他类型的文件添加不同的加载器(loader)。
loader
loader是webpack实现整个前端模块化的核心,借助于loader就可以加载任何类型的资源。
安装loader,然后在配置文件中的module
属性下配置rules
数组。
rules
是针对其他资源模块的加载规则的配置。
每个规则对象,都需要设置两个属性:
test
:一个正则表达式,用于匹配打包过程中遇到的文件路径use
:用于指定匹配到的文件需要使用的loader - 可以配置loader模块名称,也可以配置loader相对路径,原理同require函数
use: 'css-loader'
使用npm上的loader模块use: './xxx-loader'
使用自定义的.js文件的相对路径
- 如何配置了多个loader(数组),执行顺序是从后向前
一些插件介绍:
- css-loader
- 将css文件转换称一个js模块,具体实现是:
- 将css代码push到一个数组当中
- 但是整个过程中,并没有使用到这个数组
- 还需要一个style-loader去使用它
- style-loader
- 将css-loader转换后的结果,通过style标签的形式追加到页面上
- html-loader
- 使webpack识别
.html
的模块
webpack 导入资源模块
虽然可以通过loader将任何类型的资源作为入口去打包。
但webpack一般还是将Javascript作为打包入口。
打包入口可以说是项目的运行入口。
webpack建议:
- 编写代码过程中,根据代码的需要,动态导入资源
- 需要资源的不是应用,而是代码
目前而言,Javascript代码负责完成整个应用的业务功能。Javascript驱动前端应用的业务。
在实现业务的过程中,需要其他类型的资源(例如样式、图片)。
webpack建立Javascript和资源的依赖关系的目的:
- 逻辑合理,JS确实需要这些资源文件
- 确保上线资源不缺失,都是必要的
webpack 文件资源加载器
大部分loader都类似css-loader,都是将资源模块转换为JS代码的实现方式。
还有一部分(例如 图片 字体)不能通过JS的方式去表示的资源,需要用到文件资源加载器,例如file-loader
。
webpack默认将输出目录作为网站的根目录,所以资源的路径默认以dist
为根目录。
通过配置publicPath
,告诉webpack打包的文件在网站中的位置,默认为空''
及网站根目录。
例如:publicPath: 'dist/'
,注意/
不能省略。
publicPath
即打包后文件中webpack使用的变量__webpack_require__.p
。
使用图片时,它拼接在图片路径前,即__webpack_require__.p + imgSrc
,所以/
不能省略。
总结
webpack 在打包时遇到图片等文件,根据配置匹配对应的文件加载器,先将文件拷贝到输出目录,然后将输出的文件的路径,作为返回值返回,从而可以通过import
拿到访问这个文件的路径。
webpack URL 加载器
除了file-loader
这种通过拷贝文件的方式去处理文件资源以外,还有一种通过 Data URLs 表示文件的常见方式。
Data URLs
传统的URL一般要求服务器有一个对应的文件,然后通过请求这个地址得到服务器上的这个文件。
Data URLs 是特殊的URL协议,它可以直接表示一个文件的内容,即url中的文本已经包含了文件的内容。
所以使用Data URLs时就不会再发送HTTP请求。
data:[][;base64],
data:
协议[][;base64],
媒体类型和编码
文件内容
例如:data:text/html;charset=UTF-8,html content
表示一个编码为UTF-8
,内容为html content
的html内容。
可以通过浏览器打开这个地址查看效果。
而如果是图片或字体这种无法直接通过文本去表示的二进制类型的文件,可以通过将文件的内容进行base64
编码,然后以编码后的字符串去表示文件的内容。
例如:data:image/png;base64,iVBORw0KGg...
表示base64
编码的png
类型的文件。
通过Data URLs可以表示任意类型的文件。
url-loader
file-loader
通过拷贝的方式打包文件,最终返回输出文件的路径。
url-loader
将文件转换为Data URLs,最终返回一个完整的 Data URLs类型的url地址,不会输出独立的物理文件。
最佳实践
- 小文件使用Data URLs,减少请求次数
- 大文件单独提取存放(传统方式),提高加载速度(Data URLs表示大文件内容过大)
每个loader加载器都有options
配置选项。
通过配置url-loader
的limit
(字节上限)实现最佳实践。
- 超出
limit
的文件单独提取存放(调用file-loader
加载器) - 小于
limit
的文件转换为Data URLs嵌入代码
注意:如果url-loader
配置了limit
,大文件是使用file-loader
加载器处理的,所以需要安装它依赖的file-loader
。
常用加载器分类
- 编译转换类
- 把加载到的模块,转换为Javascript代码
- 例如 css-loader
- 文件操作类
- 把加载到的资源模块,拷贝到输出的目录
- 同时导出文件的访问路径
- 例如 file-loader
- 代码检查类
- 对代码加载的文件中的代码进行校验
- 目的:统一代码风格,从而提高代码质量
- 一般不会修改生产环境的代码
- 例如 eslint-loader
webpack 处理 ES2015
webpack默认就能处理代码当中的 import
和export
,但这不表示webpack会自动编译ES6的代码。
因为模块打包需要,所以webpack对代码中的import
和export
作了相应的转换。
webpack并不能转换代码中其他的ES6特性。
如果需要webpack处理代码中其他ES6特性的转换,就需要为JS文件配置一个额外的编译类型的loader,例如常见的babel-loader
babel-loader
依赖额外的babel的核心模块@babel/core
另外可以安装一个babel的插件集合(预设)@babel/preset-env
babel只是转换JS代码的一个平台,我们需要基于这个平台,通过不同的插件去转换代码中具体的特性。
所以需要配置babel要使用的插件。
- webpack只是打包工具,不会处理代码中的ES6的新特性
- 通过配置加载器实现编译转换代码
webpack 模块加载方式
除了代码中的import
可以触发模块的加载,webpack还提供了其他几种方式:
- 遵循 ES Modules 标准的
import
声明 - 遵循 CommonJS 标准的
require
函数 - 注:如果
require
一个 ESM 的模块,需要通过require().default
获取 ESM 模块的默认属性
- 遵循 AMD 标准的
define
函数和require
函数
建议:除非必要情况,否则不要再项目中混合使用这几种标准。
webpack除了以上3个方式,loader加载的非Javascript也会触发资源加载。(一些独立的加载器,在工作时也会处理所加载到的资源当中导入的模块)
例如
css-loader
加载的css文件(样式代码中@import
指令和url
函数)- html代码的图片标签的
src
属性,a标签的href
属性
html-loader
默认只会处理html中的src
属性,如果要实现其他标签的属性也能触发webpack打包,需要为加载器添加一些相应配置。
代码中所有引用到的资源(有引用资源可能性的地方)都会被webpack找到,然后根据配置交给对应的loader去处理,最后将处理的结果整体打包到输出目录。
webpack就是依据这样的特点,去实现整个项目的模块化。
webpack 核心工作原理
在项目中一般都会散落着各种各样的代码及资源文件(.js .html .css .png .json .scss…)。
webpack会根据配置找到其中的一个文件作为打包入口(entry),一般是一个Javascript文件。
然后它会顺着入口文件的代码,根据代码中出现的import
或require
之类的语句,解析推断出这个资源所依赖的模块。
然后分别再解析每个模块对应的依赖。
最后形成了整个项目中,所有用到的文件之间的依赖关系的依赖树。
webpack会递归这个依赖树,找到每个节点对应的资源文件。
最后根据配置文件中的rules
属性,找到模块对应的加载器去加载这个模块。
最后会将加载到的结果,放到打包文件bundle.js
中,从而实现整个项目的打包。
Loader机制是webpack的核心。
如果没有Loader,webpack就没有办法实现各种资源文件的加载,而只是打包合并JS代码的工具。
webpack Loader的工作原理
开发一个loader
开发一个markdown文件加载器(markdown-loader
),实现在代码中直接导入markdown文件。
原理:将md内容转换为html呈现到页面中。
起步
-
创建一个loader的js文件,编写内容
-
每个loader都需要导出一个函数,这个函数是loader对所加载到的资源的处理过程。
- 输入:就是资源所加载到的内容,参数
source
接收 - 输出:
return
处理后的结果
-
在webpack配置文件中配置rules,使用这个loader
module.exports = source => {console.log(source)return 'hello ~'
}
module.exports = source => {module: {rules: [{test: /.md$/,use: './markdown-loader', }]}
}
此时执行打包命令,会报错You may need an additional loader to handle the result of these loaders.
“你可能需要一个额外的loader去处理这个自定义加载器的结果”。
webpack加载资源的过程,类似于一个工作管道,可以在这个过程中依次使用多个loader。
Source => loader1 -> loader2 -> loaderx => Result
但是它要求最终这个管道工作过后的结果,必须是一段Javascript代码。
而上面的loader返回的是hello ~
,它不是Javascript代码,所以才会出现这个错误提示。
解决办法:
- 直接返回Javascript代码
- 例如
return 'exports default "hello ~"'
- 或者找一个合适的loader继续处理
markdown-loader
处理的结果
使用合适的loader处理
继续完善功能,安装markdown解析模块marked,yarn add marked --dev
该模块可以解析md内容,并返回一个字符串。
markdown-loader
仍然返回解析后的html字符串,将结果交给下一个loader处理。
const market = require('market')
module.exports = source => {const html = market(source)return html
}
安装用于加载html的loader(html-loader
)。
将这个加载器,配置在markdown-loader
的后面执行(代码中位置靠前)。
总结
- Loader负责资源文件从输入到输出的转换。
- Loader实际上是一种管道的概念,对于同一个资源可以依次使用多个Loader,将此次loader返回的结果交给下一个loader处理。
- 例如 上面的
markdown-loader -> html-loader
- 以及处理样式的
css-loader -> style-loader