让 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 天前
深入解析 import.meta.url:与 new URL() 的关系及 Vite 中的 base 路径影响
前端·vite
迷你二鹏2 天前
前端之Vite原理
前端·vite
MINO吖2 天前
基于 qiankun + vite + vue3 构建微前端应用实践
vue·vite·微前端·qiankun·single-spa
依旧004 天前
react native webview加载本地HTML,解决iOS无法加载成功问题
react native·ios·html·vite·webview
果粒chenl4 天前
vite构建工具
前端·vite
萌萌哒草头将军5 天前
🚀🚀🚀 rolldown-vite 实践结果记录,它是真的快!⚡️⚡️⚡️
vue.js·react.js·vite
晓得迷路了6 天前
栗子前端技术周刊第 83 期 - Rolldown-Vite、Angular v20、Docusaurus 3.8...
前端·javascript·vite
霸王蟹6 天前
从前端工程化角度解析 Vite 打包策略:为何选择 Rollup 而非 esbuild。
前端·笔记·学习·react.js·vue·rollup·vite
霸王蟹7 天前
React 项目中封装 Excel 导入导出组件:技术分享与实践
前端·笔记·学习·react.js·typescript·excel·vite
萌萌哒草头将军7 天前
🚀🚀🚀这几个为 vue 设计的 vite 插件,你一定要知道!
前端·vue.js·vite