让 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 天前
写页面时别再把 Element Plus 整个搬进来啦!Vue3按需加载的坑我帮你踩平了
vue3·web·vite·icon·element plus·vs code·import·unplugin
Linsk5 天前
一个案例教你彻底搞明白`AbortController` 、`AbortSignal`
vite·前端工程化
ZengLiangYi5 天前
Tailwind CSS v4 + Vite:现代前端样式方案
前端·css·vite
发现一只大呆瓜6 天前
超全 Vite 性能优化指南:网络、资源、预渲染三维落地方案
前端·面试·vite
发现一只大呆瓜6 天前
Vite 兼容降级全解:语法降级、Polyfill 原理与 legacy 插件底层机制
前端·面试·vite
发现一只大呆瓜7 天前
Vite 开发预构建机制详解,搞懂 esbuild 与 Rollup 分工差异
前端·面试·vite
__zRainy__8 天前
uni-app 全局容器实战系列(一):全局容器的实现
uni-app·vite
发现一只大呆瓜9 天前
Vite凭什么这么快?3分钟带你彻底搞懂 Vite 热更新的幕后黑手
前端·面试·vite
Hello--_--World9 天前
利用CDN进行首屏优化。能不能看CDN与本地服务器谁快用谁?
运维·服务器·前端·javascript·vite
Hello--_--World9 天前
为什么 用vite进行分包后,可以通过 浏览器强制缓存 提高性能?路由懒加载进行的分包与 vite进行的分包有什么不同?
前端·javascript·缓存·vite