Skip to content

Rollup 插件

一、插件基础

  • 本质:一个函数,返回一个对象
  • 中文官网
  • 流程参考上述官网,非常详细

二、插件案例

开发 commonjs 插件

js
npm i @rollup/plugin-commonjs -D

npm i magic-string -D

src 模块测试代码

js
// src/index.js
import title from "./title.js";
console.log(title);

import { home } from "./home.js";
console.log(home);

// src/title.js
module.exports = "title";

// src/home.js
module.exports.home = "china";

插件实现代码

js
import { createFilter } from "@rollup/pluginutils";
import MagicString from "magic-string";
import { walk } from "estree-walker";
import path from "path";

function commonjsPlugin(options = {}) {
  const defaultExtensions = [".js", ".jsx"];
  const { exclude, include, extensions = defaultExtensions } = options;
  const regExp = new RegExp(`(${extensions.join("|")})$`);
  const userDefinedFilter = createFilter(include, exclude);
  const filter = (id) => regExp.test(id) && userDefinedFilter(id);
  return {
    name: "commonjs",
    async transform(code, filename) {
      if (!filter(filename)) {
        return null;
      }
      // this.parse = acorn ast语法解析库
      // code = module.exports = "title"
      // filename = title.js
      const result = transformAndCheckExports(this.parse, code, filename);
      return result;
    },
  };
}

function transformAndCheckExports(parse, code, filename) {
	const {isEsModule, ast} = analyzeTopLevelStatements((parse, code)
	if (isEsModule) {
		// 如果是es module,则返回null, 不做任何处理
		return 	null;
	}
	return transformCommonjs(ast, code, filename);
}

function analyzeTopLevelStatements(parse, code) {
	const ast = parse(code)
	let isEsModule = false;
	for (const statement of ast.body) {
		switch (statement.type) {
			case "ExportNamedDeclaration":
				isEsModule = true;
			break;
			case "ExportDefaultDeclaration":
				isEsModule = true;
				break;
			case "ImportDeclaration":
				isEsModule = true;
				break;
			default:
				break;
		}
	 }
	 return {isEsModule, ast}
}

function transformCommonjs(options = {}) {
	const ms = new MagicString(code);
	let moduleExportsAssignment = null
	let nameExports = []
	walk(ast, {
		enter(node) {
			switch (node.type) {
				case "AssignmentExpression":
					if (node.left.type === "MemberExpression") {
						const keys = getKeyPath(node.left)
						if (keys === 'module.exports') {
							moduleExportsAssignment = node
						} else if (keys.startsWith('module.exports')) {
							nameExports.push({
								keys,
								node: node.left
							})
						}
					}
					break;
				default:
					break;
			}
		}
	})

	if (moduleExportsAssignment) {
		const { left } = moduleExportsAssignment
		const exportName = path.basename(filename, path.extname(filename)) // title
		ms.overwrite(left.start, left.end, exportName) // module.exports = title
		ms.prependRight(left.start, `var `)
		ms.append(`\n exports default ${exportName}; \n`)
	}

	if (nameExports.length) {
		let exportNames = []
		for (let i = 0; i < nameExports.length; i++) {
			let { keys, node } = nameExports[i]
			let exportName = keys.slice(15)
			exportNames.push(exportName)
			ms.overwrite(node.start, node.end, exportName)
			ms.prependRight(node.start, `var `)

		}
		ms.append(`\n exports default {${exportNames.join(',')}}; \n`)
	}

	return {code: ms.toString()}
}

// parts = [module, exports, home]
function getKeyPath(node) {
	const parts = []
	while (node.type === "MemberExpression") {
		parts.unshift(node.property.name)
		node = node.object
	}
	parts.unshift(node.name)
	return parts.join('.')
}

构建结果

js
var title = "title";
var home = "china";

console.log(title);
console.log(home);

Released under the MIT License.