-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support loading config files via @config
#14239
Conversation
7f1ea32
to
514d27d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing work getting this working dudes! 🤩 This is a beefy one so I think would be best to do a real review together on a call on Monday morning.
let resolvedPath = path.resolve(inputBasePath, pluginPath) | ||
fullRebuildPaths.push(Promise.resolve([resolvedPath])) | ||
fullRebuildPaths.push(getModuleDependencies(resolvedPath)) | ||
return import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()).then( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does the query string stuff do here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It serves as a way to "bust" the cache for the file. ESM doesn't have the equivalent of a require cache in Node so we have to use this to ensure the file is loaded fresh.
if (configPath[0] !== '.') { | ||
return import(configPath).then((m) => m.default ?? m) | ||
} | ||
|
||
return import(pluginPath).then((m) => m.default ?? m) | ||
let resolvedPath = path.resolve(inputBasePath, configPath) | ||
fullRebuildPaths.push(Promise.resolve([resolvedPath])) | ||
fullRebuildPaths.push(getModuleDependencies(resolvedPath)) | ||
return import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()).then( | ||
(m) => m.default ?? m, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks identical to the code for loading plugins, makes me wonder if we should either extract this so we don't have to keep it in sync, or if we should replace loadPlugin
and loadConfig
with something else that makes more sense, like maybe that's not what we're actually doing here and it's more about loading JS or resolving a module or some other terminology? Not sure 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We talked about exactly this and were both wondering the same thing. I think we can pretty likely merge these two — only thing is if we want to handle errors differently or something.
@@ -128,17 +132,34 @@ export async function handle(args: Result<ReturnType<typeof options>>) { | |||
|
|||
let inputFile = args['--input'] && args['--input'] !== '-' ? args['--input'] : process.cwd() | |||
let inputBasePath = path.dirname(path.resolve(inputFile)) | |||
let fullRebuildPaths: Promise<string[]>[] = [Promise.resolve(cssImportPaths)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to make this an array of arrays just because we want to store these promises before they are resolved and we have no way to store it as a flat list in the first place? Seems fine if so but weird quirk if it's not actually necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah it's because we want to store the promises to be resolved later. Maybe there's something else we could do here though. 🤔
@@ -0,0 +1,44 @@ | |||
{ | |||
"name": "@tailwindcss/node", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be an internal package like some of our others or do we need to actually publish it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it needs to be because of how the import works otherwise we'll have to duplicate these files into each package + configure the exports (which is something we could do — I just think it's probably a worse tradeoff)
for (let file of resolvedConfig.content.files) { | ||
if ('raw' in file) { | ||
throw new Error( | ||
`Error in the config file/plugin/preset. The \`content\` key contains a \`raw\` entry:\n\n${JSON.stringify(file, null, 2)}\n\nThis feature is not currently supported.`, | ||
) | ||
} | ||
|
||
globs.push(file.pattern) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this currently throws away file.base
. We'll be fixing this up with another change briefly.
…dencies between our clients
ff3554d
to
ae63a57
Compare
ae63a57
to
3a8dd7a
Compare
To make TypeScript more happy.
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Builds on #14239 — that PR needs to be merged first. This PR allows plugins defined with `plugin.withOptions` to receive options in CSS when using `@plugin` as long as the options are simple key/value pairs. For example, the following is now valid and will include the forms plugin with only the base styles enabled: ```css @plugin "@tailwindcss/forms" { strategy: base; } ``` We handle `null`, `true`, `false`, and numeric values as expected and will convert them to their JavaScript equivalents. Comma separated values are turned into arrays. All other values are converted to strings. For example, in the following plugin definition, the options that are passed to the plugin will be the correct types: - `debug` will be the boolean value `true` - `threshold` will be the number `0.5` - `message` will be the string `"Hello world"` - `features` will be the array `["base", "responsive"]` ```css @plugin "my-plugin" { debug: false; threshold: 0.5; message: Hello world; features: base, responsive; } ``` If you need to pass a number or boolean value as a string, you can do so by wrapping the value in quotes: ```css @plugin "my-plugin" { debug: "false"; threshold: "0.5"; message: "Hello world"; } ``` When duplicate options are encountered the last value wins: ```css @plugin "my-plugin" { message: Hello world; message: Hello plugin; /* this will be the value of `message` */ } ``` It's important to note that this feature is **only available for plugins defined with `plugin.withOptions`**. If you try to pass options to a plugin that doesn't support them, you'll get an error message when building: ```css @plugin "my-plugin" { debug: false; threshold: 0.5; } /* Error: The plugin "my-plugin" does not accept options */ ``` Additionally, if you try to pass in more complex values like objects or selectors you'll get an error message: ```css @plugin "my-plugin" { color: { red: 100; green: 200; blue: 300 }; } /* Error: Objects are not supported in `@plugin` options. */ ``` ```css @plugin "my-plugin" { .some-selector > * { primary: "blue"; secondary: "green"; } } /* Error: `@plugin` can only contain declarations. */ ``` --------- Co-authored-by: Philipp Spiess <hello@philippspiess.com> Co-authored-by: Robin Malfait <malfait.robin@gmail.com> Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
…uilds (#14269) Fixes #14205 Fixes #14106 This PR reworks the Vite extension in order to supprt `lightningcss` as the pre-processor, enable faster rebuilds, and adds support for `vite build --watch` mode. To make this change possible, we've done two major changes to the extension that have caused the other changes. ## 1. Tailwind CSS is a preprocessor We now run all of our modifications in `enforce: 'pre'`. This means that Tailwind CSS now gets the untransformed CSS files rather than the one already going through postcss or lightningcss. We do this because Tailwind CSS _is_ a preprocessor at the same level as those tools and we do sometimes use the language in ways that [creates problems when it's the input for other bundlers](#14269). The correct solution here is to make Tailwind not depend on any other transformations. The main reason we were not using the `enforce: 'pre'` phase in Vite before was becuase we relied on the `@import` flattening of postcss so we now have to do this manually. `@import` flattening is now a concern that every Tailwind V4 client has to deal with so this might actually be something we want to inline into tailwindcss in the future. ## 2. A Vite config can have multiple Tailwind roots This is something that we have not made very explicit in the previous iteration of the Vite plugin but we have to support multiple Tailwind roots in a single Vite workspace. A Tailwind root is a CSS file that is used to configure Tailwind. Technically, any CSS file can be the input for `tailwindcss` but you have to add certain rules (e.g. `@import "tailwindcss";`) to make the compiler do something. A workspace can have multiple of these rules (e.g. by having different Tailwind configures for different sub-pages). With the addition of [support for `@source` rules](#14078) and [JS config files](#14239), Tailwind roots become more complex and can have a custom list of _dependencies_ (that is other JavaScript modules the compiler includes as part of these new rules). In order to _only rebuild the roots we need to_, we have to make this separation very clear. --------- Co-authored-by: Jordan Pittman <jordan@cryptica.me> Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
In Tailwind v4 the CSS file is the main entry point to your project and is generally configured via
@theme
. However, given that all v3 projects were configured via atailwind.config.js
file we definitely need to support those. This PR adds support for loading existing Tailwind config files by adding an@config
directive to the CSS — similar to how v3 supported multiple config files except that this is now required to use a config file.You can load a config file like so:
A few notes:
import()
in Node)