让 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 天前
vite 安装 vue3 和 tailwindcss
vue3·vite·tailwindcss
weixin79893765432...2 天前
Electron + React + Vite 实践
react.js·electron·vite
weixin79893765432...4 天前
Electron + Vue 3 + Vite 实践
vue.js·electron·vite
AAA阿giao6 天前
使用 Vite + Vue 3 搭建项目并配置路由的全流程(含国内镜像加速)
vue.js·node.js·vite
笨笨狗吞噬者6 天前
【uniapp】小程序实现自由控制组件JSON文件配置
vue.js·微信小程序·vite
特级业务专家8 天前
续集:Vite 字体插件重构之路 —— 从“能用”到“生产级稳定”
javascript·vue.js·vite
特级业务专家10 天前
把 16MB 中文字体压到 400KB:我写了一个 Vite 字体子集插件
javascript·vue.js·vite
isyuah13 天前
vite-plugin-openapi-ts CLI 使用指南
前端·vite
用户740546399430917 天前
Vite 库模式输出 ESM 格式时的依赖处理方案
前端·vite
用户97141718142724 天前
前端开发中的跨域问题:Vite 开发环境配置指南
vue.js·vite