让 vite 支持 require 方法,只需要 50 行代码 (vite-plugin-require 插件)

背景

众所周知,vite 作为一个新生的前端构建工具,它很快!

早在 2021 年,我本想将公司中正在使用的 webpack 更替为 vite,让公司中的其他同事也能体验体验 vite 带来的快乐。

由于不支持 require() 方法, 模块化的样式文件必须是 .module.xxx 后缀...

问题当然远不止我上述所列举的这些,所以当一个项目想要迁移时,那工作量是非常大的。而且公司项目过多,所以到最后放弃了...

当时在探索迁移可否实现的几天中还是很乐观的,所以开始一个问题一个问题解决。

首先css 模块化的问题,为了解决这个问题我向 vite 提交了一个 pr,可给出的回复是要求使用插件解决。当时也没有现成插件,好吧,手写一个,于是写了一个 vite 插件 vite-plugin-css-modules用于解决问题,目前为止这个插件周下载量很少,也不是本文的重点,只是让我知道了让解决问题都必须开发 vite 插件。

然后 的大问题是项目中不能使用 require 这个方法。于是又继续写了一个插件 vite-plugin-require。本插件到目前为止周下载量都在 6k-1w 之间,接下来的文章内容将会展开叙述本插件。

插件实现思路

实现思路比较简单,不考虑 require.content 方法。

就五步,每一步实现代码都不会很多,所以标题中的 50 行代码都多了,用不了这么多,根本用不了...

  1. 首先是拿到代码中的 require(xxx)
  2. 然后在对 require 的参数进行解析,拿到一个文件路径
  3. 将拿到的文件路径放置到代码文件顶部使用 import 或者 importMetaUrl 引入。
  4. 将最开始拿到的 require(xxx) 替换为定义在顶部的 import\importMetaUrl 变量
  5. 生成并输出最终代码

实现代码

理论有了,那就来实践。 代码的操作使用babel提供的AST插件

typescript 复制代码
import * as parser from "@babel/parser";
import traverse from "@babel/traverse";
import generate from "@babel/generator";
import { Plugin } from "vite"; 
import { importDeclaration, importDefaultSpecifier, stringLiteral, identifier } from "@babel/types";

export default function vitePluginRequire(opts?: { fileRegex?: RegExp; log?: (...arg: any[]) => void }): Plugin {
	const { fileRegex = /(.jsx?|.tsx?|.vue)$/, log } = opts || {};
	return {
		name: "vite-plugin-require",
		async transform(code: string, id: string) {
			//  Exclude files in node_modules
			if (/\/node_modules\//g.test(id)) return;
			let newCode = code;
			if (fileRegex.test(id)) {
				let plugins: parser.ParserPlugin[] = /(.vue)$/.test(id) ? [require("vue-loader")] : ["jsx"];
				const ast = parser.parse(code, {
					sourceType: "module",
					plugins,
				});
				traverse(ast, {
					enter(path) {
						if (path.isIdentifier({ name: "require" })) {
							if ((path.container as Record<string, any>)?.arguments?.[0]) {
								path.node.name = "";
								if ((path.container as Record<string, any>)?.arguments?.[0]?.value) {
									// Insert import at the top to pack resources when vite packs
									const realPath = `vitePluginRequire_${new Date().getTime()}_${parseInt(Math.random() * 10000 + 100 + "")}`;
									const importAst = importDeclaration(
										[importDefaultSpecifier(identifier(realPath))],
										stringLiteral((path.container as Record<string, any>)?.arguments?.[0]?.value as string)
									); 
									ast.program.body.unshift(importAst as any);  
									(path.container as Record<string, any>).arguments[0].value = realPath;
									(path.container as Record<string, any>).arguments[0].extra.raw = realPath;
									(path.container as Record<string, any>).arguments[0].extra.rawValue = realPath;
								}
							}
						}
					},
				});
				const output = generate(ast, {});
				newCode = output.code;
			}
			return { code: newCode };
		},
	};
} 

代码分析

  • 23 行代码实现了 思路1
  • 31 行代码实现了 思路2
  • 33 行代码实现了 思路3
  • 34 行代码实现了 思路4
  • 45 行代码实现了 思路5

实现虽然实现了,可是会有很多的特殊情况,比如人家在 require 函数中传入的是一个变量参数,或者其他参数..., 又或者别人想使用 import.meta.url 的实现原理。

所以这个插件在发布后就不断地升级,到目前已经达到了 175 行代码。

最后

可以看到一旦使用场景增加,再小的插件也会面临很多的问题需要考虑。

仓库地址: github.com/wangzongmin...

开源与维护不易,记得给个 start 🌹

相关推荐
天蓝色的鱼鱼2 天前
都2026年了还不会Vite插件开发?手写一个版本管理插件,5分钟包会!
前端·vite
codingWhat6 天前
前端组件库开发实践:从零到发布
前端·npm·vite
小岛前端6 天前
Cloudflare 掀桌子了,Next.js 迎来重大变化,尤雨溪都说酷!
前端·vite·next.js
Java陈序员8 天前
太香了!一款轻量级的 Elasticsearch 可视化管理工具!
vue.js·elasticsearch·vite
天蓝色的鱼鱼10 天前
Vite 8:从“混动”到“纯电”,构建性能提升10倍+
前端·vite
Roc.Chang13 天前
Vite 启动报错:listen EACCES: permission denied 0.0.0.0:80 解决方案
linux·前端·vue·vite
问道飞鱼20 天前
【前端知识】Vite用法从入门到实战
前端·vite·项目构建
weixin_425543731 个月前
TRAE CN3.3.25 构建的Electron简易DEMO应用
前端·typescript·electron·vite·nestjs
敲敲了个代码1 个月前
从N倍人力到1次修改:Vite Plugin Modular 如何拯救多产品前端维护困境
前端·javascript·面试·职场和发展·typescript·vite
打小就很皮...1 个月前
React 19 + Vite 6 + SWC 构建优化实践
前端·react.js·vite·swc