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

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 string


    const 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.ts
        writeFileSync(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 changes
                addWatched = true
            }
            createEnvTypes();
        },
        renderChunk(code, chunk) {
            if (chunk.isEntry) {
                // entry file needs to be modified to inject environment variables
                return `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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

NETSH端口转发

NETSH介绍 netsh是windows系统自带命令行程序,攻击者无需上传第三方工具即可利用netsh程序可进行端口转 发操作,可将内网中其他服务器的端口转发至本地访问运行这个工具需要管理员的权限 实验场景 现在有如下的网络,电脑A是攻击机器&#x…

衡石分析平台系统分析人员手册-仪表盘控件概述

控件​ 控件是仪表盘的基本组成单位。控件种类很多,有展示分析数据的图表类类控件,有展示图片、文字的展示类控件,还有可导出数据、刷新数据、过滤数据等功能类控件。一个完整的仪表盘由多种不同功能的控件构成。 控件类型​ 根据控件是否展…

10月18日笔记(基于系统服务的权限提升)

系统内核漏洞提权 当目标系统存在该漏洞且没有更新安全补丁时,利用已知的系统内核漏洞进行提权,测试人员往往可以获得系统级别的访问权限。 查找系统潜在漏洞 手动寻找可用漏洞 在目标主机上执行以下命令,查看已安装的系统补丁。 system…

详解Java之Spring MVC篇一

目录 Spring MVC 官方介绍 MVC RequestMapping 传递参数 无参数 单个参数 针对String类型 针对Integer类型 针对int类型 针对自定义类型 多个参数 参数重命名 参数强制一致 参数不强制一致 传递数组 ​编辑传递List ​编辑 传递JSON ​编辑 从路径中获取参…

树上启发式合并(详解)

核心思想 借用了一个节点到根的路径上轻边个数不会超过logn条。 故重节点保留&#xff0c;轻节点删去&#xff0c;多重统计。 实际复杂度&#xff08;nlogn&#xff09; 例题 Lomsat gelral - 洛谷 AC 代码 #include<bits/stdc.h> #define int long long using na…

无需扩散,下一个token预测直达AGI!

目录 简单概括1 背景知识方法数据视觉 Tokenizer架构预训练 实验结果视频生成未来预测 简单概括 虽然&#xff0c;下一token预测已在大语言模型领域实现了ChatGPT等突破&#xff0c;但是在多模态模型中的适用性仍不明确&#xff0c;多模态任务仍然由扩散模型&#xff08;如Sta…

大规模创新类竞赛评审方案的建模与研究

随着科技的发展和教育制度的改革&#xff0c;近年来涌现出一批以“创新”为主题的竞赛项目。这类竞赛的运行模式为&#xff0c;参赛队伍提交文档、视频或幻灯片等文本形式的作品&#xff0c;专家对参赛队伍提交的作品评阅判分&#xff0c;一份作品将由多位专家独立进行评阅打分…

19.面试算法-树的深度优先遍历(一)

1. 深入理解前中后序遍历 深度优先遍历有前中后三种情况&#xff0c;大部分人看过之后就能写出来&#xff0c;很遗憾大部分只是背下来的&#xff0c;稍微变换一下就废了。 我们再从二叉树的角度看递归&#xff0c;每次遇到递归&#xff0c;都按照前面说的四步来写&#xff0c…

从壹开始解读Yolov11【源码研读系列】——cfg:模型配置加载功能

目录 一、模型配置操作&#xff1a;cfg.__init__.py 1.cfg.cfg2dict&#xff1a;yaml转字典 2.cfg.get_cfg&#xff1a;读取覆盖配置 3.cfg全局配置参数查询表 ①*基础参数配置&#xff1a; ②*训练参数配置&#xff1a; ③验证测试参数配置&#xff1a; ④*预测参数配置&…

element plus中menu菜单技巧

我在使用element plus的menu&#xff08;侧边栏&#xff09;组件的过程中遇到了一些问题&#xff0c;就是menu编写样式和路由跳转&#xff0c;下面给大家分享以下&#xff0c;我是怎么解决的。 1.页面效果 我要实现的网站布局是这样的&#xff1a; 侧边栏折叠以后的效果&#…

使用 Spring 框架构建 MVC 应用程序:初学者教程

Spring Framework 是一个功能强大、功能丰富且设计精良的 Java 平台框架。它提供了一系列编程和配置模型&#xff0c;旨在简化和精简 Java 中健壮且可测试的应用程序的开发过程。 人们常说 Java 太复杂了&#xff0c;构建简单的应用程序需要很长时间。尽管如此&#xff0c;Jav…

PHP露营地管理小程序系统源码

&#x1f3d5;️露营新风尚&#xff01;露营地管理小程序系统&#xff0c;打造完美露营体验✨ &#x1f4cd;营地预订&#xff0c;轻松搞定&#x1f4c5; 想要逃离城市的喧嚣&#xff0c;享受大自然的宁静&#xff1f;露营地管理小程序系统让你的露营计划轻松实现&#xff01…

Vulnhub打靶-Empire-LupinOne

基本信息 靶机下载&#xff1a;https://download.vulnhub.com/empire/01-Empire-Lupin-One.zip 攻击机器&#xff1a;192.168.20.128&#xff08;Windows操作系统&#xff09;& 192.168.20.138&#xff08;kali&#xff09; 提示信息&#xff1a; 这个盒子被创建为中等…

FineReport 填报简介vs控件vs页面设置

填报简介 填报功能可以将页面数据写入到数据库&#xff0c;包括数据的增加、删除和修改操作。同时也支持对填写数据的自定义校验&#xff0c;Excel 导入数据&#xff0c;根据填写值智能联动等功能。 填报控件 设计填报报表时&#xff0c;如果需要修改和新增数据&#xff0c;则…

vue3使用element-plus手动更改url后is-active和菜单的focus颜色不同步问题

在实习&#xff0c;给了个需求做个新的ui界面&#xff0c;遇到了一个非常烦人的问题 如下&#xff0c;手动修改url时&#xff0c;is-active和focus颜色不同步 虽然可以直接让el-menu-item:focus为白色能解决这个问题&#xff0c;但是我就是想要有颜色哈哈哈&#xff0c;有些执…

【JAVA面试题】什么是Springboot的自动配置以及注意事项

文章目录 强烈推荐核心概念&#xff1a;自动配置的关键特点&#xff1a;示例&#xff1a; 需要注意的点1.默认配置可能不适合所有场景2.Bean 冲突与覆盖3.应用启动慢的问题4.过度依赖自动配置5.安全性问题6.依赖冲突与版本兼容7.过多不必要的自动配置8.调试困难 专栏集锦 强烈推…

python实战项目43:采集汽车之家数据

python采集汽车之家数据 一、寻找数据接口二、发送请求获取响应三、解析数据四、完整代码一、寻找数据接口 如下图所示,在汽车之家首页点击报价图标: 在下图中选择价位,例如选择15-20万: 打开浏览器开发者工具,刷新页面,找到数据接口。接下来,通过翻页寻找接口url的变…

如果你不幸成为家里第一个GIS专业的学生

家里无法给我很多建设性意见&#xff0c;大学四年到工作都是自己一个人跌跌撞撞走过来的&#xff0c;期间因为信息差走了不少弯路。对于GIS专业而言&#xff0c;没有家里人的指路&#xff0c;信息差就成了同学之间拉开差距的重要因素。现在我们要做的就是打破专业信息差&#x…

Vue+ECharts+iView实现大数据可视化大屏模板

Vue数据可视化 三个大屏模板 样式还是比较全的 包括世界地图、中国地图、canvas转盘等 项目演示&#xff1a; 视频&#xff1a; vue大数据可视化大屏模板

uiautomatorviewer安卓9以上正常使用及问题处理

一、安卓9以上使用uiautomatorviewer问题现象 打开Unexpected error while obtaining UI hierarchy 问题详情 Unexpected error while obtaining UI hierarchy java.lang.reflect.InvocationTargetException 二、问题处理 需要的是替换对应D:\software\android-sdk-windows…