2024-12-11 15:27:20 +01:00
|
|
|
import { compile, env, Features, Instrumentation, normalizePath } from '@tailwindcss/node'
|
2024-09-02 12:03:16 -04:00
|
|
|
import { clearRequireCache } from '@tailwindcss/node/require-cache'
|
Improve Oxide scanner API (#14187)
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>
2024-08-16 15:05:42 +02:00
|
|
|
import { Scanner } from '@tailwindcss/oxide'
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
import { Features as LightningCssFeatures, transform } from 'lightningcss'
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
import fs from 'node:fs/promises'
|
2024-11-04 17:52:11 +01:00
|
|
|
import path from 'node:path'
|
2025-02-20 15:23:44 +01:00
|
|
|
import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite'
|
2024-03-05 14:23:26 +01:00
|
|
|
|
2024-12-11 15:27:20 +01:00
|
|
|
const DEBUG = env.DEBUG
|
2025-02-04 12:45:23 +01:00
|
|
|
const SPECIAL_QUERY_RE = /[?&](?:worker|sharedworker|raw|url)\b/
|
|
|
|
|
const COMMON_JS_PROXY_RE = /\?commonjs-proxy/
|
2025-01-31 15:26:45 +01:00
|
|
|
const INLINE_STYLE_ID_RE = /[?&]index\=\d+\.css$/
|
2024-10-22 16:03:07 +00:00
|
|
|
|
2024-03-05 14:23:26 +01:00
|
|
|
export default function tailwindcss(): Plugin[] {
|
2024-09-04 16:53:15 +02:00
|
|
|
let servers: ViteDevServer[] = []
|
2024-08-07 16:38:44 +02:00
|
|
|
let config: ResolvedConfig | null = null
|
2024-09-04 10:09:24 +02:00
|
|
|
|
2024-04-09 00:10:08 +02:00
|
|
|
let isSSR = false
|
2024-03-05 14:23:26 +01:00
|
|
|
let minify = false
|
2024-09-04 10:09:24 +02:00
|
|
|
|
2024-11-27 17:48:55 +01:00
|
|
|
let roots: DefaultMap<string, Root> = new DefaultMap((id) => {
|
|
|
|
|
let cssResolver = config!.createResolver({
|
|
|
|
|
...config!.resolve,
|
|
|
|
|
extensions: ['.css'],
|
|
|
|
|
mainFields: ['style'],
|
|
|
|
|
conditions: ['style', 'development|production'],
|
|
|
|
|
tryIndex: false,
|
|
|
|
|
preferRelative: true,
|
|
|
|
|
})
|
|
|
|
|
function customCssResolver(id: string, base: string) {
|
2024-12-02 23:08:13 +01:00
|
|
|
return cssResolver(id, base, true, isSSR)
|
2024-11-27 17:48:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let jsResolver = config!.createResolver(config!.resolve)
|
|
|
|
|
function customJsResolver(id: string, base: string) {
|
|
|
|
|
return jsResolver(id, base, true, isSSR)
|
|
|
|
|
}
|
2025-02-20 15:23:44 +01:00
|
|
|
return new Root(id, config!.root, customCssResolver, customJsResolver)
|
2024-11-27 17:48:55 +01:00
|
|
|
})
|
2024-09-04 10:09:24 +02:00
|
|
|
|
2024-03-05 14:23:26 +01:00
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
// Step 1: Scan source files for candidates
|
|
|
|
|
name: '@tailwindcss/vite:scan',
|
|
|
|
|
enforce: 'pre',
|
|
|
|
|
|
2024-09-04 16:53:15 +02:00
|
|
|
configureServer(server) {
|
|
|
|
|
servers.push(server)
|
2024-03-05 14:23:26 +01:00
|
|
|
},
|
|
|
|
|
|
2024-08-07 16:38:44 +02:00
|
|
|
async configResolved(_config) {
|
|
|
|
|
config = _config
|
2024-03-05 14:23:26 +01:00
|
|
|
minify = config.build.cssMinify !== false
|
2024-04-09 00:10:08 +02:00
|
|
|
isSSR = config.build.ssr !== false && config.build.ssr !== undefined
|
2024-03-05 14:23:26 +01:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-22 14:57:44 -04:00
|
|
|
// Step 2 (serve mode): Generate CSS
|
2024-03-05 14:23:26 +01:00
|
|
|
name: '@tailwindcss/vite:generate:serve',
|
|
|
|
|
apply: 'serve',
|
2024-09-04 10:09:24 +02:00
|
|
|
enforce: 'pre',
|
2024-03-20 16:40:50 -04:00
|
|
|
|
2024-03-28 22:57:16 -04:00
|
|
|
async transform(src, id, options) {
|
2024-09-04 10:09:24 +02:00
|
|
|
if (!isPotentialCssRootFile(id)) return
|
2024-03-05 14:23:26 +01:00
|
|
|
|
2024-12-11 15:27:20 +01:00
|
|
|
using I = new Instrumentation()
|
2025-02-20 15:23:44 +01:00
|
|
|
DEBUG && I.start('[@tailwindcss/vite] Generate CSS (serve)')
|
2024-12-11 15:27:20 +01:00
|
|
|
|
2024-09-04 10:09:24 +02:00
|
|
|
let root = roots.get(id)
|
2024-03-05 14:23:26 +01:00
|
|
|
|
2024-12-11 15:27:20 +01:00
|
|
|
let generated = await root.generate(src, (file) => this.addWatchFile(file), I)
|
2024-09-04 10:09:24 +02:00
|
|
|
if (!generated) {
|
|
|
|
|
roots.delete(id)
|
|
|
|
|
return src
|
|
|
|
|
}
|
2025-02-20 15:23:44 +01:00
|
|
|
|
|
|
|
|
DEBUG && I.end('[@tailwindcss/vite] Generate CSS (serve)')
|
2024-09-04 10:09:24 +02:00
|
|
|
return { code: generated }
|
2024-03-05 14:23:26 +01:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
// Step 2 (full build): Generate CSS
|
|
|
|
|
name: '@tailwindcss/vite:generate:build',
|
|
|
|
|
apply: 'build',
|
2024-09-04 10:09:24 +02:00
|
|
|
enforce: 'pre',
|
|
|
|
|
|
|
|
|
|
async transform(src, id) {
|
|
|
|
|
if (!isPotentialCssRootFile(id)) return
|
2024-03-22 14:57:44 -04:00
|
|
|
|
2024-12-11 15:27:20 +01:00
|
|
|
using I = new Instrumentation()
|
2025-02-20 15:23:44 +01:00
|
|
|
DEBUG && I.start('[@tailwindcss/vite] Generate CSS (build)')
|
2024-12-11 15:27:20 +01:00
|
|
|
|
2024-09-04 10:09:24 +02:00
|
|
|
let root = roots.get(id)
|
|
|
|
|
|
2024-12-11 15:27:20 +01:00
|
|
|
let generated = await root.generate(src, (file) => this.addWatchFile(file), I)
|
2024-09-04 10:09:24 +02:00
|
|
|
if (!generated) {
|
|
|
|
|
roots.delete(id)
|
|
|
|
|
return src
|
|
|
|
|
}
|
2025-02-20 15:23:44 +01:00
|
|
|
DEBUG && I.end('[@tailwindcss/vite] Generate CSS (build)')
|
2024-03-22 14:57:44 -04:00
|
|
|
|
2025-02-20 15:23:44 +01:00
|
|
|
DEBUG && I.start('[@tailwindcss/vite] Optimize CSS')
|
|
|
|
|
generated = optimizeCss(generated, { minify })
|
|
|
|
|
DEBUG && I.end('[@tailwindcss/vite] Optimize CSS')
|
2024-04-09 00:10:08 +02:00
|
|
|
|
2025-02-20 15:23:44 +01:00
|
|
|
return { code: generated }
|
2024-03-05 14:23:26 +01:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
] satisfies Plugin[]
|
|
|
|
|
}
|
2024-03-13 17:25:16 +01:00
|
|
|
|
2024-03-22 14:57:44 -04:00
|
|
|
function getExtension(id: string) {
|
|
|
|
|
let [filename] = id.split('?', 2)
|
|
|
|
|
return path.extname(filename).slice(1)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-04 10:09:24 +02:00
|
|
|
function isPotentialCssRootFile(id: string) {
|
2024-11-08 20:01:16 +01:00
|
|
|
if (id.includes('/.vite/')) return
|
2024-08-09 12:41:58 +02:00
|
|
|
let extension = getExtension(id)
|
2024-09-04 19:14:05 +02:00
|
|
|
let isCssFile =
|
2025-01-31 15:26:45 +01:00
|
|
|
(extension === 'css' || id.includes('&lang.css') || id.match(INLINE_STYLE_ID_RE)) &&
|
2024-10-22 16:03:07 +00:00
|
|
|
// Don't intercept special static asset resources
|
2025-02-04 12:45:23 +01:00
|
|
|
!SPECIAL_QUERY_RE.test(id) &&
|
|
|
|
|
!COMMON_JS_PROXY_RE.test(id)
|
2024-09-04 10:09:24 +02:00
|
|
|
return isCssFile
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-13 17:25:16 +01:00
|
|
|
function optimizeCss(
|
|
|
|
|
input: string,
|
|
|
|
|
{ file = 'input.css', minify = false }: { file?: string; minify?: boolean } = {},
|
|
|
|
|
) {
|
2025-02-20 15:23:44 +01:00
|
|
|
function optimize(code: Buffer | Uint8Array | any) {
|
2024-11-06 12:39:09 +01:00
|
|
|
return transform({
|
|
|
|
|
filename: file,
|
|
|
|
|
code,
|
|
|
|
|
minify,
|
|
|
|
|
sourceMap: false,
|
|
|
|
|
drafts: {
|
|
|
|
|
customMedia: true,
|
|
|
|
|
},
|
|
|
|
|
nonStandard: {
|
|
|
|
|
deepSelectorCombinator: true,
|
|
|
|
|
},
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
include: LightningCssFeatures.Nesting,
|
2025-01-09 17:14:48 +01:00
|
|
|
exclude:
|
|
|
|
|
LightningCssFeatures.LogicalProperties |
|
|
|
|
|
LightningCssFeatures.DirSelector |
|
|
|
|
|
LightningCssFeatures.LightDark,
|
2024-11-06 12:39:09 +01:00
|
|
|
targets: {
|
|
|
|
|
safari: (16 << 16) | (4 << 8),
|
2024-11-26 12:22:12 +01:00
|
|
|
ios_saf: (16 << 16) | (4 << 8),
|
|
|
|
|
firefox: 128 << 16,
|
2024-12-13 15:07:04 +01:00
|
|
|
chrome: 111 << 16,
|
2024-11-06 12:39:09 +01:00
|
|
|
},
|
|
|
|
|
errorRecovery: true,
|
|
|
|
|
}).code
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Running Lightning CSS twice to ensure that adjacent rules are merged after
|
|
|
|
|
// nesting is applied. This creates a more optimized output.
|
|
|
|
|
return optimize(optimize(Buffer.from(input))).toString()
|
2024-03-13 17:25:16 +01:00
|
|
|
}
|
2024-09-04 10:09:24 +02:00
|
|
|
|
|
|
|
|
function idToPath(id: string) {
|
|
|
|
|
return path.resolve(id.replace(/\?.*$/, ''))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A Map that can generate default values for keys that don't exist.
|
|
|
|
|
* Generated default values are added to the map to avoid recomputation.
|
|
|
|
|
*/
|
|
|
|
|
class DefaultMap<K, V> extends Map<K, V> {
|
|
|
|
|
constructor(private factory: (key: K, self: DefaultMap<K, V>) => V) {
|
|
|
|
|
super()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get(key: K): V {
|
|
|
|
|
let value = super.get(key)
|
|
|
|
|
|
|
|
|
|
if (value === undefined) {
|
|
|
|
|
value = this.factory(key, this)
|
|
|
|
|
this.set(key, value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Root {
|
|
|
|
|
// The lazily-initialized Tailwind compiler components. These are persisted
|
|
|
|
|
// throughout rebuilds but will be re-initialized if the rebuild strategy is
|
|
|
|
|
// set to `full`.
|
|
|
|
|
private compiler?: Awaited<ReturnType<typeof compile>>
|
|
|
|
|
|
2025-02-20 15:23:44 +01:00
|
|
|
// The lazily-initialized Tailwind scanner.
|
2024-09-04 10:09:24 +02:00
|
|
|
private scanner?: Scanner
|
|
|
|
|
|
|
|
|
|
// List of all candidates that were being returned by the root scanner during
|
|
|
|
|
// the lifetime of the root.
|
|
|
|
|
private candidates: Set<string> = new Set<string>()
|
|
|
|
|
|
2025-02-20 15:23:44 +01:00
|
|
|
// List of all build dependencies (e.g. imported stylesheets or plugins) and
|
2025-02-25 11:29:58 +01:00
|
|
|
// their last modification timestamp. If no mtime can be found, we need to
|
|
|
|
|
// assume the file has always changed.
|
|
|
|
|
private buildDependencies = new Map<string, number | null>()
|
2024-11-13 16:42:49 +01:00
|
|
|
|
2024-09-04 10:09:24 +02:00
|
|
|
constructor(
|
|
|
|
|
private id: string,
|
|
|
|
|
private base: string,
|
2024-11-27 17:48:55 +01:00
|
|
|
|
|
|
|
|
private customCssResolver: (id: string, base: string) => Promise<string | false | undefined>,
|
|
|
|
|
private customJsResolver: (id: string, base: string) => Promise<string | false | undefined>,
|
2024-09-04 10:09:24 +02:00
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
// Generate the CSS for the root file. This can return false if the file is
|
|
|
|
|
// not considered a Tailwind root. When this happened, the root can be GCed.
|
|
|
|
|
public async generate(
|
|
|
|
|
content: string,
|
2025-03-05 14:55:07 +01:00
|
|
|
_addWatchFile: (file: string) => void,
|
2024-12-11 15:27:20 +01:00
|
|
|
I: Instrumentation,
|
2024-09-04 10:09:24 +02:00
|
|
|
): Promise<string | false> {
|
2025-03-05 14:55:07 +01:00
|
|
|
function addWatchFile(file: string) {
|
|
|
|
|
// Scanning `.svg` file containing a `#` or `?` in the path will
|
|
|
|
|
// crash Vite. We work around this for now by ignoring updates to them.
|
|
|
|
|
//
|
|
|
|
|
// https://github.com/tailwindlabs/tailwindcss/issues/16877
|
|
|
|
|
if (/[\#\?].*\.svg$/.test(file)) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
_addWatchFile(file)
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-20 15:23:44 +01:00
|
|
|
let requiresBuildPromise = this.requiresBuild()
|
2024-09-04 10:09:24 +02:00
|
|
|
let inputPath = idToPath(this.id)
|
|
|
|
|
let inputBase = path.dirname(path.resolve(inputPath))
|
|
|
|
|
|
2025-02-20 15:23:44 +01:00
|
|
|
if (!this.compiler || !this.scanner || (await requiresBuildPromise)) {
|
|
|
|
|
clearRequireCache(Array.from(this.buildDependencies.keys()))
|
|
|
|
|
this.buildDependencies.clear()
|
|
|
|
|
|
|
|
|
|
this.addBuildDependency(idToPath(inputPath))
|
2024-09-04 10:09:24 +02:00
|
|
|
|
2024-12-11 15:27:20 +01:00
|
|
|
DEBUG && I.start('Setup compiler')
|
2025-02-20 15:23:44 +01:00
|
|
|
let addBuildDependenciesPromises: Promise<void>[] = []
|
Resolve `@import` in core (#14446)
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>
2024-09-23 17:05:55 +02:00
|
|
|
this.compiler = await compile(content, {
|
2024-09-04 10:09:24 +02:00
|
|
|
base: inputBase,
|
2024-11-07 09:51:58 -05:00
|
|
|
shouldRewriteUrls: true,
|
2024-09-04 10:09:24 +02:00
|
|
|
onDependency: (path) => {
|
|
|
|
|
addWatchFile(path)
|
2025-02-20 15:23:44 +01:00
|
|
|
addBuildDependenciesPromises.push(this.addBuildDependency(path))
|
2024-09-04 10:09:24 +02:00
|
|
|
},
|
2024-11-27 17:48:55 +01:00
|
|
|
|
|
|
|
|
customCssResolver: this.customCssResolver,
|
|
|
|
|
customJsResolver: this.customJsResolver,
|
2024-09-04 10:09:24 +02:00
|
|
|
})
|
2025-02-20 15:23:44 +01:00
|
|
|
await Promise.all(addBuildDependenciesPromises)
|
2024-12-11 15:27:20 +01:00
|
|
|
DEBUG && I.end('Setup compiler')
|
2024-09-04 10:09:24 +02:00
|
|
|
|
2025-02-20 15:23:44 +01:00
|
|
|
DEBUG && I.start('Setup scanner')
|
|
|
|
|
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
let sources = (() => {
|
|
|
|
|
// Disable auto source detection
|
|
|
|
|
if (this.compiler.root === 'none') {
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-20 15:23:44 +01:00
|
|
|
// No root specified, auto-detect based on the `**/*` pattern
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
if (this.compiler.root === null) {
|
2025-02-20 15:23:44 +01:00
|
|
|
return [{ base: this.base, pattern: '**/*' }]
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use the specified root
|
|
|
|
|
return [this.compiler.root]
|
|
|
|
|
})().concat(this.compiler.globs)
|
|
|
|
|
|
|
|
|
|
this.scanner = new Scanner({ sources })
|
2025-02-20 15:23:44 +01:00
|
|
|
DEBUG && I.end('Setup scanner')
|
2025-03-24 11:54:55 +01:00
|
|
|
} else {
|
|
|
|
|
for (let buildDependency of this.buildDependencies.keys()) {
|
|
|
|
|
addWatchFile(buildDependency)
|
|
|
|
|
}
|
2024-09-04 10:09:24 +02:00
|
|
|
}
|
|
|
|
|
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
if (
|
|
|
|
|
!(
|
|
|
|
|
this.compiler.features &
|
|
|
|
|
(Features.AtApply | Features.JsPluginCompat | Features.ThemeFunction | Features.Utilities)
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-20 15:23:44 +01:00
|
|
|
if (this.compiler.features & Features.Utilities) {
|
2024-11-13 16:42:49 +01:00
|
|
|
// This should not be here, but right now the Vite plugin is setup where we
|
|
|
|
|
// setup a new scanner and compiler every time we request the CSS file
|
|
|
|
|
// (regardless whether it actually changed or not).
|
2024-12-11 15:27:20 +01:00
|
|
|
DEBUG && I.start('Scan for candidates')
|
2024-11-13 16:42:49 +01:00
|
|
|
for (let candidate of this.scanner.scan()) {
|
|
|
|
|
this.candidates.add(candidate)
|
|
|
|
|
}
|
2024-12-11 15:27:20 +01:00
|
|
|
DEBUG && I.end('Scan for candidates')
|
2024-09-04 10:09:24 +02:00
|
|
|
}
|
|
|
|
|
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
if (this.compiler.features & Features.Utilities) {
|
|
|
|
|
// Watch individual files found via custom `@source` paths
|
|
|
|
|
for (let file of this.scanner.files) {
|
|
|
|
|
addWatchFile(file)
|
2024-09-04 10:09:24 +02:00
|
|
|
}
|
|
|
|
|
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
// Watch globs found via custom `@source` paths
|
|
|
|
|
for (let glob of this.scanner.globs) {
|
|
|
|
|
if (glob.pattern[0] === '!') continue
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
let relative = path.relative(this.base, glob.base)
|
|
|
|
|
if (relative[0] !== '.') {
|
|
|
|
|
relative = './' + relative
|
|
|
|
|
}
|
|
|
|
|
// Ensure relative is a posix style path since we will merge it with the
|
|
|
|
|
// glob.
|
|
|
|
|
relative = normalizePath(relative)
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
addWatchFile(path.posix.join(relative, glob.pattern))
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
let root = this.compiler.root
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
if (root !== 'none' && root !== null) {
|
|
|
|
|
let basePath = normalizePath(path.resolve(root.base, root.pattern))
|
|
|
|
|
|
|
|
|
|
let isDir = await fs.stat(basePath).then(
|
|
|
|
|
(stats) => stats.isDirectory(),
|
|
|
|
|
() => false,
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
)
|
|
|
|
|
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
if (!isDir) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`The path given to \`source(…)\` must be a directory but got \`source(${basePath})\` instead.`,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
}
|
2024-09-04 10:09:24 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-11 15:27:20 +01:00
|
|
|
DEBUG && I.start('Build CSS')
|
2025-02-20 15:23:44 +01:00
|
|
|
let result = this.compiler.build([...this.candidates])
|
2024-12-11 15:27:20 +01:00
|
|
|
DEBUG && I.end('Build CSS')
|
2024-09-30 16:39:21 +02:00
|
|
|
|
|
|
|
|
return result
|
2024-09-04 10:09:24 +02:00
|
|
|
}
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
|
2025-02-20 15:23:44 +01:00
|
|
|
private async addBuildDependency(path: string) {
|
2025-02-25 11:29:58 +01:00
|
|
|
let mtime: number | null = null
|
|
|
|
|
try {
|
|
|
|
|
mtime = (await fs.stat(path)).mtimeMs
|
|
|
|
|
} catch {}
|
|
|
|
|
this.buildDependencies.set(path, mtime)
|
2025-02-20 15:23:44 +01:00
|
|
|
}
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
|
2025-02-20 15:23:44 +01:00
|
|
|
private async requiresBuild(): Promise<boolean> {
|
|
|
|
|
for (let [path, mtime] of this.buildDependencies) {
|
2025-02-25 11:29:58 +01:00
|
|
|
if (mtime === null) return true
|
|
|
|
|
try {
|
|
|
|
|
let stat = await fs.stat(path)
|
|
|
|
|
if (stat.mtimeMs > mtime) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
2025-02-20 15:23:44 +01:00
|
|
|
return true
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-02-20 15:23:44 +01:00
|
|
|
return false
|
Auto source detection improvements (#14820)
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-10-29 21:33:34 +01:00
|
|
|
}
|
2024-09-04 10:09:24 +02:00
|
|
|
}
|