DEV Community

KeJun
KeJun

Posted on

Provide Vite with the ability to conditionally compile directives

Anyone who has used uni-app knows that it has built-in 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 gives you the following regular.

const reg = /^.*?#v-if(n?)def\s*(\S+).*[\r\n]{1,2}([\s\S]+?)\s*.*?#v-endif.*?$/gm
Enter fullscreen mode Exit fullscreen mode

image.png
See visualization 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.

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;
Enter fullscreen mode Exit fullscreen mode

Next, focus on the replaceMatched function

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,
  };
};
Enter fullscreen mode Exit fullscreen mode

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.

(_, $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 : "";
}
Enter fullscreen mode Exit fullscreen mode

Use

The plugin I've uploaded to KeJunMao/vite-plugin-conditional-compile is exceptionally easy to use!

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

export default defineConfig({
  plugins: [ConditionalCompile()],
});
Enter fullscreen mode Exit fullscreen mode

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.

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~

Top comments (0)