【rollup】04-rollup插件开发

77 阅读4分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第n篇文章,点击查看活动详情

一、插件的上下文

在插件运行时,会给插件的上下文绑定一些工具函数,这些工具函数,可以通过this访问到。

1)this.addWatchFile

添加一个监听文件,当监听文件发送变化时,会重新构建。

入参:

  • id:string 。 文件路径,可以是相对路径或者绝对路径

无返回值

2)this.emitFile

在构建输出中,发出一个新文件,然后返回这个文件的引用id,这个引用id可以在各个地方用来获取到发出的这个新文件。

入参:

  • emittedFile:可以是EmittedChunk或者EmittedAsset这两种类型中的一个。

返回值:

  • string : 引用id。

入参的一般格式

  • name和fileName : 如果提供了fileName,则直接当作生成的文件名。如果提供了name,name会当作output.chunkFileNames 或者 output.assetFileNames 配置中的[name]参数,后面可能会加唯一的数字。如果都没提供,就用默认的。
  • type:如果是'chunk',则根据id参数,来生成一个chunk。如果是'asset',则生成的新文件中,使用'source'作为文件内容。
  • id:type为chunk时,用来当作入口,参数和resolveID的钩子函数参数相同。
  • importer : 如果提供了,和id配合使用,当作id的上级文件,导入id所在文件使用。

在默认情况下,rollup假设发出的chunk是独立于其他入口而执行的,甚至在所有代码之前执行。这意味着,如果发出的chunk和现有的入口之间存在共享的依赖的话,rollup将会将这些入口之间的依赖,分离为一个单独的chunk。这时候可以配置'implicitlyLoadeAferOneOf'这个参数,这个参数是一个id列表。所以,如果emitFile的类型是chunk,其实相当于多配置了一个input入口。

3)this.getModuleIds

获取所有模块的id。返回可迭代的模块id类数组对象。

4)this.getModuleInfo

根据模块id,查询模块信息。

入参:

  • id:模块id

返回值:

  • info: ModuleInfo 结构的返回值,或者null

5)this.parse

将代码转为AST。

入参:

  • code:string
  • acornOptions:acorn的一些参数

出参:

ESTree.Program

\

二、插件开发示例

我们假设需要处理js文件中导入的css,这种情况常见,我们在vue项目,需要挂载全局样式时就是在打包的入口,导入css文件,例如如下代码:

import demo from './demo.js';
import './index.css';
import './reset.less';
export default function () {
	console.log(demo.name);
}

如果直接 用rollup打包,则会报错,提示需要用插件处理非JavaScript格式的文件。

接下来我们自己开发一个插件,来捕获这个错误,并且能解析css文件。组件部分基础代码:

function pluginExample(){
  return {
    name:"rollup-plugin-example"
  }
}

1、在导入阶段进行截停,不对css格式的文件进行转为module操作。

这需要我们在resolveId 钩子函数中,进行截停。在pluginExample 中添加 resolveId 钩子函数 (示例中,没有对css、less等进行详细处理,只是做演示用):

function pluginExample() {
        let css_map = [];
	return {
		name: 'rollup-plugin-example',
		/**
		 * 根据id判断导入的文件类型,如果是样式文件,则返回false,不进行打包
		 * @param {*} id
		 * @returns string || false || null
		 */
		resolveId(id) {
			let fileType = id.split('.');
			fileType = fileType.slice(-1)[0];
			switch (fileType) {
				case 'css':
					css_map[id] = fs.readFile(id, { encoding: 'utf8' });
					break;
				case 'less':
					css_map[id] = fs.readFile(id, { encoding: 'utf8' });
					break;
				case 'sass':
				case 'scss':
					css_map[id] = fs.readFile(id, { encoding: 'utf8' });
					break;
				default:
					console.log(fileType);
					return null;
			}
			return false;
		}
	};
}

2、将所有样式文件进行合并

在拿到所有的样式文件后,我们把样式文件进行汇总(注意,这里可能会报错,因为如果是rollup的工程,配置的eslint对钩子函数的顺序有要求,不用管):

function pluginExample() {
	let css_map = [];
	return {
		name: 'rollup-plugin-example',
		/**
		 * 根据id判断导入的文件类型,如果是样式文件,则返回false,不进行打包
		 * @param {*} id
		 * @returns string || false || null
		 */
		resolveId(id) {
			let fileType = id.split('.');
			fileType = fileType.slice(-1)[0];
			switch (fileType) {
				case 'css':
					css_map[id] = fs.readFile(id, { encoding: 'utf8' });
					break;
				case 'less':
					css_map[id] = fs.readFile(id, { encoding: 'utf8' });
					break;
				case 'sass':
				case 'scss':
					css_map[id] = fs.readFile(id, { encoding: 'utf8' });
					break;
				default:
					console.log(fileType);
					return null;
			}
			return false;
		},
		// 构建结束后,将所有的样式文件,拼接到一起
		buildEnd() {
			css_map = Promise.all(Object.keys(css_map).map(cb => css_map[cb])).then(data => {
				return data.join('\r\n');
			});
		}
	};
}

3、获取输出配置,进行文件写入

为了输出到准确的位置,我们需要在输出阶段的钩子函数中,获取到输出配置,然后拼接出输出的目录:

function pluginExample() {
  let css_map = [];
  return {
    name: 'rollup-plugin-example',
    /**
     * 根据id判断导入的文件类型,如果是样式文件,则返回false,不进行打包
     * @param {*} id
     * @returns string || false || null
     */
    resolveId(id) {
      let fileType = id.split('.');
      fileType = fileType.slice(-1)[0];
      switch (fileType) {
        case 'css':
          css_map[id] = fs.readFile(id, { encoding: 'utf8' });
          break;
        case 'less':
          css_map[id] = fs.readFile(id, { encoding: 'utf8' });
          break;
        case 'sass':
        case 'scss':
          css_map[id] = fs.readFile(id, { encoding: 'utf8' });
          break;
        default:
          console.log(fileType);
          return null;
      }
      return false;
    },
    // 构建结束后,将所有的样式文件,拼接到一起
    buildEnd() {
      css_map = Promise.all(Object.keys(css_map).map(cb => css_map[cb])).then(data => {
        return data.join('\r\n');
      });
    },
    // 一进入输出阶段,就开始写样式文件
    outputOptions(opts) {
      // 判断file中是否能解出上级文件地址
      const dir = opts.dir || path.join(opts.file, '../');
      css_map.then(chunk => {
        fs.writeFile(dir + 'bundle.css', chunk).then(e => {
          console.log(e ? '存储失败' : 'css文件处理成功', e ? e : '');
        });
      });
      return null
    }
  };
}

4、完善

通过这个示例,基本就能实现css文件的打包功能。在示例中,我们还可以给函数传一个参数,来控制我们最终打包的css的文件的名称,或者哪些css文件可以单独生成文件。