Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Conversation

@jgttech
Copy link

@jgttech jgttech commented Apr 17, 2025

Tailwind v4 Monorepo Support

Adds support for monorepo applications that make use of multiple packages within a single repository with Tailwind v4 without a configuration file. As well as supports custom Tailwind v4 monorepo packages.

Feature

Any package within a monorepo that contains tailwindcss in the package.json file can be detected and have the LSP enabled on the buffer. Additionally, this also supports custom tailwind namespaced packages. For example, if someone has a monorepo and creates a @foo/tailwindcss, @foo/tailwind, and/or @foo/tw package within a monorepo, that will qualify as a valid package and enable the LSP on the buffer for custom packages. This is particularly useful for custom monorepo architectures that use a shared Tailwind v4 implementation across multiple packages.

For non-monorepo applications, this would default back to the standard approach for detection.

Caching

This also contains an in-memory caching for monorepos that keeps track of the base directories that where detected when the buffer was loaded for a file. This is done so that subsequent opening of many buffers or the reloading of a buffer does not have to perform any additional I/O operations to detect if the LSP needs to be enabled for that active buffer. Optimizing performance over time.

The way this works is that it detects the base directory that a packages package.json exists in and if the LSP needs to be enabled in that buffer, it stores a reference to that base path. The next buffer that gets loaded the cache checking looks to see if that file contains the same base path as what exists in cache. If it does, no I/O is performed and the cached path is returned, enabling the LSP without the need to even read the file system.

For example, if you had package/foo/src/components/MyComp1.tsx and package/foo/src/components/MyComp2.tsx, after loading MyComp1.tsx into the buffer, when you open up MyComp2.tsx it will not perform an I/O read operation because the base path of package/foo will exist in cache and return the cached path which will enable the LSP. This would apply to all files under package/foo. However, you have a package package/bar, then an I/O operation would be performed on the first file (assuming it matches the criteria to enable it) and not on the subsequent files loaded into buffers.

Ultimately, this means that a single cache entry can serve a potentially infinite number of buffers and only perform I/O 1 time for the first file opened. It does this without increasing cache either. So, no excessive memory usage will occur and the cache stays lean and fast with minimal traversals.

Example Usage

This is how I use it and this would support monorepos as well.

return {
  "luckasRanarison/tailwind-tools.nvim",
  name = "tailwind-tools",
  build = ":UpdateRemotePlugins",
  dependencies = {
    "nvim-treesitter/nvim-treesitter",
    "nvim-telescope/telescope.nvim",
    "neovim/nvim-lspconfig",
  },
  opts = {
    server = {
      settings = {
        experimental = {
          classRegex = {
            { "[a-zA-Z]+`([^`]*)`", "([^`]*)" },
            { "cva\\(([^)]*)\\)",   "[\"'`]([^\"'`]*).*?[\"'`]" },
            { "cx\\(([^)]*)\\)",    "(?:'|\"|`)([^']*)(?:'|\"|`)" },
            ": `([^`]*)",           -- : `...`
            "= `([^`]*)",           -- = `...`
            "tw`([^`]*)",           -- tw`...`
            "\\$`([^`]*)",          -- $`...`
            "classes`([^`]*)",      -- classes`...`
            'tw="([^"]*)',          -- <div tw="..." />
            "tw='([^']*)",          -- <div tw='...' />
            'tw={"([^"}]*)',        -- <div tw={"..."} />
            "tw={'([^'}]*)",        -- <div tw={'...'} />
            "tw={`([^`}]*)",        -- <div tw={`...`} />
            'className="([^"]*)',   -- <div className="..." />
            "className='([^']*)",   -- <div className='...' />
            'className={"([^"}]*)', -- <div className={"..."} />
            "className={'([^'}]*)", -- <div className={'...'} />
            "className={`([^`}]*)", -- <div className={`...`} />
          },
        },
      },
    },
  },
}

jgttech added 2 commits April 17, 2025 02:13
…or a namespaced package of @foo/tailwindcss, @foo/tailwind, and/or @foo/tw in your package.json it will automatically enable the LSP within the monorepo. Useful for monorepo structures that use a shared Tailwind v4 configuration in a separate package and want to support developing within Neovim.
@jgttech
Copy link
Author

jgttech commented Apr 17, 2025

@luckasRanarison just pinging to see if I can get this in so I can use this for my own IDE through the plugin rather than maintaining a fork.

@luckasRanarison
Copy link
Owner

@jgttech No worries, thanks for your PR, I already took a quick look but I'm still kind of busy right now. I'll definitely review it later!

@jgttech
Copy link
Author

jgttech commented Apr 17, 2025

Thank you! I appreciate that. 🤞

@jgttech
Copy link
Author

jgttech commented Apr 25, 2025

@luckasRanarison any updates on this?

return nil
end

local repo_package_json = repo_root .. "/package.json"
Copy link
Contributor

@tyler-dot-earth tyler-dot-earth May 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jgttech if you're using pnpm, you may not have a "workspaces" key in your package.json in favor of a pnpm-workspace.yaml (docs)

suggestion: also check for pnpm-workspace.yaml (i think there's additional code needed, but haven't worked through everything in this PR yet) EDIT: PR here — more comprehensive than the suggestion block in this comment.

Suggested change
local repo_package_json = repo_root .. "/package.json"
-- Some monorepos use pnpm-workspace.yaml without
-- any reference to "workspaces" in the `package.json`,
-- so let's check for that before reading the `package.json`
local repo_pnpm_workspace = repo_root .. "/pnpm-workspace.yaml"
if vim.fn.filereadable(repo_pnpm_workspace) == 1 then
cache.is_monorepo = true
return repo_root
end
local repo_package_json = repo_root .. "/package.json"

please take my input with relatively low weight — i'm not a monorepo expert by any means, just trying to get your branch working with my own monorepo.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made a PR to your branch here: https://github.com/jgttech/tailwind-tools.nvim/pull/1

tested locally, seems to be working ✅

noting: works as long as i start nvim from the repo root. if i start nvim from <repo>/apps/foo it will detect <repo>/apps/foo as the root for some reason, but i think that may be unrelated to any changes being made here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for these additions! I really appreciate that. I was only going for the more generic NPM-based use case as a "first run" approach to trying to fix this issue so if an addition can improve the capabilities, why not, right?

Yes, this only works if your in the root directory of the repo, but that intentional for a few reasons; most notably because "what" counts as the "root" of a repo can be highly subjective and I did not want to introduce the idea of some "root": true ESLint kind of idea to the, already standardized, package.json or something. So because I can't really guarentee what the "root" is, I assume you are at the root. That is greatly simplifies things, I think.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a heads up, I merged in your upstream PR. Thanks for the addition.

Copy link
Contributor

@tyler-dot-earth tyler-dot-earth May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good insights on the root behavior - thanks @jgttech.

After a little more testing on the branch, adding these observations:

  • ✔️ class name suggestions working as expected in the packages/ui project that sets up tailwind, has shadcn/ui, etc
  • ❌ class name suggestions not working in the apps/web app that consumes the aforementioned packages/ui.

Haven't had time to dig in on the code to figure out how the tailwind suggestions are discovered relative to file location.

EDIT: Huh... sometimes it's working, sometimes it's not. Interesting. I'll keep monitoring.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is strange. I will monitor. Ideally, when I originally created it I wanted it to find the root of the monorepo and just do a glob search from that context, but idk if that is a good approach or not.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi guys, I'm really sorry for the delayed response. Thanks for the great work! I took a look at everything and I want to add some tests myself for the LSP activation like the ones we already have in the repo. @tyler-dot-earth is this template enough for reproducing the issue? I want to use a setup similar to yours for the e2e tests

Copy link
Contributor

@tyler-dot-earth tyler-dot-earth May 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luckasRanarison Yes, shadcn/ui's monorepo template should be sufficient for reproducing an issue if it exists.

EDIT: and my neovim config, if useful.

adds check for pnpm-workspace.yaml file for pnpm monorepos that do not use a package.json with "workspaces" entry

This comment was marked as resolved.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would be a bad idea because they closed this PR. This one can still merge in against master, atm.

@jgttech jgttech requested a review from tyler-dot-earth May 13, 2025 03:23
@jgttech jgttech closed this by deleting the head repository Oct 25, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants