前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入了解rollup(四)插件开发示例

深入了解rollup(四)插件开发示例

原创
作者头像
can4hou6joeng4
发布2023-11-16 15:27:02
3670
发布2023-11-16 15:27:02
举报
文章被收录于专栏:前端到底怎么学好来

引言

--

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

@rollup/pluginutils介绍


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

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

  1. createFilter(include?: string | RegExp | (string | RegExp)[], exclude?: string | RegExp | (string | RegExp)[]): FilterPattern
代码语言:txt
复制
*   用于创建一个过滤器,可以根据指定的包含和排除规则来过滤文件。
*   可以传入字符串、正则表达式或字符串/正则表达式数组作为参数。
*   返回一个函数,该函数接受文件路径作为参数,并返回一个布尔值,表示该文件是否应该被处理。
代码语言:txt
复制
*   用于将给定的字符串转换为合法的JavaScript标识符。
*   主要用于处理可能包含非法字符或保留字的模块名称。
代码语言:txt
复制
*   将给定的数据转换为ES模块导出语法的字符串。
*   可以传入选项对象来自定义导出语法。
代码语言:txt
复制
*   将作用域信息附加到AST(抽象语法树)节点上。
*   可以帮助插件在处理代码时正确地处理变量作用域。

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

插件上下文


插件上下文

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

自定义插件


rollup-plugin-custom

代码语言:javascript
复制
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

代码语言:javascript
复制
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文件的内容的,我们自己写一个插件处理一下。

安装

代码语言:shell
复制
npm install @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/pluginutils -D

rollup-plugin-json

代码语言:javascript
复制
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 模块格式的代码。

页面使用

代码语言:javascript
复制
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

代码语言:shell
复制
npm install mini-svg-data-uri -D

rollup-plugin-image

代码语言:javascript
复制
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

代码语言:javascript
复制
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模块打包工具。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档