This PR brings `@import` resolution into Tailwind CSS core. This means
that our clients (PostCSS, Vite, and CLI) no longer need to depend on
`postcss` and `postcss-import` to resolve `@import`. Furthermore this
simplifies the handling of relative paths for `@source`, `@plugin`, or
`@config` in transitive CSS files (where the relative root should always
be relative to the CSS file that contains the directive). This PR also
fixes a plugin resolution bug where non-relative imports (e.g. directly
importing node modules like `@plugin '@tailwindcss/typography';`) would
not work in CSS files that are based in a different npm package.
### Resolving `@import`
The core of the `@import` resolution is inside
`packages/tailwindcss/src/at-import.ts`. There, to keep things
performant, we do a two-step process to resolve imports. Imagine the
following input CSS file:
```css
@import "tailwindcss/theme.css";
@import "tailwindcss/utilities.css";
```
Since our AST walks are synchronous, we will do a first traversal where
we start a loading request for each `@import` directive. Once all loads
are started, we will await the promise and do a second walk where we
actually replace the AST nodes with their resolved stylesheets. All of
this is recursive, so that `@import`-ed files can again `@import` other
files.
The core `@import` resolver also includes extensive test cases for
[various combinations of media query and supports conditionals as well
als layered
imports](https://developer.mozilla.org/en-US/docs/Web/CSS/@import).
When the same file is imported multiple times, the AST nodes are
duplicated but duplicate I/O is avoided on a per-file basis, so this
will only load one file, but include the `@theme` rules twice:
```css
@import "tailwindcss/theme.css";
@import "tailwindcss/theme.css";
```
### Adding a new `context` node to the AST
One limitation we had when working with the `postcss-import` plugin was
the need to do an additional traversal to rewrite relative `@source`,
`@plugin`, and `@config` directives. This was needed because we want
these paths to be relative to the CSS file that defines the directive
but when flattening a CSS file, this information is no longer part of
the stringifed CSS representation. We worked around this by rewriting
the content of these directives to be relative to the input CSS file,
which resulted in added complexity and caused a lot of issues with
Windows paths in the beginning.
Now that we are doing the `@import` resolution in core, we can use a
different data structure to persist this information. This PR adds a new
`context` node so that we can store arbitrary context like this inside
the Ast directly. This allows us to share information with the sub tree
_while doing the Ast walk_.
Here's an example of how the new `context` node can be used to share
information with subtrees:
```ts
const ast = [
rule('.foo', [decl('color', 'red')]),
context({ value: 'a' }, [
rule('.bar', [
decl('color', 'blue'),
context({ value: 'b' }, [
rule('.baz', [decl('color', 'green')]),
]),
]),
]),
]
walk(ast, (node, { context }) => {
if (node.kind !== 'declaration') return
switch (node.value) {
case 'red': assert(context.value === undefined)
case 'blue': assert(context.value === 'a')
case 'green': assert(context.value === 'b')
}
})
```
In core, we use this new Ast node specifically to persist the `base`
path of the current CSS file. We put the input CSS file `base` at the
root of the Ast and then overwrite the `base` on every `@import`
substitution.
### Removing the dependency on `postcss-import`
Now that we support `@import` resolution in core, our clients no longer
need a dependency on `postcss-import`. Furthermore, most dependencies
also don't need to know about `postcss` at all anymore (except the
PostCSS client, of course!).
This also means that our workaround for rewriting `@source`, the
`postcss-fix-relative-paths` plugin, can now go away as a shared
dependency between all of our clients. Note that we still have it for
the PostCSS plugin only, where it's possible that users already have
`postcss-import` running _before_ the `@tailwindcss/postcss` plugin.
Here's an example of the changes to the dependencies for our Vite client
✨ :
<img width="854" alt="Screenshot 2024-09-19 at 16 59 45"
src="https://github.com/user-attachments/assets/ae1f9d5f-d93a-4de9-9244-61af3aff1237">
### Performance
Since our Vite and CLI clients now no longer need to use `postcss` at
all, we have also measured a significant improvement to the initial
build times. For a small test setup that contains only a hand full of
files (nothing super-complex), we measured an improvement in the
**3.5x** range:
<img width="1334" alt="Screenshot 2024-09-19 at 14 52 49"
src="https://github.com/user-attachments/assets/06071fb0-7f2a-4de6-8ec8-f202d2cc78e5">
The code for this is in the commit history if you want to reproduce the
results. The test was based on the Vite client.
### Caveats
One thing to note is that we previously relied on finding specific
symbols in the input CSS to _bail out of Tailwind processing
completely_. E.g. if a file does not contain a `@tailwind` or `@apply`
directive, it can never be a Tailwind file.
Since we no longer have a string representation of the flattened CSS
file, we can no longer do this check. However, the current
implementation was already inconsistent with differences on the allowed
symbol list between our clients. Ideally, Tailwind CSS should figure out
wether a CSS file is a Tailwind CSS file. This, however, is left as an
improvement for a future API since it goes hand-in-hand with our planned
API changes for the core `tailwindcss` package.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR improves how the `text-{size}` utilities interact with the
`leading-*`, `tracking-*`, and `font-{weight}` utilities, ensuring that
if the user explicitly uses any of those utilities that those values are
not squashed by any defaults baked into the `text-{size}` utilities.
Prior to this PR, if you wrote something like this:
```html
<div class="text-lg leading-none md:text-2xl">
```
…the `leading-none` class would be overridden by the default line-height
value baked into the `text-2xl` utility at the `md` breakpoint. This has
been a point of confusion and frustration for people [in the
past](https://github.com/tailwindlabs/tailwindcss/issues/6504) who are
annoyed they have to keep repeating their custom `leading-*` value like
this:
```html
<div class="text-lg leading-none md:text-2xl md:leading-none lg:text-4xl lg:leading-none">
```
This PR lets you write this HTML instead but get the same behavior as
above:
```html
<div class="text-lg leading-none md:text-2xl lg:text-4xl">
```
It's important to note that this change _only_ applies to line-height
values set explicitly with a `leading-*` utility, and does not apply to
the line-height modifier.
In this example, the line-height set by `text-sm/6` does _not_ override
the default line-height included in the `md:text-lg` utility:
```html
<div class="text-sm/6 md:text-lg">
```
That means these two code snippets behave differently:
```html
<div class="text-sm/6 md:text-lg">…</div>
<div class="text-sm leading-6 md:text-lg">…</div>
```
In the top one, the line-height `md:text-lg` overrides the line-height
set by `text-sm/6`, but in the bottom one, the explicit `leading-6`
utility takes precedence.
This PR applies the same improvements to `tracking-*` and
`font-{weight}` as well, since all font size utilities can also
optionally specify default `letter-spacing` and `font-weight` values.
We achieve this using new semi-private CSS variables like we do for
things like transforms, shadows, etc., which are set by the `leading-*`,
`tracking-*`, and `font-{weight}` utilities respectively. The
`text-{size}` utilities always use these values first if they are
defined, and the default values become fallbacks for those variables if
they aren't present.
We use `@property` to make sure these variables are reset to `initial`
on a per element basis so that they are never inherited, like with every
other variable we define.
This PR does slightly increase the amount of CSS generated, because now
utilities like `leading-5` look like this:
```diff
.leading-5 {
+ --tw-leading: 1.25rem;
line-height: 1.25rem;
}
```
…and utilites like `text-sm` include a `var(…)` lookup that they didn't
include before:
```diff
.text-sm {
font-size: 0.875rem;
- line-height: var(--font-size-sm--line-height, 1.25rem);
+ line-height: var(--tw-leading, var(--font-size-sm--line-height, 1.25rem));
}
```
If this extra CSS doesn't feel worth it for the small improvement in
behavior, we may consider just closing this PR and keeping things as
they are.
This PR is also a breaking change for anyone who was depending on the
old behavior, and expected the line-height baked into the `md:text-lg`
class to take precedence over the explicit `leading-6` class:
```html
<div class="text-sm leading-6 md:text-lg">…</div>
```
Personally I am comfortable with this because of the fact that you can
still get the old behavior by preferring a line-height modifier:
```html
<div class="text-sm/6 md:text-lg">…</div>
```
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
When you configure custom content globs inside an `@config` file, we
want to tread these globs as being relative to that config file and not
the CSS file that requires the content file. A config can be used by
multiple CSS configs.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
<!--
👋 Hey, thanks for your interest in contributing to Tailwind!
**Please ask first before starting work on any significant new
features.**
It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create an issue to first
discuss any significant new features. This includes things like adding
new utilities, creating new at-rules, or adding new component examples
to the documentation.
https://github.com/tailwindcss/tailwindcss/blob/master/.github/CONTRIBUTING.md
-->
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 a `tailwind.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:
```
@import "tailwindcss";
@config "./path/to/tailwind.config.js";
```
A few notes:
- Both CommonJS and ESM config files are supported (loaded directly via
`import()` in Node)
- This is not yet supported in Intellisense or Prettier — should
hopefully land next week
- TypeScript is **not yet** supported in the config file — this will be
handled in a future PR.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Closes#14253
Since we changed the export strategy for the postcss client in #14132,
we accidentally no longer generated type exports for this package.
This PR adds a type export back. We now use a similar pattern to the
`./colors` and `./defaultTheme` exports in the tailwindcss package where
we have a separate cjs entrypoint.
The changes were validated manually in a playground project that were
installing the updated dependencies from tarballs.
Here is one example of it working as expected:
<img width="750" alt="Screenshot 2024-08-26 at 14 10 07"
src="https://github.com/user-attachments/assets/83de15f2-1543-4805-9231-9b8df1636c5e">
This PR updates the API for interacting with the Oxide API. Until now,
we used the name `scanDir(…)` which is fine, but we do way more work
right now.
We now have features such as:
1. Auto source detection (can be turned off, e.g.: `@tailwindcss/vite`
doesn't need it)
2. Scan based on `@source`s found in CSS files
3. Do "incremental" rebuilds (which means that the `scanDir(…)` result
was stateful).
To solve these issues, this PR introduces a new `Scanner` class where
you can pass in the `detectSources` and `sources` options. E.g.:
```ts
let scanner = new Scanner({
// Optional, omitting `detectSources` field disables automatic source detection
detectSources: { base: __dirname },
// List of glob entries to scan. These come from `@source` directives in CSS.
sources: [
{ base: __dirname, pattern: "src/**/*.css" },
// …
],
});
```
The scanner object has the following API:
```ts
export interface ChangedContent {
/** File path to the changed file */
file?: string
/** Contents of the changed file */
content?: string
/** File extension */
extension: string
}
export interface DetectSources {
/** Base path to start scanning from */
base: string
}
export interface GlobEntry {
/** Base path of the glob */
base: string
/** Glob pattern */
pattern: string
}
export interface ScannerOptions {
/** Automatically detect sources in the base path */
detectSources?: DetectSources
/** Glob sources */
sources?: Array<GlobEntry>
}
export declare class Scanner {
constructor(opts: ScannerOptions)
scan(): Array<string>
scanFiles(input: Array<ChangedContent>): Array<string>
get files(): Array<string>
get globs(): Array<GlobEntry>
}
```
The `scanFiles(…)` method is used for incremental rebuilds. It takes the
`ChangedContent` array for all the new/changes files. It returns whether
we scanned any new candidates or not.
Note that the `scanner` object is stateful, this means that we don't
have to track candidates in a `Set` anymore. We can just call
`getCandidates()` when we need it.
This PR also removed some unused code that we had in the `scanDir(…)`
function to allow for sequential or parallel `IO`, and sequential or
parallel `Parsing`. We only used the same `IO` and `Parsing` strategies
for all files, so I just got rid of it.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR bumps dependencies
We also make some dependencies `catalog:` dependencies, which allows us
to keep
the version in sync. E.g.: `lightningcss` and `@types/node`.
Bumped `turbo` to the latest version + enabled the new UI
Fixed a bug in the tests now that `lightningcss` outputs the correct
value.
Alternative to #14110
This PR changes the way how we load plugins to be compatible with ES6
async `import`s. This allows us to load plugins even inside the browser
but it comes at a downside: We now have to change the `compile` API to
return a `Promise`...
So most of this PR is rewriting all of the call sites of `compile` to
expect a promise instead of the object.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR is an umbrella PR where we will add support for the new
`@source` directive. This will allow you to add explicit content glob
patterns if you want to look for Tailwind classes in other files that
are not automatically detected yet.
Right now this is an addition to the existing auto content detection
that is automatically enabled in the `@tailwindcss/postcss` and
`@tailwindcss/cli` packages. The `@tailwindcss/vite` package doesn't use
the auto content detection, but uses the module graph instead.
From an API perspective there is not a lot going on. There are only a
few things that you have to know when using the `@source` directive, and
you probably already know the rules:
1. You can use multiple `@source` directives if you want.
2. The `@source` accepts a glob pattern so that you can match multiple
files at once
3. The pattern is relative to the current file you are in
4. The pattern includes all files it is matching, even git ignored files
1. The motivation for this is so that you can explicitly point to a
`node_modules` folder if you want to look at `node_modules` for whatever
reason.
6. Right now we don't support negative globs (starting with a `!`) yet,
that will be available in the near future.
Usage example:
```css
/* ./src/input.css */
@import "tailwindcss";
@source "../laravel/resources/views/**/*.blade.php";
@source "../../packages/monorepo-package/**/*.js";
```
It looks like the PR introduced a lot of changes, but this is a side
effect of all the other plumbing work we had to do to make this work.
For example:
1. We added dedicated integration tests that run on Linux and Windows in
CI (just to make sure that all the `path` logic is correct)
2. We Have to make sure that the glob patterns are always correct even
if you are using `@import` in your CSS and use `@source` in an imported
file. This is because we receive the flattened CSS contents where all
`@import`s are inlined.
3. We have to make sure that we also listen for changes in the files
that match any of these patterns and trigger a rebuild.
PRs:
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14063
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14085
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14079
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14067
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14076
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14080
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14127
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14135
Once all the PRs are merged, then this umbrella PR can be merged.
> [!IMPORTANT]
> Make sure to merge this without rebasing such that each individual PR
ends up on the main branch.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR adds support for a new `inline` option when defining a `@theme`
block that tells Tailwind to use raw theme values for utilities instead
of referencing the corresponding generated CSS variable.
```css
/* Input */
@theme inline {
--color-red-500: #ef4444;
/* ... */
}
/* Example output */
:root {
--color-red-500: #ef4444;
}
.text-red-500 {
color: #ef4444;
}
```
This can be composed with the existing `reference` option in case you
want to define a `@theme` block as both `reference` (so the variables
aren't generated) and `inline`:
```css
/* Input */
@theme inline reference {
--color-red-500: #ef4444;
/* ... */
}
/* Example output */
.text-red-500 {
color: #ef4444;
}
```
Since you can have multiple `@theme` blocks, you can even define some
values normally and some as inline based on how you're using them. For
example you might want to use `inline` for defining literal tokens like
`--color-red-500`, but include the variable for tokens that you want to
be able to theme like `--color-primary`:
```css
/* Input */
@theme inline {
--color-red-500: #ef4444;
/* ... */
}
@theme {
--color-primary: var(--color-red-500);
}
/* Example output */
:root {
--color-red-500: #ef4444;
--color-primary: var(--color-red-500);
}
.text-red-500 {
color: #ef4444;
}
.text-primary {
color: var(--color-primary, var(--color-red-500));
}
```
## Breaking changes
Prior to this PR, you could `@import` a stylesheet that contained
`@theme` blocks as reference by adding the `reference` keyword to your
import:
```css
@import "./my-theme.css" reference;
```
Now that `reference` isn't the only possible option when declaring your
`@theme`, this syntax has changed to a new `theme(…)` function that
accepts `reference` and `inline` as potential space-separated values:
```css
@import "./my-theme.css";
@import "./my-theme.css" theme(reference);
@import "./my-theme.css" theme(inline);
@import "./my-theme.css" theme(reference inline);
```
If you are using the `@import … reference` option with an earlier alpha
release, you'll need to update your code to `@import … theme(reference)`
once this PR lands in a release.
## Motivation
This PR is designed to solve an issue pointed out in #14091.
Prior to this PR, generated utilities would always reference variables
directly, with the raw value as a fallback:
```css
/* Input */
@theme {
--color-red-500: #ef4444;
/* ... */
}
/* Example output */
:root {
--color-red-500: #ef4444;
}
.text-red-500 {
color: var(--color-red-500, #ef4444);
}
```
But this can create issues with variables resolving to an unexpected
value when a theme value is referencing another variable defined on
`:root`.
For example, say you have a CSS file like this:
```css
:root, .light {
--text-fg: #000;
}
.dark {
--text-fg: #fff;
}
@theme {
--color-fg: var(--text-fg);
}
```
Without `@theme inline`, we'd generate this output if you used the
`text-fg` utility:
```css
:root, .light {
--text-fg: #000;
}
.dark {
--text-fg: #fff;
}
:root {
--color-fg: var(--text-fg);
}
.text-fg {
color: var(--color-fg, var(--text-fg));
}
```
Now if you wrote this HTML, you're probably expecting your text to be
the dark mode color:
```html
<div class="dark">
<h1 class="text-fg">Hello world</h1>
</div>
```
But you'd actually get the light mode color because of this rule:
```css
:root {
--color-fg: var(--text-fg);
}
.text-fg {
color: var(--color-fg, var(--text-fg));
}
```
The browser will try to resolve the `--color-fg` variable, which is
defined on `:root`. When it tries to resolve the value, _it uses the
value of `var(--text-fg)` as it would resolve at `:root`_, not what it
would resolve to based on the element that has the `text-fg` class.
So `var(--color-fg)` resolves to `#000` because `var(--text-fg)`
resolved to `#000` at the point in the tree where the browser resolved
the value of `var(--color-fg)`.
By using `@theme inline`, the `.text-fg` class looks like this:
```css
.text-fg {
color: var(--text-fg);
}
```
With this definition, the browser doesn't try to resolve `--color-fg` at
all and instead resolves `--text-fg` directly which correctly resolves
to `#fff` as expected.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR updates vitest to v2. The changes are mostly around using fork
instead of threads for how tests are run which should fix one of the
issues we've found.
Ever since adding the unit tests on Windows, we started seeing
occacional flags of vitest crashing with the following error:
```
ELIFECYCLE Command failed with exit code 3221225477.
Error: Process completed with exit code 1.
```
When reading the [v2
changelog](https://github.com/vitest-dev/vitest/releases/tag/v2.0.0) we
saw many bug fixes related to segfaulting so we believe this was the
issue.
When upgrading `vitest` alone, we got a bunch of dependency mismatches
though (specifically, vite was installed two times with different peer
dependencies for `@types/node` which causes our vite plugin's `Plugin`
type to be different from the one in the vite playground. Yikes. These
were eventually fixed by having pnpm create a new lockfile for us. So,
unfortunatly this PR also bumps a bunch of patch versions for some
transitive dependencies. Tests seem fine, though 🤞
This PR also removes the `bench` script from CI. It doesn't give us
value in its current state (since it's not reporting when performance
regresses) but added a few seconds of unnecessary overhead to each test
run.
Last week we discussed bringing in some consistency for our non-public
npm packages in the repo. We discussed using custom namespaces (e.g.
`@tailwindcss-internal`) vs. simple prefixes but it does not matter too
much if we are both consistent with our pattern and it's easy for us to
see whether a plugin is public or not.
Since we have a mixture of public namespaced (`@tailwindcss/*`) and
non-namespaced (`tailwindcss`) packages, I think it would be best if we
use a prefix to signal internal dependencies. This PR proposes we use
`internal-*` as the prefix and renames `test-utils` to
`internal-example-plugin` (since, really, this package is just an
example for the Tailwind plugin system).
This PR changes the GitHub action workflow for V4 to start running all
unit tests and build on both Ubuntu (our current default) _and_ Windows.
This is to ensure we catch issues with paths and other Windows-specific
things sooner. It does, however, not replace testing on Windows.
* Add basic `addVariant` plugin support
* Return early
* Load plugins right away instead later
* Use correct type for variant name
* Preliminary support for addVariant plugins in PostCSS plugin
* Add test for compounding plugin variants
* Add basic `loadPlugin` support to Vite plugin
* Add basic `loadPlugin` support to CLI
* add `io.ts` for integrations
* use shared `loadPlugin` from `tailwindcss/io`
* add `tailwindcss-test-utils` to `@tailwindcss/cli` and `@tailwindcss/vite`
* only add `tailwindcss-test-utils` to `tailwindcss` as a dev dependency
Because `src/io.ts` is requiring the plugin.
* move `tailwindcss-test-utils` to `@tailwindcss/postcss `
This is the spot where we actually need it.
* use newer pnpm version
* Duplicate loadPlugin implementation instead of exporting io file
* Remove another io reference
* update changelog
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
* run `pnpm update --recursive`
* update tests to reflect lightningcss bump
It looks like it's mainly (re-)ordering properties. Not 100% sure why
though.
* Fixes exports when importing CJS form ESM file
* Build a real ESM version of the postcss plugin
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
* Switch breakpoints to rem #8378
* Fix broken test
* Update snapshots
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
* Use variables with fallbacks for utility classes
* Update playwright test
* rename `resolveBare` to `resolveValue`
* make private methods really private
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
* 3D rotation utilities
* Validate rotate values
* Replace forEach with for loop
* transform-style, transform-box, and backface-visibility utilities
* Tests for transform utilities
* 'perspective' utility
* Fix tests
* Remove unnecessary suggestion; move function comments
* scale-z
* Fix Intellisense test
* perspective-origin
* scale-3d
* Only include the z component of scale if it's defined
We want to avoid triggerring unnecessary 3D transformations.
* Comment the reason for setting --tw-rotate
* Test full bare rotate
* Fix merge
* Comment on rotate arbitrary value
* perspective bare values
Support `perspective-123` (but not `perspective-potato`)
* scale-3d as a static modifier to scale
Instead of scale-3d taking a separate scale, it modifies scale to apply in three dimensions.
* Test that scale-x overrides scale
* scale arbitrary values
Support arbitrary value for scale (e.g. `scale-[1_2_3.5]`).
* Specify rotation axis using a modifier
Support single rotation angles in line with the [CSS `rotate` property](https://developer.mozilla.org/en-US/docs/Web/CSS/rotate). Using modifiers (e.g. `rotate-45/x`) makes it clearer that the axis of rotation is modified. Thanks @adamwathan for this suggestion.
Composing angles is only supported in CSS via a pipeline of `transform` functions. I'll add arbitrary value support to `transform` next as an escape hatch for those cases that need more complex transformations.
* Use property defaults for scale-3d
* `transform` arbitrary values
Support arbitrary values for `transform`. The `skew-x` and `skew-y` transforms are applied before any arbitrary transformations.
* Add translate-z and translate-3d
Both work the same way as scale-z and scale-3d.
* Add translate-[xyz]-px
* Comment on how skewX and skewY are applied
* Remove unnecessary suggest
* Simplify translate
* Fix up comment on rotate syntax
* Back to rotate-x and rotate-y rather than rotate modifiers
* 3D transform test fixes
* add `@tailwindcss/optimize` as a separate package
* remove lightningcss from `tailwindcss`
* import `optimizeCss` from `@tailwindcss/optimize`
* ensure we use `src/` files in development
* move `devDependencies` after `dependencies`
Just for consistency
* inline `optimizeCss` in leaf packages
Instead of introducing a custom `@tailwindcss/optimize` package
* update changelog
* fix changelog
* Don't rely on existence of --default-transition-* variables in transition utilities
* Update changelog
* Add test with no default transition values defined
* Inline value for --default-transition-timing-function
This is more consistent with how things worked in v3 and ensures things will still work if the user suppresses the output of all CSS variables.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
* ensure we don't crash on deleted files
* change return type of `compile` to include a `rebuild()` function
This will allow us in the future to perform incremental rebuilds after
the initial rebuild. This is purely the API change so that we can
prepare all the call sites to use this new API.
* set `@tailwind utilities` nodes
Instead of replacing the node that represents the `@tailwind utilities`
with the generated AST nodes from the rawCandidates, we will set the
nodes of the `@tailwind utilities` rule to the AST nodes instead.
This way we dont' have to remove and replace the `@tailwind utilities`
rule with `n` new nodes. This will later allow us to track the
`@tailwindcss utilities` rule itself and update its `nodes` for
incremental rebuilds.
This also requires a small change to the printer where we now need to
print the children of the `@tailwind utilities` rule. Note: we keep the
same `depth` as-if the `@tailwindcss utilities` rule was not there.
Otherwise additional indentation would be present.
* move sorting to the `ast.sort()` call
This will allow us to keep sorting AST nodes in a single spot.
* move parser functions to the `DesignSystem`
This allows us to put all the parsers in the `DesignSystem`, this allows
us to scope the parsers to the current design system (the current theme,
current utility values and variants).
The implementation of these parsers are also using a `DefaultMap`
implementation. This allows us to make use of caching and only parse a
candidate, parse a variant or compile AST nodes for a given raw
candidate once if we've already done this work in the past.
Again, this is scoped to the `DesignSystem` itself. This means that if
the corresponding theme changes, then we will create a new
`DesignSystem` entirely and the caches will be garbage collected. This
is important because a candidate like `bg-primary` can be invalid in
`DesignSystem` A, but can be valid in `DesignSystem` B and vice versa.
* ensure we sort variants alphabetically by root
For incremental rebuilds we don't know all the used variants upfront,
which means that we can't sort them upfront either (what we used to do).
This code now allows us to sort the variants deterministically when
sorting the variants themselves instead of relying on the fact that they
used to be sorted before.
The sort itself could change slightly compared to the previous
implementation (especially when you used stacked variants in your
candidates), but it will still be deterministic.
* replace `localeCompare` comparisons
Use cheaper comparisons than `localeCompare` when comparing 2 strings.
We currently don't care if it is 100% correctly sorted, but we just want
consistent sorting. This is currently faster compared to
`localeCompare`.
Another benefit is that `localeCompare` could result in
non-deterministic results if the CSS was generated on 2 separate
computers where the `locale` is different.
We could solve that by adding a dedicated locale, but it would still be
slower compared to this.
* track invalid candidates
When an incoming raw candidates doesn't produce any output, then we can
mark it as an invalid candidate. This will allow us to reduce the amount
of candidates to handle in incremental rebuilds.
* add initial incremental rebuild implementation
This includes a number of steps:
1. Track the `@tailwind utilities` rule, so that we can adjust its nodes
later without re-parsing the full incoming CSS.
2. Add the new incoming raw candidates to the existing set of
candidates.
3. Parse the merged set to `compileCandidates` (this can accept any
`Iterable<string>`, which means `string[]`, `Set<string>`, ...)
4. Get the new AST nodes, update the `@tailwind utilities` rule's nodes
and re-print the AST to CSS.
* improvement 1: ignore known invalid candidates
This will reduce the amount of candidates to handle. They would
eventually be skipped anyway, but now we don't even have to re-parse
(and hit a cache) at all.
* improvement 2: skip work, when generated AST is the same
Currently incremental rebuilds are additive, which means that we are not
keeping track if we should remove CSS again in development.
We can exploit this information, because now we can quickly check the
amoutn of generated AST nodes.
- If they are the same then nothing new is generated — this means that
we can re-use the previous compiled CSS. We don't even have to
re-print the AST because we already did do that work in the past.
- If there are more AST nodes, something new is generated — this means
that we should update the `@tailwind utilities` rule and re-print the
CSS. We can store the result for future incremental rebuilds.
* improvement 3: skip work if no new candidates are detected
- We already know a set of candidates from previous runs.
- We also already know a set of candidates that are invalid and don't
produce anything.
This means that an incremental rebuild could give us a new set of
candidates that either already exist or are invalid.
If nothing changes, then we can re-use the compiled CSS.
This actually happens more often than you think, and the bigger your
project is the better this optimization will be.
For example:
```
// Imagine file A exists:
<div class="flex items-center justify-center"></div>
<button class="text-red-500">Delete</button>
```
```
// Now you add a second file B:
<div class="text-red-500 flex"></div>
```
You just created a brand new file with a bunch of HTML elements and
classes, yet all of the candidates in file B already exist in file A, so
nothing changes to the actual generated CSS.
Now imagine the other hundreds of files that already contain hundreds of
classes.
The beauty of this optimization is two-fold:
- On small projects, compiling is very fast even without this check.
This means it is performant.
- On bigger projects, we will be able to re-use existing candidates.
This means it stays performant.
* remove `getAstNodeSize`
We can move this up the tree and move it to the `rebuild` function
instead.
* remove invalid candidate tracking from `DesignSystem`
This isn't used anywhere but only in the `rebuild` of the compile step.
This allows us to remove it entirely from core logic, and move it up the
chain where it is needed.
* replace `throwOnInvalidCandidate` with `onInvalidCanidate`
This was only needed for working with `@apply`, now this logic _only_
exists in the code path where we are handling `@apply`.
* update `compile` API signature
* update callsite of `compile()` function
* fix typo