rollup.js 插件实现原理与自定义

devtools/2024/10/18 10:25:58/

Rollup.js 是一个JavaScript模块打包器,它主要用于将小块代码编译成大块复杂的库或应用程序。相较于Webpack,Rollup更专注于代码的ES模块转换和优化,特别适合构建库或者那些对代码体积、执行效率有严格要求的应用。Rollup的核心特性之一就是它的插件系统,这使得其高度可扩展,可以很容易地通过插件来支持各种编译转换、代码分析、资源处理等任务。

Rollup.js 插件实现原理

Rollup的插件主要基于JavaScript编写,每个插件都是一个对象,至少需要实现一个特定的函数(如transformloadresolveId等),这些函数会在Rollup构建的不同阶段被调用,以执行相应的任务。插件的工作流程大致可以分为以下几个阶段:

  1. 解析(Resolving): 在此阶段,插件可以帮助解析模块的导入语句,决定如何处理这些导入(比如别名、路径映射等)。常用的钩子函数有resolveIdload
  2. 转换(Transforming): 这是插件最常介入的阶段,用于将源代码转换为浏览器或其他环境可理解的格式。例如,将ES6+语法转换为ES5,或将TypeScript转换为JavaScript。主要使用的钩子函数是transform
  3. 捆绑(Bundling): 在这个阶段,Rollup会根据模块之间的依赖关系生成最终的捆绑包。虽然这一阶段更多的是Rollup核心的功能,但插件可以通过提供模块信息(如moduleInfo钩子)来影响捆绑过程。
  4. 输出(Outputting): 最后,当所有模块被处理并捆绑后,插件可以通过修改或添加到输出中来进一步处理生成的代码或资源,如压缩代码、添加元数据等。常用钩子包括generateBundlerenderChunk

其他常用钩子函数

在 Rollup 中开发插件时,你可以通过使用一系列的钩子函数来控制构建过程的不同阶段。下面是一些常用的钩子函数及其简要说明:

  1. options - 这个钩子允许你在解析命令行选项后修改最终的配置对象。这对于基于环境动态调整配置非常有用。
  2. buildStart - 当构建开始时触发,可用于执行一些初始化工作,比如清理输出目录。
  3. resolveId - 在尝试加载模块前调用此钩子,可以用来重定向模块请求到其他路径,或者处理虚拟模块等场景。
  4. load - 当 Rollup 需要从文件系统或其他来源加载源码时会调用这个钩子。你可以返回自定义内容代替实际文件的内容,适用于注入全局变量、模拟数据等情况。
  5. transform - 该钩子允许你转换已加载的源代码。这是实现代码转译(如 Babel)、添加头部注释等操作的好地方。
  6. moduleParsed - 每当一个模块被完全解析之后都会调用这个钩子,这使得可以在生成图表之前对模块进行额外处理。
  7. renderStart - 开始渲染输出时触发,适合于需要根据输出格式做不同处理的情况。
  8. generateBundle - 当所有文件和 chunk 已经生成完毕但还未写入磁盘时调用。可以通过此钩子访问并修改即将输出的所有资源。
  9. writeBundle - 文件已经写入磁盘后触发。如果你需要在构建完成后执行某些任务(例如运行测试),那么这是一个很好的时机。
  10. closeBundle - 整个构建过程结束时调用。对于清理临时文件或者其他收尾工作很有帮助。
  11. watchChange - 当监听模式下检测到文件变动时触发,可用于决定是否应该重新构建整个项目还是只更新特定部分。
     

上下文环境

每个Rollup插件在执行过程中都可以访问到一些上下文信息(context),这些信息对于编写高效的插件非常有用。当你为Rollup创建一个插件时,可以通过配置对象来定义几个钩子函数(hooks)。这些钩子函数会在构建过程中的不同阶段被调用,并且会接收到不同的参数,其中包括了当前的插件上下文。以下是Rollup插件中可用的一些主要上下文属性和方法:

  1. options: 包含传递给Rollup的原始选项对象。这对于理解用户是如何配置Rollup以及可能需要调整你的插件行为是非常有用的。
  2. moduleIds: 一个映射表,用于存储模块ID与其对应的输出文件名之间的关系。这有助于追踪哪些模块被如何命名。
  3. getModuleInfo(id): 根据提供的ID获取模块的具体信息。
  4. emitFile(options): 允许向最终输出添加额外的文件。这对于生成除了JavaScript之外的其他资源文件很有帮助。
  5. setAsset(name, source, options): 设置一个非JavaScript资产,类似于emitFile()但更专注于处理非JS资源。
  6. error(message, code, loc, frame, pos, id, pluginCode, url, hint): 抛出错误的一种方式,支持丰富的错误信息格式化选项,便于调试。

如何自定义Rollup插件?

自定义Rollup插件通常涉及以下步骤:

  1. 创建插件对象: 首先,你需要定义一个对象,该对象包含你想要实现的钩子函数。每个钩子函数接收特定的参数,并返回处理结果或Promise。
const myPlugin = {name: 'my-plugin', // 插件名称,用于在日志中标识resolveId(source, importer) {// 解析模块ID的逻辑},load(id) {// 加载模块源码的逻辑},transform(code, id) {// 转换代码的逻辑return { code: transformedCode, map: sourcemap };},generateBundle(options, bundle) {// 输出阶段处理逻辑},
};
  1. 注册插件: 在Rollup配置文件中,通过plugins选项注册你的插件。
export default {input: 'src/index.js',output: {file: 'dist/bundle.js',format: 'iife',},plugins: [myPlugin],
};
  1. 测试和调试: 编写完插件后,运行Rollup构建命令,观察控制台输出,确保插件按预期工作,并根据需要进行调试。
     

编写插件时,应尽量遵循Rollup的最佳实践,比如异步操作使用Promise,保持钩子函数的幂等性等,以保证插件的稳定性和兼容性。此外,查看Rollup的官方文档和现有的开源插件源码,也是学习和理解插件开发的好方法。

自定义插件实现

通过上述介绍的插件自定义过程,这里给出一个通过实现读取.env、.env.development等环境配置文件生成对应dts文件,并注入到js的环境变量中,如下所示:

// It reads the environment variables from the .env file and generates a type definition file for them.
const { config } = require('dotenv') // 读取.env文件
const fs = require('fs');
const path = require('path');const { writeFileSync } = fs;
/*** @typedef {Object} Plugin* @property {string} name - Plugin name* @property {function} buildStart - Called when the build starts* @property {function} renderChunk - Called for each chunk during the build* @property {function} buildEnd - Called when the build ends*//*** @typedef {Object} ENV* @property {string} [key] - Environment variable key* @property {string|number|boolean} [value] - Environment variable value   *//*** @typedef {Object} Options* @property {string} [mode='development'] - Build mode* @property {string} [path='.env.development'] - Path to the environment variables file* @property {string} [dto='env.d.ts'] - Path to the generated type definition file* @property {ENV} [env] - Environment variables object (optional)*//*** Rollup plugin to inject environment variables into the bundle and generate a type definition file for them.* @constructor* @param {Options} options - Plugin options object (optional)* @returns {Plugin}*/
function injectEnv(options) {let addWatched = false // 是否添加监听const transformEnv = (env) => {const transformed = {};Object.keys(env).forEach(key => {const value = env[key];if (/^\d+(\.\d+)?$/.test(value)) {// 可以转成数字类型transformed[key] = Number(value);} else if (value === 'true' || value === 'false') {// 可以转成布尔类型transformed[key] = Boolean(value);} else {// 其他类型都转成字符串类型transformed[key] = String(value);}});return transformed;}if (options === void 0) options = {};if (typeof options.mode === 'undefined') {options.mode = process.env.NODE_ENV || '';;}if (typeof options.path === 'undefined') {if (options.mode)options.path = `.env.${options.mode}`;else options.path = '.env';}if (typeof options.dto === 'undefined') {options.dto = 'env.d.ts';}if (typeof options.env === 'undefined') {options.env = config({ path: options.path }).parsed || {}; // read .env file}options.env = transformEnv(options.env); // transform env values to number or boolean or stringconst createEnvTypes = () => {let envTypes = `/* eslint-disable */
/**
* ${options.dto}
*  This file is automatically generated by 'rollup-plugin-inject-env' plugin. 
*  Use 'dotenv' npm package to load your environment variables from .env file.
*  You can also manually edit this file to add or remove environment variables.
*  Global environment variables.
*//**
* Global environment variables.
*/
export interface GlobalEnv {
`;Object.keys(options.env).forEach(key => {const value = options.env[key];if (typeof value === 'number') {// 可以转成数字类型envTypes += `   ${key}: number;\n`;} else if (typeof value === 'boolean') {// 可以转成布尔类型envTypes += `   ${key}: boolean;\n`;} else {// 其他类型都转成字符串类型envTypes += `   ${key}: string;\n`;}});envTypes += `}\n`;envTypes += `declare global{\n  const ENV: GlobalEnv;\n}\nexport {};`const dtoPath = path.dirname(options.dto);if (dtoPath && !path.isAbsolute(dtoPath)) {fs.mkdirSync(dtoPath, { recursive: true });}// 写入dist/env.d.tswriteFileSync(options.dto, envTypes);console.log('rollup-plugin-inject-env global env types:', options.dto, ' generated successfully.');}return {name: 'rollup-plugin-inject-env',buildStart() {if (!addWatched) {this.addWatchFile(options.path); // listen to .env file changesaddWatched = true}createEnvTypes();},renderChunk(code, chunk) {if (chunk.isEntry) {// entry file needs to be modified to inject environment variablesreturn `window.ENV = ${JSON.stringify(options.env)};${code}`;}},watchChange(id, change) {createEnvTypes();},}
}module.exports = injectEnv;

如何配置?

通过在rollup.config.js中配置自定义插件,实现环境变量读取和注入:

// rollup.config.js
import injectEnv from 'rollup-plugin-inejct-dotenv';export default {input: 'index.js',output: {file: 'dist/bundle.js',format: 'cjs'},plugins: [    injectEnv({dto: 'typings/env.d.ts', // 输出的类型定义文件路径mode: 'development' // 环境模式,development或production,对应读取.env.development或.env.production文件, 不设置的时候会默认从process.env.NODE_ENV获取,不设置从.env文件读取path: '.env' // 环境变量文件路径,若设置path,则会读取该文件,否则会通过mode配置获取文件地址}),// other plugins]
};

通过上述配置,运行rollup -c 生成对应的dto文件:

具体使用可参照:rollup-plugin-inject-dotenv


http://www.ppmy.cn/devtools/126701.html

相关文章

鸿蒙NEXT开发-知乎评论小案例(基于最新api12稳定版)

注意:博主有个鸿蒙专栏,里面从上到下有关于鸿蒙next的教学文档,大家感兴趣可以学习下 如果大家觉得博主文章写的好的话,可以点下关注,博主会一直更新鸿蒙next相关知识 专栏地址: https://blog.csdn.net/qq_56760790/…

机器学习和深度学习的差别

定义和基本原理 机器学习: 定义:机器学习是一种让计算机自动从数据中学习规律和模式的方法,无需明确编程。它通过构建数学模型,利用已知数据进行训练,然后对新的数据进行预测或决策。基本原理:机器学习算…

Flink移除器Evictor

前言 在 Flink 窗口计算模型中,数据被 WindowAssigner 划分到对应的窗口后,再经过触发器 Trigger 判断窗口是否要 fire 计算,如果窗口要计算,会把数据丢给移除器 Evictor,Evictor 可以先移除部分元素再交给 ProcessFu…

【Linux】< 条件等待>解决< 线程饥饿问题 >——【多线程同步问题】

前言 大家好吖,欢迎来到 YY 滴Linux系列 ,热烈欢迎! 本章主要内容面向接触过C的老铁 主要内容含: 欢迎订阅 YY滴C专栏!更多干货持续更新!以下是传送门! YY的《C》专栏YY的《C11》专栏YY的《Lin…

解析:ARM 工业计算机在光伏储能中的关键作用

在当今能源转型的大背景下,光伏储能作为一种可持续、高效的能源解决方案,正受到越来越广泛的关注。而在光伏储能系统中,ARM 工业计算机以其卓越的性能和特点,成为了理想的选择。 一、光伏储能的重要性与挑战 全球对清洁能源的需…

【前端】Matter:过滤与高级碰撞检测

在物理引擎中,控制物体的碰撞行为是物理模拟的核心之一。Matter.js 提供了强大的碰撞检测机制和碰撞过滤功能,让开发者可以控制哪些物体能够相互碰撞,如何处理复杂的碰撞情况。本文将详细介绍 碰撞过滤 (Collision Filtering) 与 高级碰撞检测…

【NLP】GloVe模型

一、Glove简介 GloVe (Global Vectors for Word Representation) 是一种基于词共现矩阵的词向量生成方法,由斯坦福大学的 Jeffrey Pennington、Richard Socher 和 Christopher D. Manning 提出。与 Word2Vec 不同,GloVe 通过全局统计信息(词…

C语言 | Leetcode C语言题解之第492题构造矩形

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> constructRectangle(int area) {int w sqrt(1.0 * area);while (area % w) {--w;}return {area / w, w};} };