mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00: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>
This commit is contained in:
parent
6abd8086c3
commit
99b73ee368
@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Nothing yet!
|
||||
### Fixed
|
||||
|
||||
- Don't scan source files for utilities unless `@tailwind utilities` is present in the CSS in `@tailwindcss/postcss` and `@tailwindcss/vite` ([#15226](https://github.com/tailwindlabs/tailwindcss/pull/15226))
|
||||
- Skip reserializing CSS files that don't use Tailwind features in `@tailwindcss/postcss` and `@tailwindcss/vite` ([#15226](https://github.com/tailwindlabs/tailwindcss/pull/15226))
|
||||
|
||||
## [4.0.0-beta.3] - 2024-11-27
|
||||
|
||||
|
||||
@ -7,10 +7,13 @@ import { pathToFileURL } from 'node:url'
|
||||
import {
|
||||
__unstable__loadDesignSystem as ___unstable__loadDesignSystem,
|
||||
compile as _compile,
|
||||
Features,
|
||||
} from 'tailwindcss'
|
||||
import { getModuleDependencies } from './get-module-dependencies'
|
||||
import { rewriteUrls } from './urls'
|
||||
|
||||
export { Features }
|
||||
|
||||
export type Resolver = (id: string, base: string) => Promise<string | false | undefined>
|
||||
|
||||
export async function compile(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import * as Module from 'node:module'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import * as env from './env'
|
||||
export { __unstable__loadDesignSystem, compile } from './compile'
|
||||
export { __unstable__loadDesignSystem, compile, Features } from './compile'
|
||||
export * from './normalize-path'
|
||||
export { env }
|
||||
|
||||
|
||||
@ -40,6 +40,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "catalog:",
|
||||
"@types/postcss-import": "14.0.3",
|
||||
"dedent": "1.5.3",
|
||||
"internal-example-plugin": "workspace:*",
|
||||
"postcss-import": "^16.1.0"
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import dedent from 'dedent'
|
||||
import { unlink, writeFile } from 'node:fs/promises'
|
||||
import postcss from 'postcss'
|
||||
import { afterEach, beforeEach, describe, expect, test } from 'vitest'
|
||||
@ -9,16 +10,20 @@ import tailwindcss from './index'
|
||||
// We place it in packages/ because Vitest runs in the monorepo root,
|
||||
// and packages/tailwindcss must be a sub-folder for
|
||||
// @import 'tailwindcss' to work.
|
||||
const INPUT_CSS_PATH = `${__dirname}/fixtures/example-project/input.css`
|
||||
function inputCssFilePath() {
|
||||
// Including the current test name to ensure that the cache is invalidated per
|
||||
// test otherwise the cache will be used across tests.
|
||||
return `${__dirname}/fixtures/example-project/input.css?test=${expect.getState().currentTestName}`
|
||||
}
|
||||
|
||||
const css = String.raw
|
||||
const css = dedent
|
||||
|
||||
test("`@import 'tailwindcss'` is replaced with the generated CSS", async () => {
|
||||
let processor = postcss([
|
||||
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
||||
])
|
||||
|
||||
let result = await processor.process(`@import 'tailwindcss'`, { from: INPUT_CSS_PATH })
|
||||
let result = await processor.process(`@import 'tailwindcss'`, { from: inputCssFilePath() })
|
||||
|
||||
expect(result.css.trim()).toMatchSnapshot()
|
||||
|
||||
@ -49,8 +54,6 @@ test('output is optimized by Lightning CSS', async () => {
|
||||
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
||||
])
|
||||
|
||||
// `@apply` is used because Lightning is skipped if neither `@tailwind` nor
|
||||
// `@apply` is used.
|
||||
let result = await processor.process(
|
||||
css`
|
||||
@layer utilities {
|
||||
@ -65,7 +68,7 @@ test('output is optimized by Lightning CSS', async () => {
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ from: INPUT_CSS_PATH },
|
||||
{ from: inputCssFilePath() },
|
||||
)
|
||||
|
||||
expect(result.css.trim()).toMatchInlineSnapshot(`
|
||||
@ -86,8 +89,6 @@ test('@apply can be used without emitting the theme in the CSS file', async () =
|
||||
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
||||
])
|
||||
|
||||
// `@apply` is used because Lightning is skipped if neither `@tailwind` nor
|
||||
// `@apply` is used.
|
||||
let result = await processor.process(
|
||||
css`
|
||||
@import 'tailwindcss/theme.css' theme(reference);
|
||||
@ -95,7 +96,7 @@ test('@apply can be used without emitting the theme in the CSS file', async () =
|
||||
@apply text-red-500;
|
||||
}
|
||||
`,
|
||||
{ from: INPUT_CSS_PATH },
|
||||
{ from: inputCssFilePath() },
|
||||
)
|
||||
|
||||
expect(result.css.trim()).toMatchInlineSnapshot(`
|
||||
@ -116,7 +117,7 @@ describe('processing without specifying a base path', () => {
|
||||
test('the current working directory is used by default', async () => {
|
||||
let processor = postcss([tailwindcss({ optimize: { minify: false } })])
|
||||
|
||||
let result = await processor.process(`@import "tailwindcss"`, { from: INPUT_CSS_PATH })
|
||||
let result = await processor.process(`@import "tailwindcss"`, { from: inputCssFilePath() })
|
||||
|
||||
expect(result.css).toContain(
|
||||
".md\\:\\[\\&\\:hover\\]\\:content-\\[\\'testing_default_base_path\\'\\]",
|
||||
@ -142,7 +143,7 @@ describe('plugins', () => {
|
||||
@import 'tailwindcss/utilities';
|
||||
@plugin './plugin.js';
|
||||
`,
|
||||
{ from: INPUT_CSS_PATH },
|
||||
{ from: inputCssFilePath() },
|
||||
)
|
||||
|
||||
expect(result.css.trim()).toMatchInlineSnapshot(`
|
||||
@ -202,7 +203,7 @@ describe('plugins', () => {
|
||||
@import 'tailwindcss/utilities';
|
||||
@plugin 'internal-example-plugin';
|
||||
`,
|
||||
{ from: INPUT_CSS_PATH },
|
||||
{ from: inputCssFilePath() },
|
||||
)
|
||||
|
||||
expect(result.css.trim()).toMatchInlineSnapshot(`
|
||||
@ -222,3 +223,28 @@ describe('plugins', () => {
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('bail early when Tailwind is not used', async () => {
|
||||
let processor = postcss([
|
||||
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
||||
])
|
||||
|
||||
let result = await processor.process(
|
||||
css`
|
||||
.custom-css {
|
||||
color: red;
|
||||
}
|
||||
`,
|
||||
{ from: inputCssFilePath() },
|
||||
)
|
||||
|
||||
// `fixtures/example-project` includes an `underline` candidate. But since we
|
||||
// didn't use `@tailwind utilities` we didn't scan for utilities.
|
||||
expect(result.css).not.toContain('.underline {')
|
||||
|
||||
expect(result.css.trim()).toMatchInlineSnapshot(`
|
||||
".custom-css {
|
||||
color: red;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import QuickLRU from '@alloc/quick-lru'
|
||||
import { compile, env } from '@tailwindcss/node'
|
||||
import { compile, env, Features } from '@tailwindcss/node'
|
||||
import { clearRequireCache } from '@tailwindcss/node/require-cache'
|
||||
import { Scanner } from '@tailwindcss/oxide'
|
||||
import { Features, transform } from 'lightningcss'
|
||||
import { Features as LightningCssFeatures, transform } from 'lightningcss'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import postcss, { type AcceptedPlugin, type PluginCreator } from 'postcss'
|
||||
@ -63,7 +63,9 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
|
||||
|
||||
async function createCompiler() {
|
||||
env.DEBUG && console.time('[@tailwindcss/postcss] Setup compiler')
|
||||
clearRequireCache(context.fullRebuildPaths)
|
||||
if (context.fullRebuildPaths.length > 0 && !isInitialBuild) {
|
||||
clearRequireCache(context.fullRebuildPaths)
|
||||
}
|
||||
|
||||
context.fullRebuildPaths = []
|
||||
|
||||
@ -86,6 +88,10 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
|
||||
// guarantee a `build()` function is available.
|
||||
context.compiler ??= await createCompiler()
|
||||
|
||||
if (context.compiler.features === Features.None) {
|
||||
return
|
||||
}
|
||||
|
||||
let rebuildStrategy: 'full' | 'incremental' = 'incremental'
|
||||
|
||||
// Track file modification times to CSS files
|
||||
@ -154,46 +160,49 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
|
||||
}
|
||||
|
||||
env.DEBUG && console.time('[@tailwindcss/postcss] Scan for candidates')
|
||||
let candidates = context.scanner.scan()
|
||||
let candidates =
|
||||
context.compiler.features & Features.Utilities ? context.scanner.scan() : []
|
||||
env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Scan for candidates')
|
||||
|
||||
// Add all found files as direct dependencies
|
||||
for (let file of context.scanner.files) {
|
||||
result.messages.push({
|
||||
type: 'dependency',
|
||||
plugin: '@tailwindcss/postcss',
|
||||
file,
|
||||
parent: result.opts.from,
|
||||
})
|
||||
}
|
||||
|
||||
// Register dependencies so changes in `base` cause a rebuild while
|
||||
// giving tools like Vite or Parcel a glob that can be used to limit
|
||||
// the files that cause a rebuild to only those that match it.
|
||||
for (let { base: globBase, pattern } of context.scanner.globs) {
|
||||
// Avoid adding a dependency on the base directory itself, since it
|
||||
// causes Next.js to start an endless recursion if the `distDir` is
|
||||
// configured to anything other than the default `.next` dir.
|
||||
if (pattern === '*' && base === globBase) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (pattern === '') {
|
||||
if (context.compiler.features & Features.Utilities) {
|
||||
// Add all found files as direct dependencies
|
||||
for (let file of context.scanner.files) {
|
||||
result.messages.push({
|
||||
type: 'dependency',
|
||||
plugin: '@tailwindcss/postcss',
|
||||
file: globBase,
|
||||
parent: result.opts.from,
|
||||
})
|
||||
} else {
|
||||
result.messages.push({
|
||||
type: 'dir-dependency',
|
||||
plugin: '@tailwindcss/postcss',
|
||||
dir: globBase,
|
||||
glob: pattern,
|
||||
file,
|
||||
parent: result.opts.from,
|
||||
})
|
||||
}
|
||||
|
||||
// Register dependencies so changes in `base` cause a rebuild while
|
||||
// giving tools like Vite or Parcel a glob that can be used to limit
|
||||
// the files that cause a rebuild to only those that match it.
|
||||
for (let { base: globBase, pattern } of context.scanner.globs) {
|
||||
// Avoid adding a dependency on the base directory itself, since it
|
||||
// causes Next.js to start an endless recursion if the `distDir` is
|
||||
// configured to anything other than the default `.next` dir.
|
||||
if (pattern === '*' && base === globBase) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (pattern === '') {
|
||||
result.messages.push({
|
||||
type: 'dependency',
|
||||
plugin: '@tailwindcss/postcss',
|
||||
file: globBase,
|
||||
parent: result.opts.from,
|
||||
})
|
||||
} else {
|
||||
result.messages.push({
|
||||
type: 'dir-dependency',
|
||||
plugin: '@tailwindcss/postcss',
|
||||
dir: globBase,
|
||||
glob: pattern,
|
||||
parent: result.opts.from,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
env.DEBUG && console.time('[@tailwindcss/postcss] Build CSS')
|
||||
@ -237,8 +246,8 @@ function optimizeCss(
|
||||
nonStandard: {
|
||||
deepSelectorCombinator: true,
|
||||
},
|
||||
include: Features.Nesting,
|
||||
exclude: Features.LogicalProperties,
|
||||
include: LightningCssFeatures.Nesting,
|
||||
exclude: LightningCssFeatures.LogicalProperties,
|
||||
targets: {
|
||||
safari: (16 << 16) | (4 << 8),
|
||||
ios_saf: (16 << 16) | (4 << 8),
|
||||
|
||||
@ -13,7 +13,7 @@ import { toKeyPath } from '../../../../tailwindcss/src/utils/to-key-path'
|
||||
import * as ValueParser from '../../../../tailwindcss/src/value-parser'
|
||||
import { printCandidate } from '../candidates'
|
||||
|
||||
export enum Convert {
|
||||
export const enum Convert {
|
||||
All = 0,
|
||||
MigrateModifier = 1 << 0,
|
||||
MigrateThemeOnly = 1 << 1,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export enum WalkAction {
|
||||
export const enum WalkAction {
|
||||
// Continue walking the tree. Default behavior.
|
||||
Continue,
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { compile, env, normalizePath } from '@tailwindcss/node'
|
||||
import { compile, env, Features, normalizePath } from '@tailwindcss/node'
|
||||
import { clearRequireCache } from '@tailwindcss/node/require-cache'
|
||||
import { Scanner } from '@tailwindcss/oxide'
|
||||
import { Features, transform } from 'lightningcss'
|
||||
import { Features as LightningCssFeatures, transform } from 'lightningcss'
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { sveltePreprocess } from 'svelte-preprocess'
|
||||
@ -360,8 +360,8 @@ function optimizeCss(
|
||||
nonStandard: {
|
||||
deepSelectorCombinator: true,
|
||||
},
|
||||
include: Features.Nesting,
|
||||
exclude: Features.LogicalProperties,
|
||||
include: LightningCssFeatures.Nesting,
|
||||
exclude: LightningCssFeatures.LogicalProperties,
|
||||
targets: {
|
||||
safari: (16 << 16) | (4 << 8),
|
||||
ios_saf: (16 << 16) | (4 << 8),
|
||||
@ -497,7 +497,16 @@ class Root {
|
||||
this.scanner = new Scanner({ sources })
|
||||
}
|
||||
|
||||
if (!this.overwriteCandidates) {
|
||||
if (
|
||||
!(
|
||||
this.compiler.features &
|
||||
(Features.AtApply | Features.JsPluginCompat | Features.ThemeFunction | Features.Utilities)
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!this.overwriteCandidates || this.compiler.features & Features.Utilities) {
|
||||
// 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).
|
||||
@ -508,44 +517,46 @@ class Root {
|
||||
env.DEBUG && console.timeEnd('[@tailwindcss/vite] Scan for candidates')
|
||||
}
|
||||
|
||||
// Watch individual files found via custom `@source` paths
|
||||
for (let file of this.scanner.files) {
|
||||
addWatchFile(file)
|
||||
}
|
||||
|
||||
// Watch globs found via custom `@source` paths
|
||||
for (let glob of this.scanner.globs) {
|
||||
if (glob.pattern[0] === '!') continue
|
||||
|
||||
let relative = path.relative(this.base, glob.base)
|
||||
if (relative[0] !== '.') {
|
||||
relative = './' + relative
|
||||
if (this.compiler.features & Features.Utilities) {
|
||||
// Watch individual files found via custom `@source` paths
|
||||
for (let file of this.scanner.files) {
|
||||
addWatchFile(file)
|
||||
}
|
||||
// Ensure relative is a posix style path since we will merge it with the
|
||||
// glob.
|
||||
relative = normalizePath(relative)
|
||||
|
||||
addWatchFile(path.posix.join(relative, glob.pattern))
|
||||
// Watch globs found via custom `@source` paths
|
||||
for (let glob of this.scanner.globs) {
|
||||
if (glob.pattern[0] === '!') continue
|
||||
|
||||
let root = this.compiler.root
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
if (!isDir) {
|
||||
throw new Error(
|
||||
`The path given to \`source(…)\` must be a directory but got \`source(${basePath})\` instead.`,
|
||||
)
|
||||
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)
|
||||
|
||||
this.basePath = basePath
|
||||
} else if (root === null) {
|
||||
this.basePath = null
|
||||
addWatchFile(path.posix.join(relative, glob.pattern))
|
||||
|
||||
let root = this.compiler.root
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
if (!isDir) {
|
||||
throw new Error(
|
||||
`The path given to \`source(…)\` must be a directory but got \`source(${basePath})\` instead.`,
|
||||
)
|
||||
}
|
||||
|
||||
this.basePath = basePath
|
||||
} else if (root === null) {
|
||||
this.basePath = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { Features } from '.'
|
||||
import { walk, WalkAction, type AstNode } from './ast'
|
||||
import { compileCandidates } from './compile'
|
||||
import type { DesignSystem } from './design-system'
|
||||
import { escape } from './utils/escape'
|
||||
|
||||
export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
|
||||
let features = Features.None
|
||||
walk(ast, (node, { replaceWith }) => {
|
||||
if (node.kind !== 'at-rule') return
|
||||
|
||||
@ -18,6 +20,7 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
|
||||
}
|
||||
|
||||
if (node.name !== '@apply') return
|
||||
features |= Features.AtApply
|
||||
|
||||
let candidates = node.params.split(/\s+/g)
|
||||
|
||||
@ -75,4 +78,5 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
|
||||
replaceWith(newNodes)
|
||||
}
|
||||
})
|
||||
return features
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { expect, it } from 'vitest'
|
||||
import { context, decl, styleRule, toCss, walk } from './ast'
|
||||
import { context, decl, styleRule, toCss, walk, WalkAction } from './ast'
|
||||
import * as CSS from './css-parser'
|
||||
|
||||
it('should pretty print an AST', () => {
|
||||
@ -64,3 +64,33 @@ it('allows the placement of context nodes', () => {
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
it('should stop walking when returning `WalkAction.Stop`', () => {
|
||||
let ast = [
|
||||
styleRule('.foo', [styleRule('.nested', [styleRule('.bail', [decl('color', 'red')])])]),
|
||||
styleRule('.bar'),
|
||||
styleRule('.baz'),
|
||||
styleRule('.qux'),
|
||||
]
|
||||
|
||||
let seen = new Set()
|
||||
|
||||
walk(ast, (node) => {
|
||||
if (node.kind === 'rule') {
|
||||
seen.add(node.selector)
|
||||
}
|
||||
|
||||
if (node.kind === 'rule' && node.selector === '.bail') {
|
||||
return WalkAction.Stop
|
||||
}
|
||||
})
|
||||
|
||||
// We do not want to see `.bar`, `.baz`, or `.qux` because we bailed early
|
||||
expect(seen).toMatchInlineSnapshot(`
|
||||
Set {
|
||||
".foo",
|
||||
".nested",
|
||||
".bail",
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
@ -97,7 +97,7 @@ export function atRoot(nodes: AstNode[]): AtRoot {
|
||||
}
|
||||
}
|
||||
|
||||
export enum WalkAction {
|
||||
export const enum WalkAction {
|
||||
/** Continue walking, which is the default */
|
||||
Continue,
|
||||
|
||||
@ -131,7 +131,11 @@ export function walk(
|
||||
// whenever we encounter one, we immediately walk through its children and
|
||||
// furthermore we also don't update the parent.
|
||||
if (node.kind === 'context') {
|
||||
walk(node.nodes, visit, parentPath, { ...context, ...node.context })
|
||||
if (
|
||||
walk(node.nodes, visit, parentPath, { ...context, ...node.context }) === WalkAction.Stop
|
||||
) {
|
||||
return WalkAction.Stop
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@ -150,13 +154,15 @@ export function walk(
|
||||
}) ?? WalkAction.Continue
|
||||
|
||||
// Stop the walk entirely
|
||||
if (status === WalkAction.Stop) return
|
||||
if (status === WalkAction.Stop) return WalkAction.Stop
|
||||
|
||||
// Skip visiting the children of this node
|
||||
if (status === WalkAction.Skip) continue
|
||||
|
||||
if (node.kind === 'rule' || node.kind === 'at-rule') {
|
||||
walk(node.nodes, visit, path, context)
|
||||
if (walk(node.nodes, visit, path, context) === WalkAction.Stop) {
|
||||
return WalkAction.Stop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import dedent from 'dedent'
|
||||
import { expect, test, vi } from 'vitest'
|
||||
import type { Plugin } from './compat/plugin-api'
|
||||
import { compile, type Config } from './index'
|
||||
import plugin from './plugin'
|
||||
import { optimizeCss } from './test-utils/run'
|
||||
|
||||
const css = String.raw
|
||||
const css = dedent
|
||||
|
||||
async function run(
|
||||
css: string,
|
||||
@ -161,10 +162,7 @@ test('url() imports are passed-through', async () => {
|
||||
`,
|
||||
{ loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
|
||||
),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
"@import url('example.css');
|
||||
"
|
||||
`)
|
||||
).resolves.toMatchInlineSnapshot(`"@import url('example.css');"`)
|
||||
|
||||
await expect(
|
||||
run(
|
||||
@ -173,10 +171,7 @@ test('url() imports are passed-through', async () => {
|
||||
`,
|
||||
{ loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
|
||||
),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
"@import url('./example.css');
|
||||
"
|
||||
`)
|
||||
).resolves.toMatchInlineSnapshot(`"@import url('./example.css');"`)
|
||||
|
||||
await expect(
|
||||
run(
|
||||
@ -185,10 +180,7 @@ test('url() imports are passed-through', async () => {
|
||||
`,
|
||||
{ loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
|
||||
),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
"@import url('/example.css');
|
||||
"
|
||||
`)
|
||||
).resolves.toMatchInlineSnapshot(`"@import url('/example.css');"`)
|
||||
|
||||
await expect(
|
||||
run(
|
||||
@ -197,10 +189,7 @@ test('url() imports are passed-through', async () => {
|
||||
`,
|
||||
{ loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
|
||||
),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
"@import url(example.css);
|
||||
"
|
||||
`)
|
||||
).resolves.toMatchInlineSnapshot(`"@import url(example.css);"`)
|
||||
|
||||
await expect(
|
||||
run(
|
||||
@ -209,10 +198,7 @@ test('url() imports are passed-through', async () => {
|
||||
`,
|
||||
{ loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
|
||||
),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
"@import url(./example.css);
|
||||
"
|
||||
`)
|
||||
).resolves.toMatchInlineSnapshot(`"@import url(./example.css);"`)
|
||||
|
||||
await expect(
|
||||
run(
|
||||
@ -221,10 +207,7 @@ test('url() imports are passed-through', async () => {
|
||||
`,
|
||||
{ loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
|
||||
),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
"@import url(/example.css);
|
||||
"
|
||||
`)
|
||||
).resolves.toMatchInlineSnapshot(`"@import url(/example.css);"`)
|
||||
})
|
||||
|
||||
test('handles case-insensitive @import directive', async () => {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Features } from '.'
|
||||
import { atRule, context, walk, WalkAction, type AstNode } from './ast'
|
||||
import * as CSS from './css-parser'
|
||||
import * as ValueParser from './value-parser'
|
||||
@ -10,6 +11,7 @@ export async function substituteAtImports(
|
||||
loadStylesheet: LoadStylesheet,
|
||||
recurseCount = 0,
|
||||
) {
|
||||
let features = Features.None
|
||||
let promises: Promise<void>[] = []
|
||||
|
||||
walk(ast, (node, { replaceWith }) => {
|
||||
@ -17,6 +19,8 @@ export async function substituteAtImports(
|
||||
let parsed = parseImportParams(ValueParser.parse(node.params))
|
||||
if (parsed === null) return
|
||||
|
||||
features |= Features.AtImport
|
||||
|
||||
let { uri, layer, media, supports } = parsed
|
||||
|
||||
// Skip importing data or remote URIs
|
||||
@ -58,7 +62,11 @@ export async function substituteAtImports(
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
if (promises.length > 0) {
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
return features
|
||||
}
|
||||
|
||||
// Modified and inlined version of `parse-statements` from
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Features } from '..'
|
||||
import { styleRule, toCss, walk, WalkAction, type AstNode } from '../ast'
|
||||
import type { DesignSystem } from '../design-system'
|
||||
import { segment } from '../utils/segment'
|
||||
@ -32,6 +33,7 @@ export async function applyCompatibilityHooks({
|
||||
) => Promise<{ module: any; base: string }>
|
||||
globs: { origin?: string; pattern: string }[]
|
||||
}) {
|
||||
let features = Features.None
|
||||
let pluginPaths: [{ id: string; base: string }, CssPluginOptions | null][] = []
|
||||
let configPaths: { id: string; base: string }[] = []
|
||||
|
||||
@ -98,6 +100,7 @@ export async function applyCompatibilityHooks({
|
||||
])
|
||||
|
||||
replaceWith([])
|
||||
features |= Features.JsPluginCompat
|
||||
return
|
||||
}
|
||||
|
||||
@ -113,6 +116,7 @@ export async function applyCompatibilityHooks({
|
||||
|
||||
configPaths.push({ id: node.params.slice(1, -1), base: context.base })
|
||||
replaceWith([])
|
||||
features |= Features.JsPluginCompat
|
||||
return
|
||||
}
|
||||
})
|
||||
@ -132,7 +136,7 @@ export async function applyCompatibilityHooks({
|
||||
|
||||
// If the theme value is not found in the simple resolver, we upgrade to the full backward
|
||||
// compatibility support implementation of the `resolveThemeValue` function.
|
||||
upgradeToFullPluginSupport({
|
||||
features |= upgradeToFullPluginSupport({
|
||||
designSystem,
|
||||
base,
|
||||
ast,
|
||||
@ -145,7 +149,7 @@ export async function applyCompatibilityHooks({
|
||||
|
||||
// If there are no plugins or configs registered, we don't need to register
|
||||
// any additional backwards compatibility hooks.
|
||||
if (!pluginPaths.length && !configPaths.length) return
|
||||
if (!pluginPaths.length && !configPaths.length) return Features.None
|
||||
|
||||
let [configs, pluginDetails] = await Promise.all([
|
||||
Promise.all(
|
||||
@ -171,7 +175,7 @@ export async function applyCompatibilityHooks({
|
||||
),
|
||||
])
|
||||
|
||||
upgradeToFullPluginSupport({
|
||||
features |= upgradeToFullPluginSupport({
|
||||
designSystem,
|
||||
base,
|
||||
ast,
|
||||
@ -179,6 +183,8 @@ export async function applyCompatibilityHooks({
|
||||
configs,
|
||||
pluginDetails,
|
||||
})
|
||||
|
||||
return features
|
||||
}
|
||||
|
||||
function upgradeToFullPluginSupport({
|
||||
@ -205,6 +211,7 @@ function upgradeToFullPluginSupport({
|
||||
options: CssPluginOptions | null
|
||||
}[]
|
||||
}) {
|
||||
let features = Features.None
|
||||
let pluginConfigs = pluginDetails.map((detail) => {
|
||||
if (!detail.options) {
|
||||
return { config: { plugins: [detail.plugin] }, base: detail.base }
|
||||
@ -229,7 +236,11 @@ function upgradeToFullPluginSupport({
|
||||
userConfig,
|
||||
)
|
||||
|
||||
let pluginApi = buildPluginApi(designSystem, ast, resolvedConfig)
|
||||
let pluginApi = buildPluginApi(designSystem, ast, resolvedConfig, {
|
||||
set current(value: number) {
|
||||
features |= value
|
||||
},
|
||||
})
|
||||
|
||||
for (let { handler } of resolvedConfig.plugins) {
|
||||
handler(pluginApi)
|
||||
@ -323,4 +334,5 @@ function upgradeToFullPluginSupport({
|
||||
|
||||
globs.push(file)
|
||||
}
|
||||
return features
|
||||
}
|
||||
|
||||
@ -213,7 +213,7 @@ function isValidThemeTuple(value: unknown): value is [string, Record<string, str
|
||||
return true
|
||||
}
|
||||
|
||||
enum WalkAction {
|
||||
const enum WalkAction {
|
||||
/** Continue walking, which is the default */
|
||||
Continue,
|
||||
|
||||
@ -241,10 +241,12 @@ function walk(
|
||||
let result = callback(value, keyPath) ?? WalkAction.Continue
|
||||
|
||||
if (result === WalkAction.Skip) continue
|
||||
if (result === WalkAction.Stop) break
|
||||
if (result === WalkAction.Stop) return WalkAction.Stop
|
||||
|
||||
if (!Array.isArray(value) && typeof value !== 'object') continue
|
||||
|
||||
walk(value as any, keyPath, callback)
|
||||
if (walk(value as any, keyPath, callback) === WalkAction.Stop) {
|
||||
return WalkAction.Stop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { Features } from '..'
|
||||
import { substituteAtApply } from '../apply'
|
||||
import { atRule, decl, rule, walk, type AstNode } from '../ast'
|
||||
import type { Candidate, CandidateModifier, NamedUtilityValue } from '../candidate'
|
||||
@ -83,11 +84,12 @@ export function buildPluginApi(
|
||||
designSystem: DesignSystem,
|
||||
ast: AstNode[],
|
||||
resolvedConfig: ResolvedConfig,
|
||||
featuresRef: { current: Features },
|
||||
): PluginAPI {
|
||||
let api: PluginAPI = {
|
||||
addBase(css) {
|
||||
let baseNodes = objectToAst(css)
|
||||
substituteFunctions(baseNodes, api.theme)
|
||||
featuresRef.current |= substituteFunctions(baseNodes, api.theme)
|
||||
ast.push(atRule('@layer', 'base', baseNodes))
|
||||
},
|
||||
|
||||
@ -260,7 +262,7 @@ export function buildPluginApi(
|
||||
|
||||
designSystem.utilities.static(className, () => {
|
||||
let clonedAst = structuredClone(ast)
|
||||
substituteAtApply(clonedAst, designSystem)
|
||||
featuresRef.current |= substituteAtApply(clonedAst, designSystem)
|
||||
return clonedAst
|
||||
})
|
||||
}
|
||||
@ -382,7 +384,7 @@ export function buildPluginApi(
|
||||
}
|
||||
|
||||
let ast = objectToAst(fn(value, { modifier }))
|
||||
substituteAtApply(ast, designSystem)
|
||||
featuresRef.current |= substituteAtApply(ast, designSystem)
|
||||
return ast
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ function value(value: string): SelectorValueNode {
|
||||
}
|
||||
}
|
||||
|
||||
export enum SelectorWalkAction {
|
||||
export const enum SelectorWalkAction {
|
||||
/** Continue walking, which is the default */
|
||||
Continue,
|
||||
|
||||
@ -105,13 +105,15 @@ export function walk(
|
||||
}) ?? SelectorWalkAction.Continue
|
||||
|
||||
// Stop the walk entirely
|
||||
if (status === SelectorWalkAction.Stop) return
|
||||
if (status === SelectorWalkAction.Stop) return SelectorWalkAction.Stop
|
||||
|
||||
// Skip visiting the children of this node
|
||||
if (status === SelectorWalkAction.Skip) continue
|
||||
|
||||
if (node.kind === 'function') {
|
||||
walk(node.nodes, visit, node)
|
||||
if (walk(node.nodes, visit, node) === SelectorWalkAction.Stop) {
|
||||
return SelectorWalkAction.Stop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Features } from '.'
|
||||
import { walk, type AstNode } from './ast'
|
||||
import * as ValueParser from './value-parser'
|
||||
import { type ValueAstNode } from './value-parser'
|
||||
@ -7,9 +8,11 @@ export const THEME_FUNCTION_INVOCATION = 'theme('
|
||||
type ResolveThemeValue = (path: string) => string | undefined
|
||||
|
||||
export function substituteFunctions(ast: AstNode[], resolveThemeValue: ResolveThemeValue) {
|
||||
let features = Features.None
|
||||
walk(ast, (node) => {
|
||||
// Find all declaration values
|
||||
if (node.kind === 'declaration' && node.value?.includes(THEME_FUNCTION_INVOCATION)) {
|
||||
features |= Features.ThemeFunction
|
||||
node.value = substituteFunctionsInValue(node.value, resolveThemeValue)
|
||||
return
|
||||
}
|
||||
@ -23,10 +26,12 @@ export function substituteFunctions(ast: AstNode[], resolveThemeValue: ResolveTh
|
||||
node.name === '@supports') &&
|
||||
node.params.includes(THEME_FUNCTION_INVOCATION)
|
||||
) {
|
||||
features |= Features.ThemeFunction
|
||||
node.params = substituteFunctionsInValue(node.params, resolveThemeValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
return features
|
||||
}
|
||||
|
||||
export function substituteFunctionsInValue(
|
||||
|
||||
@ -69,6 +69,35 @@ function parseThemeOptions(params: string) {
|
||||
return [options, prefix] as const
|
||||
}
|
||||
|
||||
type Root =
|
||||
// Unknown root
|
||||
| null
|
||||
|
||||
// Explicitly no root specified via `source(none)`
|
||||
| 'none'
|
||||
|
||||
// Specified via `source(…)`, relative to the `base`
|
||||
| { base: string; pattern: string }
|
||||
|
||||
export const enum Features {
|
||||
None = 0,
|
||||
|
||||
// `@apply` was used
|
||||
AtApply = 1 << 0,
|
||||
|
||||
// `@import` was used
|
||||
AtImport = 1 << 1,
|
||||
|
||||
// `@plugin` or `@config` was used
|
||||
JsPluginCompat = 1 << 2,
|
||||
|
||||
// `theme(…)` was used
|
||||
ThemeFunction = 1 << 3,
|
||||
|
||||
// `@tailwind utilities` was used
|
||||
Utilities = 1 << 4,
|
||||
}
|
||||
|
||||
async function parseCss(
|
||||
css: string,
|
||||
{
|
||||
@ -77,9 +106,10 @@ async function parseCss(
|
||||
loadStylesheet = throwOnLoadStylesheet,
|
||||
}: CompileOptions = {},
|
||||
) {
|
||||
let features = Features.None
|
||||
let ast = [contextNode({ base }, CSS.parse(css))] as AstNode[]
|
||||
|
||||
await substituteAtImports(ast, base, loadStylesheet)
|
||||
features |= await substituteAtImports(ast, base, loadStylesheet)
|
||||
|
||||
let important = null as boolean | null
|
||||
let theme = new Theme()
|
||||
@ -88,11 +118,7 @@ async function parseCss(
|
||||
let firstThemeRule = null as StyleRule | null
|
||||
let utilitiesNode = null as AtRule | null
|
||||
let globs: { base: string; pattern: string }[] = []
|
||||
let root:
|
||||
| null // Unknown root
|
||||
| 'none' // Explicitly no root specified via `source(none)`
|
||||
// Specified via `source(…)`, relative to the `base`
|
||||
| { base: string; pattern: string } = null
|
||||
let root = null as Root
|
||||
|
||||
// Handle at-rules
|
||||
walk(ast, (node, { parent, replaceWith, context }) => {
|
||||
@ -138,6 +164,7 @@ async function parseCss(
|
||||
}
|
||||
|
||||
utilitiesNode = node
|
||||
features |= Features.Utilities
|
||||
}
|
||||
|
||||
// Collect custom `@utility` at-rules
|
||||
@ -414,7 +441,13 @@ async function parseCss(
|
||||
// of random arguments because it really just needs access to "the world" to
|
||||
// do whatever ungodly things it needs to do to make things backwards
|
||||
// compatible without polluting core.
|
||||
await applyCompatibilityHooks({ designSystem, base, ast, loadModule, globs })
|
||||
features |= await applyCompatibilityHooks({
|
||||
designSystem,
|
||||
base,
|
||||
ast,
|
||||
loadModule,
|
||||
globs,
|
||||
})
|
||||
|
||||
for (let customVariant of customVariants) {
|
||||
customVariant(designSystem)
|
||||
@ -464,9 +497,9 @@ async function parseCss(
|
||||
}
|
||||
|
||||
// Replace `@apply` rules with the actual utility classes.
|
||||
substituteAtApply(ast, designSystem)
|
||||
features |= substituteAtApply(ast, designSystem)
|
||||
|
||||
substituteFunctions(ast, designSystem.resolveThemeValue)
|
||||
features |= substituteFunctions(ast, designSystem.resolveThemeValue)
|
||||
|
||||
// Remove `@utility`, we couldn't replace it before yet because we had to
|
||||
// handle the nested `@apply` at-rules first.
|
||||
@ -488,6 +521,7 @@ async function parseCss(
|
||||
globs,
|
||||
root,
|
||||
utilitiesNode,
|
||||
features,
|
||||
}
|
||||
}
|
||||
|
||||
@ -496,13 +530,11 @@ export async function compile(
|
||||
opts: CompileOptions = {},
|
||||
): Promise<{
|
||||
globs: { base: string; pattern: string }[]
|
||||
root:
|
||||
| null // Unknown root
|
||||
| 'none' // Explicitly no root specified via `source(none)`
|
||||
| { base: string; pattern: string } // Specified via `source(…)`, relative to the `base`
|
||||
root: Root
|
||||
features: Features
|
||||
build(candidates: string[]): string
|
||||
}> {
|
||||
let { designSystem, ast, globs, root, utilitiesNode } = await parseCss(css, opts)
|
||||
let { designSystem, ast, globs, root, utilitiesNode, features } = await parseCss(css, opts)
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
ast.unshift(comment(`! tailwindcss v${version} | MIT License | https://tailwindcss.com `))
|
||||
@ -517,12 +549,13 @@ export async function compile(
|
||||
// resulted in a generated AST Node. All the other `rawCandidates` are invalid
|
||||
// and should be ignored.
|
||||
let allValidCandidates = new Set<string>()
|
||||
let compiledCss = toCss(ast)
|
||||
let compiledCss = features !== Features.None ? toCss(ast) : css
|
||||
let previousAstNodeCount = 0
|
||||
|
||||
return {
|
||||
globs,
|
||||
root,
|
||||
features,
|
||||
build(newRawCandidates: string[]) {
|
||||
let didChange = false
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ function separator(value: string): ValueSeparatorNode {
|
||||
}
|
||||
}
|
||||
|
||||
export enum ValueWalkAction {
|
||||
export const enum ValueWalkAction {
|
||||
/** Continue walking, which is the default */
|
||||
Continue,
|
||||
|
||||
@ -76,13 +76,15 @@ export function walk(
|
||||
}) ?? ValueWalkAction.Continue
|
||||
|
||||
// Stop the walk entirely
|
||||
if (status === ValueWalkAction.Stop) return
|
||||
if (status === ValueWalkAction.Stop) return ValueWalkAction.Stop
|
||||
|
||||
// Skip visiting the children of this node
|
||||
if (status === ValueWalkAction.Skip) continue
|
||||
|
||||
if (node.kind === 'function') {
|
||||
walk(node.nodes, visit, node)
|
||||
if (walk(node.nodes, visit, node) === ValueWalkAction.Stop) {
|
||||
return ValueWalkAction.Stop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
@ -204,6 +204,9 @@ importers:
|
||||
'@types/postcss-import':
|
||||
specifier: 14.0.3
|
||||
version: 14.0.3
|
||||
dedent:
|
||||
specifier: 1.5.3
|
||||
version: 1.5.3
|
||||
internal-example-plugin:
|
||||
specifier: workspace:*
|
||||
version: link:../internal-example-plugin
|
||||
@ -1477,11 +1480,13 @@ packages:
|
||||
'@parcel/watcher-darwin-arm64@2.5.0':
|
||||
resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@parcel/watcher-darwin-x64@2.5.0':
|
||||
resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@parcel/watcher-freebsd-x64@2.5.0':
|
||||
@ -1505,21 +1510,25 @@ packages:
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.0':
|
||||
resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.0':
|
||||
resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.0':
|
||||
resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.0':
|
||||
resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.0':
|
||||
@ -1537,6 +1546,7 @@ packages:
|
||||
'@parcel/watcher-win32-x64@2.5.0':
|
||||
resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher@2.5.0':
|
||||
@ -2032,6 +2042,7 @@ packages:
|
||||
|
||||
bun@1.1.29:
|
||||
resolution: {integrity: sha512-SKhpyKNZtgxrVel9ec9xon3LDv8mgpiuFhARgcJo1YIbggY2PBrKHRNiwQ6Qlb+x3ivmRurfuwWgwGexjpgBRg==}
|
||||
cpu: [arm64, x64]
|
||||
os: [darwin, linux, win32]
|
||||
hasBin: true
|
||||
|
||||
@ -2854,11 +2865,13 @@ packages:
|
||||
lightningcss-darwin-arm64@1.26.0:
|
||||
resolution: {integrity: sha512-n4TIvHO1NY1ondKFYpL2ZX0bcC2y6yjXMD6JfyizgR8BCFNEeArINDzEaeqlfX9bXz73Bpz/Ow0nu+1qiDrBKg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-darwin-x64@1.26.0:
|
||||
resolution: {integrity: sha512-Rf9HuHIDi1R6/zgBkJh25SiJHF+dm9axUZW/0UoYCW1/8HV0gMI0blARhH4z+REmWiU1yYT/KyNF3h7tHyRXUg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-freebsd-x64@1.26.0:
|
||||
@ -2876,21 +2889,25 @@ packages:
|
||||
lightningcss-linux-arm64-gnu@1.26.0:
|
||||
resolution: {integrity: sha512-iJmZM7fUyVjH+POtdiCtExG+67TtPUTer7K/5A8DIfmPfrmeGvzfRyBltGhQz13Wi15K1lf2cPYoRaRh6vcwNA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.26.0:
|
||||
resolution: {integrity: sha512-XxoEL++tTkyuvu+wq/QS8bwyTXZv2y5XYCMcWL45b8XwkiS8eEEEej9BkMGSRwxa5J4K+LDeIhLrS23CpQyfig==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.26.0:
|
||||
resolution: {integrity: sha512-1dkTfZQAYLj8MUSkd6L/+TWTG8V6Kfrzfa0T1fSlXCXQHrt1HC1/UepXHtKHDt/9yFwyoeayivxXAsApVxn6zA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-x64-musl@1.26.0:
|
||||
resolution: {integrity: sha512-yX3Rk9m00JGCUzuUhFEojY+jf/6zHs3XU8S8Vk+FRbnr4St7cjyMXdNjuA2LjiT8e7j8xHRCH8hyZ4H/btRE4A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.26.0:
|
||||
@ -2902,6 +2919,7 @@ packages:
|
||||
lightningcss-win32-x64-msvc@1.26.0:
|
||||
resolution: {integrity: sha512-pYS3EyGP3JRhfqEFYmfFDiZ9/pVNfy8jVIYtrx9TVNusVyDK3gpW1w/rbvroQ4bDJi7grdUtyrYU6V2xkY/bBw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss@1.26.0:
|
||||
@ -5685,7 +5703,7 @@ snapshots:
|
||||
eslint: 9.15.0(jiti@2.4.0)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-plugin-jsx-a11y: 6.10.1(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-plugin-react: 7.37.2(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-plugin-react-hooks: 5.0.0(eslint@9.15.0(jiti@2.4.0))
|
||||
@ -5705,7 +5723,7 @@ snapshots:
|
||||
eslint: 9.15.0(jiti@2.4.0)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-plugin-jsx-a11y: 6.10.1(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-plugin-react: 7.37.2(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-plugin-react-hooks: 5.0.0(eslint@9.15.0(jiti@2.4.0))
|
||||
@ -5730,13 +5748,13 @@ snapshots:
|
||||
debug: 4.3.7
|
||||
enhanced-resolve: 5.17.1
|
||||
eslint: 9.15.0(jiti@2.4.0)
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.4.0)))(eslint@9.15.0(jiti@2.4.0))
|
||||
fast-glob: 3.3.2
|
||||
get-tsconfig: 4.8.1
|
||||
is-bun-module: 1.2.1
|
||||
is-glob: 4.0.3
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.4.0))
|
||||
transitivePeerDependencies:
|
||||
- '@typescript-eslint/parser'
|
||||
- eslint-import-resolver-node
|
||||
@ -5749,20 +5767,20 @@ snapshots:
|
||||
debug: 4.3.7
|
||||
enhanced-resolve: 5.17.1
|
||||
eslint: 9.15.0(jiti@2.4.0)
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.4.0)))(eslint@9.15.0(jiti@2.4.0))
|
||||
fast-glob: 3.3.2
|
||||
get-tsconfig: 4.8.1
|
||||
is-bun-module: 1.2.1
|
||||
is-glob: 4.0.3
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint@9.15.0(jiti@2.4.0))
|
||||
transitivePeerDependencies:
|
||||
- '@typescript-eslint/parser'
|
||||
- eslint-import-resolver-node
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.4.0)):
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.4.0)))(eslint@9.15.0(jiti@2.4.0)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
@ -5773,7 +5791,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.4.0)):
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.4.0)))(eslint@9.15.0(jiti@2.4.0)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
@ -5784,7 +5802,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint@9.15.0(jiti@2.4.0)):
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.4.0)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.8
|
||||
@ -5795,7 +5813,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.15.0(jiti@2.4.0)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.4.0)))(eslint@9.15.0(jiti@2.4.0))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.15.1
|
||||
is-glob: 4.0.3
|
||||
@ -5813,7 +5831,7 @@ snapshots:
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.4.0)):
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint@9.15.0(jiti@2.4.0)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.8
|
||||
@ -5824,7 +5842,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.15.0(jiti@2.4.0)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.4.0))
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.4.0)))(eslint@9.15.0(jiti@2.4.0))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.15.1
|
||||
is-glob: 4.0.3
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user