rollup 插件开发示例

✨专栏介绍

Rollup专栏是一个专门介绍Rollup打包工具的系列文章。Rollup是一个现代化的JavaScript模块打包工具,它可以将多个模块打包成一个或多个文件,以提高应用程序的性能和加载速度。

在Rollup专栏中,您将学习到如何安装和配置Rollup,以及如何使用它来打包JavaScript模块。我们将深入探讨不同类型的模块(如CommonJS、ES6等)的处理方式,以及如何处理依赖关系和循环引用。

此外,我们还将介绍一些常用的插件和配置选项,以帮助您更好地使用Rollup。您将学习到如何使用动态导入来实现按需加载,如何优化打包结果以减小文件大小,并了解与其他工具(如Babel、TypeScript等)集成使用的技巧。

通过阅读Rollup专栏,您将掌握使用这个强大工具的基本知识,并学会一些高级特性和技巧。让我们一起开始吧!

rollup 插件开发示例_第1张图片

文章目录

    • ✨专栏介绍
    • 引言
    • @rollup/pluginutils介绍
    • 插件上下文
    • 自定义插件
    • JSON插件示例
    • 图片读取
    • 总结
    • 写在结尾


引言

Rollup是一个JavaScript模块打包器,它可以将多个模块打包成一个单独的文件,以便在浏览器中使用。与其他打包工具相比,Rollup的主要优势在于它可以生成更小、更快的代码。在本文中,我们将深入了解Rollup的插件开发。

@rollup/pluginutils介绍

@rollup/pluginutils是一个官方提供的Rollup插件开发工具库,它提供了一些实用的函数和工具,用于简化插件开发过程中的一些常见任务。

该工具库提供了以下常用的函数和工具方法:

  1. createFilter(include?: string | RegExp | (string | RegExp)[], exclude?: string | RegExp | (string | RegExp)[]): FilterPattern

    • 用于创建一个过滤器,可以根据指定的包含和排除规则来过滤文件。
    • 可以传入字符串、正则表达式或字符串/正则表达式数组作为参数。
    • 返回一个函数,该函数接受文件路径作为参数,并返回一个布尔值,表示该文件是否应该被处理。
  2. makeLegalIdentifier(name: string): string

    • 用于将给定的字符串转换为合法的JavaScript标识符。
    • 主要用于处理可能包含非法字符或保留字的模块名称。
  3. dataToEsm(data: any, options?: DataToEsmOptions): string

    • 将给定的数据转换为ES模块导出语法的字符串。
    • 可以传入选项对象来自定义导出语法。
  4. attachScopes(ast: any, scope: Scope): void

    • 将作用域信息附加到AST(抽象语法树)节点上。
    • 可以帮助插件在处理代码时正确地处理变量作用域。

这些函数和工具可以帮助开发者更方便地处理文件过滤、标识符转换、数据转换和作用域处理等常见任务,提高插件开发的效率和可靠性。

插件上下文

插件上下文

这个其实也是插件中很常用的一些api,可以通过 this 从大多数钩子中访问一些实用函数和信息位。

自定义插件

rollup-plugin-custom

import { createFilter } from '@rollup/pluginutils';
import path from "path";

export default function customPlugin(options = {}) { 
  const filter = createFilter(options.include, options.exclude);

  return {
    name: "custom-plugin",
    transform(code, id) { 
      if (!filter(id)) { 
        return null;
      }

      const parsedCode = this.parse(code);

      const source = `${code} \n\n ${JSON.stringify(parsedCode, null, 2)}`;

      const fileName = path.basename(id, path.extname(id));

      console.log(fileName);

      if (options.emitFile) { 
        this.emitFile({
          type: "asset",
          fileName: fileName + ".txt",
          source
        })
      }
    }
  }

}

首先,通过 createFilter 函数创建一个过滤器,用于确定哪些文件需要被处理。options.includeoptions.exclude 分别指定了需要包含和排除的文件。

然后,返回一个对象,其中包含了插件的名称和一个 transform 方法。transform 方法会在每个模块被转换时调用。 在 transform 方法中,首先使用过滤器检查当前模块是否需要处理。如果不需要处理,则返回 null

接下来,使用 this.parse(code) 方法解析代码,并将解析结果与原始代码拼接成一个新的字符串 source。 然后,使用 path.basename(id, path.extname(id)) 获取当前模块的文件名(不包含扩展名),并打印输出。 如果设置了 options.emitFile 为 true,则调用 this.emitFile() 方法将处理后的代码作为一个 asset 文件输出。输出的文件名为当前模块的文件名加上 .txt 扩展名。

最后,这个插件可以通过在 Rollup 配置文件中引入并添加到插件列表中来使用。

rollup.config.mjs

import { defineConfig } from "rollup";
import customPlugin from "./plugins/rollup-plugin-custom.js";

export default defineConfig({
  input: "src/index.js",
  output: {
    dir: "dist",
    format: "esm",
    sourcemap: true,
  },
  plugins: [
    customPlugin({
      emitFile:true
    })
  ],
});

JSON插件示例

rollup默认是不能直接读取json文件的内容的,我们自己写一个插件处理一下。

安装

npm install @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/pluginutils -D

rollup-plugin-json

import { createFilter,dataToEsm } from '@rollup/pluginutils';
import path from 'path';

export default function myJson(options = {}) {
  // rollup 推荐每一个 transform 类型的插件都需要提供 include 和 exclude 选项,生成过滤规则
  const filter = createFilter(options.include, options.exclude);
  return {
    name: 'rollup-plugin-json',
    transform: {
      order: "pre",
      handler(code, id) {
        if (!filter(id) || path.extname(id) !== '.json') return null;
        try {
          const parse = JSON.stringify(JSON.parse(code));
          return {
            // dataToEsm 将数据转换成esm模块
            // 其实就是 export default "xxx"
            code: dataToEsm(parse), 
            map: { mappings: '' }
          };
        } catch (err) { 
          const message = 'Could not parse JSON file';
          this.error({ message, id, cause: err });
          return null;
        }
      }
    }
  };
}

首先,通过 createFilter 方法创建一个过滤器,用于确定哪些文件需要被处理。options.includeoptions.exclude 分别指定了需要包含和排除的文件。

然后,返回一个对象,其中包含了插件的名称和一个 transform 对象。transform 对象中有两个属性:orderhandler

  • order: "pre" 表示这个插件在转换过程中应该在其他插件之前执行。
  • handler(code, id) 是一个处理函数,它会在每个模块被转换时调用。

handler 函数中,首先使用过滤器检查当前模块是否需要处理,并且判断当前模块是否是 JSON 文件。如果不需要处理或者不是 JSON 文件,则返回 null

接下来,尝试将代码解析为 JSON 对象,并使用 dataToEsm(parse) 方法将解析后的对象转换为 ES 模块格式的代码。然后返回一个对象,其中包含了转换后的代码和一个空的 Source Map。

如果解析过程中出现错误,则会捕获错误并通过调用 this.error() 方法抛出错误信息,并返回 null

最后,这个插件可以通过在 Rollup 配置文件中引入并添加到插件列表中来使用。它会在构建过程中将 JSON 文件转换为 ES 模块格式的代码。

页面使用

import pkg from "../package.json";
console.log(pkg.name)

图片读取

mini-svg-data-uri是一个用于将SVG图像转换为mini data URI格式的JavaScript库。它可以将SVG图像的内容转换为base64编码,并生成一个data URI,以便在HTML或CSS中直接使用。

安装mini-svg-data-uri

npm install mini-svg-data-uri -D

rollup-plugin-image

import { createFilter,dataToEsm } from "@rollup/pluginutils";
import { extname,resolve,basename,relative,normalize,sep } from "path";
import fs from "fs";
import svgToMiniDataURI from "mini-svg-data-uri";

const defaults = {
  fileSize: 1024 * 4,
  target: "./dist",
  include: null,
  exclude: null,
}

const mimeTypes = {
  ".png": "image/png",
  ".jpg": "image/jpeg",
  ".jpeg": "image/jpeg",
  ".gif": "image/gif",
  ".svg": "image/svg+xml",
  ".ico": "image/x-icon",
  ".webp": "image/webp",
  ".avif": "image/avif"
}

const getDataUri = ({ format, isSvg, mime, source }) =>
  isSvg ? svgToMiniDataURI(source) : `data:${mime};${format},${source}`;


const ensureDirExists = async (dirPath) => { 
  try {
    await fs.promises.access(dirPath);
    return true;
  } catch (err) { 
    // 文件夹不存在就创建文件夹
    try {
      await fs.promises.mkdir(dirPath, { recursive: true });
      return true;
    }
    catch (err) { 
      console.error(err);
      return false;
    }
    
  }
}

export default function myImage(opts = {}) { 
  const options = Object.assign({}, defaults, opts);
  const filter = createFilter(options.include, options.exclude);
  return {
    name: "my-image",
    async transform(code, id) { 
      if (!filter(id)) return null;
      
      // 获取后缀
      const ext = extname(id);
      // 判断是否是图片
      if(!mimeTypes.hasOwnProperty(ext)) {
        return null;
      }

      // 获取图片的mime类型
      const mime = mimeTypes[ext];
      // 判断是否svg
      const isSvg = mime === mimeTypes[".svg"];
      // 图片format格式
      const format = isSvg ? "utf-8" : "base64";

      // 目标路径
      const assetsPath = resolve(process.cwd(), options.target);

      //获取文件名
      const fileName = basename(id);
      // 最终文件路径
      const filePath = resolve(assetsPath, fileName);

      let relativePath = normalize(relative(process.cwd(), filePath));
      relativePath = relativePath.substring(relativePath.indexOf(sep) + 1);

      try {

        // 如果图片文件过大,就应该直接拷贝文件,返回文件路径
        // 读取图片文件大小与设置的大小进行比较
        const stat = await fs.promises.stat(id);
        if (stat.size > options.fileSize) {
          // 文件的拷贝,以及对象的返回
          // 文件拷贝,无非就是文件源路径,目标路径
          // copyFile 拷贝文件地址的文件夹必须存在
          // 如果文件夹不存在,那么就创建文件夹
          const dirExists = await ensureDirExists(assetsPath);
          dirExists && await fs.promises.copyFile(id, filePath);
          return {
            code: dataToEsm(relativePath), //返回拷贝之后处理的路径
            map: { mappings: "" }
          }

        } else {
          // 否则转换为base64格式
          // 读取文件
          const source = await fs.promises.readFile(id, format);

          return {
            code: dataToEsm(getDataUri({ format, isSvg, mime, source })),
            map: { mappings: "" }
          }
        }

      } catch (err) { 
        const message = "图片转换失败:" + id;
        this.error({ message, id, cause: err });
        return null;
      }

    }
  }
}
  1. createFilter(include, exclude): 这个函数来自于@rollup/pluginutils包,用于创建一个过滤器函数,根据给定的include和exclude规则来判断文件是否需要被处理。
  2. extname(id): 这个函数来自于path模块,用于获取文件路径的扩展名。
  3. resolve(...paths): 这个函数来自于path模块,用于将多个路径片段解析为绝对路径。
  4. basename(path): 这个函数来自于path模块,用于获取文件路径的基本名称(不包含目录部分)。
  5. relative(from, to): 这个函数来自于path模块,用于获取从一个路径到另一个路径的相对路径。
  6. normalize(path): 这个函数来自于path模块,用于规范化给定的路径字符串。
  7. sep: 这是一个常量,表示操作系统特定的路径分隔符(例如,在Windows上是反斜杠``)。
  8. fs.promises.access(path): 这是一个Promise-based API,用于检查指定路径是否可访问。
  9. fs.promises.mkdir(path, options): 这是一个Promise-based API,用于创建指定路径的目录。options参数可以包含递归选项,以便创建多级目录。
  10. fs.promises.stat(path): 这是一个Promise-based API,用于获取指定路径的文件信息,例如文件大小。
  11. fs.promises.copyFile(src, dest): 这是一个Promise-based API,用于将源文件复制到目标文件。
  12. fs.promises.readFile(path, encoding): 这是一个Promise-based API,用于读取指定路径的文件内容。encoding参数用于指定读取的编码格式。
  13. dataToEsm(value): 这个函数来自于@rollup/pluginutils包,用于将给定的值转换为ES模块导出语法。
  14. svgToMiniDataURI(svg): 这个函数来自于mini-svg-data-uri包,用于将SVG图像转换为mini data URI格式。

在插件的transform方法中,首先使用过滤器函数判断是否需要处理当前文件。然后根据文件扩展名判断是否为图片文件,并获取对应的MIME类型。接下来根据配置的目标路径和文件名构建最终的文件路径。如果图片文件大小超过了设置的阈值,则直接拷贝该文件到目标路径,并返回拷贝后的路径。否则,将图片内容转换为base64格式,并返回对应的data URI。

rollup.config.mjs

import { defineConfig } from "rollup";
import imagePlugin from './plugins/rollup-plugin-image.js'

export default defineConfig({
  input: "src/index.js",
  output: {
    dir: "dist",
    format: "esm",
    sourcemap: true,
  },
  plugins: [
    imagePlugin({
      fileSize: 1024 * 10,
      target: './dist/assets'
    })
  ],
});

总结

  1. Rollup插件机制允许开发者通过编写自定义插件来扩展Rollup的功能。

  2. 插件是由一个或多个钩子函数组成的,钩子函数定义了在打包过程中的不同阶段执行的操作。

  3. 常用的钩子函数有optionsresolveIdloadtransformgenerateBundle,每个钩子函数都有特定的调用时机和参数。

  4. 插件可以通过返回一个Promise对象来处理异步操作。

  5. Rollup插件可以使用第三方库来辅助开发,例如rollup-pluginutils用于创建过滤器。

  6. 开发者可以根据自己的需求编写自定义插件,并将其添加到Rollup配置中,以实现各种功能扩展,例如压缩代码、处理CSS、加载和解析JSON等。

  7. 插件开发需要注意性能和代码质量,避免不必要的操作和副作用。

通过使用Rollup插件机制,开发者可以灵活地定制打包过程,并根据项目需求添加各种功能扩展。这使得Rollup成为一个强大而灵活的JavaScript模块打包工具。


写在结尾

前端设计模式专栏
在这里插入图片描述
设计模式是软件开发中不可或缺的一部分,它们帮助我们解决了许多常见问题,并提供了一种优雅而可靠的方式来构建应用程序。在本专栏中,我们介绍了所有的前端设计模式,包括观察者模式、单例模式、策略模式等等。通过学习这些设计模式,并将其应用于实际项目中,我们可以提高代码的可维护性、可扩展性和可重用性。希望这个专栏能够帮助你在前端开发中更好地应用设计模式,写出高质量的代码。点击订阅前端设计模式专栏

Vue专栏
在这里插入图片描述
Vue.js是一款流行的JavaScript框架,用于构建用户界面。它采用了MVVM(Model-View-ViewModel)的架构模式,通过数据驱动和组件化的方式,使开发者能够更轻松地构建交互性强、可复用的Web应用程序。在这个专栏中,我们将深入探讨Vue.js的核心概念、组件开发、状态管理、路由和性能优化等方面的知识。我们将学习如何使用Vue.js构建响应式的用户界面,并探索其强大的生态系统,如Vue Router和Vuex、Pinia。通过学习这些内容,你将能够成为一名熟练的Vue.js开发者,并能够应用这些知识来构建复杂而高效的Web应用程序。点击订阅Vue专栏

JavaScript(ES6)专栏在这里插入图片描述
JavaScript是一种广泛应用于网页开发和后端开发的脚本语言。它具有动态性、灵活性和易学性的特点,是构建现代Web应用程序的重要工具之一。在这个专栏中,我们将深入探讨JavaScript语言的基本语法、DOM操作、事件处理、异步编程以及常见算法和数据结构等内容。此外,我们还将介绍ES6(ECMAScript 2015)及其后续版本中引入的新特性,如箭头函数、模块化、解构赋值等。通过学习这些内容,你将能够成为一名熟练的JavaScript开发者,并能够应用这些知识来构建出高质量和可维护的Web应用程序。点击订阅JavaScript(ES6)专栏

你可能感兴趣的:(rollup,rollup,前端,打包工具)