Provide Vite with the ability to conditionally compile directives
Tags: vite, frontend, javascript, regex
Published: 2023-09-20 02:54:08

Anyone who has used uni-app knows that it has built-in [preprocess](https://github.com/jsoverson/preprocess), which implements the ability to exclude code that doesn't belong to the current compilation platform via annotations such as `#ifdef`, `#ifndef`, and so on, thus reducing the size of the built package.

So how can we provide Vite with a similar capability?


## Thinking

The simplest implementation of `/#ifn?def/` is centered around two things: environment variable reading and text replacement.

Environment variables can be read directly from `process.env`, but this is not safe by default, so we choose to read `config.env` from the `configResolved` hook.

Text Replacement is just a matter of matching and replacing the incoming code with a regular in the `transform` hook. But this regular needs to match the keywords and conditions in the comments of any language.

Turning on [regex101](https://regex101.com/) gives you the following regular.

```ts
const reg = /^.*?#v-if(n?)def\s*(\S+).*[\r\n]{1,2}([\s\S]+?)\s*.*?#v-endif.*?$/gm
```
>![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/064277db8b484af7a45d6755fc2fd462~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=599&h=467&s=39893&e=png&b=f6f6f6)
> See [visualization](https://regex-vis.com/?r=%2F%5E.*%3F%23v-if%28n%3F%29def%5Cs*%28%5CS%2B%29.*%5B%5Cr%5Cn%5D%7B1%2C2%7D%28%5B%5Cs%5CS%5D%2B%3F%29%5Cs*.*%3F%23v-endif.*%3F%24%2Fgm) on Regex Vis

- The first grouping `(n?)` is used to determine if it is an inverse pattern or not
- Second grouping `(\S+). *` Used to capture conditions
- Third grouping `([\s\S]+?) ` Used to capture the contents of the conditional compilation block.


## Implementation

First, the classic plugin definition, where config is promoted to the next level for ease of use everywhere.

```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;
```

Next, focus on the replaceMatched function

```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,
  };
};
```

The first is `const env = config.env;` which gets the environment variables that vite provides to the application (non-built-in environment variables need to start with `VITE_`).

Then pinch, is the second function parameter of the core replace, `$1`, `$2`, `$3` for three groups, `isNot` according to the presence or absence of `n` to determine whether it is an inverse pattern, in order to support the condition `||` (or), `isKeep` will be the condition part of the use of `split` to split, and then use `some` (i.e., `true` if one is `true`) to judge the single condition inside `some`, and finally keep `isKeep` if it is `true`, and discard it otherwise.

```ts
(_, $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 : "";
}
```
## Use

The plugin I've uploaded to [KeJunMao/vite-plugin-conditional-compile](https://github.com/KeJunMao/vite-plugin-conditional-compile) is exceptionally easy to use!

```ts
// vite.config.ts
import { defineConfig } from "vite";
import ConditionalCompile from "vite-plugin-conditional-compiler";

export default defineConfig({
  plugins: [ConditionalCompile()],
});
```

## Question.

This plugin already does most of the conditional compilation capabilities, but there are still a couple of issues:

- Nesting is not supported
- no support for else, elif
- No support for && or more complex expressions.

Though I did support the else directive at a later stage.

Finally, if you have a need for conditional compilation, I no longer recommend this plugin, but rather [KeJunMao/unplugin-preprocessor-directives](https://github.com/KeJunMao/unplugin-preprocessor-directives).

It uses unplugin to work with any major packager, and secondly conditional compilation takes care of all the above issues.

Most importantly, this plugin abstracts the directives into plugins for plugins. In addition to the built-in conditional compilation directives, it also supports defining, undefining, and printing information at the line of code (at compile time).

You can also customize the directives , yes , if you want , you can even define foreach, include and other directives .

If it's useful to you, give it a like before you go~