benjamincanac / ui3

A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.
https://ui.nuxt.com
MIT License
53 stars 7 forks source link

Replace dynamic class inside `<template>` with output value at build whenever possible #106

Open mukundshah opened 3 months ago

mukundshah commented 3 months ago

Description

For example:

<script setup lang="ts">
import { tv } from 'tailwind-variants';

const button = tv({
  base: 'font-medium bg-blue-500 text-white rounded-full active:opacity-80 hover:opacity-90',
  variants: {
    color: {
      primary: 'bg-blue-500 text-white',
      secondary: 'bg-purple-500 text-white'
    },
    size: {
      sm: 'text-sm',
      md: 'text-base',
      lg: 'px-4 py-3 text-lg'
    }
  },
  compoundVariants: [
    {
      size: ['sm', 'md'],
      class: 'px-3 py-1'
    }
  ],
  defaultVariants: {
    size: 'md',
    color: 'primary'
  }
});
</script>

<template>
  <div>
    <button :class="button({ size: 'sm', color: 'secondary' })">
      Click me
    </button>
  </div>
</template>

In this component, the dynamic class could have been replaced with it's expected output, most probalby during the build time, eliminating the runtime call and probably reducing script size.

The compiled template will be then only:

<template>
  <div>
    <button class="font-medium rounded-full active:opacity-80 bg-purple-500 text-white text-sm px-3 py-1">
      Click me
    </button>
  </div>
</template>

This is what I have been able to come with so far:

const ComputedCSS = createUnplugin(() => {
  const filter = createFilter([/App\.vue$/])

  const resolveClass = (code, binding) => {

    // Implement this method to resolve the class binding
    return binding;
  }

  return {
    name: 'computed-css',
    enforce: 'pre',
    transformInclude(id) {
      return filter(id)
    },
    transform(code, id) {
      console.log('transform', id)
      const vueTemplateRegex = /<template>\s*([\s\S]*?)<\/template>/s;
      const match = code.match(vueTemplateRegex);
      if (match && match[1]) {
        const templateContent = match[1];
        const classBindingRegex = /\:class\s*=\s*"([^"]*)"/g;
        let classBindings = templateContent.match(classBindingRegex);
        if (classBindings) {
          classBindings.forEach((binding) => {
            const resolvedClass = resolveClass(code, binding);
            code = code.replace(binding, `class="${resolvedClass}"`);
          });
        }
      }
      return code
    },
  }
})

Additional context

No response

benjamincanac commented 3 months ago

What if you change the prop dynamically if your app? Those will no longer be reactive 🤔

<template>
  <UButton :color="status === 'error' ? 'red' : 'green'" />
</template>
mukundshah commented 3 months ago

Such should be avoided/skipped. We should be able to figure out whether or not the call is dynamic or not. I have been looking into pinceau, stitches and other libraries which does such transformation.

Even a quarter reduction in runtime calls would be nice I guess

benjamincanac commented 2 months ago

Do you plan to make a PR for this? 😊

mukundshah commented 2 months ago

Not yet. I haven't been able to work on this because of my finals. You can close this till I get a breakthrough.