Chapter 4 ยท CORE

Configuration Matchers

๐Ÿ“„ 04_configuration_matchers.md ๐Ÿท Core

Chapter 4: Configuration Matchers

Welcome to Chapter 4! In Chapter 3: Optimization Heuristics, we acted as "Security Scanners," identifying which standard packages needed to be bundled to work in the browser.

Now we have a list of packages to optimize. But wait! What if the user has already manually configured some of these in their vite.config.js? We don't want to duplicate work or override their custom settings.

This chapter introduces Configuration Matchers: a set of tools to check if a package is already handled by an existing configuration.

The Motivation: The Club Bouncer

Imagine your Vite configuration is a Guest List for an exclusive club.

  1. The Club: The Vite Build Process.
  2. The Guests: Your Dependencies (packages).
  3. The Guest List: The arrays in your config (e.g., include: ['react'], exclude: ['my-ui']).

You (using vitefu) act as a promoter trying to bring a busload of new guests (dependencies) into the club. Before you write their names on the list, you need to check with the Bouncer.

The Bouncer (Configuration Matcher): You ask: "Is 'lodash' already on the list?" The Bouncer checks the list. The list might say "lodash", or it might have a rule like "Anyone whose name starts with 'lod' (/lod.*/)".

If the Bouncer says "Yes", you do nothing. If the Bouncer says "No", you add the name to the list.

Without the Bouncer, you might add lodash twice, or add my-ui to the "Allowed" list when the owner explicitly put it on the "Banned" list!

How to Use It

vitefu provides four synchronous functions to act as the Bouncer for different parts of the Vite config:

  1. isDepIncluded (Checks optimizeDeps.include)
  2. isDepExcluded (Checks optimizeDeps.exclude)
  3. isDepNoExternaled (Checks ssr.noExternal)
  4. isDepExternaled (Checks ssr.external)

The Use Case: Merging Configs

Let's say vitefu crawled your project and found that axios needs to be optimized. However, your existing vite.config.js already includes axios.

Here is how you use the matcher to avoid duplicates:

import { isDepIncluded } from 'vitefu';

const existingConfig = ['react', 'axios']; // From vite.config.js
const newFoundDeps = ['axios', 'lodash'];  // Found by vitefu

// Filter out dependencies that are already in the existing config
const finalToAdd = newFoundDeps.filter(dep => {
  // Ask the Bouncer: Is this dep already included?
  return !isDepIncluded(dep, existingConfig);
});

console.log(finalToAdd); 
// Output: ['lodash'] ('axios' was dropped because it existed)

This ensures your configuration remains clean and respects the user's manual settings.

Internal Implementation: How it Works

The logic seems simple (check if a string is in an array), but Vite configurations are powerful. They support:

  1. Exact Strings: 'lodash'
  2. Deep Paths: 'my-lib > deep-dep'
  3. Regular Expressions: /^my-lib-/

The Matchers handle all this complexity for you.

High-Level Flow

sequenceDiagram participant U as User (Promoter) participant M as Matcher (Bouncer) participant L as Config List U->>M: isDepIncluded('my-lib-core', list) M->>L: Read entry 1: "react" (String) M->>M: "my-lib-core" === "react"? NO. M->>L: Read entry 2: /^my-lib-/ (Regex) M->>M: Does "my-lib-core" match /^my-lib-/? YES! M->>U: Return TRUE

Code Walkthrough

Let's look at src/sync.cjs to see how the logic handles different data types.

1. The Core Matcher

Most functions rely on a helper called isMatch. This function figures out if the item in the config is a String or a Regex.

// src/sync.cjs
function isMatch(target, pattern) {
  // If the config has a Regex (e.g., /my-lib/)
  if (pattern instanceof RegExp) {
    return pattern.test(target);
  }
  // If the config is just a string (e.g., 'react')
  if (typeof pattern === 'string') {
    return target === pattern;
  }
  // ... handles arrays recursively
}

2. Handling Nested Includes

Vite allows you to optimize a dependency inside another dependency using the > syntax (e.g., parent > child). If you check isDepIncluded('child'), the matcher needs to understand that parent > child is a match.

// src/sync.cjs
function parseIncludeStr(raw) {
  const lastArrow = raw.lastIndexOf('>');
  // If "parent > child", return "child". 
  // If just "child", return "child".
  return lastArrow === -1 ? raw : raw.slice(lastArrow + 1).trim();
}

The isDepIncluded function uses this to normalize the names before checking.

// src/sync.cjs
function isDepIncluded(dep, optimizeDepsInclude) {
  return optimizeDepsInclude.some((id) => {
    // Check if the cleaned-up name matches
    return parseIncludeStr(id) === dep;
  });
}

3. Handling Exclusions

Exclusions are slightly different. If you exclude my-lib, you effectively exclude my-lib/subpath as well. The logic checks for this prefix.

// src/sync.cjs
function isDepExcluded(dep, optimizeDepsExclude) {
  return optimizeDepsExclude.some((id) => {
    // Match exact name OR if it starts with "id/"
    return id === dep || dep.startsWith(`${id}/`);
  });
}

Special Case: SSR NoExternal

The ssr.noExternal option is unique because it can be set to true (boolean), which means "Bundle everything, externalize nothing."

// src/sync.cjs
function isDepNoExternaled(dep, ssrNoExternal) {
  // If set to true, EVERYTHING is noExternal
  if (ssrNoExternal === true) {
    return true;
  } 
  // Otherwise, run the standard regex/string match
  return isMatch(dep, ssrNoExternal);
}

Summary

In this chapter, we learned:

  1. The Goal: Do not overwrite or duplicate existing Vite configurations.
  2. The Mechanism: Use Configuration Matchers (isDepIncluded, isDepExcluded, etc.).
  3. The Logic: These functions handle simple strings, complex regular expressions, and nested dependency paths (e.g., a > b).

Now that we know what to optimize and we've verified it against the config, there is one final piece of the puzzle. How do we actually find where these packages live on the hard drive?

Vite users often use complex setups like Monorepos or Yarn PnP (Plug'n'Play), where files aren't in standard locations.

Next Chapter: Package Resolution & PnP Support


Generated by Code IQ