让 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 🌹

相关推荐
minko15 小时前
这给我干哪儿来了,这还是react-router吗
react.js·vite
cs_dn_Jie5 天前
uniapp + vite + 使用多个 ui 库
vue.js·ui·uni-app·vite
PBitW10 天前
vue3+vite+eslint|prettier+elementplus+国际化+axios封装+pinia
vue.js·vite·eslint·prettier·vue3+vite·eslint+prettier
程序猿000001号14 天前
Vite:现代前端开发的利器
前端·vite
漂流瓶jz1 个月前
谈一谈前端构建工具的本地代理配置(Webpack与Vite)
前端·webpack·node.js·vite·proxy·代理
柠檬豆腐脑1 个月前
前端构建工具的发展和现状:Webpack、Vite和其他
前端·webpack·vite
起来改bug1 个月前
vite5.x配置https
https·vite
WEB前端圈1 个月前
【bug修复系列】package.json中“type”: “module”的作用,解决明明是ES module却报是CommonJS的问题
json·bug·vite
theMuseCatcher1 个月前
Vue Amazing UI 组件库(Vue3+TypeScript+Vite 等最新技术栈开发)
ui·typescript·vue3·vite·components
SamLee2 个月前
vite项目集成i18n,实现语言包懒加载【原创】
vite