nextui-org / tailwind-variants

🦄 Tailwindcss first-class variant API
https://tailwind-variants.org
MIT License
2.42k stars 68 forks source link

TypeScript responsive variants missing custom screens on Vercel #189

Open troymcginnis opened 6 months ago

troymcginnis commented 6 months ago

Describe the bug When developing locally, the types for responsiveVariants appears to pick up all of our custom screens from our tailwind.config.js. This means we're able to see, get autocomplete for, and build components using our custom screens such as xs and 3xl using tv(). However, if we're referencing any of these custom screens, our builds are breaking on Vercel stating:

Type error: Object literal may only specify known properties, and 'xs' does not exist in type '{ sm?: "horizontal" | "vertical"; md?: "horizontal" | "vertical"; lg?: "horizontal" | "vertical"; xl?: "horizontal" | "vertical"; "2xl"?: "horizontal" | "vertical"; initial?: "horizontal" | "vertical"; }'.

It looks like the default Tailwind screens are being generated rather than the custom screens—though only on Vercel (works locally but we haven't tested this anywhere else other than locally and Vercel).

My assumption is that this has something to do with our generated.d.ts file for TVGeneratedScreens. I'm unsure how to proceed without doing something hacky in TS to get our builds to succeed on Vercel or if I'm just doing something inappropriate.

This is our tailwind.config.ts setup:

// ./src/design/theme/tokens/screens.ts
export const screens = {
  xs: '340px',
  'archive-mobile': '450px',
  'team-mobile': '450px',
  'navigation-mobile': '500px',
  sm: '640px',
  md: '900px',
  'navigation-desktop': '1000px',
  lg: '1200px',
  xl: '1600px',
  '2xl': '2000px',
  '3xl': '2400px',
  '560': '560px',
  '750': '750px',
}
// tailwind.config.ts
import type { Config } from 'tailwindcss'
// ..
import screens from './src/design/theme/tokens/screens'

export default withTV({
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
  plugins: [
    // ...
  ],
  theme: {
    screens
    // ...
  },
}) satisfies Config

And the component raising the TS error on Vercel:

const filterList = tv(
  {
    base: 'mt-2 flex items-start gap-2',
    variants: {
      layout: {
        horizontal:
          'flex-row mb-2 box-[mx*-1] box-px overflow-y-hidden overflow-x-scroll [-ms-overflow-style:none] scrollbar-width-none [&::-webkit-scrollbar]:hidden',
        vertical: 'flex-col',
      },
    },
  },
  { responsiveVariants: true }
)

// ...

<div
  className={filterList({
    layout: { initial: 'horizontal', xs: 'vertical' },
  })}
>

Note that we can see all of the correct screens here:

Screenshot 2024-04-30 at 3 08 55 PM

Vercel error:

Type error: Object literal may only specify known properties, and 'xs' does not exist in type '{ sm?: "horizontal" | "vertical"; md?: "horizontal" | "vertical"; lg?: "horizontal" | "vertical"; xl?: "horizontal" | "vertical"; "2xl"?: "horizontal" | "vertical"; initial?: "horizontal" | "vertical"; }'.

To Reproduce Steps to reproduce the behavior:

  1. Override the default screens in your tailwind.config.ts
  2. Use tv  local config to set responsiveVariants to true
  3. Set a non-standard screen size in your responsive variant
  4. Deploy project to Vercel
  5. Note the error in the build log

Note: If I use a standard screen size (ie. 'sm' | 'md' | 'lg' | 'xl' | '2xl', there's no error)

Expected behavior No build error and everything works as expected as it does locally.

Screenshots

Screenshot 2024-04-30 at 3 15 49 PM

Desktop (please complete the following information):

Additional context

troymcginnis commented 6 months ago

As an update, we're currently using a hacky workaround that seems to be appeasing the issue both locally and on Vercel.

// types.ts
export type ResponsiveScreens = keyof typeof screens
import { ResponsiveScreens } from '@types'

// ...

const filterList = tv(
  {
    base: 'mt-2 flex items-start gap-2',
    variants: {
      layout: {
        horizontal:
          'flex-row mb-2 box-[mx*-1] box-px overflow-y-hidden overflow-x-scroll [-ms-overflow-style:none] scrollbar-width-none [&::-webkit-scrollbar]:hidden',
        vertical: 'flex-col',
      },
    },
  },
  { responsiveVariants: true as unknown as ResponsiveScreens[] }
)
Screenshot 2024-05-01 at 10 33 35 AM

The solution is a bit cumbersome to implement and doesn't feel great since we're casting a boolean  to an array of screen sizes. On the flip side, we're getting accurate types and autocomplete across the codebase and we're no longer seeing the error on Vercel.


However, we're now seeing a new error in the Vercel logs, though it doesn't stop the build.

Tailwind Variants Transform Failed: Unexpected identifier

Screenshot 2024-05-01 at 10 36 09 AM

Checking the frontend, our responsive variants are working as expected, including non-standard screens, so I'm not sure what the deal is.

troymcginnis commented 6 months ago

Looks like I've spoken too soon. Another one of our projects is seeing an error causing a build failure.

Screenshot 2024-05-01 at 11 07 34 AM

My TypeScript knowledge is a bit limited but I wonder if there's a way to have allow screen sizes, if custom to be statically typed? Or perhaps there's some further insight?

troymcginnis commented 6 months ago

Another update:

I've found a reliable workaround that feels better than previous attempts, though not great.

Further information:

It looks like it's somehow related to how Vercel caches node_modules. On a new branch cut, creating a new preview branch on Vercel, all of the TVGeneratedScreens are showing the correct, custom values. If screens are added/removed/updated, these changes are not picked up. If a deployment is made without cache (ie. by deselecting "Use existing Build Cache" in the dashboard) all of the custom screens generated during the initial branch cut are wiped out and only the default Tailwind screens ('sm' | 'md' | 'lg' | 'xl' | '2xl') remain.

Current Workaround:

Our current workaround involves forcefully generating the TVGeneratedScreen type on each deployment with the following:

// generate-tv-types.ts
import { generateTypes } from 'tailwind-variants/dist/generator.js'
import { screens } from './src/design/tokens/screens'

generateTypes({ screens })
// package.json
{
  // ...
  "scripts": {
    "dev": "next",
    "build": "yarn build:web",
    "build:web": "yarn build:tv && yarn build:favicon && next build && yarn build:sitemap",
    "build:tv": "tsx ./generate-tv-types.ts",
    // ...
  },
  // ...
  "devDependencies": {
    "tsx": "^4.8.2"
    // ...
  }
}

Could be worse 🤷🏼

I'm not really sure how to further address the issue as I would expect the generateTypes() call from within the withTV() method to work fine on build since we build TW CSS on deploy as well which, I would expect, would include tailwind.config.ts, thus withTV(), thus generateTypes()  would fire—alas, I'm missing something in my understanding to further diagnose the issue.

Happy for any further insight. Also happy to roll this into our base and move forward with it if there's no further insights at this time.

troymcginnis commented 6 months ago

Update:

I've opened a support ticket with Vercel to investigate on their end as well. In the meanwhile, if there's interest in troubleshooting further, I've setup a pubic minimal reproducible repository which can be cloned/forked and deployed to Vercel to immediately see the error: https://github.com/gearbox-built/tailwind-variants-vercel

Screenshot 2024-05-02 at 4 35 34 PM
troymcginnis commented 6 months ago

Update:

Vercel suggests that it's solely the responsibility of the package and falls out of their scope of support. I am not familiar enough with tailwind's JIT compiler, how it relates to the tailwind.config.js and how this all, ultimately, effects the withTV function.

Can anyone (@mskelton?) lend some understanding around how the withTV function ties into a build process to help narrow down if this is a tailwind-variants thing or a Vercel this?

mskelton commented 6 months ago

@troymcginnis I don't use withTV in any of my projects at work, so I'd honestly have to lean more on @jrgarciadev in regards to his experience with responsive variants and the transformer.

jrgarciadev commented 6 months ago

@tianenpang

Code-Victor commented 5 months ago

@troymcginnis I don't have any idea of how to fix your issue, but your documentation is top notch!!! It should make it easier for @tianenpang to help

tianenpang commented 4 months ago

Hi @troymcginnis, could you please provide a repository to reproduce the issue or please share the versions of next, tailwind-variants, tailwindcss, and typescript you're using in your project. thanks!

The generateTypes method will be triggered every time tailwind.config is changed and executed before linting and checking the validity of types in the Next App. At this point, the types for screens should already be synchronized with the custom settings 🤔

I can't reproduce this issue here https://github.com/tianenpang/nextui-demo

The build logs

image
troymcginnis commented 4 months ago

Hi troymcginnis, could you please provide a repository to reproduce the issue or please share the versions of next, tailwind-variants, tailwindcss, and typescript you're using in your project. thanks!

@tianenpang Repository to reproduce available here: https://github.com/gearbox-built/tailwind-variants-vercel.

Redeployment attempted today:

Screenshot 2024-07-15 at 5 00 09 PM
samogray commented 1 month ago

The same issue. We catch TS issue on CI

image

but locally, we don't see any problem; TS works correctly, looks withTV, skips its own breakpoints config, and uses default tailwind.

image image
samogray commented 3 weeks ago

So, as we understood, generateTypes is running before resolving our tailwind config, and it does not have access to our types. our solution is to create our type cast

import { VariantProps } from 'tailwind-variants';

// override default responsive tailwind variant type
type ResponsiveVariantT<T> = T | (T & { xs?: T; xxl?: T });

// create generic for reuse  this type with overriding types

export type VariantsT<
  StylesT extends (...args: any) => any,
  OmittedKeys extends keyof VariantProps<StylesT>,
> = Omit<VariantProps<StylesT>, OmittedKeys> & {
  [Key in OmittedKeys]?: ResponsiveVariantT<VariantProps<StylesT>[Key]>;
};

export const buttonStyles = tv(buttonStylesConfig)

// overidding variants with support our breakpoints keys   'wide' | 'size' | 'variant' | 'multiline'

export type ButtonVariantsT = VariantsT<
  typeof buttonStyles,
  'wide' | 'size' | 'variant' | 'multiline'
>;