什么是 Loader、手写 Webpack Loader

news2024/9/25 13:24:58

目录

1. 什么是 Loader

1.1 Loader 工作原理

1.2 Loader 执行顺序

1.3 内联 Loader 前缀​​​​​​​

2. 如何开发 Loader

2.1 Loader 长什么样子

2.2 配置本地 Loader 的四种方法

2.2.1 在配置 rules 时,指定 Loader 的绝对路径

2.2.2 在 resolveLoader 里,配置 alias 别名属性

2.2.3 在 resolveLoader 里,配置 modules 属性

2.2.4 使用 npm link 引用 Loader

2.2.5 配置完成后,测试下 2.1 中的简单 Loader

2.3 带 pitch 的 Loader —— 阻断 Loader 链

2.4 手写 mini style-loader

2.4.1 style-loader 需求描述

2.4.2 mini style-loader 设计思路

2.4.3 mini style-loader 代码编写

2.4.4 使用 mini style-loader

2.4.5 在 Vue 项目中使用 Loader

3. 开发 Loader 必备工具包

4. 开发异步 Loader

5. Loader raw 设为 true,用于支持 二进制格式资源

6. file-loader 基本原理、输出文件

7. Loader 开发约定

8. 参考文章


1. 什么是 Loader

1.1 Loader 工作原理

Webpack 只能直接处理 JavaScript 代码

任何非 JavaScript 文件,都必须被预先处理为 JavaScript 代码,才可以参与打包

Loader(加载器)就是这样一个代码转换器:

  • 它由 Webpack 的 loader runner 执行调用,接收原始资源数据作为参数
  • 当多个加载器联合使用时,上一个 Loader 的结果,会传入下一个 Loader
  • 最终输出 JavaScript 代码(和可选的 source map)给 Webpack 做进一步编译

1.2 Loader 执行顺序

  • pre: 前置 Loader
  • normal: 普通 Loader
  • inline: 内联 Loader
  • post: 后置 Loader

执行优先级为:pre > normal > inline > post

相同优先级的 Loader 执行顺序为:从右到左,从下到上

举个栗子~

use: ['loader1', 'loader2', 'loader3'],执行顺序为 loader3 → loader2 → loader1

1.3 内联 Loader 前缀

内联 Loader 可以通过添加不同前缀,跳过其他类型 Loader

  • ! 跳过 normal loader
  • -! 跳过 pre 和 normal loader
  • !! 跳过 pre、 normal 和 post loader

1.4 常用的 Loader

名称作用
style-loader用于将 css 编译完成的样式,挂载到页面 style 标签上
css-loader用于识别 .css文件, 须配合 style-loader 共同使用
sass-loader/less-loadercss 预处理器
postcss-loader用于补充 css 样式各种浏览器内核前缀
url-loader处理图片类型资源,可以转 base64
vue-loader用于编译 .vue 文件
worker-loader通过内联 loader 的方式,使用 web worker 功能
style-resources-loader全局引用对应的 css,避免页面再分别引入

2. 如何开发 Loader

2.1 Loader 长什么样子

Loader 本质是一个 Node 模块,该模块导出一个函数

函数接收 source (源文件),返回处理后的source

比如下面的 Loader,接收源码,打印文字,原样返回源码

// loaders/simple-loader.js

// Loader 本质是一个 Node 模块,该模块导出一个函数
module.exports = function loader (source) {
  console.log('Lyrelion simple-loader is working');
  return source;
}

2.2 配置本地 Loader 的四种方法

为了测试 2.1 中的 Loader,需要在 webpack.config.js 里,配置 Loader

Webpack 默认会去 node_modules 里找所有第三方模块

通过 npm 或者 yarn 安装的 Loader,配置时只需直接使用 Loader 的名字,不用关心 Loader 的路径(因为他们都会安装在 node_modules 目录下)

如果使用本地自己开发的 Loader,也就是他们不在 node_modules 里,就需要告诉 Webpack Loader 的位置

在 Webpack4.0 里,有四种方法配置本地 Loader

2.2.1 在配置 rules 时,指定 Loader 的绝对路径

module.exports = {
  // xxx
  module: {
    rules: [
      {
        test: /\.js$/,
        // 在这里配置绝对路径
       use: path.resolve(__dirname, 'loaders/myLoader.js')
      }
    ]
  }
}

2.2.2 在 resolveLoader 里,配置 alias 别名属性

module.exports = {
  // xxx
  resolveLoader: {
  // 配置 resolveLoader.alias
    alias: {
      myLoader: path.resolve(__dirname, 'loaders/myLoader.js')
    }
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'myLoader'
      }
    ]
  }
}

2.2.3 在 resolveLoader 里,配置 modules 属性

将可能放置 Loader 的目录,存放到 resolveLoader.modules 数组中,告诉 Webpack

当 Webpack 在默认目录下,找不到指定 Loader 时,会自动去 resolveLoader.modules 数组 中查找

module.exports = {
  // xxx
  resolveLoader: {
  // 配置 resolveLoader.modules
    modules: ['node_modules', path.resolve(__dirname, 'loaders']
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'myLoader'
      }
    ]
  }
}

2.2.4 使用 npm link 引用 Loader

基本步骤:

  • 把 Loader 从当前项目抽离出来,构建独立工程
  • 在 Loader 工程目录下,执行 npm link,在全局 link 中添加 loader
  • 回到原项目目录,执行 npm link xxx (xxx 为 Loader 的名称),实现 require Loader
  • 最后,在原项目中,直接使用 Loader 名称即可 (跟 npm install 的 Loader 一样用法)

关于 npm link,可以参考下面的文章:

使用 npm link 测试本地编写的 node 模块 / 引入全局安装的 node 模块_Lyrelion的博客-CSDN博客使用 npm link 测试本地编写的 node 模块 / 引入全局安装的 node 模块https://blog.csdn.net/Lyrelion/article/details/128506812

2.2.5 配置完成后,测试下 2.1 中的简单 Loader

在 webpack.config.js 中,输入以下内容:

// webpack.config.js

const path = require('path');
module.exports = {
  entry: {...},
  output: {...},
  module: {
    rules: [
      {
        test: /\.js$/,
        // 指明 Loader 的绝对路径
        use: path.resolve(__dirname, 'loaders/simple-loader')
      }
    ]
  }
}

 

执行打包命令 yarn build,输出结果如下:

 

2.3 带 pitch 的 Loader —— 阻断 Loader 链

pitch 是 Loader 上的一个方法(非必须的),它的作用 —— 阻断 Loader 链

如果有 pitch,Loader 的执行会分为两个阶段:

  • pitch 阶段 —— Webpack 会先 从左到右 执行 Loader 链中,每个 Loader 上的 pitch 方法(如果有)
  • normal execution 阶段 —— Webpack 会再 从右到左 执行 Loader 链中,每个 Loader 上的普通 Loader 方法

举个栗子~~ 假设配置了下面的 Loader

use: ['loader1', 'loader2', 'loader3']

 

Loader 执行过程:

 

在这个过程中,如果任何 pitch 有返回值,则 Loader 链被阻断

Webpack 会跳过后面所有的的 pitch 和 loader,直接进入上一个 Loader 的 normal execution 阶段

 

也就是说,假设在 loader2 的 pitch 中返回了一个字符串,此时 Loader 链发生阻断:

 

pitch 方法有三个参数:

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

2.4 手写 mini style-loader

// 将 css 内容,通过 style 标签插入到页面中

// source —— 要处理的 css 源文件
function loader(source) {
  let style = `
    let style = document.createElement('style');
    style.setAttribute("type", "text/css"); 
    style.innerHTML = ${source};
    document.head.appendChild(style)`;
  return style;
}
module.exports = loader;

 

2.4.1 style-loader 需求描述

style-loader 通常不会独自使用,而是跟 css-loader 连用

css-loader 的返回值是一个 JavaScript 模块,大致长这样:

// 打印 css-loader 的返回值

// Imports
var ___CSS_LOADER_API_IMPORT___ = require("../node_modules/css-loader/dist/runtime/api.js");
exports = ___CSS_LOADER_API_IMPORT___(false);
// Module
exports.push([module.id, "\nbody {\n    background: yellow;\n}\n", ""]);
// Exports
module.exports = exports;

这个模块在运行时,返回一段字符串代码 —— “\nbody {\n background: yellow;\n}\n”

style-loader 的作用:将这段 css 代码,转成 style 标签,插入到 html 的 head 中

2.4.2 mini style-loader 设计思路

style-loader 最终需返回一个 JavaScript 脚本:在脚本中创建一个 style 标签,将 css 代码赋给 style 标签,再将这个 style 标签插入 html 的 head 中

难点:获取 css 代码;因为 css-loader 的返回值只能在 运行时 的上下文中执行,而执行 Loader 是在编译阶段。换句话说,css-loader 的返回值在 style-loader 里派不上用场


曲线救国方案:使用获取 css 代码的表达式,在运行时再获取 css (类似 require('css-loader!index.css'))

在处理 css 的 loader 中又去调用 inline loader require css 文件,会产生循环执行 loader 的问题:

  • 需要利用 pitch 方法,让 style-loader 在 pitch 阶段返回脚本,跳过剩下的 loader
  • 同时还需要内联前缀 !! 的加持

注:pitch 方法有3个参数:

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

可以利用 remainingRequest 参数获取 loader 链的剩余部分

2.4.3 mini style-loader 代码编写

// loaders/simple-style-loader.js

const loaderUtils = require('loader-utils');
module.exports = function (source) {
  // do nothing
}

/**
 * @param {*} remainingRequest loader 链中排在自己后面的 loader 以及资源文件的绝对路径以!作为连接符组成的字符串
 * @returns 
 */
module.exports.pitch = function (remainingRequest) {
  console.log('Lyrelion simple-style-loader is working');
  // 在 pitch 阶段返回脚本
  return (
    `
      // 创建 style 标签
      let style = document.createElement('style');

      /**
      * 利用 remainingRequest 参数获取 loader 链的剩余部分
      * 利用 ‘!!’ 前缀跳过其他 loader 
      * 利用 loaderUtils 的 stringifyRequest 方法将模块的绝对路径转为相对路径
      * 将获取 css 的 require 表达式赋给 style 标签
      */
      style.innerHTML = require(${loaderUtils.stringifyRequest(this, '!!' + remainingRequest)});

      // 将 style 标签插入 head
      document.head.appendChild(style);
      `
  )
}

2.4.4 使用 mini style-loader

webpack.config.js

// webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: {...},
  output: {...},
  // 手动配置 loader 路径
  resolveLoader: {
    modules: [path.resolve(__dirname, 'loaders'), 'node_modules']
  },
  module: {
    rules: [
      {
        // 配置处理 css 的 loader
        test: /\.css$/,
        use: ['simple-style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    // 渲染首页
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
}

src/index.css

body {
  background: pink;
}

src/index.js

require('./index.css');
// let p = require('./nodejs.png');
// console.log(p);

console.log('----------- Lyrelion 测试 Loader -----------');

 

目录整体结构: 

打包后的页面效果展示:

 

2.4.5 在 Vue 项目中使用 Loader

在 vue.config.js 引入 Loader:

const MyStyleLoader = require('./simple-style-loader')

 

在 configureWebpack 中,添加配置:

module.exports = {
  configureWebpack: {
    module: {
      rules: [
        {
          // 对 main.css 文件使用 MyStyleLoader 处理
          test: /main.css/,
          loader: MyStyleLoader
        }
      ]
    }
  }
};

 

3. 开发 Loader 必备工具包

loader-utils

该模块中,常用的几个方法:

  • getOptions 获取 loader 的配置项
  • interpolateName 处理生成文件的名字
  • stringifyRequest 把绝对路径处理成相对根目录的相对路径

 

schema-utils 

该模块用于验证 loader option 配置的合法性

使用方法:

// loaders/simple-loader-with-validate.js

const loaderUtils = require('loader-utils');
const validate = require('schema-utils');
module.exports = function(source) {
  // 获取 loader 配置项
  let options = loaderUtils.getOptions(this) || {};
  // 定义配置项结构和类型
  let schema = {
    type: 'object',
    properties: {
      name: {
        type: 'string'
      }
    }
  }
  // 验证配置项是否符合要求
  validate(schema, options);
  return source;
}

4. 开发异步 Loader

异步 Loader 的开发(例如:需要读取文件的操作),需要通过 this.async() 获取异步回调,然后手动调用它

使用方法:

// loaders/simple-async-loader.js

module.exports = function(source) {
    console.log('async loader');
    let cb = this.async();
    setTimeout(() => {
      console.log('ok');
      // 在异步回调中手动调用 cb 返回处理结果
      cb(null, source);
    }, 3000);
}

PS:异步回调 cb() 的第一个参数是 error,第二个参数是返回结果

5. Loader raw 设为 true,用于支持 二进制格式资源

Webpack 默认是以 utf-8 的格式读取文件内容给 Loader

如果是用于处理 图片、字体 等资源的 Loader,需要将 Loader 上的 raw 属性设置为 true,让 loader 支持二进制格式资源

使用方法:

// loaders/simple-raw-loader.js

module.exports = function(source) {
  // 将输出 buffer 类型的二进制数据
  console.log(source);
  // todo handle source
  let result = 'results of processing source'
  return `
    module.exports = '${result}'
  `;
}

// 告诉 webpack 这个 loader 需要接收的是二进制格式的数据
module.exports.raw = true;

 

6. file-loader 基本原理、输出文件

在开发一些处理资源文件(比如图片、字体等)的 Loader 中,需要拷贝或生成新的文件,可以使用内部的 this.emitFile() 方法

 

file-loader 基本原理:

  • Loader 读取图片内容(buffer),将其重命名
  • 调用 this.emitFile() 输出到指定目录
  • 返回一个模块,这个模块导出重命名后的图片地址
  • 最终实现了:当 require 图片的时候,就相当于 require 了一个模块,从而得到图片路径

基本用法:

// loaders/simple-file-loader.js

const loaderUtils = require('loader-utils');

module.exports = function(source) {
  // 获取 loader 的配置项
  let options = loaderUtils.getOptions(this) || {};

  // 获取用户设置的文件名或者制作新的文件名
  // 注意第三个参数,是计算 contenthash 的依据
  let url = loaderUtils.interpolateName(this, options.filename || '[contenthash].[ext]', {content: source});

  // 输出文件
  this.emitFile(url, source);

  // 返回导出文件地址的模块脚本
  return `module.exports = '${JSON.stringify(url)}'`;
}

module.exports.raw = true;

 

7. Loader 开发约定

Loader 的本质是一个 node 模块,这个模块导出一个函数,这个函数上可能还有一个 pitch 方法

了解了 Loader 的本质、Loader 链的执行机制,其实就已经具备了 Loader 开发基础了

开发 Loader 不难上手,但是要开发一款高质量的 Loader,仍需不断实践

 

在 Webpack 社区,有一份 loader 开发准则:

  • 保持简单
  • 利用多个 loader 链
  • 模块化输出
  • 确保 loader 是无状态的
  • 使用 loader-utils 包
  • 标记加载程序依赖项
  • 解析模块依赖关系
  • 提取公共代码
  • 避免绝对路径
  • 使用 peerDependency 对等依赖项

8. 参考文章

揭秘webpack loader | ChampYin's BlogLoader(加载器) 是 webpack 的核心之一。它用于将不同类型的文件转换为 webpack 可识别的模块。本文将尝试深入探索 webpack 中的 loader,揭秘它的工作原理,以及如何开发一个 loader。https://champyin.com/2020/01/28/%E6%8F%AD%E7%A7%98webpack-loader/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/131464.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows配置万德(Wind)量化接口

原理:wind会在python的第三方库中安装一个属于wind的库 文章目录步骤1:确定python的路径步骤2:配置wind的接口步骤3:检查配置步骤4:使用python提取任意的wind数据步骤1:确定python的路径 如果是默认安装,一般路径是:C:\Users\用户名\Anacond…

磨金石教育摄影技能干货分享|优秀作品欣赏—技巧十足的艺术摄影

想要赏析艺术类的摄影,就得立足于画面身后的意蕴,想作者所想,思作者所思。 这有一定的难度,但也不乏趣味。 今天我们就再来看一组艺术类摄影作品,看看作者如何用高明的技巧表达自己心中的感受吧。 1、江苏省-李玉龙-《…

表白墙 -- 前后端代码详解

表白墙 -- 前后端代码详解一、前端二、后端实现2.1 需求2.2 创建项目及初始化2.3 实现提交数据 (存档)2.3.1 实现 doPost2.3.2 构造请求 (修改 html 文件)2.3.3 验证2.4 实现获取数据 (读档)2.4.1 实现 doGet2.4.2 构造请求 (修改 html 文件)2.4.3 验证三、JDBC 版本 (MySQL)3.…

回味2022

回味20221.前言2.过去的十二个月3.我期望的20231.前言 2021年写给自己的总结:回味2021 一年又一年飞逝的光阴,我想唯有时间留给人的印象最为深刻吧。春去秋来,四季轮回间都是时光的印记。2022年12月30日,25岁的我依旧在这间写下2…

从socket开始讲解网络模式

从socket开始讲解网络模式 windows采用IOCP网络模型,而linux采用epoll网络模型(Linux得以实现高并发,并被作为服务器首选的重要原因),接下来讲下epoll模型对网络编程高并发的作用 简单的socket连接 socket连接交互的…

LaoCat带你认识容器与镜像(一)

准备更新一个容器与镜像相关的系列,从Docker到K8s的入门再到实际项目进阶应用,这里感谢好朋友泽鹏,是他让我结识容器与镜像;也感谢上家公司菲恩曼,是它给了我去学习、实践的机会;最后感谢翼哥,一…

Linux系统下at任务调度机制

Linux系统下at任务调度机制 基本介绍 at命令是一次性定时计划任务,at 的守护进程 atd 会以后台模式运行,检查作业队列来运行。默认情况下,atd 守护进程每60秒检查作业队列,有作业时,会检查作业运行时间,如果…

深入理解计算机系统_可重定位目标文件的格式---elf格式

本篇笔记记录可重定位目标文件的格式— elf格式,也是《深入理解计算机系统》第7章的内容。了解这些内容,对我们很有帮助,比如代码排错,可以深入了解C/C 实现原理。 分别介绍如何得到可重定位目标文件及其格式。 2.1 如何得到可重…

操作系统~Linux~线程的互斥,mutex互斥锁的使用及其原理

1.一些基本概念 1.临界资源:凡是被线程共享访问的资源都是临界资源(多线程、多进程打印数据到显示器,显示器就是临界资源) 2.临界区:代码中访问临界资源的代码(在代码中,…

kotlin学习笔记之注解与反射

一、声明并应用注解 一个注解允许你把额外的元数据关联到一个声明上。然后元数据就可以被相关的源代码工具访问,通过编译好的类文件或是在运行时,取决于这个注解是如何配置的。 1、应用注解 在kotlin中使用注解的方法和java一样。要应用一个注解&#xf…

如何通过3个月自学成为网络安全工程师!

前言: 趁着今天下班,我花了几个小时整理了下,非常不易,希望大家可以点赞收藏支持一波,谢谢。 我的经历: 我 19 年毕业,大学专业是物联网工程,我相信很多人在象牙塔里都很迷茫&…

Pycharm配置关于pyside6的外部工具

文章目录一、前言二、Pycharm配置1、designer.exe(1)打开Pycharm的设置(2)相关参数(可复制粘贴)2、Pyside6-uic.exe(1)设置(2)相关参数(可复制粘贴…

Java--抽象类和接口的区别

今天是22年最后一天了, 写篇博客记录一下吧, 这一年发生了很多事情, 也学到了很多知识, 后面要继续加油啊, 大家也要加油啊, 米娜桑. 目录 概述 区别 1. 定义关键字不同 2. 继承或实现的关键字不同 3. 子类扩展的数量不同 4. 属性访问控制符不同 5. 方法控制符不同 6.…

python互联网程序设计GUI程序设计和网络程序设计(人机互动聊天软件)

1.项目意义 1、了解网络的结构; 2、了解网络传输协议; 3、掌握基本的网络编程方法。 2.总体设计 使用 TCP 协议实现人机聊天互动,程序具有服务端和客户端: (1)必备功能&#xff1…

Java财务在线咨询网站系统财务咨询网

简介 财务咨询网站,可以咨询公司代办,代理记账等一系列的财务问题的资讯服务网站 演示视频 https://www.bilibili.com/video/BV1T54y1H7Ar/?share_sourcecopy_web&vd_sourceed0f04fbb713154db5cc611225d92156 角色 管理员客服注册用户游客 技术…

Spring之DI入门案例

目录 一:DI入门案例实现思路分析 1.要想实现依赖注入,必须要基于 IOC 管理 Bean 2.Service 中使用 new 形式创建的 Dao 对象是否保留 ? 3.Service 中需要的 Dao 对象如何进入到 Service 中 ? 4.Service 与 Dao 间的关系如何描述 ? 二&#xff1…

(Qt) cmake编译Qt项目

文章目录前言环境cmake基础预备的项目代码文件资源路径demo.promain.cppres.qrcmywidget.cppmywidget.hmywidget.ui运行效果CMake文件资源路径CMakeLists.txt生成与构建END前言 通常我们在编写qt的时候都是在Qt creator中。而如何在VS Code中编写qt就是本文需要解决的问题 环…

顺序表 —— 初始化、销毁、打印、增加、删除、查找、修改

1.何为线性表 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…线性表在逻辑上是线性结构,也就说是连续的一条直…

zotero导出pdf

今天老师给我改论文的时候布置了一个任务,让我把所有论文的pdf按格式打包发给她。可是之前我用zotero的时候都是在线保存的,有些是没有pdf的,怎么办?而且就算有pdf,他们的命名格式也五花八门,难道一个个手改…

kafka 消息日志原理 指定偏移量消费 指定时间戳消费

Kafka 日志详解 Apache Kafka日志存储在物理磁盘上各种数据的集合,日志按照topic分区进行文件组织,每一个分区日志由一个或者多个文件组成。生产者发送的消息被顺序追加到日志文件的末尾。 如上图所述,Kafka主题被划分为3个分区。在Kafka中&…