如果你想学到更多实用前端知识。
可以关注我的公众号:【前端驿站Lite】,一个不止分享前端的地方 ᕦ( •̀∀•́)ᕤ
阅读收获
阅读完本篇文章,你将会有以下收获:
- 为什么会有loader机制。
- loader是什么,有什么作用。
- loader的分类、执行顺序是怎样的。
- 同步、异步loader的区别。
- 如何自定义一个loader。
- 12个常见loader介绍与用法。
为什么会有loader
本文开篇,我们先来聊一下,webpack为什么会引入loader机制。
webpack只能处理.js
和.json
文件,但打包过程中遇到其他类型文件,如.vue
、.ts
、图片
、.css
等,webpack就无能为力了。
面对这一问题,webpack提供了loader机制。
loader的出现,让webpack拥有了处理其他类型文件的能力。
比如,我们经常会看到下面这样的配置:
js
module.exports = {
// ...
module: {
rules: [
{
test: /.less$/i, // 匹配.less结尾的文件
use: [
"style-loader",
"css-loader",
'less-loader'
],
}
],
}
// ...
};
js
//js文件
import "./style.less";
js代码中,使用import导入了一个.less
文件,webpack碰到.less
后缀的文件,不知所措了,因为它只能处理以.js
和.json
结尾的文件。
这时候就需要loader登场了,有了loader的赋能,webpack便有了处理.less
文件的能力。
- 比如,根据上面配置,webpack一旦碰到
.less
后缀结尾的文件,webpack会先将文件内容发送给less-loader
处理,less-loader
会将所有less
语法转换成普通css
语法。 - 普通的
css
样式继续发送给css-loader
处理,css-loader
最主要的功能是解析css
语法中的@import
和图片
路径,处理完后导入的css合并在了一起。 - 合并后的
css
文件再继续传递,发送给style-loader
处理,它最终将样式内容插入到了html
头部的style
标签下,页面也因此添加了样式。
从上面的案例我们看出,每个loader
的职责都是单一的,自己只负责自己的那一小块.但不管什么格式的文件,只要将特定功能的loader
组合起来,它就能增强webpack
的能力,使各种稀奇古怪的文件都能被正确识别并处理。
什么是loader
简单了解loader作用后,那loader到底是什么呢?
loader
其实就是一个内容转换器,将webpack
不能识别的文件,转换为标准的js
模块,交给webpack
处理。
loader
的本质是一个导出一个函数的 JavaScript
模块,这也正体现了 webpack 中一切皆模块
的思想。
webpack内部的loader runner
会调用此函数,然后将上一个loader
产生的结果或者资源文件传入进去。函数中的this
作为上下文会被webpack
填充。
js
/**
* @param {string/Buffer} content源文件的内容
* @param {object}[map] sourcemap相关的数据
* @param {any} [meta] 元数据,可以是任何内容
*/
module.exports = function (content, map, meta) {
// 将webpack不能识别的文件内容content,转换处理后,进行返回
return content
}
loader分类
在 Webpack 中,loader 可以被分为 4 类,分别是:
pre
前置 loadernormal
普通 loaderinline
内联 loaderpost
后置 loader
其中 pre
和 post
loader,可以通过 rule
对象的 enforce
属性来指定,不指定时,默认为normal
loader。
js
// webpack.config.js
const path = require("path");
module.exports = {
module: {
rules: [
{
test: /.txt$/i,
use: ["a-loader"],
enforce: "post", // post loader
},
{
test: /.txt$/i,
use: ["b-loader"], // normal loader
},
{
test: /.txt$/i,
use: ["c-loader"],
enforce: "pre", // pre loader
},
],
},
};
inline loader
说完,上面三种loader后,我们再简单介绍下 inline
loader
webpack允许在引入模块的时候直接指定loader,这样指定loader的方式称之为inline loader,具体如下所示:
js
import common from 'loader-a!loader-b!loader-c?type=abc!./common.js'
每个loader之前同!
隔开,允许携带query参数,最后的模块也使用!
隔开
内联的 inline
loader 不在webpack的配置文件中配置,它仅在业务代码中配置且使用不多。我们不过多讲解。
loader执行顺序
那loader执行顺序是怎样的呢?
1. 相同优先级
首先我们要知道,loader执行过程分为两个阶段,分别是 pitching
和 normal
阶段。
js
// a-loader.js
const loader = function (content, map, meta) {
console.log("a-loader执行");
return content;
};
loader.pitch = function () {
console.log("a-loader pitch执行");
};
module.exports = loader;
// b-loader.js
const loader = function (content, map, meta) {
console.log("b-loader执行");
return content;
};
loader.pitch = function () {
console.log("b-loader pitch执行");
};
module.exports = loader;
// c-loader.js
const loader = function (content, map, meta) {
console.log("c-loader执行");
return content;
};
loader.pitch = function () {
console.log("c-loader pitch执行");
};
module.exports = loader;
// 打印结果
// a-loader pitch执行
// b-loader pitch执行
// c-loader pitch执行
// c-loader执行
// b-loader执行
// a-loader执行
所以,对相同优先级的loader来说,
- 先按
从上到下
,从左到右
的顺序执行每个loader上的pitch
方法。 - 再按
从右到左
,从下到上
的顺序执行每个loader函数。
2. 不同优先级
对于不同优先级来说,pre
(前置) => normal
(普通) => inline
(内联) => post
(后置)
pitching阶段
上面loader的执行顺序中,提到了loader的pitching
阶段,那再来说一下这个pitching
阶段到底是怎么一回事。
webpack规定,在每个loader上可以有一个pitch
属性,该属性指向一个函数。
js
// a-loader.js
const loader = function (content, map, meta) {
console.log("a-loader执行");
return content;
};
loader.pitch = function () {
console.log("a-loader pitch执行");
};
module.exports = loader;
当loader.pitch
没有返回值时,按照上面的执行顺序执行。
那如果picth loader有返回值呢?
js
function loader2(content, map, meta) {
console.log("loader2 的 normal 阶段")
return content + "//(loader2)";
}
loader2.pitch = function() {
console.log("loader2 的 pitching 阶段")
return 'Jolyne'
}
module.exports = loader2
在 Pitching 阶段,如果当前 Loader.pitch
有返回值,就直接结束当前 loader
的 Pitching
阶段,并直接跳到当前 Loader
执行 pitching
阶段时的 前一个 loader 的normal
阶段,然后继续执行。
同步、异步loader
1.同步loader
如果我们loader中没有异步操作,那这就是一个同步loader。
这个loader必须通过return
或者this.callback
来返回结果,交给下一个loader
来处理。this.callback
方法则更加灵活,因为它允许传递多个参数,不仅仅是content。
js
module.exports = function (content,map,meta){
return content;
}
module.exports = function (content,map,meta){
this.callback(null,content,map,meta)
// 第一个参数必须是error或者null
// 第二个参数是一个string 或者 Buffer
// 第三个和第四个参数是可选的
}
2.异步loader
那么,有时候我们使用loader时会进行一些异步的操作;我们希望在异步操作完成后,再返回这个loader处理的结果,这个时候就要使用异步的loader。
对于异步loader,使用this.async
来获取callback
函数,等后端操作完成后,再调用callback
函数。
js
module.exports = function(content,map,meta) {
const callback = this.async();
setTimeOut(function(){
console.log("loader执行完成",content);
callback(null,content);
},2000)
}
自定义loader
我们在日常开发中,会遇到一些特殊的需求,需要自定义loader来处理。
比如,我们想将px
单位转换成vw
单位,就可以自定义一个loader来处理。
js
// 创建jsxPxToVwLoader.js文件自定义loader
const defaultOptions = {
viewportWidth: 375, // 设计稿视口宽度
unitPrecision: 5, // 转换后保留精度
}
module.exports = function (source) {
// 拿到options参数
const option = this.getOptions();
// 合并参数
const options = { ...defaultOptions, ...option };
// 匹配以数字开头,中间可能带小数点的,并且以px结尾的
const reg = /\b(\d+(.d+)?)px\b/g;
if (reg.test(source)) {
const { viewportWidth, unitPrecision } = options;
const p = 100 / viewportWidth; // 1px = (val)vw
return source.replace(reg, (data, val) => {
let value = p * val;
value = parseFloat(value.toFixed(unitPrecision))
return value === 0 ? value : value + 'vw'
})
} else {
return source;
}
}
js
// webpack.config.js引入
module: {
rules: [
{
test: /.jsx/,
exclude: /node_modules/,
use: [{
loader: path.resolve(__dirname, './jsxPxToVwLoader.js'),
options: {
viewportWidth: 375, // 设计稿视口宽度
unitPrecision: 3, // 转换后保留精度
}
}]
}
]
}
常见loader
下面是我们开发中,一些常见的loader介绍与用法,让我们一起来看一下吧。
1. file-loader
file-loader 将打包过程中遇到的文件,复制到对应输出目录,并返回文件的路径。
shell
npm install --save-dev file-loader
js
rules: [
// ...,
{
test: /.(png|jpe?g|gif)$/,
use: {
loader: "file-loader",
options: {
// placeholder 占位符 [name] 源资源模块的名称
// [ext] 源资源模块的后缀
name: "[name]_[hash].[ext]",
//打包后的存放位置
outputPath: "./images",
// 打包后文件的 url
publicPath: './images',
}
}
}
]
2. url-loader
url-loader 可以将打包过程中用到的文件转换为 base64 格式字符串,这样可以减少网络请求次数。
js
npm install --save-dev url-loader
js
rules: [
// ...,
{
test: /.(png|jpe?g|gif)$/,
use: {
loader: "url-loader",
options: {
// placeholder 占位符 [name] 源资源模块的名称
// [ext] 源资源模块的后缀
name: "[name]_[hash].[ext]",
//打包后的存放位置
outputPath: "./images",
// 打包后文件的 url
publicPath: './images',
// 小于 100 字节转成 base64 格式
limit: 100
}
}
}
]
3. raw-loader
raw-loader 提取文件的内容,转换为字符串形式进行返回。
js
npm install --save-dev raw-loader
js
module.exports = {
// ...,
module: {
rules: [
{
test: /.(txt|md)$/,
use: 'raw-loader'
}
]
}
}
4. style-loader
style-loader 可以将 css 代码注入到 js 中,运行时,通过创建 style 标签,将样式插入到 head 中。
js
npm install --save-dev style-loader
js
module.exports = {
// ...,
module: {
rules: [
{
test: /.css$/,
use: 'style-loader'
}
]
}
}
5. css-loader
分析 css 模块之间的关系,并合成⼀个 css
shell
npm install --save-dev css-loader
js
rules: [
// ...,
{
test: /.css/,
use: {
loader: "css-loader",
options: {
// 启用/禁用 url() 处理
url: true,
// 启用/禁用 @import 处理
import: true,
// 启用/禁用 Sourcemap
sourceMap: false
}
}
}
]
6. less-loader
将 less 转换为 css,sass-loader 同理
shell
npm install --save-dev less-loader
js
rules: [
// ...,
{
test: /.less$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
// 启用/禁用 url() 处理
url: true,
// 启用/禁用 @import 处理
import: true,
// 启用/禁用 Sourcemap
sourceMap: false
}
},
"less-loader"
]
}
]
7. postcss-loader
postcss-loader 可以将 css 代码转换为兼容性更好的 css 代码,如添加浏览器前缀等。
autoprefixer
、cssnano
都是 postcss-loader 的插件,autoprefixer
用来自动添加浏览器前缀,cssnano
用来压缩 css 代码。
shell
npm install postcss-loader autoprefixer cssnano --save-dev
项目根目录创建postcss.config.js
,也可以在postcss-loader的options
中配置
js
module.exports = {
plugins: [
require('precss'),
require('autoprefixer')({
'browsers': [
'defaults',
'not ie < 11',
'last 2 versions',
'> 1%',
'iOS 7',
'last 3 iOS versions'
]
})
]
}
添加 postcss-loader
js
rules: [
// ...,
{
test: /.css$/,
use: [
"style-loader",
"css-loader",
"postcss-loader"
]
}
]
8. html-loader
html-loader 可以将 html 文件转换为字符串形式,我们有时候想引入一个html页面代码片段赋值给DOM元素内容使用。
shell
npm install --save-dev html-loader
js
import Content from "../template.html"
document.body.innerHTML = Content
js
rules: [
// ...,
{
test: /.html$/,
use: 'html-loader'
}
]
9. babel-loader
babel-loader 可以将 ES6+ 代码转换为 ES5 代码,以便浏览器可以兼容。
shell
npm install --save-dev babel-loader @babel/core @babel/preset-env
js
module.exports = {
// ...,
module: {
rules: [
{
test: /.js$/,
use: {
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
{
// 按需加载
useBuiltIns: "usage",
// 指定core-js版本
corejs: 3
}
]
]
}
}
}
]
}
}
10. ts-loader
ts-loader 可以将 TypeScript 代码转换为 JavaScript 代码。
shell
npm install --save-dev ts-loader typescript
js
module.exports = {
// ...,
module: {
rules: [
{
test: /.ts$/,
use: 'ts-loader'
}
]
}
}
11. eslint-loader
eslint-loader 可以将 eslint 代码检查集成到 webpack 中。
shell
npm install --save-dev eslint-loader eslint
js
module.exports = {
// ...,
module: {
rules: [
{
test: /.js$/,
use: {
loader: "eslint-loader",
options: {
// 自动修复
fix: true
}
}
}
]
}
}
12. vue-loader
vue-loader 用来转换 .vue
文件。
shell
npm install -D vue-loader vue-template-compiler
js
// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
module: {
rules: [
// ... 其它规则
{
test: /.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
// 请确保引入这个插件!
new VueLoaderPlugin()
]
}
以上loader配置仅为参考,具体还要与时俱进,以官方文档为准。
完结撒花,你又进步了一点点。
ᕦ( •̀∀•́)ᕤ