UzumakiItachi
首页
  • JavaSript
  • Vue

    • Vue2
    • Vue3
  • React

    • React_18
  • WebPack
  • 浏览器相关
  • 工程化相关
  • 工作中遇到的问题以及解决方案
  • Git
  • 面试
  • 学习
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 个人产出
  • 实用工具
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

UzumakiItachi

起风了,唯有努力生存。
首页
  • JavaSript
  • Vue

    • Vue2
    • Vue3
  • React

    • React_18
  • WebPack
  • 浏览器相关
  • 工程化相关
  • 工作中遇到的问题以及解决方案
  • Git
  • 面试
  • 学习
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 个人产出
  • 实用工具
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • WebPack

    • WebPack热更新原理
    • 实现一个通用的WebPack配置包
    • WebPack打包多页面应用
    • WebPack构建速度以及体积优化策略
    • 手写一个简易的WebPack
    • 实现一个简易的webpack-loader
      • 前言
      • 四种loader
        • 同步loader
        • 异步loader
        • "Raw" loader
        • Pitching loader
        • 其他api
        • 实践
      • 总结
    • 实现一个简易的Webpack-plugin
    • 深入WebPack code splitting
    • 实现一个合成雪碧图的loader
    • 实现一个压缩文件的plugin
    • webpack5都升级了哪些东西
    • 实现一个自动收集路由信息的webpack插件
  • 浏览器相关

  • 工程化相关

  • 工作中遇到的问题以及解决方案

  • Git

  • Vite

  • 一些小工具

  • 算法

  • 服务器

  • HTTP

  • 技术
  • WebPack
hanhanbuku
2023-05-17
目录

实现一个简易的webpack-loader

# 前言

loader是webpack中非常重要的一个东西。大家都知道webpack是只认识js文件和json文件的。那在构建过程中,他遇到的其他文件该怎么办呢?loader就是来干这些事情的,把一些webpack无法识别的资源转变成可以识别的类型。 下面就来剖析一下loader

一个loader其实就是一个nodejs模块,他导出一个函数,这个函数只有一个入参,那就是待处理的文件内容的字符串。而这个函数的返回值就是处理过后的文件内容字符串。 也就是说,最简单的loader长这样

module.exports = function (content){
    // content 就是传入的源内容字符串
    return content
}
1
2
3
4

以上就是一个最简单的loader了。下面让我们来看看怎么让这个loader变得更丰富

# 四种loader

我们可以把loader大致分为四种

  • 同步loader
  • 异步loader
  • “Raw” loader
  • pitching loader

# 同步loader

同步loader就像上面最简单的loader一样,直接采用return的方式返回处理结果。当然也可以采用callback的方式(webpack提供了很多方法让开发者使用)然后在最后直接 **return undefined **的方式告诉 webpack 去 this.callback() 寻找他要的结果,这个 api 接受这些参数:

this.callback(
  err: Error | null, // 一个无法正常编译时的 Error 或者 直接给个 null
  content: string | Buffer,// 我们处理后返回的内容 可以是 string 或者 Buffer()
  sourceMap?: SourceMap, // 可选 可以是一个被正常解析的 source map
  meta?: any // 可选 可以是任何东西,比如一个公用的 AST 语法树
);

1
2
3
4
5
6
7

和上述提到的callback一样,还有一个常用的api---getOptions,这个api是用来获取用户传入的参数的

module.exports = function (content) {
  // 获取到用户传给当前 loader 的参数
  const options = this.getOptions()
  const res = someSyncOperation(content, options)
  this.callback(null, res, sourceMaps);
  // 注意这里由于使用了 this.callback 直接 return 就行
  return
}
1
2
3
4
5
6
7
8

这样,一个同步的loader就完成了

# 异步loader

而异步的loader和同步的loader其实区别不大。只需要通过async函数来返回处理结果就行了

module.exports = function (content) {
  var callback = this.async()
  someAsyncOperation(content, function (err, result) {
    if (err) return callback(err)
    callback(null, result, sourceMaps, meta)
  })
}

1
2
3
4
5
6
7
8

# "Raw" loader

Raw laoder其实就是给这个loader设置一个raw属性为true,

module.exports = function (content) {
  console.log(content instanceof Buffer); // true
  return doSomeOperation(content)
}
// 划重点↓
module.exports.raw = true;

1
2
3
4
5
6
7

默认情况下资源文件会被转换成utf-8的字符串传递给loader,而通过设置raw为true,我们的loader就可以接收到原始的buffer文件流。

# Pitching loader

每个loader都可以有一个pitch方法,我们都知道loader的执行顺序是从右往左的,其实在这之前还会从左到右去执行一遍loader的pitch方法。 pitch方法共有三个参数

  • remainingRequest:loader 链中排在自己后面的 loader 以及资源文件的绝对路径以!作为连接符组成的字符串。
  • precedingRequest:loader 链中排在自己前面的 loader 的绝对路径以!作为连接符组成的字符串。
  • data:每个 loader 中存放在上下文中的固定字段,可用于 pitch 给 loader 传递数据。

在 pitch 中传给 data 的数据,在后续的调用执行阶段,是可以在 this.data 中获取到的:

module.exports = function (content) {
  return someSyncOperation(content, this.data.value);// 这里的 this.data.value === 42
};

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  data.value = 42;
};

1
2
3
4
5
6
7
8

注意,如果某个loader的pitch直接返回的值,那他就会往回走。跳过后续的步骤,举个例子 现在有如下几个loader:use:['a-loader','b-loader','c-loader']。那么他的执行顺序应该是这样的

a-loader pitch->b-loader pitch->c-loader pitch->c-loader->b-loader->a-loader

而当有一个pitch有返回值时,就会这样。比如b-loader的pitch有返回值

a-loader pitch->b-loader pitch->b-loader pitch返回了值->a-loader

# 其他api

  • this.addDependency:加入一个文件进行监听,一旦文件产生变化就会重新调用这个 loader 进行处理
  • this.cacheable:默认情况下 loader 的处理结果会有缓存效果,给这个方法传入 false 可以关闭这个效果
  • this.clearDependencies:清除 loader 的所有依赖
  • this.context:文件所在的目录(不包含文件名)
  • this.data:pitch 阶段和正常调用阶段共享的对象
  • this.getOptions(schema):用来获取配置的 loader 参数选项
  • this.resolve:像 require 表达式一样解析一个 request。resolve(context: string, request: string, callback: function(err, result: string))
  • this.loaders:所有 loader 组成的数组。它在 pitch 阶段的时候是可以写入的。
  • this.resource:获取当前请求路径,包含参数:'/abc/resource.js?rrr'
  • this.resourcePath:不包含参数的路径:'/abc/resource.js'
  • this.sourceMap:bool 类型,是否应该生成一个 sourceMap 官方还提供了很多实用 Api ,这边值列举一些可能常用的,更多可以戳链接 更多详见官方链接 (opens new window)

# 实践

下面来写两个简单的loader练练手,第一个 给每个js文件都添加一段注释,第二个删除代码中的console.log

添加注释

module.exports = function (source) {
  const options = this.getOptions() // 获取 webpack 配置中传来的 option
  this.callback(null, addSign(source, options.sign))
  return
}

function addSign(content, sign) {
  return `/** ${sign} */\n${content}`
}

// 使用

{
    test: /\.js$/
    use: [
        {
            loader: path.resolve('path/to/loader.js'),
            options: {
                sign:'xxxxxx'
            }
        }
    ]
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

删除打印

module.exports = function (content) {
  return handleConsole(content)
}

function handleConsole(content) {
  return content.replace(/console.log\(['|"](.*?)['|"]\)/, '')
}

1
2
3
4
5
6
7
8

本地测试可以采用path.resolve的方式引入loader。

# 总结

loader在webpack生态链里扮演了将webpack不认识的资源编译成他认识的资源的角色,他本质上就是一个nodejs模块,导出一个函数,这个函数接收当前文件资源的内容字符串。并且这个函数需要返回处理之后的资源内容。loader也分为异步和同步两种,同步的直接返回值即可,异步的需要使用webpack提供的async函数来返回处理完成的资源内容。 当我们设置loader的raw属性为true时就可以接受buffer资源而不是字符串资源。 loader的执行顺序是从右到左,但是在这之前还会从左到右的去执行一遍loader的pitch函数,这个函数可以获取到上一个和下一个loader的信息,以及可以在当前loader的data中定义属性,我们可以在loader被执行的时候再this.data中访问到。 当pitch有返回值时,那么loader的执行顺序会立即回头,并不会再按原先的顺序执行下去,而是直接掉头开始执行上一个loader。

编辑 (opens new window)
上次更新: 2023/05/17, 18:06:44
手写一个简易的WebPack
实现一个简易的Webpack-plugin

← 手写一个简易的WebPack 实现一个简易的Webpack-plugin→

最近更新
01
前端检测更新,自动刷新网页
06-09
02
swiper渲染大量数据的优化方案
06-06
03
仿抖音短视频组件实现方案
02-28
更多文章>
Theme by Vdoing | Copyright © 2023-2025 UzumakiItachi | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式