rollup打包js库 占位符替换成文件名和行号输出日志中定位报错位置

rollup 自定义插件进行代码替换

简易版

问题:在打包的js库中有很多日志或者error的控制台输出,但是打包后的js调用报错后无法从控制台看到堆栈信息,无法定位报错的位置是在哪个文件的哪一行

需求:能够从报错中观察到报错在哪个文件的那一行,便于排查错误

实现:自定义插件来实现此功能

思路:在打包时遍历要打包的文件,将文件内容按照换行符进行分割,得到的集合就是这个文件的代码,便可以拿到行号,然后通过正则表达式对占位符进行全局替换。

代码:

  • replace.js

    // 导入path包获取文件名,也可以通过正则来自己获取
    import path from 'path';

    export default function replace(options = {}) {
    const { placeholders = [] } = options;
    let fileCount = 0;
    let replaceCount = 0;
    return {
    //插件名
    name: 'placeholder-replace',
    transform(code, id) {
    // 将代码转换为 UTF-8 编码
    code = code.toString('utf-8');

              // 获取文件名
              const fileName = path.basename(id);
    
              // 遍历每个占位符
              code = placeholders.reduce((modifiedCode, placeholder) => {
                  // 构建占位符的正则表达式
                  const placeholderRegex = new RegExp(`${placeholder}`, 'g');
                  return modifiedCode
                      .split('\n')
                      .map((line, row) => {
                          let newLine = '';
                          let cursor = 0;
                          let result;
                          while ((result = placeholderRegex.exec(line))) {
                              const col = result.index;
                              // 替换占位符为文件名:行号=>>的形式
                              newLine += line.slice(cursor, result.index) + `${fileName}:${row + 1} =>> `;
                              cursor += col + result[0].length;
                          }
                          newLine += line.slice(cursor);
                          return newLine;
                      })
                      .join('\n');
              }, code);
    
              return code;
          },
          buildEnd() {
              console.log(`\n替换的文件数: ${fileCount}`);
              console.log(`\n替换的次数: ${replaceCount}`);
          },
      };
    

    }

配置:

import replace from './replace.js';

export default {
  // ...其他配置项
  plugins: [
    replace({
      placeholders: ['log-position-placeholder', 'another-placeholder'], // 设置占位符数组
    }),
  ],
};

进阶版

使用简易版的存在一个问题, 打包后生成的sourceMap文件中的映射内容是空的,如果不需要这个文件的可以使用简易版的,进阶版则是完善了这个问题的解决

代码

replcae.js

import path from 'path';
// 导入 rollup-pluginutils 包,它提供了一些工具函数来处理 sourcemap
import {createFilter} from 'rollup-pluginutils';
// 导入 magic-string 包,它可以让你修改字符串并生成 sourcemap
import MagicString from 'magic-string';

export default function replace(options = {}) {

    const {placeholders = []} = options;
    let fileCount = 0;
    let replaceCount = 0;

    // 使用 createFilter 函数来过滤掉不需要处理的文件
    const filter = createFilter(options.include, options.exclude);

    return {
        // 插件名
        name: 'placeholder-replace',

        transform(code, id) {
            // 如果文件不符合过滤条件,直接返回
            if (!filter(id)) return null;

            // 将代码转换为 UTF-8 编码
            code = code.toString('utf-8');

            // 创建一个 MagicString 对象,用于修改代码和生成 sourcemap
            const magicString = new MagicString(code);

            // 获取文件名
            const fileName = path.basename(id);

            // 遍历每个占位符
            placeholders.forEach(placeholder => {
                // 构建占位符的正则表达式
                const placeholderRegex = new RegExp(`${placeholder}`, 'g');
                let result;
                while ((result = placeholderRegex.exec(code))) {

                    const start = result.index;
                    const end = start + result[0].length;

                    // 获取行号和列号
                    const line = getRow(result.input, start, '\n');

                    // 替换占位符为文件名:行号=>>的形式
                    magicString.overwrite(start, end, `${fileName}:${line} =>> `);

                    replaceCount++;
                }
            });

            fileCount++;

            // 返回修改后的代码和 sourcemap 对象
            return {
                code: magicString.toString(),
                map: magicString.generateMap({hires: true})
            };
        },

        buildEnd() {
            console.log(`\n替换的文件数: ${fileCount}`);
            console.log(`\n替换的次数: ${replaceCount}`);
        },
    };
}

/**
 * 获取行号
 * @param str 代码内容
 * @param end  截止位置
 * @param tag  换行符
 * @return {number} 行号
 */
function getRow(str, end, tag) {
    let slice = str.slice(0, end);
    let split = slice.split(tag);
    let length = split.length;
    return  length;
}

配置:

配置和简易版是一样的,只是增加了一个包含和排除掉选项

扩展:

  • 除了 transform钩子函数之外,Rollup 还提供了其他一些常用的钩子函数,用于在构建过程中执行特定的逻辑。以下是一些常用的钩子函数:

    buildStart:在构建过程开始时调用,可以在其中执行一些初始化操作。
    buildEnd:在构建完成后调用。
    resolveId:在解析模块标识符时调用,可以自定义模块的解析逻辑。
    load:在加载模块时调用,可以自定义模块的加载逻辑。
    transform:在转换模块代码时调用,可以修改模块的代码内容。
    renderStart:在开始渲染生成输出文件时调用,可以执行一些准备工作。
    renderChunk:在渲染每个输出块(chunk)时调用,可以修改输出块的代码内容。
    renderDynamicImport:在渲染动态导入语句时调用,可以自定义动态导入的行为。
    writeBundle:在生成输出文件完成后调用,可以执行一些清理或后处理的操作。
    这些钩子函数可以根据需要选择使用,并根据特定的构建过程进行定制。你可以根据自己的需求在插件配置中添加这些钩子函数,并在相应的钩子函数中编写你想要的逻辑。

相关推荐
逆旅行天涯35 分钟前
【Threejs】从零开始(六)--GUI调试开发3D效果
前端·javascript·3d
长风清留扬1 小时前
小程序毕业设计-音乐播放器+源码(可播放)下载即用
javascript·小程序·毕业设计·课程设计·毕设·音乐播放器
m0_748247802 小时前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
ZJ_.2 小时前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
joan_853 小时前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特3 小时前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
Watermelo6173 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
一个处女座的程序猿O(∩_∩)O5 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
燃先生._.11 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js