使用过 uni-app 的人都知道,它内置了 preprocess,实现了通过 #ifdef
、#ifndef
等注释来排除不属于当前编译平台的代码,从而减少构建后的包体积。
那么如何为 Vite 提供类似的能力?
思考
要实现最简单的 /#ifn?def/
核心就两点,环境变量的读取和文本替换。
环境变量可以直接读取 process.env
,但是默认情况这是不安全的,所以我们选择从 configResolved
钩子中读取 config.env
即可。
文本替换即在 transform
钩子中对传入的代码进行正则匹配并替换即可。不过这个正则需要匹配到任意语言的注释当中的关键词和条件。
打开 regex101 一顿操作,便有了如下正则:
ts
const reg = /^.*?#v-if(n?)def\s*(\S+).*[\r\n]{1,2}([\s\S]+?)\s*.*?#v-endif.*?$/gm
查看在 Regex Vis 上的可视化效果
- 第一个分组
(n?)
用于判断是否为反模式 - 第二个分组
(\S+).*
用于捕获条件 - 第三个分组
([\s\S]+?)
用于捕获条件编译块内容
实现
首先还是经典插件定义,这里把 config 提升到跟,方便到处使用。
ts
import type { Plugin, ResolvedConfig } from "vite";
let config: ResolvedConfig = undefined!;
const replaceMatched = (code: string, _id: string) => {}
const VitePluginConditionalCompile = (): Plugin => {
return {
name: "vite-plugin-conditional-compile",
enforce: "pre",
configResolved(_config) {
config = _config;
},
transform(code, id) {
return replaceMatched(code, id);
},
};
};
export default VitePluginConditionalCompile;
接下来,聚焦 replaceMatched 函数
ts
const replaceMatched = (code: string, _id: string) => {
const env = config.env;
code = code.replace(
/^.*?#v-if(n?)def\s*(\S+).*[\r\n]{1,2}([\s\S]+?)\s*.*?#v-endif.*?$/gm,
(_, $1, $2, $3) => {
const isNot = !!$1;
const isKeep = $2.split("||").some((v: string) => {
let flag = false;
const [key, value] = v.split("=");
if (value === undefined) {
flag = !!env[key];
} else {
flag = String(env[key]) === value;
}
flag = isNot ? !flag : flag;
return flag;
});
return isKeep ? $3 : "";
}
);
return {
code,
map: null,
};
};
首先是 const env = config.env;
即获取 vite 提供给应用的环境变量(非内置环境变量需要使用 VITE_
开头)。
然后捏,就是核心 replace 的第二个函数参数, $1
, $2
, $3
为三个分组,isNot
根据有没有 n
来判断是否为反模式,为了支持条件 ||
(或),isKeep
将条件部分使用 split
进行拆分,然后用 some
(即有一个为 true
就为 true
)判断,在 some
内部对单一条件进行判断,最后如果 isKeep
为 true
则保留,否则就舍弃。
ts
/**
* 条件替换
* @param _ 匹配的字符串
* @param $1 是否为 not 模式
* @param $2 条件
* @param $3 code
*/
(_, $1, $2, $3) => {
const isNot = !!$1;
const isKeep = $2.split("||").some((v: string) => {
let flag = false;
const [key, value] = v.split("=");
if (value === undefined) {
flag = !!env[key];
} else {
flag = String(env[key]) === value;
}
flag = isNot ? !flag : flag;
return flag;
});
return isKeep ? $3 : "";
}
使用
插件我已经上传到 KeJunMao/vite-plugin-conditional-compile 了,使用起来特别简单!
ts
// vite.config.ts
import { defineConfig } from "vite";
import ConditionalCompile from "vite-plugin-conditional-compiler";
export default defineConfig({
plugins: [ConditionalCompile()],
});
问题
这个插件已经能完成绝大多数条件编译能力了,不过依然有几个问题:
- 不支持嵌套
- 不支持 else、elif
- 不支持 && 或者 更复杂的表达式
尽管在后期,我已经支持了 else 指令。
最后,如果你有条件编译的需求,我已经不再推荐你用这个插件了,而是推荐你使用 KeJunMao/unplugin-preprocessor-directives。
它使用了 unplugin 可以在任何主流打包器上使用,其次条件编译把上述问题都解决了。
最重要是这个插件将指令抽象成了插件的插件,除了内置了条件编译指令外、还支持定义、取消定义、在代码行处打印信息(编译时)
你还可以自定义指令,是的,如果你愿意,你甚至可以定义出 foreach、include 等指令。

如果对你有用,点个赞再走呗~