阅读 168

rollup学习--03开发一个rollup插件

前言

这一篇记录rollup如何开发一个插件。文档参考: 官方文档。

rollup插件概述

首先,rollup插件是一个对象,需要遵循rollup的一些规定。rollup也为插件提供了对应的属性与一些钩子函数。在开发rollup插件之前可以先去插件列表中寻找是否有对应的插件 。

rollup插件的约定

首先来看rollup插件的约定,具体有几下几点 :

  • 插件应该有一个带有rollup-plugin-前缀的明确名称。

  • 中包含rollup-plugin关键字package.json。

  • 应该测试插件。我们推荐mocha或ava,它们支持开箱即用的 promise。

  • 尽可能使用异步方法。

  • 用英语记录您的插件。

  • 如果合适,请确保您的插件输出正确的源映射。

  • 如果您的插件使用“虚拟模块”(例如用于辅助函数),请在模块 ID 前加上\0. 这可以防止其他插件尝试处理它。

rollup 插件的属性值

rollup插件的属性值就一个:name,string类型值,插件的名称,用于错误消息和警告。

rollup 插件的hooks(钩子)

rollup 插件hooks 分为 build hooks和output generation hooks。hooks就是在构建和生产时的各个阶段调用的函数。

build hooks

build hooks是在构建阶段运行,我理解就是当rollup.rollup(inputOptions)时运行,这些hooks主要作用就是在处理之前定位,提供和转换输入文件。钩子可以影响构建的运行方式,有不同种类的hooks(以下用钩子来替代hooks了):

  • async : 异步钩子,此类型的hooks需要返回一个promise resolve的值。否则会被标记成sync类型

  • sync : 同步钩子

  • first : 当插件执行此类型钩子时,此钩子一直会顺序的执行,直到返回一个bull或者undefind

  • sequential : 这个类型钩子会按指定顺序运行,如果其中某个钩子是异步的,之后的钩子会等当前钩子解析后再执行

  • parallel:这种类型的钩子和上面的sequential类型一样,不通点在与如果顺序执行时某个钩子时异步的,后续钩子不需等待,可以并行执行。

这几种类型的钩子官方文档上游明确说明,并且有流程图 ,如下所示 :

构建阶段的第一个钩子是options,最后一个总是buildEnd。如果出现构建错误,closeBundle将在此之后调用。此处不再贴出具体hooks的含义与用法,详见文档。

Output Generation Hooks

可以提供有关生成的包的信息,并在完成后修改构建。它们与Build Hooks 的工作方式和类型相同,但在每次调用bundle.generate(outputOptions)or 时分别调用bundle.write(outputOptions)。仅使用输出生成挂钩的插件也可以通过输出选项传入,因此仅针对某些输出运行。

输出生成阶段的第一钩是outputOptions,generateBundle产生的输出bundle.generate(...),writeBundle输出经成功生成bundle.write(...),或者renderError输出代期间的任何时候发生了错误。

流程图如下 :

插件间的通信

主要的插件通信方式有:

  • 在this.resolve中添加自定义内容用于通信

function requestingPlugin() {
  return {
    name: 'requesting',
    async buildStart() {
      const resolution = await this.resolve('foo', undefined, {
        custom: { resolving: { specialResolution: true } }
      });
      console.log(resolution.id); // "special"
    }
  };
}

function resolvingPlugin() {
  return {
    name: 'resolving',
    resolveId(id, importer, { custom }) {
      if (custom.resolving?.specialResolution) {
        return 'special';
      }
      return null;
    }
  };
}复制代码
  • 通过模块自定义的meta通信

function annotatingPlugin() {
  return {
    name: 'annotating',
    transform(code, id) {
      if (thisModuleIsSpecial(code, id)) {
        return { meta: { annotating: { special: true } } };
      }
    }
  };
}

function readingPlugin() {
  let parentApi;
  return {
    name: 'reading',
    buildEnd() {
      const specialModules = Array.from(this.getModuleIds()).filter(
        id => this.getModuleInfo(id).meta.annotating?.special
      );
      // do something with this list
    }
  };
}复制代码
  • 直接插件通信

需要注意的是api属性永远不会与任何即将推出的插件hooks发生冲突。

function parentPlugin() {
  return {
    name: 'parent',
    api: {
      //...methods and properties exposed for other plugins
      doSomething(...args) {
        // do something interesting
      }
    }
    // ...plugin hooks
  }
}

function dependentPlugin() {
  let parentApi;
  return {
    name: 'dependent',
    buildStart({ plugins }) {
      const parentName = 'parent';
      const parentPlugin = options.plugins
              .find(plugin => plugin.name === parentName);
      if (!parentPlugin) {
        // or handle this silently if it is optional
        throw new Error(`This plugin depends on the "${parentName}" plugin.`);
      }
      // now you can access the API methods in subsequent hooks
      parentApi = parentPlugin.api;
    }
    transform(code, id) {
      if (thereIsAReasonToDoSomething(id)) {
        parentApi.doSomething(id);
      }
    }
  }
}复制代码

通过插件源码分析开发插件

我这里找了rollup-plugin-uglify的源码,如下 :

const { codeFrameColumns } = require("@babel/code-frame");
const Worker = require("jest-worker").default;
const serialize = require("serialize-javascript");

function uglify(userOptions = {}) {
  if (userOptions.sourceMap != null) {
    throw Error("sourceMap option is removed, use sourcemap instead");
  }

  const normalizedOptions = Object.assign({}, userOptions, {
    sourceMap: userOptions.sourcemap !== false
  });

  ["sourcemap"].forEach(key => {
    if (normalizedOptions.hasOwnProperty(key)) {
      delete normalizedOptions[key];
    }
  });

  const minifierOptions = serialize(normalizedOptions);

  return {
    name: "uglify",

    renderStart() {
      this.worker = new Worker(require.resolve("./transform.js"), {
        numWorkers: userOptions.numWorkers
      });
    },

    renderChunk(code) {
      return this.worker.transform(code, minifierOptions).catch(error => {
        const { message, line, col: column } = error;
        console.error(
          codeFrameColumns(code, { start: { line, column } }, { message })
        );
        throw error;
      });
    },

    generateBundle() {
      this.worker.end();
    },

    renderError() {
      this.worker.end();
    }
  };
}

exports.uglify = uglify;复制代码

其实可以看出主要就是用了renderStart,renderChunk这两个钩子处理代码,然后再generateBundle与renderError中关闭。

自己简单封装一个插件

这个插件就是把项目中所有的console删除,具体代码如下

export default function removeConsole() {
    return {
        name: 'remove-console',
        transform(code, id) {
            const Reg = /console\.log\(.*\)/ig;
            return code.replace(Reg, "")
        },
    }
}复制代码

没错,就是这几行代码,当然这这是一个demo级插件,真正的生产级插件还需要丰富的边界判断。

后记

以上就是我在学习开发rollup插件的学习笔记,希望可以对大家有所帮助。


作者:小q同学
链接:https://juejin.cn/post/7023284800966361124


文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐