diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1bcda15312b0..b8c3e59ed84e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Disallow negative bare values in core utilities and variants ([#14453](https://github.com/tailwindlabs/tailwindcss/pull/14453))
- Preserve explicit shadow color when overriding shadow size ([#14458](https://github.com/tailwindlabs/tailwindcss/pull/14458))
- Preserve explicit transition duration and timing function when overriding transition property ([#14490](https://github.com/tailwindlabs/tailwindcss/pull/14490))
+- Change the implementation for `@import` resolution to speed up initial builds ([#14446](https://github.com/tailwindlabs/tailwindcss/pull/14446))
## [4.0.0-alpha.24] - 2024-09-11
diff --git a/integrations/postcss/index.test.ts b/integrations/postcss/index.test.ts
index d4e4f4d394ba..273f07f73cfd 100644
--- a/integrations/postcss/index.test.ts
+++ b/integrations/postcss/index.test.ts
@@ -79,6 +79,86 @@ test(
},
)
+test(
+ 'production build with `postcss-import` (string)',
+ {
+ fs: {
+ 'package.json': json`{}`,
+ 'pnpm-workspace.yaml': yaml`
+ #
+ packages:
+ - project-a
+ `,
+ 'project-a/package.json': json`
+ {
+ "dependencies": {
+ "postcss": "^8",
+ "postcss-cli": "^10",
+ "postcss-import": "^16",
+ "tailwindcss": "workspace:^",
+ "@tailwindcss/postcss": "workspace:^"
+ }
+ }
+ `,
+ 'project-a/postcss.config.js': js`
+ module.exports = {
+ plugins: {
+ 'postcss-import': {},
+ '@tailwindcss/postcss': {},
+ },
+ }
+ `,
+ 'project-a/index.html': html`
+
+ `,
+ 'project-a/plugin.js': js`
+ module.exports = function ({ addVariant }) {
+ addVariant('inverted', '@media (inverted-colors: inverted)')
+ addVariant('hocus', ['&:focus', '&:hover'])
+ }
+ `,
+ 'project-a/tailwind.config.js': js`
+ module.exports = {
+ content: ['../project-b/src/**/*.js'],
+ }
+ `,
+ 'project-a/src/index.css': css`
+ @import 'tailwindcss/utilities';
+ @config '../tailwind.config.js';
+ @source '../../project-b/src/**/*.html';
+ @plugin '../plugin.js';
+ `,
+ 'project-a/src/index.js': js`
+ const className = "content-['a/src/index.js']"
+ module.exports = { className }
+ `,
+ 'project-b/src/index.html': html`
+
+ `,
+ 'project-b/src/index.js': js`
+ const className = "content-['b/src/index.js']"
+ module.exports = { className }
+ `,
+ },
+ },
+ async ({ root, fs, exec }) => {
+ await exec('pnpm postcss src/index.css --output dist/out.css', {
+ cwd: path.join(root, 'project-a'),
+ })
+
+ await fs.expectFileToContain('project-a/dist/out.css', [
+ candidate`underline`,
+ candidate`flex`,
+ candidate`content-['a/src/index.js']`,
+ candidate`content-['b/src/index.js']`,
+ candidate`inverted:flex`,
+ candidate`hocus:underline`,
+ ])
+ },
+)
+
test(
'production build (ESM)',
{
diff --git a/packages/@tailwindcss-cli/package.json b/packages/@tailwindcss-cli/package.json
index e36b6e2ada6c..c8dbf8cce678 100644
--- a/packages/@tailwindcss-cli/package.json
+++ b/packages/@tailwindcss-cli/package.json
@@ -36,12 +36,6 @@
"lightningcss": "catalog:",
"mri": "^1.2.0",
"picocolors": "^1.0.1",
- "postcss-import": "^16.1.0",
- "postcss": "^8.4.41",
"tailwindcss": "workspace:^"
- },
- "devDependencies": {
- "@types/postcss-import": "^14.0.3",
- "internal-postcss-fix-relative-paths": "workspace:^"
}
}
diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts
index 3ca7f4ad0367..daf12294cf74 100644
--- a/packages/@tailwindcss-cli/src/commands/build/index.ts
+++ b/packages/@tailwindcss-cli/src/commands/build/index.ts
@@ -2,13 +2,10 @@ import watcher from '@parcel/watcher'
import { compile } from '@tailwindcss/node'
import { clearRequireCache } from '@tailwindcss/node/require-cache'
import { Scanner, type ChangedContent } from '@tailwindcss/oxide'
-import fixRelativePathsPlugin from 'internal-postcss-fix-relative-paths'
import { Features, transform } from 'lightningcss'
-import { existsSync, readFileSync } from 'node:fs'
+import { existsSync } from 'node:fs'
import fs from 'node:fs/promises'
import path from 'node:path'
-import postcss from 'postcss'
-import atImport from 'postcss-import'
import type { Arg, Result } from '../../utils/args'
import { Disposables } from '../../utils/disposables'
import {
@@ -19,7 +16,6 @@ import {
println,
relative,
} from '../../utils/renderer'
-import { resolveCssId } from '../../utils/resolve'
import { drainStdin, outputFile } from './utils'
const css = String.raw
@@ -83,17 +79,13 @@ export async function handle(args: Result>) {
let start = process.hrtime.bigint()
- // Resolve the input
- let [input, cssImportPaths] = await handleImports(
- args['--input']
- ? args['--input'] === '-'
- ? await drainStdin()
- : await fs.readFile(args['--input'], 'utf-8')
- : css`
- @import 'tailwindcss';
- `,
- args['--input'] ?? base,
- )
+ let input = args['--input']
+ ? args['--input'] === '-'
+ ? await drainStdin()
+ : await fs.readFile(args['--input'], 'utf-8')
+ : css`
+ @import 'tailwindcss';
+ `
let previous = {
css: '',
@@ -128,7 +120,7 @@ export async function handle(args: Result>) {
let inputFile = args['--input'] && args['--input'] !== '-' ? args['--input'] : process.cwd()
let inputBasePath = path.dirname(path.resolve(inputFile))
- let fullRebuildPaths: string[] = cssImportPaths.slice()
+ let fullRebuildPaths: string[] = []
function createCompiler(css: string) {
return compile(css, {
@@ -143,12 +135,7 @@ export async function handle(args: Result>) {
let compiler = await createCompiler(input)
let scanner = new Scanner({
detectSources: { base },
- sources: compiler.globs.map(({ origin, pattern }) => ({
- // Ensure the glob is relative to the input CSS file or the config file
- // where it is specified.
- base: origin ? path.dirname(path.resolve(inputBasePath, origin)) : inputBasePath,
- pattern,
- })),
+ sources: compiler.globs,
})
// Watch for changes
@@ -196,17 +183,16 @@ export async function handle(args: Result>) {
// Clear all watchers
cleanupWatchers()
- // Collect the new `input` and `cssImportPaths`.
- ;[input, cssImportPaths] = await handleImports(
- args['--input']
- ? await fs.readFile(args['--input'], 'utf-8')
- : css`
- @import 'tailwindcss';
- `,
- args['--input'] ?? base,
- )
+ // Read the new `input`.
+ let input = args['--input']
+ ? args['--input'] === '-'
+ ? await drainStdin()
+ : await fs.readFile(args['--input'], 'utf-8')
+ : css`
+ @import 'tailwindcss';
+ `
clearRequireCache(resolvedFullRebuildPaths)
- fullRebuildPaths = cssImportPaths.slice()
+ fullRebuildPaths = []
// Create a new compiler, given the new `input`
compiler = await createCompiler(input)
@@ -214,12 +200,7 @@ export async function handle(args: Result>) {
// Re-scan the directory to get the new `candidates`
scanner = new Scanner({
detectSources: { base },
- sources: compiler.globs.map(({ origin, pattern }) => ({
- // Ensure the glob is relative to the input CSS file or the
- // config file where it is specified.
- base: origin ? path.dirname(path.resolve(inputBasePath, origin)) : inputBasePath,
- pattern,
- })),
+ sources: compiler.globs,
})
// Scan the directory for candidates
@@ -367,51 +348,6 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) {
}
}
-function handleImports(
- input: string,
- file: string,
-): [css: string, paths: string[]] | Promise<[css: string, paths: string[]]> {
- // TODO: Should we implement this ourselves instead of relying on PostCSS?
- //
- // Relevant specification:
- // - CSS Import Resolve: https://csstools.github.io/css-import-resolve/
-
- if (!input.includes('@import')) {
- return [input, [file]]
- }
-
- return postcss()
- .use(
- atImport({
- resolve(id, basedir) {
- let resolved = resolveCssId(id, basedir)
- if (!resolved) {
- throw new Error(`Could not resolve ${id} from ${basedir}`)
- }
- return resolved
- },
- load(id) {
- // We need to synchronously read the file here because when bundled
- // with bun, some of the ids might resolve to files inside the bun
- // embedded files root which can only be read by `node:fs` and not
- // `node:fs/promises`.
- return readFileSync(id, 'utf-8')
- },
- }),
- )
- .use(fixRelativePathsPlugin())
- .process(input, { from: file })
- .then((result) => [
- result.css,
-
- // Use `result.messages` to get the imported files. This also includes the
- // current file itself.
- [file].concat(
- result.messages.filter((msg) => msg.type === 'dependency').map((msg) => msg.file),
- ),
- ])
-}
-
function optimizeCss(
input: string,
{ file = 'input.css', minify = false }: { file?: string; minify?: boolean } = {},
diff --git a/packages/@tailwindcss-cli/tsup.config.ts b/packages/@tailwindcss-cli/tsup.config.ts
index 23628127060d..7d82eee2c882 100644
--- a/packages/@tailwindcss-cli/tsup.config.ts
+++ b/packages/@tailwindcss-cli/tsup.config.ts
@@ -5,5 +5,4 @@ export default defineConfig({
clean: true,
minify: true,
entry: ['src/index.ts'],
- noExternal: ['internal-postcss-fix-relative-paths'],
})
diff --git a/packages/@tailwindcss-node/package.json b/packages/@tailwindcss-node/package.json
index 652e65538982..97f63f77e40a 100644
--- a/packages/@tailwindcss-node/package.json
+++ b/packages/@tailwindcss-node/package.json
@@ -40,6 +40,7 @@
"tailwindcss": "workspace:^"
},
"dependencies": {
+ "enhanced-resolve": "^5.17.1",
"jiti": "^2.0.0-beta.3"
}
}
diff --git a/packages/@tailwindcss-node/src/compile.ts b/packages/@tailwindcss-node/src/compile.ts
index 78c68d9ae06b..02332d578262 100644
--- a/packages/@tailwindcss-node/src/compile.ts
+++ b/packages/@tailwindcss-node/src/compile.ts
@@ -1,5 +1,8 @@
+import EnhancedResolve from 'enhanced-resolve'
import { createJiti, type Jiti } from 'jiti'
-import path from 'node:path'
+import fs from 'node:fs'
+import fsPromises from 'node:fs/promises'
+import path, { dirname, extname } from 'node:path'
import { pathToFileURL } from 'node:url'
import { compile as _compile } from 'tailwindcss'
import { getModuleDependencies } from './get-module-dependencies'
@@ -9,12 +12,25 @@ export async function compile(
{ base, onDependency }: { base: string; onDependency: (path: string) => void },
) {
return await _compile(css, {
- loadPlugin: async (pluginPath) => {
- if (pluginPath[0] !== '.') {
- return importModule(pluginPath).then((m) => m.default ?? m)
+ base,
+ async loadModule(id, base) {
+ if (id[0] !== '.') {
+ let resolvedPath = await resolveJsId(id, base)
+ if (!resolvedPath) {
+ throw new Error(`Could not resolve '${id}' from '${base}'`)
+ }
+
+ let module = await importModule(pathToFileURL(resolvedPath).href)
+ return {
+ base: dirname(resolvedPath),
+ module: module.default ?? module,
+ }
}
- let resolvedPath = path.resolve(base, pluginPath)
+ let resolvedPath = await resolveJsId(id, base)
+ if (!resolvedPath) {
+ throw new Error(`Could not resolve '${id}' from '${base}'`)
+ }
let [module, moduleDependencies] = await Promise.all([
importModule(pathToFileURL(resolvedPath).href + '?id=' + Date.now()),
getModuleDependencies(resolvedPath),
@@ -24,25 +40,31 @@ export async function compile(
for (let file of moduleDependencies) {
onDependency(file)
}
- return module.default ?? module
+ return {
+ base: dirname(resolvedPath),
+ module: module.default ?? module,
+ }
},
- loadConfig: async (configPath) => {
- if (configPath[0] !== '.') {
- return importModule(configPath).then((m) => m.default ?? m)
- }
+ async loadStylesheet(id, basedir) {
+ let resolvedPath = await resolveCssId(id, basedir)
+ if (!resolvedPath) throw new Error(`Could not resolve '${id}' from '${basedir}'`)
- let resolvedPath = path.resolve(base, configPath)
- let [module, moduleDependencies] = await Promise.all([
- importModule(pathToFileURL(resolvedPath).href + '?id=' + Date.now()),
- getModuleDependencies(resolvedPath),
- ])
+ if (typeof globalThis.__tw_readFile === 'function') {
+ let file = await globalThis.__tw_readFile(resolvedPath, 'utf-8')
+ if (file) {
+ return {
+ base: path.dirname(resolvedPath),
+ content: file,
+ }
+ }
+ }
- onDependency(resolvedPath)
- for (let file of moduleDependencies) {
- onDependency(file)
+ let file = await fsPromises.readFile(resolvedPath, 'utf-8')
+ return {
+ base: path.dirname(resolvedPath),
+ content: file,
}
- return module.default ?? module
},
})
}
@@ -62,3 +84,58 @@ async function importModule(path: string): Promise {
throw error
}
}
+
+const cssResolver = EnhancedResolve.ResolverFactory.createResolver({
+ fileSystem: new EnhancedResolve.CachedInputFileSystem(fs, 4000),
+ useSyncFileSystemCalls: true,
+ extensions: ['.css'],
+ mainFields: ['style'],
+ conditionNames: ['style'],
+})
+async function resolveCssId(id: string, base: string): Promise {
+ if (typeof globalThis.__tw_resolve === 'function') {
+ let resolved = globalThis.__tw_resolve(id, base)
+ if (resolved) {
+ return Promise.resolve(resolved)
+ }
+ }
+
+ // CSS imports that do not have a dir prefix are considered relative. Since
+ // the resolver does not account for this, we need to do a first pass with an
+ // assumed relative import by prefixing `./${path}`. We don't have to do this
+ // when the path starts with a `.` or when the path has no extension (at which
+ // case it's likely an npm package and not a relative stylesheet).
+ let skipRelativeCheck = extname(id) === '' || id.startsWith('.')
+
+ if (!skipRelativeCheck) {
+ try {
+ let dotResolved = await runResolver(cssResolver, `./${id}`, base)
+ if (!dotResolved) throw new Error()
+ return dotResolved
+ } catch {}
+ }
+
+ return runResolver(cssResolver, id, base)
+}
+
+const jsResolver = EnhancedResolve.ResolverFactory.createResolver({
+ fileSystem: new EnhancedResolve.CachedInputFileSystem(fs, 4000),
+ useSyncFileSystemCalls: true,
+})
+
+function resolveJsId(id: string, base: string): Promise {
+ return runResolver(jsResolver, id, base)
+}
+
+function runResolver(
+ resolver: EnhancedResolve.Resolver,
+ id: string,
+ base: string,
+): Promise {
+ return new Promise((resolve, reject) =>
+ resolver.resolve({}, base, id, {}, (err, result) => {
+ if (err) return reject(err)
+ resolve(result)
+ }),
+ )
+}
diff --git a/packages/@tailwindcss-node/src/index.cts b/packages/@tailwindcss-node/src/index.cts
index a143865efb84..ee0de7ff5503 100644
--- a/packages/@tailwindcss-node/src/index.cts
+++ b/packages/@tailwindcss-node/src/index.cts
@@ -1,6 +1,7 @@
import * as Module from 'node:module'
import { pathToFileURL } from 'node:url'
export * from './compile'
+export * from './normalize-path'
// In Bun, ESM modules will also populate `require.cache`, so the module hook is
// not necessary.
diff --git a/packages/@tailwindcss-node/src/index.ts b/packages/@tailwindcss-node/src/index.ts
index 85b292ed022f..f42c4ff4e40d 100644
--- a/packages/@tailwindcss-node/src/index.ts
+++ b/packages/@tailwindcss-node/src/index.ts
@@ -1,6 +1,7 @@
import * as Module from 'node:module'
import { pathToFileURL } from 'node:url'
export * from './compile'
+export * from './normalize-path'
// In Bun, ESM modules will also populate `require.cache`, so the module hook is
// not necessary.
diff --git a/packages/internal-postcss-fix-relative-paths/src/normalize-path.ts b/packages/@tailwindcss-node/src/normalize-path.ts
similarity index 100%
rename from packages/internal-postcss-fix-relative-paths/src/normalize-path.ts
rename to packages/@tailwindcss-node/src/normalize-path.ts
diff --git a/packages/@tailwindcss-postcss/package.json b/packages/@tailwindcss-postcss/package.json
index 94d561f597bc..11da052a9e60 100644
--- a/packages/@tailwindcss-postcss/package.json
+++ b/packages/@tailwindcss-postcss/package.json
@@ -33,14 +33,13 @@
"@tailwindcss/node": "workspace:^",
"@tailwindcss/oxide": "workspace:^",
"lightningcss": "catalog:",
- "postcss-import": "^16.1.0",
"tailwindcss": "workspace:^"
},
"devDependencies": {
"@types/node": "catalog:",
- "@types/postcss-import": "^14.0.3",
"postcss": "^8.4.41",
- "internal-example-plugin": "workspace:*",
- "internal-postcss-fix-relative-paths": "workspace:^"
+ "postcss-import": "^16.1.0",
+ "@types/postcss-import": "14.0.3",
+ "internal-example-plugin": "workspace:*"
}
}
diff --git a/packages/@tailwindcss-postcss/src/index.ts b/packages/@tailwindcss-postcss/src/index.ts
index 4f9eccbb56ef..881445a8016e 100644
--- a/packages/@tailwindcss-postcss/src/index.ts
+++ b/packages/@tailwindcss-postcss/src/index.ts
@@ -2,11 +2,10 @@ import { compile } from '@tailwindcss/node'
import { clearRequireCache } from '@tailwindcss/node/require-cache'
import { Scanner } from '@tailwindcss/oxide'
import fs from 'fs'
-import fixRelativePathsPlugin from 'internal-postcss-fix-relative-paths'
import { Features, transform } from 'lightningcss'
import path from 'path'
-import postcss, { AtRule, type AcceptedPlugin, type PluginCreator } from 'postcss'
-import postcssImport from 'postcss-import'
+import postcss, { type AcceptedPlugin, type PluginCreator } from 'postcss'
+import fixRelativePathsPlugin from './postcss-fix-relative-paths'
/**
* A Map that can generate default values for keys that don't exist.
@@ -51,30 +50,16 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
}
})
- let hasApply: boolean, hasTailwind: boolean
-
return {
postcssPlugin: '@tailwindcss/postcss',
plugins: [
- // We need to run `postcss-import` first to handle `@import` rules.
- postcssImport(),
+ // We need to handle the case where `postcss-import` might have run before the Tailwind CSS
+ // plugin is run. In this case, we need to manually fix relative paths before processing it
+ // in core.
fixRelativePathsPlugin(),
{
postcssPlugin: 'tailwindcss',
- Once() {
- // Reset some state between builds
- hasApply = false
- hasTailwind = false
- },
- AtRule(rule: AtRule) {
- if (rule.name === 'apply') {
- hasApply = true
- } else if (rule.name === 'tailwind') {
- hasApply = true
- hasTailwind = true
- }
- },
async OnceExit(root, { result }) {
let inputFile = result.opts.from ?? ''
let context = cache.get(inputFile)
@@ -133,23 +118,14 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
}
}
- // Do nothing if neither `@tailwind` nor `@apply` is used
- if (!hasTailwind && !hasApply) return
-
let css = ''
// Look for candidates used to generate the CSS
let scanner = new Scanner({
detectSources: { base },
- sources: context.compiler.globs.map(({ origin, pattern }) => ({
- // Ensure the glob is relative to the input CSS file or the config
- // file where it is specified.
- base: origin ? path.dirname(path.resolve(inputBasePath, origin)) : inputBasePath,
- pattern,
- })),
+ sources: context.compiler.globs,
})
- //
let candidates = scanner.scan()
// Add all found files as direct dependencies
@@ -177,10 +153,8 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
if (rebuildStrategy === 'full') {
context.compiler = await createCompiler()
- css = context.compiler.build(hasTailwind ? candidates : [])
- } else if (rebuildStrategy === 'incremental') {
- css = context.compiler.build!(candidates)
}
+ css = context.compiler.build(candidates)
// Replace CSS
if (css !== context.css && optimize) {
diff --git a/packages/internal-postcss-fix-relative-paths/src/fixtures/example-project/src/index.css b/packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/fixtures/example-project/src/index.css
similarity index 100%
rename from packages/internal-postcss-fix-relative-paths/src/fixtures/example-project/src/index.css
rename to packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/fixtures/example-project/src/index.css
diff --git a/packages/internal-postcss-fix-relative-paths/src/fixtures/example-project/src/invalid.css b/packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/fixtures/example-project/src/invalid.css
similarity index 100%
rename from packages/internal-postcss-fix-relative-paths/src/fixtures/example-project/src/invalid.css
rename to packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/fixtures/example-project/src/invalid.css
diff --git a/packages/internal-postcss-fix-relative-paths/src/fixtures/external-import/src/index.css b/packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/fixtures/external-import/src/index.css
similarity index 100%
rename from packages/internal-postcss-fix-relative-paths/src/fixtures/external-import/src/index.css
rename to packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/fixtures/external-import/src/index.css
diff --git a/packages/internal-postcss-fix-relative-paths/src/fixtures/external-import/src/invalid.css b/packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/fixtures/external-import/src/invalid.css
similarity index 100%
rename from packages/internal-postcss-fix-relative-paths/src/fixtures/external-import/src/invalid.css
rename to packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/fixtures/external-import/src/invalid.css
diff --git a/packages/internal-postcss-fix-relative-paths/src/fixtures/external-import/src/plugins-in-root.css b/packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/fixtures/external-import/src/plugins-in-root.css
similarity index 100%
rename from packages/internal-postcss-fix-relative-paths/src/fixtures/external-import/src/plugins-in-root.css
rename to packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/fixtures/external-import/src/plugins-in-root.css
diff --git a/packages/internal-postcss-fix-relative-paths/src/fixtures/external-import/src/plugins-in-sibling.css b/packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/fixtures/external-import/src/plugins-in-sibling.css
similarity index 100%
rename from packages/internal-postcss-fix-relative-paths/src/fixtures/external-import/src/plugins-in-sibling.css
rename to packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/fixtures/external-import/src/plugins-in-sibling.css
diff --git a/packages/internal-postcss-fix-relative-paths/src/index.test.ts b/packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/index.test.ts
similarity index 100%
rename from packages/internal-postcss-fix-relative-paths/src/index.test.ts
rename to packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/index.test.ts
diff --git a/packages/internal-postcss-fix-relative-paths/src/index.ts b/packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/index.ts
similarity index 96%
rename from packages/internal-postcss-fix-relative-paths/src/index.ts
rename to packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/index.ts
index 7a0f92da714a..2b88014a3e85 100644
--- a/packages/internal-postcss-fix-relative-paths/src/index.ts
+++ b/packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/index.ts
@@ -1,12 +1,10 @@
+import { normalizePath } from '@tailwindcss/node'
import path from 'node:path'
import type { AtRule, Plugin } from 'postcss'
-import { normalizePath } from './normalize-path'
const SINGLE_QUOTE = "'"
const DOUBLE_QUOTE = '"'
-export { normalizePath }
-
export default function fixRelativePathsPlugin(): Plugin {
// Retain a list of touched at-rules to avoid infinite loops
let touched: WeakSet = new WeakSet()
diff --git a/packages/@tailwindcss-postcss/tsup.config.ts b/packages/@tailwindcss-postcss/tsup.config.ts
index 76e4fc03b0a0..684c072ac854 100644
--- a/packages/@tailwindcss-postcss/tsup.config.ts
+++ b/packages/@tailwindcss-postcss/tsup.config.ts
@@ -7,7 +7,6 @@ export default defineConfig([
cjsInterop: true,
dts: true,
entry: ['src/index.ts'],
- noExternal: ['internal-postcss-fix-relative-paths'],
},
{
format: ['cjs'],
@@ -15,6 +14,5 @@ export default defineConfig([
cjsInterop: true,
dts: true,
entry: ['src/index.cts'],
- noExternal: ['internal-postcss-fix-relative-paths'],
},
])
diff --git a/packages/@tailwindcss-standalone/src/index.ts b/packages/@tailwindcss-standalone/src/index.ts
index 5dfefaf82928..ae90e2b6efc7 100644
--- a/packages/@tailwindcss-standalone/src/index.ts
+++ b/packages/@tailwindcss-standalone/src/index.ts
@@ -1,3 +1,4 @@
+import fs from 'node:fs'
import { createRequire } from 'node:module'
import packageJson from 'tailwindcss/package.json'
@@ -42,5 +43,14 @@ globalThis.__tw_resolve = (id, baseDir) => {
}
}
globalThis.__tw_version = packageJson.version
+globalThis.__tw_readFile = async (path, encoding) => {
+ // When reading a file from the `$bunfs`, we need to use the synchronous
+ // `readFileSync` API
+ let isEmbeddedFileBase = path.includes('/$bunfs/root') || path.includes(':/~BUN/root')
+ if (!isEmbeddedFileBase) {
+ return
+ }
+ return fs.readFileSync(path, encoding)
+}
await import('../../@tailwindcss-cli/src/index.ts')
diff --git a/packages/@tailwindcss-standalone/src/types.d.ts b/packages/@tailwindcss-standalone/src/types.d.ts
index dfdac2715c0c..e13e4e4275d2 100644
--- a/packages/@tailwindcss-standalone/src/types.d.ts
+++ b/packages/@tailwindcss-standalone/src/types.d.ts
@@ -5,3 +5,6 @@ declare module '*.css' {
declare var __tw_version: string | undefined
declare var __tw_resolve: undefined | ((id: string, base?: string) => string | false)
+declare var __tw_readFile:
+ | undefined
+ | ((path: string, encoding: BufferEncoding) => Promise)
diff --git a/packages/@tailwindcss-vite/package.json b/packages/@tailwindcss-vite/package.json
index a74b4c3c81d7..20a7de978cea 100644
--- a/packages/@tailwindcss-vite/package.json
+++ b/packages/@tailwindcss-vite/package.json
@@ -31,14 +31,10 @@
"@tailwindcss/node": "workspace:^",
"@tailwindcss/oxide": "workspace:^",
"lightningcss": "catalog:",
- "postcss": "^8.4.41",
- "postcss-import": "^16.1.0",
"tailwindcss": "workspace:^"
},
"devDependencies": {
"@types/node": "catalog:",
- "@types/postcss-import": "^14.0.3",
- "internal-postcss-fix-relative-paths": "workspace:^",
"vite": "catalog:"
},
"peerDependencies": {
diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts
index 8a1c60932e00..53f2aecffb6b 100644
--- a/packages/@tailwindcss-vite/src/index.ts
+++ b/packages/@tailwindcss-vite/src/index.ts
@@ -1,13 +1,9 @@
-import { compile } from '@tailwindcss/node'
+import { compile, normalizePath } from '@tailwindcss/node'
import { clearRequireCache } from '@tailwindcss/node/require-cache'
import { Scanner } from '@tailwindcss/oxide'
-import fixRelativePathsPlugin, { normalizePath } from 'internal-postcss-fix-relative-paths'
import { Features, transform } from 'lightningcss'
-import fs from 'node:fs/promises'
import path from 'path'
-import postcss from 'postcss'
-import postcssImport from 'postcss-import'
import type { Plugin, ResolvedConfig, Rollup, Update, ViteDevServer } from 'vite'
export default function tailwindcss(): Plugin[] {
@@ -269,18 +265,6 @@ function isPotentialCssRootFile(id: string) {
return isCssFile
}
-function isCssRootFile(content: string) {
- return (
- content.includes('@tailwind') ||
- content.includes('@config') ||
- content.includes('@plugin') ||
- content.includes('@apply') ||
- content.includes('@theme') ||
- content.includes('@variant') ||
- content.includes('@utility')
- )
-}
-
function optimizeCss(
input: string,
{ file = 'input.css', minify = false }: { file?: string; minify?: boolean } = {},
@@ -378,30 +362,7 @@ class Root {
clearRequireCache(Array.from(this.dependencies))
this.dependencies = new Set([idToPath(inputPath)])
- let postcssCompiled = await postcss([
- postcssImport({
- load: (path) => {
- this.dependencies.add(path)
- addWatchFile(path)
- return fs.readFile(path, 'utf8')
- },
- }),
- fixRelativePathsPlugin(),
- ]).process(content, {
- from: inputPath,
- to: inputPath,
- })
- let css = postcssCompiled.css
-
- // This is done inside the Root#generate() method so that we can later use
- // information from the Tailwind compiler to determine if the file is a
- // CSS root (necessary because we will probably inline the `@import`
- // resolution at some point).
- if (!isCssRootFile(css)) {
- return false
- }
-
- this.compiler = await compile(css, {
+ this.compiler = await compile(content, {
base: inputBase,
onDependency: (path) => {
addWatchFile(path)
@@ -410,12 +371,7 @@ class Root {
})
this.scanner = new Scanner({
- sources: this.compiler.globs.map(({ origin, pattern }) => ({
- // Ensure the glob is relative to the input CSS file or the config
- // file where it is specified.
- base: origin ? path.dirname(path.resolve(inputBase, origin)) : inputBase,
- pattern,
- })),
+ sources: this.compiler.globs,
})
}
diff --git a/packages/@tailwindcss-vite/tsup.config.ts b/packages/@tailwindcss-vite/tsup.config.ts
index eaf99e82abae..85bf3149d3f9 100644
--- a/packages/@tailwindcss-vite/tsup.config.ts
+++ b/packages/@tailwindcss-vite/tsup.config.ts
@@ -6,5 +6,4 @@ export default defineConfig({
minify: true,
dts: true,
entry: ['src/index.ts'],
- noExternal: ['internal-postcss-fix-relative-paths'],
})
diff --git a/packages/internal-postcss-fix-relative-paths/package.json b/packages/internal-postcss-fix-relative-paths/package.json
deleted file mode 100644
index 893f7466d1d3..000000000000
--- a/packages/internal-postcss-fix-relative-paths/package.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "name": "internal-postcss-fix-relative-paths",
- "version": "0.0.0",
- "private": true,
- "scripts": {
- "lint": "tsc --noEmit",
- "build": "tsup-node ./src/index.ts --format cjs,esm --dts --cjsInterop --splitting --minify --clean",
- "dev": "pnpm run build -- --watch"
- },
- "files": [
- "dist/"
- ],
- "exports": {
- ".": {
- "types": "./dist/index.d.ts",
- "import": "./dist/index.mjs",
- "require": "./dist/index.js"
- }
- },
- "dependencies": {},
- "devDependencies": {
- "@types/node": "catalog:",
- "@types/postcss-import": "^14.0.3",
- "postcss": "8.4.41",
- "postcss-import": "^16.1.0"
- }
-}
diff --git a/packages/internal-postcss-fix-relative-paths/tsconfig.json b/packages/internal-postcss-fix-relative-paths/tsconfig.json
deleted file mode 100644
index 6ae022f65bf0..000000000000
--- a/packages/internal-postcss-fix-relative-paths/tsconfig.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "../tsconfig.base.json",
-}
diff --git a/packages/tailwindcss/package.json b/packages/tailwindcss/package.json
index 7173e7201608..10e45bdb3aa1 100644
--- a/packages/tailwindcss/package.json
+++ b/packages/tailwindcss/package.json
@@ -89,6 +89,7 @@
"devDependencies": {
"@tailwindcss/oxide": "workspace:^",
"@types/node": "catalog:",
- "lightningcss": "catalog:"
+ "lightningcss": "catalog:",
+ "dedent": "1.5.3"
}
}
diff --git a/packages/tailwindcss/src/ast.test.ts b/packages/tailwindcss/src/ast.test.ts
index da0b19204375..21915cba4a4c 100644
--- a/packages/tailwindcss/src/ast.test.ts
+++ b/packages/tailwindcss/src/ast.test.ts
@@ -1,5 +1,5 @@
import { expect, it } from 'vitest'
-import { toCss } from './ast'
+import { context, decl, rule, toCss, walk } from './ast'
import * as CSS from './css-parser'
it('should pretty print an AST', () => {
@@ -13,3 +13,54 @@ it('should pretty print an AST', () => {
"
`)
})
+
+it('allows the placement of context nodes', () => {
+ const ast = [
+ rule('.foo', [decl('color', 'red')]),
+ context({ context: 'a' }, [
+ rule('.bar', [
+ decl('color', 'blue'),
+ context({ context: 'b' }, [
+ //
+ rule('.baz', [decl('color', 'green')]),
+ ]),
+ ]),
+ ]),
+ ]
+
+ let redContext
+ let blueContext
+ let greenContext
+
+ walk(ast, (node, { context }) => {
+ if (node.kind !== 'declaration') return
+ switch (node.value) {
+ case 'red':
+ redContext = context
+ break
+ case 'blue':
+ blueContext = context
+ break
+ case 'green':
+ greenContext = context
+ break
+ }
+ })
+
+ expect(redContext).toEqual({})
+ expect(blueContext).toEqual({ context: 'a' })
+ expect(greenContext).toEqual({ context: 'b' })
+
+ expect(toCss(ast)).toMatchInlineSnapshot(`
+ ".foo {
+ color: red;
+ }
+ .bar {
+ color: blue;
+ .baz {
+ color: green;
+ }
+ }
+ "
+ `)
+})
diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts
index fa5dc6f1b277..afc1888c2659 100644
--- a/packages/tailwindcss/src/ast.ts
+++ b/packages/tailwindcss/src/ast.ts
@@ -16,7 +16,13 @@ export type Comment = {
value: string
}
-export type AstNode = Rule | Declaration | Comment
+export type Context = {
+ kind: 'context'
+ context: Record
+ nodes: AstNode[]
+}
+
+export type AstNode = Rule | Declaration | Comment | Context
export function rule(selector: string, nodes: AstNode[]): Rule {
return {
@@ -42,6 +48,14 @@ export function comment(value: string): Comment {
}
}
+export function context(context: Record, nodes: AstNode[]): Context {
+ return {
+ kind: 'context',
+ context,
+ nodes,
+ }
+}
+
export enum WalkAction {
/** Continue walking, which is the default */
Continue,
@@ -60,12 +74,23 @@ export function walk(
utils: {
parent: AstNode | null
replaceWith(newNode: AstNode | AstNode[]): void
+ context: Record
},
) => void | WalkAction,
parent: AstNode | null = null,
+ context: Record = {},
) {
for (let i = 0; i < ast.length; i++) {
let node = ast[i]
+
+ // We want context nodes to be transparent in walks. This means that
+ // 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, parent, { ...context, ...node.context })
+ continue
+ }
+
let status =
visit(node, {
parent,
@@ -76,6 +101,7 @@ export function walk(
// will process this position (containing the replaced node) again.
i--
},
+ context,
}) ?? WalkAction.Continue
// Stop the walk entirely
@@ -85,7 +111,7 @@ export function walk(
if (status === WalkAction.Skip) continue
if (node.kind === 'rule') {
- walk(node.nodes, visit, node)
+ walk(node.nodes, visit, node, context)
}
}
}
@@ -171,6 +197,13 @@ export function toCss(ast: AstNode[]) {
css += `${indent}/*${node.value}*/\n`
}
+ // Context Node
+ else if (node.kind === 'context') {
+ for (let child of node.nodes) {
+ css += stringify(child, depth)
+ }
+ }
+
// Declaration
else if (node.property !== '--tw-sort' && node.value !== undefined && node.value !== null) {
css += `${indent}${node.property}: ${node.value}${node.important ? '!important' : ''};\n`
diff --git a/packages/tailwindcss/src/at-import.test.ts b/packages/tailwindcss/src/at-import.test.ts
new file mode 100644
index 000000000000..bbd5e6863f26
--- /dev/null
+++ b/packages/tailwindcss/src/at-import.test.ts
@@ -0,0 +1,572 @@
+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'
+
+let css = String.raw
+
+async function run(
+ css: string,
+ {
+ loadStylesheet = () => Promise.reject(new Error('Unexpected stylesheet')),
+ loadModule = () => Promise.reject(new Error('Unexpected module')),
+ candidates = [],
+ optimize = true,
+ }: {
+ loadStylesheet?: (id: string, base: string) => Promise<{ content: string; base: string }>
+ loadModule?: (
+ id: string,
+ base: string,
+ resourceHint: 'plugin' | 'config',
+ ) => Promise<{ module: Config | Plugin; base: string }>
+ candidates?: string[]
+ optimize?: boolean
+ },
+) {
+ let compiler = await compile(css, { base: '/root', loadStylesheet, loadModule })
+ let result = compiler.build(candidates)
+ return optimize ? optimizeCss(result) : result
+}
+
+test('can resolve relative @imports', async () => {
+ let loadStylesheet = async (id: string, base: string) => {
+ expect(base).toBe('/root')
+ expect(id).toBe('./foo/bar.css')
+ return {
+ content: css`
+ .foo {
+ color: red;
+ }
+ `,
+ base: '/root/foo',
+ }
+ }
+
+ await expect(
+ run(
+ css`
+ @import './foo/bar.css';
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ ".foo {
+ color: red;
+ }
+ "
+ `)
+})
+
+test('can recursively resolve relative @imports', async () => {
+ let loadStylesheet = async (id: string, base: string) => {
+ if (base === '/root' && id === './foo/bar.css') {
+ return {
+ content: css`
+ @import './bar/baz.css';
+ `,
+ base: '/root/foo',
+ }
+ } else if (base === '/root/foo' && id === './bar/baz.css') {
+ return {
+ content: css`
+ .baz {
+ color: blue;
+ }
+ `,
+ base: '/root/foo/bar',
+ }
+ }
+
+ throw new Error(`Unexpected import: ${id}`)
+ }
+
+ await expect(
+ run(
+ css`
+ @import './foo/bar.css';
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ ".baz {
+ color: #00f;
+ }
+ "
+ `)
+})
+
+let exampleCSS = css`
+ a {
+ color: red;
+ }
+`
+let loadStylesheet = async (id: string) => {
+ if (!id.endsWith('example.css')) throw new Error('Unexpected import: ' + id)
+ return {
+ content: exampleCSS,
+ base: '/root',
+ }
+}
+
+test('extracts path from @import nodes', async () => {
+ await expect(
+ run(
+ css`
+ @import 'example.css';
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "a {
+ color: red;
+ }
+ "
+ `)
+
+ await expect(
+ run(
+ css`
+ @import './example.css';
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "a {
+ color: red;
+ }
+ "
+ `)
+
+ await expect(
+ run(
+ css`
+ @import '/example.css';
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "a {
+ color: red;
+ }
+ "
+ `)
+})
+
+test('url() imports are passed-through', async () => {
+ await expect(
+ run(
+ css`
+ @import url('example.css');
+ `,
+ { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@import url('example.css');
+ "
+ `)
+
+ await expect(
+ run(
+ css`
+ @import url('./example.css');
+ `,
+ { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@import url('./example.css');
+ "
+ `)
+
+ await expect(
+ run(
+ css`
+ @import url('/example.css');
+ `,
+ { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@import url('/example.css');
+ "
+ `)
+
+ await expect(
+ run(
+ css`
+ @import url(example.css);
+ `,
+ { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@import url(example.css);
+ "
+ `)
+
+ await expect(
+ run(
+ css`
+ @import url(./example.css);
+ `,
+ { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@import url(./example.css);
+ "
+ `)
+
+ await expect(
+ run(
+ css`
+ @import url(/example.css);
+ `,
+ { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@import url(/example.css);
+ "
+ `)
+})
+
+test('handles case-insensitive @import directive', async () => {
+ await expect(
+ run(
+ css`
+ @import 'example.css';
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "a {
+ color: red;
+ }
+ "
+ `)
+})
+
+test('@media', async () => {
+ await expect(
+ run(
+ css`
+ @import 'example.css' print;
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@media print {
+ a {
+ color: red;
+ }
+ }
+ "
+ `)
+
+ await expect(
+ run(
+ css`
+ @import 'example.css' print, screen;
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@media print, screen {
+ a {
+ color: red;
+ }
+ }
+ "
+ `)
+
+ await expect(
+ run(
+ css`
+ @import 'example.css' screen and (orientation: landscape);
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@media screen and (orientation: landscape) {
+ a {
+ color: red;
+ }
+ }
+ "
+ `)
+
+ await expect(
+ run(
+ css`
+ @import 'example.css' foo(bar);
+ `,
+ { loadStylesheet, optimize: false },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@media foo(bar) {
+ a {
+ color: red;
+ }
+ }
+ "
+ `)
+})
+
+test('@supports', async () => {
+ await expect(
+ run(
+ css`
+ @import 'example.css' supports(display: grid);
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@supports (display: grid) {
+ a {
+ color: red;
+ }
+ }
+ "
+ `)
+
+ await expect(
+ run(
+ css`
+ @import 'example.css' supports(display: grid) screen and (max-width: 400px);
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@supports (display: grid) {
+ @media screen and (width <= 400px) {
+ a {
+ color: red;
+ }
+ }
+ }
+ "
+ `)
+
+ await expect(
+ run(
+ css`
+ @import 'example.css' supports((not (display: grid)) and (display: flex)) screen and
+ (max-width: 400px);
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@supports (not (display: grid)) and (display: flex) {
+ @media screen and (width <= 400px) {
+ a {
+ color: red;
+ }
+ }
+ }
+ "
+ `)
+
+ await expect(
+ run(
+ // prettier-ignore
+ css`
+ @import 'example.css'
+ supports((selector(h2 > p)) and (font-tech(color-COLRv1)));
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@supports selector(h2 > p) and font-tech(color-COLRv1) {
+ a {
+ color: red;
+ }
+ }
+ "
+ `)
+})
+
+test('@layer', async () => {
+ await expect(
+ run(
+ css`
+ @import 'example.css' layer(utilities);
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@layer utilities {
+ a {
+ color: red;
+ }
+ }
+ "
+ `)
+
+ await expect(
+ run(
+ css`
+ @import 'example.css' layer();
+ `,
+ { loadStylesheet },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ "@layer {
+ a {
+ color: red;
+ }
+ }
+ "
+ `)
+})
+
+test('supports theme(reference) imports', async () => {
+ expect(
+ run(
+ css`
+ @tailwind utilities;
+ @import 'example.css' theme(reference);
+ `,
+ {
+ loadStylesheet: () =>
+ Promise.resolve({
+ content: css`
+ @theme {
+ --color-red-500: red;
+ }
+ `,
+ base: '',
+ }),
+ candidates: ['text-red-500'],
+ },
+ ),
+ ).resolves.toMatchInlineSnapshot(`
+ ".text-red-500 {
+ color: var(--color-red-500, red);
+ }
+ "
+ `)
+})
+
+test('updates the base when loading modules inside nested files', async () => {
+ let loadStylesheet = () =>
+ Promise.resolve({
+ content: css`
+ @config './nested-config.js';
+ @plugin './nested-plugin.js';
+ `,
+ base: '/root/foo',
+ })
+ let loadModule = vi.fn().mockResolvedValue({ base: '', module: () => {} })
+
+ expect(
+ (
+ await run(
+ css`
+ @import './foo/bar.css';
+ @config './root-config.js';
+ @plugin './root-plugin.js';
+ `,
+ { loadStylesheet, loadModule },
+ )
+ ).trim(),
+ ).toBe('')
+
+ expect(loadModule).toHaveBeenNthCalledWith(1, './nested-config.js', '/root/foo', 'config')
+ expect(loadModule).toHaveBeenNthCalledWith(2, './root-config.js', '/root', 'config')
+ expect(loadModule).toHaveBeenNthCalledWith(3, './nested-plugin.js', '/root/foo', 'plugin')
+ expect(loadModule).toHaveBeenNthCalledWith(4, './root-plugin.js', '/root', 'plugin')
+})
+
+test('emits the right base for @source directives inside nested files', async () => {
+ let loadStylesheet = () =>
+ Promise.resolve({
+ content: css`
+ @source './nested/**/*.css';
+ `,
+ base: '/root/foo',
+ })
+
+ let compiler = await compile(
+ css`
+ @import './foo/bar.css';
+ @source './root/**/*.css';
+ `,
+ { base: '/root', loadStylesheet },
+ )
+
+ expect(compiler.globs).toEqual([
+ { pattern: './nested/**/*.css', base: '/root/foo' },
+ { pattern: './root/**/*.css', base: '/root' },
+ ])
+})
+
+test('emits the right base for @source found inside JS configs and plugins from nested imports', async () => {
+ let loadStylesheet = () =>
+ Promise.resolve({
+ content: css`
+ @config './nested-config.js';
+ @plugin './nested-plugin.js';
+ `,
+ base: '/root/foo',
+ })
+ let loadModule = vi.fn().mockImplementation((id: string) => {
+ let base = id.includes('nested') ? '/root/foo' : '/root'
+ if (id.includes('config')) {
+ let glob = id.includes('nested') ? './nested-config/*.html' : './root-config/*.html'
+ let pluginGlob = id.includes('nested')
+ ? './nested-config-plugin/*.html'
+ : './root-config-plugin/*.html'
+ return {
+ module: {
+ content: [glob],
+ plugins: [plugin(() => {}, { content: [pluginGlob] })],
+ } satisfies Config,
+ base: base + '-config',
+ }
+ } else {
+ let glob = id.includes('nested') ? './nested-plugin/*.html' : './root-plugin/*.html'
+ return {
+ module: plugin(() => {}, { content: [glob] }),
+ base: base + '-plugin',
+ }
+ }
+ })
+
+ let compiler = await compile(
+ css`
+ @import './foo/bar.css';
+ @config './root-config.js';
+ @plugin './root-plugin.js';
+ `,
+ { base: '/root', loadStylesheet, loadModule },
+ )
+
+ expect(compiler.globs).toEqual([
+ { pattern: './nested-plugin/*.html', base: '/root/foo-plugin' },
+ { pattern: './root-plugin/*.html', base: '/root-plugin' },
+
+ { pattern: './nested-config-plugin/*.html', base: '/root/foo-config' },
+ { pattern: './nested-config/*.html', base: '/root/foo-config' },
+
+ { pattern: './root-config-plugin/*.html', base: '/root-config' },
+ { pattern: './root-config/*.html', base: '/root-config' },
+ ])
+})
+
+test('it crashes when inside a cycle', async () => {
+ let loadStylesheet = () =>
+ Promise.resolve({
+ content: css`
+ @import 'foo.css';
+ `,
+ base: '/root',
+ })
+
+ expect(
+ run(
+ css`
+ @import 'foo.css';
+ `,
+ { loadStylesheet },
+ ),
+ ).rejects.toMatchInlineSnapshot(
+ `[Error: Exceeded maximum recursion depth while resolving \`foo.css\` in \`/root\`)]`,
+ )
+})
diff --git a/packages/tailwindcss/src/at-import.ts b/packages/tailwindcss/src/at-import.ts
new file mode 100644
index 000000000000..a06000826b05
--- /dev/null
+++ b/packages/tailwindcss/src/at-import.ts
@@ -0,0 +1,147 @@
+import { context, rule, walk, WalkAction, type AstNode } from './ast'
+import * as CSS from './css-parser'
+import * as ValueParser from './value-parser'
+
+type LoadStylesheet = (id: string, basedir: string) => Promise<{ base: string; content: string }>
+
+export async function substituteAtImports(
+ ast: AstNode[],
+ base: string,
+ loadStylesheet: LoadStylesheet,
+ recurseCount = 0,
+) {
+ let promises: Promise[] = []
+
+ walk(ast, (node, { replaceWith }) => {
+ if (
+ node.kind === 'rule' &&
+ node.selector[0] === '@' &&
+ node.selector.toLowerCase().startsWith('@import ')
+ ) {
+ try {
+ let { uri, layer, media, supports } = parseImportParams(
+ ValueParser.parse(node.selector.slice(8)),
+ )
+
+ // Skip importing data or remote URIs
+ if (uri.startsWith('data:')) return
+ if (uri.startsWith('http://') || uri.startsWith('https://')) return
+
+ let contextNode = context({}, [])
+
+ promises.push(
+ (async () => {
+ // Since we do not have fully resolved paths in core, we can't reliably detect circular
+ // imports. Instead, we try to limit the recursion depth to a number that is too large
+ // to be reached in practice.
+ if (recurseCount > 100) {
+ throw new Error(
+ `Exceeded maximum recursion depth while resolving \`${uri}\` in \`${base}\`)`,
+ )
+ }
+
+ const loaded = await loadStylesheet(uri, base)
+ let ast = CSS.parse(loaded.content)
+ await substituteAtImports(ast, loaded.base, loadStylesheet, recurseCount + 1)
+
+ contextNode.nodes = buildImportNodes(ast, layer, media, supports)
+ contextNode.context.base = loaded.base
+ })(),
+ )
+
+ replaceWith(contextNode)
+ // The resolved Stylesheets already have their transitive @imports
+ // resolved, so we can skip walking them.
+ return WalkAction.Skip
+ } catch (e: any) {
+ // When an error occurs while parsing the `@import` statement, we skip
+ // the import.
+ }
+ }
+ })
+
+ await Promise.all(promises)
+}
+
+// Modified and inlined version of `parse-statements` from
+// `postcss-import`
+// Copyright (c) 2014 Maxime Thirouin, Jason Campbell & Kevin MÃ¥rtensson
+// Released under the MIT License.
+function parseImportParams(params: ValueParser.ValueAstNode[]) {
+ let uri
+ let layer: string | null = null
+ let media: string | null = null
+ let supports: string | null = null
+
+ for (let i = 0; i < params.length; i++) {
+ const node = params[i]
+
+ if (node.kind === 'separator') continue
+
+ if (node.kind === 'word' && !uri) {
+ if (!node.value) throw new Error(`Unable to find uri`)
+ if (node.value[0] !== '"' && node.value[0] !== "'") throw new Error('Unable to find uri')
+
+ uri = node.value.slice(1, -1)
+ continue
+ }
+
+ if (node.kind === 'function' && node.value.toLowerCase() === 'url') {
+ throw new Error('url functions are not supported')
+ }
+
+ if (!uri) throw new Error('Unable to find uri')
+
+ if (
+ (node.kind === 'word' || node.kind === 'function') &&
+ node.value.toLowerCase() === 'layer'
+ ) {
+ if (layer) throw new Error('Multiple layers')
+ if (supports) throw new Error('layers must be defined before support conditions')
+
+ if ('nodes' in node) {
+ layer = ValueParser.toCss(node.nodes)
+ } else {
+ layer = ''
+ }
+
+ continue
+ }
+
+ if (node.kind === 'function' && node.value.toLowerCase() === 'supports') {
+ if (supports) throw new Error('Multiple support conditions')
+ supports = ValueParser.toCss(node.nodes)
+ continue
+ }
+
+ media = ValueParser.toCss(params.slice(i))
+ break
+ }
+
+ if (!uri) throw new Error('Unable to find uri')
+
+ return { uri, layer, media, supports }
+}
+
+function buildImportNodes(
+ importedAst: AstNode[],
+ layer: string | null,
+ media: string | null,
+ supports: string | null,
+): AstNode[] {
+ let root = importedAst
+
+ if (layer !== null) {
+ root = [rule('@layer ' + layer, root)]
+ }
+
+ if (media !== null) {
+ root = [rule('@media ' + media, root)]
+ }
+
+ if (supports !== null) {
+ root = [rule(`@supports ${supports[0] === '(' ? supports : `(${supports})`}`, root)]
+ }
+
+ return root
+}
diff --git a/packages/tailwindcss/src/compat/apply-compat-hooks.ts b/packages/tailwindcss/src/compat/apply-compat-hooks.ts
index 686b37dc8f6b..4290c136ab5e 100644
--- a/packages/tailwindcss/src/compat/apply-compat-hooks.ts
+++ b/packages/tailwindcss/src/compat/apply-compat-hooks.ts
@@ -15,21 +15,25 @@ import { registerThemeVariantOverrides } from './theme-variants'
export async function applyCompatibilityHooks({
designSystem,
+ base,
ast,
- loadPlugin,
- loadConfig,
+ loadModule,
globs,
}: {
designSystem: DesignSystem
+ base: string
ast: AstNode[]
- loadPlugin: (path: string) => Promise
- loadConfig: (path: string) => Promise
+ loadModule: (
+ path: string,
+ base: string,
+ resourceHint: 'plugin' | 'config',
+ ) => Promise<{ module: any; base: string }>
globs: { origin?: string; pattern: string }[]
}) {
- let pluginPaths: [string, CssPluginOptions | null][] = []
- let configPaths: string[] = []
+ let pluginPaths: [{ id: string; base: string }, CssPluginOptions | null][] = []
+ let configPaths: { id: string; base: string }[] = []
- walk(ast, (node, { parent, replaceWith }) => {
+ walk(ast, (node, { parent, replaceWith, context }) => {
if (node.kind !== 'rule' || node.selector[0] !== '@') return
// Collect paths from `@plugin` at-rules
@@ -86,7 +90,10 @@ export async function applyCompatibilityHooks({
options[decl.property] = parts.length === 1 ? parts[0] : parts
}
- pluginPaths.push([pluginPath, Object.keys(options).length > 0 ? options : null])
+ pluginPaths.push([
+ { id: pluginPath, base: context.base },
+ Object.keys(options).length > 0 ? options : null,
+ ])
replaceWith([])
return
@@ -102,7 +109,7 @@ export async function applyCompatibilityHooks({
throw new Error('`@config` cannot be nested.')
}
- configPaths.push(node.selector.slice(9, -1))
+ configPaths.push({ id: node.selector.slice(9, -1), base: context.base })
replaceWith([])
return
}
@@ -142,38 +149,48 @@ export async function applyCompatibilityHooks({
// any additional backwards compatibility hooks.
if (!pluginPaths.length && !configPaths.length) return
- let configs = await Promise.all(
- configPaths.map(async (configPath) => ({
- path: configPath,
- config: await loadConfig(configPath),
- })),
- )
- let pluginDetails = await Promise.all(
- pluginPaths.map(async ([pluginPath, pluginOptions]) => ({
- path: pluginPath,
- plugin: await loadPlugin(pluginPath),
- options: pluginOptions,
- })),
- )
+ let [configs, pluginDetails] = await Promise.all([
+ Promise.all(
+ configPaths.map(async ({ id, base }) => {
+ let loaded = await loadModule(id, base, 'config')
+ return {
+ path: id,
+ base: loaded.base,
+ config: loaded.module as UserConfig,
+ }
+ }),
+ ),
+ Promise.all(
+ pluginPaths.map(async ([{ id, base }, pluginOptions]) => {
+ let loaded = await loadModule(id, base, 'plugin')
+ return {
+ path: id,
+ base: loaded.base,
+ plugin: loaded.module as Plugin,
+ options: pluginOptions,
+ }
+ }),
+ ),
+ ])
- let plugins = pluginDetails.map((detail) => {
+ let pluginConfigs = pluginDetails.map((detail) => {
if (!detail.options) {
- return detail.plugin
+ return { config: { plugins: [detail.plugin] }, base: detail.base }
}
if ('__isOptionsFunction' in detail.plugin) {
- return detail.plugin(detail.options)
+ return { config: { plugins: [detail.plugin(detail.options)] }, base: detail.base }
}
throw new Error(`The plugin "${detail.path}" does not accept options`)
})
- let userConfig = [{ config: { plugins } }, ...configs]
+ let userConfig = [...pluginConfigs, ...configs]
let resolvedConfig = resolveConfig(designSystem, [
- { config: createCompatConfig(designSystem.theme) },
+ { config: createCompatConfig(designSystem.theme), base },
...userConfig,
- { config: { plugins: [darkModePlugin] } },
+ { config: { plugins: [darkModePlugin] }, base },
])
let resolvedUserConfig = resolveConfig(designSystem, userConfig)
@@ -221,7 +238,7 @@ export async function applyCompatibilityHooks({
)
}
- globs.push({ origin: file.base, pattern: file.pattern })
+ globs.push(file)
}
}
diff --git a/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts b/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts
index af511a25e32f..aad2753b021d 100644
--- a/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts
+++ b/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts
@@ -35,6 +35,7 @@ test('Config values can be merged into the theme', () => {
},
},
},
+ base: '/root',
},
])
applyConfigToTheme(design, resolvedUserConfig)
diff --git a/packages/tailwindcss/src/compat/config.test.ts b/packages/tailwindcss/src/compat/config.test.ts
index acfaebc9e411..2b8819a68d83 100644
--- a/packages/tailwindcss/src/compat/config.test.ts
+++ b/packages/tailwindcss/src/compat/config.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, test } from 'vitest'
-import { compile } from '..'
+import { compile, type Config } from '..'
import plugin from '../plugin'
import { flattenColorPalette } from './flatten-color-palette'
@@ -12,10 +12,10 @@ test('Config files can add content', async () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({ content: ['./file.txt'] }),
+ loadModule: async () => ({ module: { content: ['./file.txt'] }, base: '/root' }),
})
- expect(compiler.globs).toEqual([{ origin: './config.js', pattern: './file.txt' }])
+ expect(compiler.globs).toEqual([{ base: '/root', pattern: './file.txt' }])
})
test('Config files can change dark mode (media)', async () => {
@@ -25,7 +25,7 @@ test('Config files can change dark mode (media)', async () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({ darkMode: 'media' }),
+ loadModule: async () => ({ module: { darkMode: 'media' }, base: '/root' }),
})
expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(`
@@ -45,7 +45,7 @@ test('Config files can change dark mode (selector)', async () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({ darkMode: 'selector' }),
+ loadModule: async () => ({ module: { darkMode: 'selector' }, base: '/root' }),
})
expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(`
@@ -65,7 +65,10 @@ test('Config files can change dark mode (variant)', async () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({ darkMode: ['variant', '&:where(:not(.light))'] }),
+ loadModule: async () => ({
+ module: { darkMode: ['variant', '&:where(:not(.light))'] },
+ base: '/root',
+ }),
})
expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(`
@@ -85,16 +88,19 @@ test('Config files can add plugins', async () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- plugins: [
- plugin(function ({ addUtilities }) {
- addUtilities({
- '.no-scrollbar': {
- 'scrollbar-width': 'none',
- },
- })
- }),
- ],
+ loadModule: async () => ({
+ module: {
+ plugins: [
+ plugin(function ({ addUtilities }) {
+ addUtilities({
+ '.no-scrollbar': {
+ 'scrollbar-width': 'none',
+ },
+ })
+ }),
+ ],
+ },
+ base: '/root',
}),
})
@@ -113,12 +119,15 @@ test('Plugins loaded from config files can contribute to the config', async () =
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- plugins: [
- plugin(() => {}, {
- darkMode: ['variant', '&:where(:not(.light))'],
- }),
- ],
+ loadModule: async () => ({
+ module: {
+ plugins: [
+ plugin(() => {}, {
+ darkMode: ['variant', '&:where(:not(.light))'],
+ }),
+ ],
+ },
+ base: '/root',
}),
})
@@ -139,12 +148,15 @@ test('Config file presets can contribute to the config', async () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- presets: [
- {
- darkMode: ['variant', '&:where(:not(.light))'],
- },
- ],
+ loadModule: async () => ({
+ module: {
+ presets: [
+ {
+ darkMode: ['variant', '&:where(:not(.light))'],
+ },
+ ],
+ },
+ base: '/root',
}),
})
@@ -165,24 +177,27 @@ test('Config files can affect the theme', async () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- extend: {
- colors: {
- primary: '#c0ffee',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ extend: {
+ colors: {
+ primary: '#c0ffee',
+ },
},
},
- },
- plugins: [
- plugin(function ({ addUtilities, theme }) {
- addUtilities({
- '.scrollbar-primary': {
- scrollbarColor: theme('colors.primary'),
- },
- })
- }),
- ],
+ plugins: [
+ plugin(function ({ addUtilities, theme }) {
+ addUtilities({
+ '.scrollbar-primary': {
+ scrollbarColor: theme('colors.primary'),
+ },
+ })
+ }),
+ ],
+ },
+ base: '/root',
}),
})
@@ -206,13 +221,16 @@ test('Variants in CSS overwrite variants from plugins', async () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- darkMode: ['variant', '&:is(.dark)'],
- plugins: [
- plugin(function ({ addVariant }) {
- addVariant('light', '&:is(.light)')
- }),
- ],
+ loadModule: async () => ({
+ module: {
+ darkMode: ['variant', '&:is(.dark)'],
+ plugins: [
+ plugin(function ({ addVariant }) {
+ addVariant('light', '&:is(.light)')
+ }),
+ ],
+ },
+ base: '/root',
}),
})
@@ -253,49 +271,52 @@ describe('theme callbacks', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- extend: {
- fontSize: {
- base: ['200rem', { lineHeight: '201rem' }],
- md: ['200rem', { lineHeight: '201rem' }],
- xl: ['200rem', { lineHeight: '201rem' }],
- },
-
- // Direct access
- lineHeight: ({ theme }) => ({
- base: theme('fontSize.base[1].lineHeight'),
- md: theme('fontSize.md[1].lineHeight'),
- xl: theme('fontSize.xl[1].lineHeight'),
- }),
-
- // Tuple access
- typography: ({ theme }) => ({
- '[class~=lead-base]': {
- fontSize: theme('fontSize.base')[0],
- ...theme('fontSize.base')[1],
- },
- '[class~=lead-md]': {
- fontSize: theme('fontSize.md')[0],
- ...theme('fontSize.md')[1],
+ loadModule: async () => ({
+ module: {
+ theme: {
+ extend: {
+ fontSize: {
+ base: ['200rem', { lineHeight: '201rem' }],
+ md: ['200rem', { lineHeight: '201rem' }],
+ xl: ['200rem', { lineHeight: '201rem' }],
},
- '[class~=lead-xl]': {
- fontSize: theme('fontSize.xl')[0],
- ...theme('fontSize.xl')[1],
- },
- }),
+
+ // Direct access
+ lineHeight: ({ theme }) => ({
+ base: theme('fontSize.base[1].lineHeight'),
+ md: theme('fontSize.md[1].lineHeight'),
+ xl: theme('fontSize.xl[1].lineHeight'),
+ }),
+
+ // Tuple access
+ typography: ({ theme }) => ({
+ '[class~=lead-base]': {
+ fontSize: theme('fontSize.base')[0],
+ ...theme('fontSize.base')[1],
+ },
+ '[class~=lead-md]': {
+ fontSize: theme('fontSize.md')[0],
+ ...theme('fontSize.md')[1],
+ },
+ '[class~=lead-xl]': {
+ fontSize: theme('fontSize.xl')[0],
+ ...theme('fontSize.xl')[1],
+ },
+ }),
+ },
},
- },
- plugins: [
- plugin(function ({ addUtilities, theme }) {
- addUtilities({
- '.prose': {
- ...theme('typography'),
- },
- })
- }),
- ],
+ plugins: [
+ plugin(function ({ addUtilities, theme }) {
+ addUtilities({
+ '.prose': {
+ ...theme('typography'),
+ },
+ })
+ }),
+ ],
+ } satisfies Config,
+ base: '/root',
}),
})
@@ -361,15 +382,18 @@ describe('theme overrides order', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- extend: {
- colors: {
- red: 'very-red',
- blue: 'very-blue',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ extend: {
+ colors: {
+ red: 'very-red',
+ blue: 'very-blue',
+ },
},
},
},
+ base: '/root',
}),
})
@@ -404,35 +428,43 @@ describe('theme overrides order', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- extend: {
- colors: {
- slate: {
- 200: '#200200',
- 400: '#200400',
- 600: '#200600',
- },
- },
- },
- },
- }),
-
- loadPlugin: async () => {
- return plugin(({ matchUtilities, theme }) => {
- matchUtilities(
- {
- 'hover-bg': (value) => {
- return {
- '&:hover': {
- backgroundColor: value,
+ loadModule: async (id) => {
+ if (id.includes('config.js')) {
+ return {
+ module: {
+ theme: {
+ extend: {
+ colors: {
+ slate: {
+ 200: '#200200',
+ 400: '#200400',
+ 600: '#200600',
+ },
},
- }
+ },
},
- },
- { values: flattenColorPalette(theme('colors')) },
- )
- })
+ } satisfies Config,
+ base: '/root',
+ }
+ } else {
+ return {
+ module: plugin(({ matchUtilities, theme }) => {
+ matchUtilities(
+ {
+ 'hover-bg': (value) => {
+ return {
+ '&:hover': {
+ backgroundColor: value,
+ },
+ }
+ },
+ },
+ { values: flattenColorPalette(theme('colors')) },
+ )
+ }),
+ base: '/root',
+ }
+ }
},
})
@@ -524,12 +556,15 @@ describe('default font family compatibility', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- fontFamily: {
- sans: 'Potato Sans',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ fontFamily: {
+ sans: 'Potato Sans',
+ },
},
},
+ base: '/root',
}),
})
@@ -560,12 +595,15 @@ describe('default font family compatibility', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- fontFamily: {
- sans: ['Potato Sans', { fontFeatureSettings: '"cv06"' }],
+ loadModule: async () => ({
+ module: {
+ theme: {
+ fontFamily: {
+ sans: ['Potato Sans', { fontFeatureSettings: '"cv06"' }],
+ },
},
},
+ base: '/root',
}),
})
@@ -597,12 +635,15 @@ describe('default font family compatibility', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- fontFamily: {
- sans: ['Potato Sans', { fontVariationSettings: '"XHGT" 0.7' }],
+ loadModule: async () => ({
+ module: {
+ theme: {
+ fontFamily: {
+ sans: ['Potato Sans', { fontVariationSettings: '"XHGT" 0.7' }],
+ },
},
},
+ base: '/root',
}),
})
@@ -634,15 +675,18 @@ describe('default font family compatibility', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- fontFamily: {
- sans: [
- 'Potato Sans',
- { fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' },
- ],
+ loadModule: async () => ({
+ module: {
+ theme: {
+ fontFamily: {
+ sans: [
+ 'Potato Sans',
+ { fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' },
+ ],
+ },
},
},
+ base: '/root',
}),
})
@@ -678,12 +722,15 @@ describe('default font family compatibility', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- fontFamily: {
- sans: 'Potato Sans',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ fontFamily: {
+ sans: 'Potato Sans',
+ },
},
},
+ base: '/root',
}),
})
@@ -715,12 +762,15 @@ describe('default font family compatibility', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- fontFamily: {
- sans: ['Inter', 'system-ui', 'sans-serif'],
+ loadModule: async () => ({
+ module: {
+ theme: {
+ fontFamily: {
+ sans: ['Inter', 'system-ui', 'sans-serif'],
+ },
},
},
+ base: '/root',
}),
})
@@ -751,12 +801,15 @@ describe('default font family compatibility', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- fontFamily: {
- sans: { foo: 'bar', banana: 'sandwich' },
+ loadModule: async () => ({
+ module: {
+ theme: {
+ fontFamily: {
+ sans: { foo: 'bar', banana: 'sandwich' },
+ },
},
},
+ base: '/root',
}),
})
@@ -782,12 +835,15 @@ describe('default font family compatibility', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- fontFamily: {
- mono: 'Potato Mono',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ fontFamily: {
+ mono: 'Potato Mono',
+ },
},
},
+ base: '/root',
}),
})
@@ -818,12 +874,15 @@ describe('default font family compatibility', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- fontFamily: {
- mono: ['Potato Mono', { fontFeatureSettings: '"cv06"' }],
+ loadModule: async () => ({
+ module: {
+ theme: {
+ fontFamily: {
+ mono: ['Potato Mono', { fontFeatureSettings: '"cv06"' }],
+ },
},
},
+ base: '/root',
}),
})
@@ -855,12 +914,15 @@ describe('default font family compatibility', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- fontFamily: {
- mono: ['Potato Mono', { fontVariationSettings: '"XHGT" 0.7' }],
+ loadModule: async () => ({
+ module: {
+ theme: {
+ fontFamily: {
+ mono: ['Potato Mono', { fontVariationSettings: '"XHGT" 0.7' }],
+ },
},
},
+ base: '/root',
}),
})
@@ -892,15 +954,18 @@ describe('default font family compatibility', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- fontFamily: {
- mono: [
- 'Potato Mono',
- { fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' },
- ],
+ loadModule: async () => ({
+ module: {
+ theme: {
+ fontFamily: {
+ mono: [
+ 'Potato Mono',
+ { fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' },
+ ],
+ },
},
},
+ base: '/root',
}),
})
@@ -936,12 +1001,15 @@ describe('default font family compatibility', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- fontFamily: {
- mono: 'Potato Mono',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ fontFamily: {
+ mono: 'Potato Mono',
+ },
},
},
+ base: '/root',
}),
})
@@ -973,12 +1041,15 @@ describe('default font family compatibility', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- fontFamily: {
- mono: { foo: 'bar', banana: 'sandwich' },
+ loadModule: async () => ({
+ module: {
+ theme: {
+ fontFamily: {
+ mono: { foo: 'bar', banana: 'sandwich' },
+ },
},
},
+ base: '/root',
}),
})
@@ -1000,21 +1071,24 @@ test('creates variants for `data`, `supports`, and `aria` theme options at the s
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- extend: {
- aria: {
- polite: 'live="polite"',
- },
- supports: {
- 'child-combinator': 'selector(h2 > p)',
- foo: 'bar',
- },
- data: {
- checked: 'ui~="checked"',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ extend: {
+ aria: {
+ polite: 'live="polite"',
+ },
+ supports: {
+ 'child-combinator': 'selector(h2 > p)',
+ foo: 'bar',
+ },
+ data: {
+ checked: 'ui~="checked"',
+ },
},
},
},
+ base: '/root',
}),
})
@@ -1096,14 +1170,17 @@ test('merges css breakpoints with js config screens', async () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- extend: {
- screens: {
- sm: '44rem',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ extend: {
+ screens: {
+ sm: '44rem',
+ },
},
},
},
+ base: '/root',
}),
})
diff --git a/packages/tailwindcss/src/compat/config/resolve-config.test.ts b/packages/tailwindcss/src/compat/config/resolve-config.test.ts
index 818ff8373d85..895939bcaa4a 100644
--- a/packages/tailwindcss/src/compat/config/resolve-config.test.ts
+++ b/packages/tailwindcss/src/compat/config/resolve-config.test.ts
@@ -19,6 +19,7 @@ test('top level theme keys are replaced', () => {
},
},
},
+ base: '/root',
},
{
config: {
@@ -28,6 +29,7 @@ test('top level theme keys are replaced', () => {
},
},
},
+ base: '/root',
},
{
config: {
@@ -37,6 +39,7 @@ test('top level theme keys are replaced', () => {
},
},
},
+ base: '/root',
},
])
@@ -68,6 +71,7 @@ test('theme can be extended', () => {
},
},
},
+ base: '/root',
},
{
config: {
@@ -79,6 +83,7 @@ test('theme can be extended', () => {
},
},
},
+ base: '/root',
},
])
@@ -112,6 +117,7 @@ test('theme keys can reference other theme keys using the theme function regardl
},
},
},
+ base: '/root',
},
{
config: {
@@ -124,6 +130,7 @@ test('theme keys can reference other theme keys using the theme function regardl
},
},
},
+ base: '/root',
},
{
config: {
@@ -135,6 +142,7 @@ test('theme keys can reference other theme keys using the theme function regardl
},
},
},
+ base: '/root',
},
])
@@ -192,6 +200,7 @@ test('theme keys can read from the CSS theme', () => {
}),
},
},
+ base: '/root',
},
])
diff --git a/packages/tailwindcss/src/compat/config/resolve-config.ts b/packages/tailwindcss/src/compat/config/resolve-config.ts
index 268e6883a9dc..1f59b50cf149 100644
--- a/packages/tailwindcss/src/compat/config/resolve-config.ts
+++ b/packages/tailwindcss/src/compat/config/resolve-config.ts
@@ -12,6 +12,7 @@ import {
export interface ConfigFile {
path?: string
+ base: string
config: UserConfig
}
@@ -103,7 +104,7 @@ export interface PluginUtils {
theme(keypath: string, defaultValue?: any): any
}
-function extractConfigs(ctx: ResolutionContext, { config, path }: ConfigFile): void {
+function extractConfigs(ctx: ResolutionContext, { config, base, path }: ConfigFile): void {
let plugins: PluginWithConfig[] = []
// Normalize plugins so they share the same shape
@@ -133,7 +134,7 @@ function extractConfigs(ctx: ResolutionContext, { config, path }: ConfigFile): v
}
for (let preset of config.presets ?? []) {
- extractConfigs(ctx, { path, config: preset })
+ extractConfigs(ctx, { path, base, config: preset })
}
// Apply configs from plugins
@@ -141,7 +142,7 @@ function extractConfigs(ctx: ResolutionContext, { config, path }: ConfigFile): v
ctx.plugins.push(plugin)
if (plugin.config) {
- extractConfigs(ctx, { path, config: plugin.config })
+ extractConfigs(ctx, { path, base, config: plugin.config })
}
}
@@ -150,7 +151,7 @@ function extractConfigs(ctx: ResolutionContext, { config, path }: ConfigFile): v
let files = Array.isArray(content) ? content : content.files
for (let file of files) {
- ctx.content.files.push(typeof file === 'object' ? file : { base: path!, pattern: file })
+ ctx.content.files.push(typeof file === 'object' ? file : { base, pattern: file })
}
// Then apply the "user" config
diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts
index f0d0b1102f1d..7d732ca1ccf1 100644
--- a/packages/tailwindcss/src/compat/plugin-api.test.ts
+++ b/packages/tailwindcss/src/compat/plugin-api.test.ts
@@ -15,37 +15,40 @@ describe('theme', async () => {
`
let compiler = await compile(input, {
- loadPlugin: async () => {
- return plugin(
- function ({ addBase, theme }) {
- addBase({
- '@keyframes enter': theme('keyframes.enter'),
- '@keyframes exit': theme('keyframes.exit'),
- })
- },
- {
- theme: {
- extend: {
- keyframes: {
- enter: {
- from: {
- opacity: 'var(--tw-enter-opacity, 1)',
- transform:
- 'translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))',
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(
+ function ({ addBase, theme }) {
+ addBase({
+ '@keyframes enter': theme('keyframes.enter'),
+ '@keyframes exit': theme('keyframes.exit'),
+ })
+ },
+ {
+ theme: {
+ extend: {
+ keyframes: {
+ enter: {
+ from: {
+ opacity: 'var(--tw-enter-opacity, 1)',
+ transform:
+ 'translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))',
+ },
},
- },
- exit: {
- to: {
- opacity: 'var(--tw-exit-opacity, 1)',
- transform:
- 'translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))',
+ exit: {
+ to: {
+ opacity: 'var(--tw-exit-opacity, 1)',
+ transform:
+ 'translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))',
+ },
},
},
},
},
},
- },
- )
+ ),
+ }
},
})
@@ -78,28 +81,31 @@ describe('theme', async () => {
`
let compiler = await compile(input, {
- loadPlugin: async () => {
- return plugin(
- function ({ matchUtilities, theme }) {
- matchUtilities(
- {
- scrollbar: (value) => ({ 'scrollbar-color': value }),
- },
- {
- values: theme('colors'),
- },
- )
- },
- {
- theme: {
- extend: {
- colors: {
- 'russet-700': '#7a4724',
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(
+ function ({ matchUtilities, theme }) {
+ matchUtilities(
+ {
+ scrollbar: (value) => ({ 'scrollbar-color': value }),
+ },
+ {
+ values: theme('colors'),
+ },
+ )
+ },
+ {
+ theme: {
+ extend: {
+ colors: {
+ 'russet-700': '#7a4724',
+ },
},
},
},
- },
- )
+ ),
+ }
},
})
@@ -123,30 +129,33 @@ describe('theme', async () => {
`
let compiler = await compile(input, {
- loadPlugin: async () => {
- return plugin(
- function ({ matchUtilities, theme }) {
- matchUtilities(
- {
- 'animate-duration': (value) => ({ 'animation-duration': value }),
- },
- {
- values: theme('animationDuration'),
- },
- )
- },
- {
- theme: {
- extend: {
- animationDuration: ({ theme }: { theme: (path: string) => any }) => {
- return {
- ...theme('transitionDuration'),
- }
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(
+ function ({ matchUtilities, theme }) {
+ matchUtilities(
+ {
+ 'animate-duration': (value) => ({ 'animation-duration': value }),
+ },
+ {
+ values: theme('animationDuration'),
+ },
+ )
+ },
+ {
+ theme: {
+ extend: {
+ animationDuration: ({ theme }: { theme: (path: string) => any }) => {
+ return {
+ ...theme('transitionDuration'),
+ }
+ },
},
},
},
- },
- )
+ ),
+ }
},
})
@@ -167,32 +176,35 @@ describe('theme', async () => {
`
let compiler = await compile(input, {
- loadPlugin: async () => {
- return plugin(
- function ({ matchUtilities, theme }) {
- matchUtilities(
- {
- 'animate-duration': (value) => ({ 'animation-duration': value }),
- },
- {
- values: theme('animationDuration'),
- },
- )
- },
- {
- theme: {
- extend: {
- transitionDuration: {
- slow: '800ms',
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(
+ function ({ matchUtilities, theme }) {
+ matchUtilities(
+ {
+ 'animate-duration': (value) => ({ 'animation-duration': value }),
+ },
+ {
+ values: theme('animationDuration'),
},
+ )
+ },
+ {
+ theme: {
+ extend: {
+ transitionDuration: {
+ slow: '800ms',
+ },
- animationDuration: ({ theme }: { theme: (path: string) => any }) => ({
- ...theme('transitionDuration'),
- }),
+ animationDuration: ({ theme }: { theme: (path: string) => any }) => ({
+ ...theme('transitionDuration'),
+ }),
+ },
},
},
- },
- )
+ ),
+ }
},
})
@@ -218,20 +230,23 @@ describe('theme', async () => {
`
let compiler = await compile(input, {
- loadPlugin: async () => {
- return plugin(function ({ addUtilities, theme }) {
- addUtilities({
- '.percentage': {
- color: theme('colors.red.500 / 50%'),
- },
- '.fraction': {
- color: theme('colors.red.500 / 0.5'),
- },
- '.variable': {
- color: theme('colors.red.500 / var(--opacity)'),
- },
- })
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(function ({ addUtilities, theme }) {
+ addUtilities({
+ '.percentage': {
+ color: theme('colors.red.500 / 50%'),
+ },
+ '.fraction': {
+ color: theme('colors.red.500 / 0.5'),
+ },
+ '.variable': {
+ color: theme('colors.red.500 / var(--opacity)'),
+ },
+ })
+ }),
+ }
},
})
@@ -258,36 +273,39 @@ describe('theme', async () => {
`
let compiler = await compile(input, {
- loadPlugin: async () => {
- return plugin(
- function ({ matchUtilities, theme }) {
- matchUtilities(
- {
- 'animate-delay': (value) => ({ 'animation-delay': value }),
- },
- {
- values: theme('animationDelay'),
- },
- )
- },
- {
- theme: {
- extend: {
- animationDuration: ({ theme }: { theme: (path: string) => any }) => ({
- ...theme('transitionDuration'),
- }),
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(
+ function ({ matchUtilities, theme }) {
+ matchUtilities(
+ {
+ 'animate-delay': (value) => ({ 'animation-delay': value }),
+ },
+ {
+ values: theme('animationDelay'),
+ },
+ )
+ },
+ {
+ theme: {
+ extend: {
+ animationDuration: ({ theme }: { theme: (path: string) => any }) => ({
+ ...theme('transitionDuration'),
+ }),
- animationDelay: ({ theme }: { theme: (path: string) => any }) => ({
- ...theme('animationDuration'),
- }),
+ animationDelay: ({ theme }: { theme: (path: string) => any }) => ({
+ ...theme('animationDuration'),
+ }),
- transitionDuration: {
- slow: '800ms',
+ transitionDuration: {
+ slow: '800ms',
+ },
},
},
},
- },
- )
+ ),
+ }
},
})
@@ -309,28 +327,31 @@ describe('theme', async () => {
`
let compiler = await compile(input, {
- loadPlugin: async () => {
- return plugin(
- function ({ matchUtilities, theme }) {
- matchUtilities(
- {
- 'animate-duration': (value) => ({ 'animation-delay': value }),
- },
- {
- values: theme('transitionDuration'),
- },
- )
- },
- {
- theme: {
- extend: {
- transitionDuration: {
- DEFAULT: '1500ms',
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(
+ function ({ matchUtilities, theme }) {
+ matchUtilities(
+ {
+ 'animate-duration': (value) => ({ 'animation-delay': value }),
+ },
+ {
+ values: theme('transitionDuration'),
+ },
+ )
+ },
+ {
+ theme: {
+ extend: {
+ transitionDuration: {
+ DEFAULT: '1500ms',
+ },
},
},
},
- },
- )
+ ),
+ }
},
})
@@ -353,29 +374,32 @@ describe('theme', async () => {
`
let compiler = await compile(input, {
- loadPlugin: async () => {
- return plugin(function ({ matchUtilities, theme }) {
- matchUtilities(
- {
- animation: (value) => ({ animation: value }),
- },
- {
- values: theme('animation'),
- },
- )
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(function ({ matchUtilities, theme }) {
+ matchUtilities(
+ {
+ animation: (value) => ({ animation: value }),
+ },
+ {
+ values: theme('animation'),
+ },
+ )
- matchUtilities(
- {
- animation2: (value) => ({ animation: value }),
- },
- {
- values: {
- DEFAULT: theme('animation.DEFAULT'),
- twist: theme('animation.spin'),
+ matchUtilities(
+ {
+ animation2: (value) => ({ animation: value }),
},
- },
- )
- })
+ {
+ values: {
+ DEFAULT: theme('animation.DEFAULT'),
+ twist: theme('animation.spin'),
+ },
+ },
+ )
+ }),
+ }
},
})
@@ -408,28 +432,31 @@ describe('theme', async () => {
`
let compiler = await compile(input, {
- loadPlugin: async () => {
- return plugin(
- function ({ matchUtilities, theme }) {
- matchUtilities(
- {
- animation: (value) => ({ '--animation': value }),
- },
- {
- values: theme('animation'),
- },
- )
- },
- {
- theme: {
- extend: {
- animation: {
- bounce: 'bounce 1s linear infinite',
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(
+ function ({ matchUtilities, theme }) {
+ matchUtilities(
+ {
+ animation: (value) => ({ '--animation': value }),
+ },
+ {
+ values: theme('animation'),
+ },
+ )
+ },
+ {
+ theme: {
+ extend: {
+ animation: {
+ bounce: 'bounce 1s linear infinite',
+ },
},
},
},
- },
- )
+ ),
+ }
},
})
@@ -459,28 +486,31 @@ describe('theme', async () => {
`
let compiler = await compile(input, {
- loadPlugin: async () => {
- return plugin(
- function ({ matchUtilities, theme }) {
- matchUtilities(
- {
- animation: (value) => ({ '--animation': value }),
- },
- {
- values: theme('animation'),
- },
- )
- },
- {
- theme: {
- extend: {
- animation: {
- DEFAULT: 'twist 1s linear infinite',
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(
+ function ({ matchUtilities, theme }) {
+ matchUtilities(
+ {
+ animation: (value) => ({ '--animation': value }),
+ },
+ {
+ values: theme('animation'),
+ },
+ )
+ },
+ {
+ theme: {
+ extend: {
+ animation: {
+ DEFAULT: 'twist 1s linear infinite',
+ },
},
},
},
- },
- )
+ ),
+ }
},
})
@@ -505,21 +535,24 @@ describe('theme', async () => {
let fn = vi.fn()
await compile(input, {
- loadPlugin: async () => {
- return plugin(
- function ({ theme }) {
- fn(theme('animation.simple'))
- },
- {
- theme: {
- extend: {
- animation: {
- simple: 'simple 1s linear',
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(
+ function ({ theme }) {
+ fn(theme('animation.simple'))
+ },
+ {
+ theme: {
+ extend: {
+ animation: {
+ simple: 'simple 1s linear',
+ },
},
},
},
- },
- )
+ ),
+ }
},
})
@@ -537,58 +570,61 @@ describe('theme', async () => {
`
let { build } = await compile(input, {
- loadPlugin: async () => {
- return plugin(function ({ matchUtilities, theme }) {
- function utility(name: string, themeKey: string) {
- matchUtilities(
- { [name]: (value) => ({ '--value': value }) },
- { values: theme(themeKey) },
- )
- }
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(function ({ matchUtilities, theme }) {
+ function utility(name: string, themeKey: string) {
+ matchUtilities(
+ { [name]: (value) => ({ '--value': value }) },
+ { values: theme(themeKey) },
+ )
+ }
- utility('my-aspect', 'aspectRatio')
- utility('my-backdrop-brightness', 'backdropBrightness')
- utility('my-backdrop-contrast', 'backdropContrast')
- utility('my-backdrop-grayscale', 'backdropGrayscale')
- utility('my-backdrop-hue-rotate', 'backdropHueRotate')
- utility('my-backdrop-invert', 'backdropInvert')
- utility('my-backdrop-opacity', 'backdropOpacity')
- utility('my-backdrop-saturate', 'backdropSaturate')
- utility('my-backdrop-sepia', 'backdropSepia')
- utility('my-border-width', 'borderWidth')
- utility('my-brightness', 'brightness')
- utility('my-columns', 'columns')
- utility('my-contrast', 'contrast')
- utility('my-divide-width', 'divideWidth')
- utility('my-flex-grow', 'flexGrow')
- utility('my-flex-shrink', 'flexShrink')
- utility('my-gradient-color-stop-positions', 'gradientColorStopPositions')
- utility('my-grayscale', 'grayscale')
- utility('my-grid-row-end', 'gridRowEnd')
- utility('my-grid-row-start', 'gridRowStart')
- utility('my-grid-template-columns', 'gridTemplateColumns')
- utility('my-grid-template-rows', 'gridTemplateRows')
- utility('my-hue-rotate', 'hueRotate')
- utility('my-invert', 'invert')
- utility('my-line-clamp', 'lineClamp')
- utility('my-opacity', 'opacity')
- utility('my-order', 'order')
- utility('my-outline-offset', 'outlineOffset')
- utility('my-outline-width', 'outlineWidth')
- utility('my-ring-offset-width', 'ringOffsetWidth')
- utility('my-ring-width', 'ringWidth')
- utility('my-rotate', 'rotate')
- utility('my-saturate', 'saturate')
- utility('my-scale', 'scale')
- utility('my-sepia', 'sepia')
- utility('my-skew', 'skew')
- utility('my-stroke-width', 'strokeWidth')
- utility('my-text-decoration-thickness', 'textDecorationThickness')
- utility('my-text-underline-offset', 'textUnderlineOffset')
- utility('my-transition-delay', 'transitionDelay')
- utility('my-transition-duration', 'transitionDuration')
- utility('my-z-index', 'zIndex')
- })
+ utility('my-aspect', 'aspectRatio')
+ utility('my-backdrop-brightness', 'backdropBrightness')
+ utility('my-backdrop-contrast', 'backdropContrast')
+ utility('my-backdrop-grayscale', 'backdropGrayscale')
+ utility('my-backdrop-hue-rotate', 'backdropHueRotate')
+ utility('my-backdrop-invert', 'backdropInvert')
+ utility('my-backdrop-opacity', 'backdropOpacity')
+ utility('my-backdrop-saturate', 'backdropSaturate')
+ utility('my-backdrop-sepia', 'backdropSepia')
+ utility('my-border-width', 'borderWidth')
+ utility('my-brightness', 'brightness')
+ utility('my-columns', 'columns')
+ utility('my-contrast', 'contrast')
+ utility('my-divide-width', 'divideWidth')
+ utility('my-flex-grow', 'flexGrow')
+ utility('my-flex-shrink', 'flexShrink')
+ utility('my-gradient-color-stop-positions', 'gradientColorStopPositions')
+ utility('my-grayscale', 'grayscale')
+ utility('my-grid-row-end', 'gridRowEnd')
+ utility('my-grid-row-start', 'gridRowStart')
+ utility('my-grid-template-columns', 'gridTemplateColumns')
+ utility('my-grid-template-rows', 'gridTemplateRows')
+ utility('my-hue-rotate', 'hueRotate')
+ utility('my-invert', 'invert')
+ utility('my-line-clamp', 'lineClamp')
+ utility('my-opacity', 'opacity')
+ utility('my-order', 'order')
+ utility('my-outline-offset', 'outlineOffset')
+ utility('my-outline-width', 'outlineWidth')
+ utility('my-ring-offset-width', 'ringOffsetWidth')
+ utility('my-ring-width', 'ringWidth')
+ utility('my-rotate', 'rotate')
+ utility('my-saturate', 'saturate')
+ utility('my-scale', 'scale')
+ utility('my-sepia', 'sepia')
+ utility('my-skew', 'skew')
+ utility('my-stroke-width', 'strokeWidth')
+ utility('my-text-decoration-thickness', 'textDecorationThickness')
+ utility('my-text-underline-offset', 'textUnderlineOffset')
+ utility('my-transition-delay', 'transitionDelay')
+ utility('my-transition-duration', 'transitionDuration')
+ utility('my-z-index', 'zIndex')
+ }),
+ }
},
})
@@ -781,23 +817,26 @@ describe('theme', async () => {
let fn = vi.fn()
await compile(input, {
- loadPlugin: async () => {
- return plugin(
- ({ theme }) => {
- // The compatibility config specifies that `accentColor` spreads in `colors`
- fn(theme('accentColor.primary'))
-
- // This should even work for theme keys specified in plugin configs
- fn(theme('myAccentColor.secondary'))
- },
- {
- theme: {
- extend: {
- myAccentColor: ({ theme }) => theme('accentColor'),
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(
+ ({ theme }) => {
+ // The compatibility config specifies that `accentColor` spreads in `colors`
+ fn(theme('accentColor.primary'))
+
+ // This should even work for theme keys specified in plugin configs
+ fn(theme('myAccentColor.secondary'))
+ },
+ {
+ theme: {
+ extend: {
+ myAccentColor: ({ theme }) => theme('accentColor'),
+ },
},
},
- },
- )
+ ),
+ }
},
})
@@ -820,12 +859,15 @@ describe('theme', async () => {
let fn = vi.fn()
await compile(input, {
- loadPlugin: async () => {
- return plugin(({ theme }) => {
- fn(theme('transitionTimingFunction.DEFAULT'))
- fn(theme('transitionTimingFunction.in'))
- fn(theme('transitionTimingFunction.out'))
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(({ theme }) => {
+ fn(theme('transitionTimingFunction.DEFAULT'))
+ fn(theme('transitionTimingFunction.in'))
+ fn(theme('transitionTimingFunction.out'))
+ }),
+ }
},
})
@@ -848,12 +890,15 @@ describe('theme', async () => {
let fn = vi.fn()
await compile(input, {
- loadPlugin: async () => {
- return plugin(({ theme }) => {
- fn(theme('color.red.100'))
- fn(theme('colors.red.200'))
- fn(theme('backgroundColor.red.300'))
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(({ theme }) => {
+ fn(theme('color.red.100'))
+ fn(theme('colors.red.200'))
+ fn(theme('backgroundColor.red.300'))
+ }),
+ }
},
})
@@ -873,13 +918,16 @@ describe('theme', async () => {
let fn = vi.fn()
await compile(input, {
- loadPlugin: async () => {
- return plugin(({ theme }) => {
- fn(theme('i.do.not.exist'))
- fn(theme('color'))
- fn(theme('color', 'magenta'))
- fn(theme('colors'))
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(({ theme }) => {
+ fn(theme('i.do.not.exist'))
+ fn(theme('color'))
+ fn(theme('color', 'magenta'))
+ fn(theme('colors'))
+ }),
+ }
},
})
@@ -896,34 +944,37 @@ describe('theme', async () => {
`
let { build } = await compile(input, {
- loadPlugin: async () => {
- return plugin(({ addUtilities, matchUtilities }) => {
- addUtilities({
- '.foo-bar': {
- color: 'red',
- },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(({ addUtilities, matchUtilities }) => {
+ addUtilities({
+ '.foo-bar': {
+ color: 'red',
+ },
+ })
- matchUtilities(
- {
- foo: (value) => ({
- '--my-prop': value,
- }),
- },
- {
- values: {
- bar: 'bar-valuer',
- baz: 'bar-valuer',
+ matchUtilities(
+ {
+ foo: (value) => ({
+ '--my-prop': value,
+ }),
},
- },
- )
+ {
+ values: {
+ bar: 'bar-valuer',
+ baz: 'bar-valuer',
+ },
+ },
+ )
- addUtilities({
- '.foo-bar': {
- backgroundColor: 'red',
- },
- })
- })
+ addUtilities({
+ '.foo-bar': {
+ backgroundColor: 'red',
+ },
+ })
+ }),
+ }
},
})
@@ -948,62 +999,65 @@ describe('theme', async () => {
`
let { build } = await compile(input, {
- loadPlugin: async () => {
- return plugin(function ({ matchUtilities }) {
- function utility(name: string, themeKey: string) {
- matchUtilities(
- { [name]: (value) => ({ '--value': value }) },
- // @ts-ignore
- { values: defaultTheme[themeKey] },
- )
- }
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: plugin(function ({ matchUtilities }) {
+ function utility(name: string, themeKey: string) {
+ matchUtilities(
+ { [name]: (value) => ({ '--value': value }) },
+ // @ts-ignore
+ { values: defaultTheme[themeKey] },
+ )
+ }
- utility('my-aspect', 'aspectRatio')
- // The following keys deliberately doesn't work as these are exported
- // as functions from the compat config.
- //
- // utility('my-backdrop-brightness', 'backdropBrightness')
- // utility('my-backdrop-contrast', 'backdropContrast')
- // utility('my-backdrop-grayscale', 'backdropGrayscale')
- // utility('my-backdrop-hue-rotate', 'backdropHueRotate')
- // utility('my-backdrop-invert', 'backdropInvert')
- // utility('my-backdrop-opacity', 'backdropOpacity')
- // utility('my-backdrop-saturate', 'backdropSaturate')
- // utility('my-backdrop-sepia', 'backdropSepia')
- // utility('my-divide-width', 'divideWidth')
- utility('my-border-width', 'borderWidth')
- utility('my-brightness', 'brightness')
- utility('my-columns', 'columns')
- utility('my-contrast', 'contrast')
- utility('my-flex-grow', 'flexGrow')
- utility('my-flex-shrink', 'flexShrink')
- utility('my-gradient-color-stop-positions', 'gradientColorStopPositions')
- utility('my-grayscale', 'grayscale')
- utility('my-grid-row-end', 'gridRowEnd')
- utility('my-grid-row-start', 'gridRowStart')
- utility('my-grid-template-columns', 'gridTemplateColumns')
- utility('my-grid-template-rows', 'gridTemplateRows')
- utility('my-hue-rotate', 'hueRotate')
- utility('my-invert', 'invert')
- utility('my-line-clamp', 'lineClamp')
- utility('my-opacity', 'opacity')
- utility('my-order', 'order')
- utility('my-outline-offset', 'outlineOffset')
- utility('my-outline-width', 'outlineWidth')
- utility('my-ring-offset-width', 'ringOffsetWidth')
- utility('my-ring-width', 'ringWidth')
- utility('my-rotate', 'rotate')
- utility('my-saturate', 'saturate')
- utility('my-scale', 'scale')
- utility('my-sepia', 'sepia')
- utility('my-skew', 'skew')
- utility('my-stroke-width', 'strokeWidth')
- utility('my-text-decoration-thickness', 'textDecorationThickness')
- utility('my-text-underline-offset', 'textUnderlineOffset')
- utility('my-transition-delay', 'transitionDelay')
- utility('my-transition-duration', 'transitionDuration')
- utility('my-z-index', 'zIndex')
- })
+ utility('my-aspect', 'aspectRatio')
+ // The following keys deliberately doesn't work as these are exported
+ // as functions from the compat config.
+ //
+ // utility('my-backdrop-brightness', 'backdropBrightness')
+ // utility('my-backdrop-contrast', 'backdropContrast')
+ // utility('my-backdrop-grayscale', 'backdropGrayscale')
+ // utility('my-backdrop-hue-rotate', 'backdropHueRotate')
+ // utility('my-backdrop-invert', 'backdropInvert')
+ // utility('my-backdrop-opacity', 'backdropOpacity')
+ // utility('my-backdrop-saturate', 'backdropSaturate')
+ // utility('my-backdrop-sepia', 'backdropSepia')
+ // utility('my-divide-width', 'divideWidth')
+ utility('my-border-width', 'borderWidth')
+ utility('my-brightness', 'brightness')
+ utility('my-columns', 'columns')
+ utility('my-contrast', 'contrast')
+ utility('my-flex-grow', 'flexGrow')
+ utility('my-flex-shrink', 'flexShrink')
+ utility('my-gradient-color-stop-positions', 'gradientColorStopPositions')
+ utility('my-grayscale', 'grayscale')
+ utility('my-grid-row-end', 'gridRowEnd')
+ utility('my-grid-row-start', 'gridRowStart')
+ utility('my-grid-template-columns', 'gridTemplateColumns')
+ utility('my-grid-template-rows', 'gridTemplateRows')
+ utility('my-hue-rotate', 'hueRotate')
+ utility('my-invert', 'invert')
+ utility('my-line-clamp', 'lineClamp')
+ utility('my-opacity', 'opacity')
+ utility('my-order', 'order')
+ utility('my-outline-offset', 'outlineOffset')
+ utility('my-outline-width', 'outlineWidth')
+ utility('my-ring-offset-width', 'ringOffsetWidth')
+ utility('my-ring-width', 'ringWidth')
+ utility('my-rotate', 'rotate')
+ utility('my-saturate', 'saturate')
+ utility('my-scale', 'scale')
+ utility('my-sepia', 'sepia')
+ utility('my-skew', 'skew')
+ utility('my-stroke-width', 'strokeWidth')
+ utility('my-text-decoration-thickness', 'textDecorationThickness')
+ utility('my-text-underline-offset', 'textUnderlineOffset')
+ utility('my-transition-delay', 'transitionDelay')
+ utility('my-transition-duration', 'transitionDuration')
+ utility('my-z-index', 'zIndex')
+ }),
+ }
},
})
@@ -1167,9 +1221,12 @@ describe('addVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
- addVariant('hocus', '&:hover, &:focus')
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ addVariant }: PluginAPI) => {
+ addVariant('hocus', '&:hover, &:focus')
+ },
}
},
},
@@ -1198,9 +1255,12 @@ describe('addVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
- addVariant('hocus', ['&:hover', '&:focus'])
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ addVariant }: PluginAPI) => {
+ addVariant('hocus', ['&:hover', '&:focus'])
+ },
}
},
},
@@ -1230,15 +1290,18 @@ describe('addVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
- addVariant('hocus', {
- '&:hover': '@slot',
- '&:focus': '@slot',
- })
- }
- },
- },
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ addVariant }: PluginAPI) => {
+ addVariant('hocus', {
+ '&:hover': '@slot',
+ '&:focus': '@slot',
+ })
+ },
+ }
+ },
+ },
)
let compiled = build(['hocus:underline', 'group-hocus:flex'])
@@ -1264,14 +1327,17 @@ describe('addVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
- addVariant('hocus', {
- '@media (hover: hover)': {
- '&:hover': '@slot',
- },
- '&:focus': '@slot',
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ addVariant }: PluginAPI) => {
+ addVariant('hocus', {
+ '@media (hover: hover)': {
+ '&:hover': '@slot',
+ },
+ '&:focus': '@slot',
+ })
+ },
}
},
},
@@ -1312,12 +1378,15 @@ describe('addVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
- addVariant(
- 'potato',
- '@media (max-width: 400px) { @supports (font:bold) { &:large-potato } }',
- )
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ addVariant }: PluginAPI) => {
+ addVariant(
+ 'potato',
+ '@media (max-width: 400px) { @supports (font:bold) { &:large-potato } }',
+ )
+ },
}
},
},
@@ -1354,15 +1423,18 @@ describe('addVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
- addVariant('hocus', {
- '&': {
- '--custom-property': '@slot',
- '&:hover': '@slot',
- '&:focus': '@slot',
- },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ addVariant }: PluginAPI) => {
+ addVariant('hocus', {
+ '&': {
+ '--custom-property': '@slot',
+ '&:hover': '@slot',
+ '&:focus': '@slot',
+ },
+ })
+ },
}
},
},
@@ -1393,9 +1465,12 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('potato', (flavor) => `.potato-${flavor} &`)
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('potato', (flavor) => `.potato-${flavor} &`)
+ },
}
},
},
@@ -1424,9 +1499,12 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('potato', (flavor) => `@media (potato: ${flavor})`)
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('potato', (flavor) => `@media (potato: ${flavor})`)
+ },
}
},
},
@@ -1459,12 +1537,16 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant(
- 'potato',
- (flavor) => `@media (potato: ${flavor}) { @supports (font:bold) { &:large-potato } }`,
- )
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant(
+ 'potato',
+ (flavor) =>
+ `@media (potato: ${flavor}) { @supports (font:bold) { &:large-potato } }`,
+ )
+ },
}
},
},
@@ -1501,14 +1583,17 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('tooltip', (side) => `&${side}`, {
- values: {
- bottom: '[data-location="bottom"]',
- top: '[data-location="top"]',
- },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('tooltip', (side) => `&${side}`, {
+ values: {
+ bottom: '[data-location="bottom"]',
+ top: '[data-location="top"]',
+ },
+ })
+ },
}
},
},
@@ -1537,16 +1622,19 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('alphabet', (side) => `&${side}`, {
- values: {
- d: '[data-order="1"]',
- a: '[data-order="2"]',
- c: '[data-order="3"]',
- b: '[data-order="4"]',
- },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('alphabet', (side) => `&${side}`, {
+ values: {
+ d: '[data-order="1"]',
+ a: '[data-order="2"]',
+ c: '[data-order="3"]',
+ b: '[data-order="4"]',
+ },
+ })
+ },
}
},
},
@@ -1588,11 +1676,14 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('test', (selector) =>
- selector.split(',').map((selector) => `&.${selector} > *`),
- )
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('test', (selector) =>
+ selector.split(',').map((selector) => `&.${selector} > *`),
+ )
+ },
}
},
},
@@ -1617,13 +1708,16 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+ },
}
},
},
@@ -1666,16 +1760,19 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- values: {
- example: '600px',
- },
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ values: {
+ example: '600px',
+ },
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+ },
}
},
},
@@ -1718,19 +1815,22 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- return parseInt(z.value) - parseInt(a.value)
- },
- })
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(z.value) - parseInt(a.value)
+ },
+ })
+ },
}
},
},
@@ -1789,19 +1889,22 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- return parseInt(z.value) - parseInt(a.value)
- },
- })
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(z.value) - parseInt(a.value)
+ },
+ })
+ },
}
},
},
@@ -1842,18 +1945,21 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- return parseInt(z.value) - parseInt(a.value)
- },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(z.value) - parseInt(a.value)
+ },
+ })
+ },
}
},
},
@@ -1911,18 +2017,21 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- return parseInt(z.value) - parseInt(a.value)
- },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(z.value) - parseInt(a.value)
+ },
+ })
+ },
}
},
},
@@ -1980,26 +2089,29 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- let lookup = ['100px', '200px']
- if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {
- throw new Error('We are seeing values that should not be there!')
- }
- return lookup.indexOf(a.value) - lookup.indexOf(z.value)
- },
- })
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- let lookup = ['300px', '400px']
- if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {
- throw new Error('We are seeing values that should not be there!')
- }
- return lookup.indexOf(z.value) - lookup.indexOf(a.value)
- },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ let lookup = ['100px', '200px']
+ if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {
+ throw new Error('We are seeing values that should not be there!')
+ }
+ return lookup.indexOf(a.value) - lookup.indexOf(z.value)
+ },
+ })
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ let lookup = ['300px', '400px']
+ if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {
+ throw new Error('We are seeing values that should not be there!')
+ }
+ return lookup.indexOf(z.value) - lookup.indexOf(a.value)
+ },
+ })
+ },
}
},
},
@@ -2057,13 +2169,16 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('foo', (value) => `.foo${value} &`, {
- values: {
- DEFAULT: '.bar',
- },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('foo', (value) => `.foo${value} &`, {
+ values: {
+ DEFAULT: '.bar',
+ },
+ })
+ },
}
},
},
@@ -2088,9 +2203,12 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('foo', (value) => `.foo${value} &`)
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('foo', (value) => `.foo${value} &`)
+ },
}
},
},
@@ -2109,11 +2227,14 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, {
- values: { DEFAULT: null },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, {
+ values: { DEFAULT: null },
+ })
+ },
}
},
},
@@ -2138,11 +2259,14 @@ describe('matchVariant', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ matchVariant }: PluginAPI) => {
- matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, {
- values: { DEFAULT: undefined },
- })
+ loadModule: async (id, base) => {
+ return {
+ base,
+ module: ({ matchVariant }: PluginAPI) => {
+ matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, {
+ values: { DEFAULT: undefined },
+ })
+ },
}
},
},
@@ -2173,14 +2297,17 @@ describe('addUtilities()', () => {
}
`,
{
- async loadPlugin() {
- return ({ addUtilities }: PluginAPI) => {
- addUtilities({
- '.text-trim': {
- 'text-box-trim': 'both',
- 'text-box-edge': 'cap alphabetic',
- },
- })
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ addUtilities }: PluginAPI) => {
+ addUtilities({
+ '.text-trim': {
+ 'text-box-trim': 'both',
+ 'text-box-edge': 'cap alphabetic',
+ },
+ })
+ },
}
},
},
@@ -2211,13 +2338,19 @@ describe('addUtilities()', () => {
@tailwind utilities;
`,
{
- async loadPlugin() {
- return ({ addUtilities }: PluginAPI) => {
- addUtilities([
- {
- '.text-trim': [{ 'text-box-trim': 'both' }, { 'text-box-edge': 'cap alphabetic' }],
- },
- ])
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ addUtilities }: PluginAPI) => {
+ addUtilities([
+ {
+ '.text-trim': [
+ { 'text-box-trim': 'both' },
+ { 'text-box-edge': 'cap alphabetic' },
+ ],
+ },
+ ])
+ },
}
},
},
@@ -2238,22 +2371,25 @@ describe('addUtilities()', () => {
@tailwind utilities;
`,
{
- async loadPlugin() {
- return ({ addUtilities }: PluginAPI) => {
- addUtilities([
- {
- '.text-trim': {
- 'text-box-trim': 'both',
- 'text-box-edge': 'cap alphabetic',
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ addUtilities }: PluginAPI) => {
+ addUtilities([
+ {
+ '.text-trim': {
+ 'text-box-trim': 'both',
+ 'text-box-edge': 'cap alphabetic',
+ },
},
- },
- {
- '.text-trim-2': {
- 'text-box-trim': 'both',
- 'text-box-edge': 'cap alphabetic',
+ {
+ '.text-trim-2': {
+ 'text-box-trim': 'both',
+ 'text-box-edge': 'cap alphabetic',
+ },
},
- },
- ])
+ ])
+ },
}
},
},
@@ -2274,15 +2410,18 @@ describe('addUtilities()', () => {
@tailwind utilities;
`,
{
- async loadPlugin() {
- return ({ addUtilities }: PluginAPI) => {
- addUtilities([
- {
- '.outlined': {
- outline: ['1px solid ButtonText', '1px auto -webkit-focus-ring-color'],
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ addUtilities }: PluginAPI) => {
+ addUtilities([
+ {
+ '.outlined': {
+ outline: ['1px solid ButtonText', '1px auto -webkit-focus-ring-color'],
+ },
},
- },
- ])
+ ])
+ },
}
},
},
@@ -2305,15 +2444,18 @@ describe('addUtilities()', () => {
}
`,
{
- async loadPlugin() {
- return ({ addUtilities }: PluginAPI) => {
- addUtilities({
- '.text-trim': {
- WebkitAppearance: 'none',
- textBoxTrim: 'both',
- textBoxEdge: 'cap alphabetic',
- },
- })
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ addUtilities }: PluginAPI) => {
+ addUtilities({
+ '.text-trim': {
+ WebkitAppearance: 'none',
+ textBoxTrim: 'both',
+ textBoxEdge: 'cap alphabetic',
+ },
+ })
+ },
}
},
},
@@ -2343,13 +2485,16 @@ describe('addUtilities()', () => {
}
`,
{
- async loadPlugin() {
- return ({ addUtilities }: PluginAPI) => {
- addUtilities({
- '.foo': {
- '@apply flex dark:underline': {},
- },
- })
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ addUtilities }: PluginAPI) => {
+ addUtilities({
+ '.foo': {
+ '@apply flex dark:underline': {},
+ },
+ })
+ },
}
},
},
@@ -2396,14 +2541,17 @@ describe('addUtilities()', () => {
}
`,
{
- async loadPlugin() {
- return ({ addUtilities }: PluginAPI) => {
- addUtilities({
- '.text-trim > *': {
- 'text-box-trim': 'both',
- 'text-box-edge': 'cap alphabetic',
- },
- })
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ addUtilities }: PluginAPI) => {
+ addUtilities({
+ '.text-trim > *': {
+ 'text-box-trim': 'both',
+ 'text-box-edge': 'cap alphabetic',
+ },
+ })
+ },
}
},
},
@@ -2422,14 +2570,17 @@ describe('addUtilities()', () => {
}
`,
{
- async loadPlugin() {
- return ({ addUtilities }: PluginAPI) => {
- addUtilities({
- '.form-input, .form-textarea': {
- appearance: 'none',
- 'background-color': '#fff',
- },
- })
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ addUtilities }: PluginAPI) => {
+ addUtilities({
+ '.form-input, .form-textarea': {
+ appearance: 'none',
+ 'background-color': '#fff',
+ },
+ })
+ },
}
},
},
@@ -2462,13 +2613,16 @@ describe('addUtilities()', () => {
}
`,
{
- async loadPlugin() {
- return ({ addUtilities }: PluginAPI) => {
- addUtilities({
- '.form-input, .form-input::placeholder, .form-textarea:hover:focus': {
- 'background-color': 'red',
- },
- })
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ addUtilities }: PluginAPI) => {
+ addUtilities({
+ '.form-input, .form-input::placeholder, .form-textarea:hover:focus': {
+ 'background-color': 'red',
+ },
+ })
+ },
}
},
},
@@ -2506,20 +2660,24 @@ describe('matchUtilities()', () => {
--breakpoint-lg: 1024px;
}
`,
+
{
- async loadPlugin() {
- return ({ matchUtilities }: PluginAPI) => {
- matchUtilities(
- {
- 'border-block': (value) => ({ 'border-block-width': value }),
- },
- {
- values: {
- DEFAULT: '1px',
- '2': '2px',
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ matchUtilities }: PluginAPI) => {
+ matchUtilities(
+ {
+ 'border-block': (value) => ({ 'border-block-width': value }),
},
- },
- )
+ {
+ values: {
+ DEFAULT: '1px',
+ '2': '2px',
+ },
+ },
+ )
+ },
}
},
},
@@ -2582,23 +2740,26 @@ describe('matchUtilities()', () => {
@tailwind utilities;
`,
{
- async loadPlugin() {
- return ({ matchUtilities }: PluginAPI) => {
- matchUtilities(
- {
- 'all-but-order-bottom-left-radius': (value) =>
- [
- { 'border-top-left-radius': value },
- { 'border-top-right-radius': value },
- { 'border-bottom-right-radius': value },
- ] as CssInJs[],
- },
- {
- values: {
- DEFAULT: '1px',
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ matchUtilities }: PluginAPI) => {
+ matchUtilities(
+ {
+ 'all-but-order-bottom-left-radius': (value) =>
+ [
+ { 'border-top-left-radius': value },
+ { 'border-top-right-radius': value },
+ { 'border-bottom-right-radius': value },
+ ] as CssInJs[],
},
- },
- )
+ {
+ values: {
+ DEFAULT: '1px',
+ },
+ },
+ )
+ },
}
},
},
@@ -2626,25 +2787,29 @@ describe('matchUtilities()', () => {
--breakpoint-lg: 1024px;
}
`,
+
{
- async loadPlugin() {
- return ({ matchUtilities }: PluginAPI) => {
- matchUtilities(
- {
- 'border-block': (value, { modifier }) => ({
- '--my-modifier': modifier ?? 'none',
- 'border-block-width': value,
- }),
- },
- {
- values: {
- DEFAULT: '1px',
- '2': '2px',
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ matchUtilities }: PluginAPI) => {
+ matchUtilities(
+ {
+ 'border-block': (value, { modifier }) => ({
+ '--my-modifier': modifier ?? 'none',
+ 'border-block-width': value,
+ }),
},
+ {
+ values: {
+ DEFAULT: '1px',
+ '2': '2px',
+ },
- modifiers: 'any',
- },
- )
+ modifiers: 'any',
+ },
+ )
+ },
}
},
},
@@ -2692,27 +2857,31 @@ describe('matchUtilities()', () => {
--breakpoint-lg: 1024px;
}
`,
+
{
- async loadPlugin() {
- return ({ matchUtilities }: PluginAPI) => {
- matchUtilities(
- {
- 'border-block': (value, { modifier }) => ({
- '--my-modifier': modifier ?? 'none',
- 'border-block-width': value,
- }),
- },
- {
- values: {
- DEFAULT: '1px',
- '2': '2px',
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ matchUtilities }: PluginAPI) => {
+ matchUtilities(
+ {
+ 'border-block': (value, { modifier }) => ({
+ '--my-modifier': modifier ?? 'none',
+ 'border-block-width': value,
+ }),
},
+ {
+ values: {
+ DEFAULT: '1px',
+ '2': '2px',
+ },
- modifiers: {
- foo: 'foo',
+ modifiers: {
+ foo: 'foo',
+ },
},
- },
- )
+ )
+ },
}
},
},
@@ -2762,22 +2931,26 @@ describe('matchUtilities()', () => {
@tailwind utilities;
@plugin "my-plugin";
`,
+
{
- async loadPlugin() {
- return ({ matchUtilities }: PluginAPI) => {
- matchUtilities(
- {
- scrollbar: (value) => ({ 'scrollbar-color': value }),
- },
- { type: ['color', 'any'] },
- )
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ matchUtilities }: PluginAPI) => {
+ matchUtilities(
+ {
+ scrollbar: (value) => ({ 'scrollbar-color': value }),
+ },
+ { type: ['color', 'any'] },
+ )
- matchUtilities(
- {
- scrollbar: (value) => ({ 'scrollbar-width': value }),
- },
- { type: ['length'] },
- )
+ matchUtilities(
+ {
+ scrollbar: (value) => ({ 'scrollbar-width': value }),
+ },
+ { type: ['length'] },
+ )
+ },
}
},
},
@@ -2813,22 +2986,26 @@ describe('matchUtilities()', () => {
@tailwind utilities;
@plugin "my-plugin";
`,
+
{
- async loadPlugin() {
- return ({ matchUtilities }: PluginAPI) => {
- matchUtilities(
- {
- scrollbar: (value) => ({ '--scrollbar-angle': value }),
- },
- { type: ['angle', 'any'] },
- )
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ matchUtilities }: PluginAPI) => {
+ matchUtilities(
+ {
+ scrollbar: (value) => ({ '--scrollbar-angle': value }),
+ },
+ { type: ['angle', 'any'] },
+ )
- matchUtilities(
- {
- scrollbar: (value) => ({ '--scrollbar-width': value }),
- },
- { type: ['length'] },
- )
+ matchUtilities(
+ {
+ scrollbar: (value) => ({ '--scrollbar-width': value }),
+ },
+ { type: ['length'] },
+ )
+ },
}
},
},
@@ -2847,22 +3024,26 @@ describe('matchUtilities()', () => {
@tailwind utilities;
@plugin "my-plugin";
`,
+
{
- async loadPlugin() {
- return ({ matchUtilities }: PluginAPI) => {
- matchUtilities(
- {
- scrollbar: (value) => ({ 'scrollbar-color': value }),
- },
- { type: ['color', 'any'], modifiers: { foo: 'foo' } },
- )
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ matchUtilities }: PluginAPI) => {
+ matchUtilities(
+ {
+ scrollbar: (value) => ({ 'scrollbar-color': value }),
+ },
+ { type: ['color', 'any'], modifiers: { foo: 'foo' } },
+ )
- matchUtilities(
- {
- scrollbar: (value) => ({ 'scrollbar-width': value }),
- },
- { type: ['length'], modifiers: { bar: 'bar' } },
- )
+ matchUtilities(
+ {
+ scrollbar: (value) => ({ 'scrollbar-width': value }),
+ },
+ { type: ['length'], modifiers: { bar: 'bar' } },
+ )
+ },
}
},
},
@@ -2887,32 +3068,36 @@ describe('matchUtilities()', () => {
--breakpoint-lg: 1024px;
}
`,
+
{
- async loadPlugin() {
- return ({ matchUtilities }: PluginAPI) => {
- matchUtilities(
- {
- scrollbar: (value) => ({ 'scrollbar-color': value }),
- },
- {
- type: ['color', 'any'],
- values: {
- black: 'black',
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ matchUtilities }: PluginAPI) => {
+ matchUtilities(
+ {
+ scrollbar: (value) => ({ 'scrollbar-color': value }),
},
- },
- )
+ {
+ type: ['color', 'any'],
+ values: {
+ black: 'black',
+ },
+ },
+ )
- matchUtilities(
- {
- scrollbar: (value) => ({ 'scrollbar-width': value }),
- },
- {
- type: ['length'],
- values: {
- 2: '2px',
+ matchUtilities(
+ {
+ scrollbar: (value) => ({ 'scrollbar-width': value }),
},
- },
- )
+ {
+ type: ['length'],
+ values: {
+ 2: '2px',
+ },
+ },
+ )
+ },
}
},
},
@@ -3006,20 +3191,24 @@ describe('matchUtilities()', () => {
--breakpoint-lg: 1024px;
}
`,
+
{
- async loadPlugin() {
- return ({ matchUtilities }: PluginAPI) => {
- matchUtilities(
- {
- scrollbar: (value) => ({ 'scrollbar-color': value }),
- },
- {
- type: ['color', 'any'],
- values: {
- black: 'black',
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ matchUtilities }: PluginAPI) => {
+ matchUtilities(
+ {
+ scrollbar: (value) => ({ 'scrollbar-color': value }),
},
- },
- )
+ {
+ type: ['color', 'any'],
+ values: {
+ black: 'black',
+ },
+ },
+ )
+ },
}
},
},
@@ -3079,24 +3268,28 @@ describe('matchUtilities()', () => {
--opacity-my-opacity: 0.5;
}
`,
+
{
- async loadPlugin() {
- return ({ matchUtilities }: PluginAPI) => {
- matchUtilities(
- {
- scrollbar: (value, { modifier }) => ({
- '--modifier': modifier ?? 'none',
- 'scrollbar-width': value,
- }),
- },
- {
- type: ['any'],
- values: {},
- modifiers: {
- foo: 'foo',
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ matchUtilities }: PluginAPI) => {
+ matchUtilities(
+ {
+ scrollbar: (value, { modifier }) => ({
+ '--modifier': modifier ?? 'none',
+ 'scrollbar-width': value,
+ }),
},
- },
- )
+ {
+ type: ['any'],
+ values: {},
+ modifiers: {
+ foo: 'foo',
+ },
+ },
+ )
+ },
}
},
},
@@ -3135,21 +3328,24 @@ describe('matchUtilities()', () => {
}
`,
{
- async loadPlugin() {
- return ({ matchUtilities }: PluginAPI) => {
- matchUtilities(
- {
- foo: (value) => ({
- '--foo': value,
- [`@apply flex`]: {},
- }),
- },
- {
- values: {
- bar: 'bar',
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ matchUtilities }: PluginAPI) => {
+ matchUtilities(
+ {
+ foo: (value) => ({
+ '--foo': value,
+ [`@apply flex`]: {},
+ }),
},
- },
- )
+ {
+ values: {
+ bar: 'bar',
+ },
+ },
+ )
+ },
}
},
},
@@ -3199,15 +3395,19 @@ describe('matchUtilities()', () => {
--breakpoint-lg: 1024px;
}
`,
+
{
- async loadPlugin() {
- return ({ matchUtilities }: PluginAPI) => {
- matchUtilities({
- '.text-trim > *': () => ({
- 'text-box-trim': 'both',
- 'text-box-edge': 'cap alphabetic',
- }),
- })
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ matchUtilities }: PluginAPI) => {
+ matchUtilities({
+ '.text-trim > *': () => ({
+ 'text-box-trim': 'both',
+ 'text-box-edge': 'cap alphabetic',
+ }),
+ })
+ },
}
},
},
@@ -3224,29 +3424,32 @@ describe('addComponents()', () => {
@tailwind utilities;
`,
{
- async loadPlugin() {
- return ({ addComponents }: PluginAPI) => {
- addComponents({
- '.btn': {
- padding: '.5rem 1rem',
- borderRadius: '.25rem',
- fontWeight: '600',
- },
- '.btn-blue': {
- backgroundColor: '#3490dc',
- color: '#fff',
- '&:hover': {
- backgroundColor: '#2779bd',
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ addComponents }: PluginAPI) => {
+ addComponents({
+ '.btn': {
+ padding: '.5rem 1rem',
+ borderRadius: '.25rem',
+ fontWeight: '600',
},
- },
- '.btn-red': {
- backgroundColor: '#e3342f',
- color: '#fff',
- '&:hover': {
- backgroundColor: '#cc1f1a',
+ '.btn-blue': {
+ backgroundColor: '#3490dc',
+ color: '#fff',
+ '&:hover': {
+ backgroundColor: '#2779bd',
+ },
},
- },
- })
+ '.btn-red': {
+ backgroundColor: '#e3342f',
+ color: '#fff',
+ '&:hover': {
+ backgroundColor: '#cc1f1a',
+ },
+ },
+ })
+ },
}
},
},
@@ -3289,9 +3492,12 @@ describe('prefix()', () => {
@plugin "my-plugin";
`,
{
- async loadPlugin() {
- return ({ prefix }: PluginAPI) => {
- fn(prefix('btn'))
+ async loadModule(id, base) {
+ return {
+ base,
+ module: ({ prefix }: PluginAPI) => {
+ fn(prefix('btn'))
+ },
}
},
},
diff --git a/packages/tailwindcss/src/compat/screens-config.test.ts b/packages/tailwindcss/src/compat/screens-config.test.ts
index 05f725a09b53..5f75504ad966 100644
--- a/packages/tailwindcss/src/compat/screens-config.test.ts
+++ b/packages/tailwindcss/src/compat/screens-config.test.ts
@@ -20,14 +20,17 @@ test('CSS `--breakpoint-*` merge with JS config `screens`', async () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- extend: {
- screens: {
- sm: '44rem',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ extend: {
+ screens: {
+ sm: '44rem',
+ },
},
},
},
+ base: '/root',
}),
})
@@ -100,17 +103,20 @@ test('JS config `screens` extend CSS `--breakpoint-*`', async () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- extend: {
- screens: {
- xs: '30rem',
- sm: '40rem',
- md: '48rem',
- lg: '60rem',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ extend: {
+ screens: {
+ xs: '30rem',
+ sm: '40rem',
+ md: '48rem',
+ lg: '60rem',
+ },
},
},
},
+ base: '/root',
}),
})
@@ -195,14 +201,17 @@ test('JS config `screens` only setup, even if those match the default-theme expo
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- screens: {
- sm: '40rem',
- md: '48rem',
- lg: '64rem',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ screens: {
+ sm: '40rem',
+ md: '48rem',
+ lg: '64rem',
+ },
},
},
+ base: '/root',
}),
})
@@ -271,14 +280,17 @@ test('JS config `screens` overwrite CSS `--breakpoint-*`', async () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- screens: {
- mini: '40rem',
- midi: '48rem',
- maxi: '64rem',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ screens: {
+ mini: '40rem',
+ midi: '48rem',
+ maxi: '64rem',
+ },
},
},
+ base: '/root',
}),
})
@@ -374,16 +386,19 @@ test('JS config with `theme: { extends }` should not include the `default-config
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- extend: {
- screens: {
- mini: '40rem',
- midi: '48rem',
- maxi: '64rem',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ extend: {
+ screens: {
+ mini: '40rem',
+ midi: '48rem',
+ maxi: '64rem',
+ },
},
},
},
+ base: '/root',
}),
})
@@ -449,22 +464,25 @@ describe('complex screen configs', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- extend: {
- screens: {
- sm: { max: '639px' },
- md: [
- //
- { min: '668px', max: '767px' },
- '868px',
- ],
- lg: { min: '868px' },
- xl: { min: '1024px', max: '1279px' },
- tall: { raw: '(min-height: 800px)' },
+ loadModule: async () => ({
+ module: {
+ theme: {
+ extend: {
+ screens: {
+ sm: { max: '639px' },
+ md: [
+ //
+ { min: '668px', max: '767px' },
+ '868px',
+ ],
+ lg: { min: '868px' },
+ xl: { min: '1024px', max: '1279px' },
+ tall: { raw: '(min-height: 800px)' },
+ },
},
},
},
+ base: '/root',
}),
})
@@ -533,15 +551,18 @@ describe('complex screen configs', () => {
`
let compiler = await compile(input, {
- loadConfig: async () => ({
- theme: {
- extend: {
- screens: {
- sm: '40rem',
- portrait: { raw: 'screen and (orientation: portrait)' },
+ loadModule: async () => ({
+ module: {
+ theme: {
+ extend: {
+ screens: {
+ sm: '40rem',
+ portrait: { raw: 'screen and (orientation: portrait)' },
+ },
},
},
},
+ base: '/root',
}),
})
diff --git a/packages/tailwindcss/src/css-functions.test.ts b/packages/tailwindcss/src/css-functions.test.ts
index b40dc9a2b3ce..521f50009852 100644
--- a/packages/tailwindcss/src/css-functions.test.ts
+++ b/packages/tailwindcss/src/css-functions.test.ts
@@ -336,7 +336,7 @@ describe('theme function', () => {
}
`,
{
- loadConfig: async () => ({}),
+ loadModule: async () => ({ module: {}, base: '/root' }),
},
)
@@ -795,23 +795,26 @@ describe('in plugins', () => {
}
`,
{
- async loadPlugin() {
- return plugin(({ addBase, addUtilities }) => {
- addBase({
- '.my-base-rule': {
- color: 'theme(colors.red)',
- 'outline-color': 'theme(colors.orange / 15%)',
- 'background-color': 'theme(--color-blue)',
- 'border-color': 'theme(--color-pink / 10%)',
- },
- })
+ async loadModule() {
+ return {
+ module: plugin(({ addBase, addUtilities }) => {
+ addBase({
+ '.my-base-rule': {
+ color: 'theme(colors.red)',
+ 'outline-color': 'theme(colors.orange / 15%)',
+ 'background-color': 'theme(--color-blue)',
+ 'border-color': 'theme(--color-pink / 10%)',
+ },
+ })
- addUtilities({
- '.my-utility': {
- color: 'theme(colors.red)',
- },
- })
- })
+ addUtilities({
+ '.my-utility': {
+ color: 'theme(colors.red)',
+ },
+ })
+ }),
+ base: '/root',
+ }
},
},
)
@@ -850,31 +853,34 @@ describe('in JS config files', () => {
}
`,
{
- loadConfig: async () => ({
- theme: {
- extend: {
- colors: {
- primary: 'theme(colors.red)',
- secondary: 'theme(--color-orange)',
+ loadModule: async () => ({
+ module: {
+ theme: {
+ extend: {
+ colors: {
+ primary: 'theme(colors.red)',
+ secondary: 'theme(--color-orange)',
+ },
},
},
+ plugins: [
+ plugin(({ addBase, addUtilities }) => {
+ addBase({
+ '.my-base-rule': {
+ background: 'theme(colors.primary)',
+ color: 'theme(colors.secondary)',
+ },
+ })
+
+ addUtilities({
+ '.my-utility': {
+ color: 'theme(colors.red)',
+ },
+ })
+ }),
+ ],
},
- plugins: [
- plugin(({ addBase, addUtilities }) => {
- addBase({
- '.my-base-rule': {
- background: 'theme(colors.primary)',
- color: 'theme(colors.secondary)',
- },
- })
-
- addUtilities({
- '.my-utility': {
- color: 'theme(colors.red)',
- },
- })
- }),
- ],
+ base: '/root',
}),
},
)
diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts
index fb226708e689..3693bbb0e878 100644
--- a/packages/tailwindcss/src/index.test.ts
+++ b/packages/tailwindcss/src/index.test.ts
@@ -1429,17 +1429,20 @@ describe('Parsing themes values from CSS', () => {
@tailwind utilities;
`,
{
- loadPlugin: async () => {
- return plugin(({}) => {}, {
- theme: {
- extend: {
- colors: {
- red: 'tomato',
- orange: '#f28500',
+ loadModule: async () => {
+ return {
+ module: plugin(({}) => {}, {
+ theme: {
+ extend: {
+ colors: {
+ red: 'tomato',
+ orange: '#f28500',
+ },
},
},
- },
- })
+ }),
+ base: '/root',
+ }
},
},
)
@@ -1472,16 +1475,19 @@ describe('Parsing themes values from CSS', () => {
@tailwind utilities;
`,
{
- loadConfig: async () => {
+ loadModule: async () => {
return {
- theme: {
- extend: {
- colors: {
- red: 'tomato',
- orange: '#f28500',
+ module: {
+ theme: {
+ extend: {
+ colors: {
+ red: 'tomato',
+ orange: '#f28500',
+ },
},
},
},
+ base: '/root',
}
},
},
@@ -1511,11 +1517,12 @@ describe('plugins', () => {
@plugin;
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
+ loadModule: async () => ({
+ module: ({ addVariant }: PluginAPI) => {
addVariant('hocus', '&:hover, &:focus')
- }
- },
+ },
+ base: '/root',
+ }),
},
),
).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: \`@plugin\` must have a path.]`))
@@ -1527,11 +1534,12 @@ describe('plugins', () => {
@plugin '';
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
+ loadModule: async () => ({
+ module: ({ addVariant }: PluginAPI) => {
addVariant('hocus', '&:hover, &:focus')
- }
- },
+ },
+ base: '/root',
+ }),
},
),
).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: \`@plugin\` must have a path.]`))
@@ -1545,11 +1553,12 @@ describe('plugins', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
+ loadModule: async () => ({
+ module: ({ addVariant }: PluginAPI) => {
addVariant('hocus', '&:hover, &:focus')
- }
- },
+ },
+ base: '/root',
+ }),
},
),
).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: \`@plugin\` cannot be nested.]`))
@@ -1565,8 +1574,8 @@ describe('plugins', () => {
}
`,
{
- loadPlugin: async () => {
- return plugin.withOptions((options) => {
+ loadModule: async () => ({
+ module: plugin.withOptions((options) => {
expect(options).toEqual({
color: 'red',
})
@@ -1578,8 +1587,9 @@ describe('plugins', () => {
},
})
}
- })
- },
+ }),
+ base: '/root',
+ }),
},
)
@@ -1616,8 +1626,8 @@ describe('plugins', () => {
}
`,
{
- loadPlugin: async () => {
- return plugin.withOptions((options) => {
+ loadModule: async () => ({
+ module: plugin.withOptions((options) => {
expect(options).toEqual({
'is-null': null,
'is-true': true,
@@ -1636,8 +1646,9 @@ describe('plugins', () => {
})
return () => {}
- })
- },
+ }),
+ base: '/root',
+ }),
},
)
})
@@ -1655,8 +1666,8 @@ describe('plugins', () => {
}
`,
{
- loadPlugin: async () => {
- return plugin.withOptions((options) => {
+ loadModule: async () => ({
+ module: plugin.withOptions((options) => {
return ({ addUtilities }) => {
addUtilities({
'.text-primary': {
@@ -1664,8 +1675,9 @@ describe('plugins', () => {
},
})
}
- })
- },
+ }),
+ base: '/root',
+ }),
},
),
).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -1692,15 +1704,16 @@ describe('plugins', () => {
}
`,
{
- loadPlugin: async () => {
- return plugin(({ addUtilities }) => {
+ loadModule: async () => ({
+ module: plugin(({ addUtilities }) => {
addUtilities({
'.text-primary': {
color: 'red',
},
})
- })
- },
+ }),
+ base: '/root',
+ }),
},
),
).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -1717,7 +1730,7 @@ describe('plugins', () => {
}
`,
{
- loadPlugin: async () => plugin(() => {}),
+ loadModule: async () => ({ module: plugin(() => {}), base: '/root' }),
},
),
).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -1738,7 +1751,7 @@ describe('plugins', () => {
}
`,
{
- loadPlugin: async () => plugin(() => {}),
+ loadModule: async () => ({ module: plugin(() => {}), base: '/root' }),
},
),
).rejects.toThrowErrorMatchingInlineSnapshot(
@@ -1763,11 +1776,12 @@ describe('plugins', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
+ loadModule: async () => ({
+ module: ({ addVariant }: PluginAPI) => {
addVariant('hocus', '&:hover, &:focus')
- }
- },
+ },
+ base: '/root',
+ }),
},
)
let compiled = build(['hocus:underline', 'group-hocus:flex'])
@@ -1794,11 +1808,12 @@ describe('plugins', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
+ loadModule: async () => ({
+ module: ({ addVariant }: PluginAPI) => {
addVariant('hocus', ['&:hover', '&:focus'])
- }
- },
+ },
+ base: '/root',
+ }),
},
)
@@ -1826,14 +1841,15 @@ describe('plugins', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
+ loadModule: async () => ({
+ module: ({ addVariant }: PluginAPI) => {
addVariant('hocus', {
'&:hover': '@slot',
'&:focus': '@slot',
})
- }
- },
+ },
+ base: '/root',
+ }),
},
)
let compiled = build(['hocus:underline', 'group-hocus:flex'])
@@ -1860,16 +1876,17 @@ describe('plugins', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
+ loadModule: async () => ({
+ module: ({ addVariant }: PluginAPI) => {
addVariant('hocus', {
'@media (hover: hover)': {
'&:hover': '@slot',
},
'&:focus': '@slot',
})
- }
- },
+ },
+ base: '/root',
+ }),
},
)
let compiled = build(['hocus:underline', 'group-hocus:flex'])
@@ -1908,8 +1925,8 @@ describe('plugins', () => {
}
`,
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
+ loadModule: async () => ({
+ module: ({ addVariant }: PluginAPI) => {
addVariant('hocus', {
'&': {
'--custom-property': '@slot',
@@ -1917,8 +1934,9 @@ describe('plugins', () => {
'&:focus': '@slot',
},
})
- }
- },
+ },
+ base: '/root',
+ }),
},
)
let compiled = build(['hocus:underline'])
@@ -1944,13 +1962,13 @@ describe('plugins', () => {
@tailwind utilities;
}
`,
-
{
- loadPlugin: async () => {
- return ({ addVariant }: PluginAPI) => {
+ loadModule: async () => ({
+ module: ({ addVariant }: PluginAPI) => {
addVariant('dark', '&:is([data-theme=dark] *)')
- }
- },
+ },
+ base: '/root',
+ }),
},
)
let compiled = build(
@@ -1981,20 +1999,29 @@ describe('plugins', () => {
describe('@source', () => {
test('emits @source files', async () => {
- let { globs } = await compile(css`
- @source "./foo/bar/*.ts";
- `)
+ let { globs } = await compile(
+ css`
+ @source "./foo/bar/*.ts";
+ `,
+ { base: '/root' },
+ )
- expect(globs).toEqual([{ pattern: './foo/bar/*.ts' }])
+ expect(globs).toEqual([{ pattern: './foo/bar/*.ts', base: '/root' }])
})
test('emits multiple @source files', async () => {
- let { globs } = await compile(css`
- @source "./foo/**/*.ts";
- @source "./php/secr3t/smarty.php";
- `)
+ let { globs } = await compile(
+ css`
+ @source "./foo/**/*.ts";
+ @source "./php/secr3t/smarty.php";
+ `,
+ { base: '/root' },
+ )
- expect(globs).toEqual([{ pattern: './foo/**/*.ts' }, { pattern: './php/secr3t/smarty.php' }])
+ expect(globs).toEqual([
+ { pattern: './foo/**/*.ts', base: '/root' },
+ { pattern: './php/secr3t/smarty.php', base: '/root' },
+ ])
})
})
@@ -2513,17 +2540,17 @@ test('addBase', async () => {
@tailwind utilities;
}
`,
-
{
- loadPlugin: async () => {
- return ({ addBase }: PluginAPI) => {
+ loadModule: async () => ({
+ module: ({ addBase }: PluginAPI) => {
addBase({
body: {
'font-feature-settings': '"tnum"',
},
})
- }
- },
+ },
+ base: '/root',
+ }),
},
)
diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts
index ad441084c28c..6ded20cd4dc0 100644
--- a/packages/tailwindcss/src/index.ts
+++ b/packages/tailwindcss/src/index.ts
@@ -1,6 +1,17 @@
import { version } from '../package.json'
import { substituteAtApply } from './apply'
-import { comment, decl, rule, toCss, walk, WalkAction, type Rule } from './ast'
+import {
+ comment,
+ context,
+ decl,
+ rule,
+ toCss,
+ walk,
+ WalkAction,
+ type AstNode,
+ type Rule,
+} from './ast'
+import { substituteAtImports } from './at-import'
import { applyCompatibilityHooks } from './compat/apply-compat-hooks'
import type { UserConfig } from './compat/config/types'
import { type Plugin } from './compat/plugin-api'
@@ -15,16 +26,21 @@ export type Config = UserConfig
const IS_VALID_UTILITY_NAME = /^[a-z][a-zA-Z0-9/%._-]*$/
type CompileOptions = {
- loadPlugin?: (path: string) => Promise
- loadConfig?: (path: string) => Promise
+ base?: string
+ loadModule?: (
+ id: string,
+ base: string,
+ resourceHint: 'plugin' | 'config',
+ ) => Promise<{ module: Plugin | Config; base: string }>
+ loadStylesheet?: (id: string, base: string) => Promise<{ content: string; base: string }>
}
-function throwOnPlugin(): never {
- throw new Error('No `loadPlugin` function provided to `compile`')
+function throwOnLoadModule(): never {
+ throw new Error('No `loadModule` function provided to `compile`')
}
-function throwOnConfig(): never {
- throw new Error('No `loadConfig` function provided to `compile`')
+function throwOnLoadStylesheet(): never {
+ throw new Error('No `loadStylesheet` function provided to `compile`')
}
function parseThemeOptions(selector: string) {
@@ -45,9 +61,15 @@ function parseThemeOptions(selector: string) {
async function parseCss(
css: string,
- { loadPlugin = throwOnPlugin, loadConfig = throwOnConfig }: CompileOptions = {},
+ {
+ base = '',
+ loadModule = throwOnLoadModule,
+ loadStylesheet = throwOnLoadStylesheet,
+ }: CompileOptions = {},
) {
- let ast = CSS.parse(css)
+ let ast = [context({ base }, CSS.parse(css))] as AstNode[]
+
+ await substituteAtImports(ast, base, loadStylesheet)
// Find all `@theme` declarations
let theme = new Theme()
@@ -55,9 +77,9 @@ async function parseCss(
let customUtilities: ((designSystem: DesignSystem) => void)[] = []
let firstThemeRule: Rule | null = null
let keyframesRules: Rule[] = []
- let globs: { origin?: string; pattern: string }[] = []
+ let globs: { base: string; pattern: string }[] = []
- walk(ast, (node, { parent, replaceWith }) => {
+ walk(ast, (node, { parent, replaceWith, context }) => {
if (node.kind !== 'rule') return
// Collect custom `@utility` at-rules
@@ -104,7 +126,7 @@ async function parseCss(
) {
throw new Error('`@source` paths must be quoted.')
}
- globs.push({ pattern: path.slice(1, -1) })
+ globs.push({ base: context.base, pattern: path.slice(1, -1) })
replaceWith([])
return
}
@@ -234,7 +256,7 @@ 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, ast, loadPlugin, loadConfig, globs })
+ await applyCompatibilityHooks({ designSystem, base, ast, loadModule, globs })
for (let customVariant of customVariants) {
customVariant(designSystem)
@@ -316,7 +338,7 @@ export async function compile(
css: string,
opts: CompileOptions = {},
): Promise<{
- globs: { origin?: string; pattern: string }[]
+ globs: { base: string; pattern: string }[]
build(candidates: string[]): string
}> {
let { designSystem, ast, globs } = await parseCss(css, opts)
diff --git a/packages/tailwindcss/src/plugin.test.ts b/packages/tailwindcss/src/plugin.test.ts
index 5af7ff1d4599..9af3a55fbb58 100644
--- a/packages/tailwindcss/src/plugin.test.ts
+++ b/packages/tailwindcss/src/plugin.test.ts
@@ -10,15 +10,16 @@ test('plugin', async () => {
`
let compiler = await compile(input, {
- loadPlugin: async () => {
- return plugin(function ({ addBase }) {
+ loadModule: async () => ({
+ module: plugin(function ({ addBase }) {
addBase({
body: {
margin: '0',
},
})
- })
- },
+ }),
+ base: '/root',
+ }),
})
expect(compiler.build([])).toMatchInlineSnapshot(`
@@ -37,8 +38,8 @@ test('plugin.withOptions', async () => {
`
let compiler = await compile(input, {
- loadPlugin: async () => {
- return plugin.withOptions(function (opts = { foo: '1px' }) {
+ loadModule: async () => ({
+ module: plugin.withOptions(function (opts = { foo: '1px' }) {
return function ({ addBase }) {
addBase({
body: {
@@ -46,8 +47,9 @@ test('plugin.withOptions', async () => {
},
})
}
- })
- },
+ }),
+ base: '/root',
+ }),
})
expect(compiler.build([])).toMatchInlineSnapshot(`
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3735d13ea5d4..7a1f10b82655 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -156,25 +156,15 @@ importers:
picocolors:
specifier: ^1.0.1
version: 1.0.1
- postcss:
- specifier: ^8.4.41
- version: 8.4.41
- postcss-import:
- specifier: ^16.1.0
- version: 16.1.0(postcss@8.4.41)
tailwindcss:
specifier: workspace:^
version: link:../tailwindcss
- devDependencies:
- '@types/postcss-import':
- specifier: ^14.0.3
- version: 14.0.3
- internal-postcss-fix-relative-paths:
- specifier: workspace:^
- version: link:../internal-postcss-fix-relative-paths
packages/@tailwindcss-node:
dependencies:
+ enhanced-resolve:
+ specifier: ^5.17.1
+ version: 5.17.1
jiti:
specifier: ^2.0.0-beta.3
version: 2.0.0-beta.3
@@ -194,9 +184,6 @@ importers:
lightningcss:
specifier: 'catalog:'
version: 1.26.0(patch_hash=5hwfyehqvg5wjb7mwtdvubqbl4)
- postcss-import:
- specifier: ^16.1.0
- version: 16.1.0(postcss@8.4.41)
tailwindcss:
specifier: workspace:^
version: link:../tailwindcss
@@ -205,17 +192,17 @@ importers:
specifier: 'catalog:'
version: 20.14.13
'@types/postcss-import':
- specifier: ^14.0.3
+ specifier: 14.0.3
version: 14.0.3
internal-example-plugin:
specifier: workspace:*
version: link:../internal-example-plugin
- internal-postcss-fix-relative-paths:
- specifier: workspace:^
- version: link:../internal-postcss-fix-relative-paths
postcss:
specifier: ^8.4.41
version: 8.4.41
+ postcss-import:
+ specifier: ^16.1.0
+ version: 16.1.0(postcss@8.4.41)
packages/@tailwindcss-standalone:
dependencies:
@@ -326,12 +313,6 @@ importers:
lightningcss:
specifier: 'catalog:'
version: 1.26.0(patch_hash=5hwfyehqvg5wjb7mwtdvubqbl4)
- postcss:
- specifier: ^8.4.41
- version: 8.4.41
- postcss-import:
- specifier: ^16.1.0
- version: 16.1.0(postcss@8.4.41)
tailwindcss:
specifier: workspace:^
version: link:../tailwindcss
@@ -339,33 +320,12 @@ importers:
'@types/node':
specifier: 'catalog:'
version: 20.14.13
- '@types/postcss-import':
- specifier: ^14.0.3
- version: 14.0.3
- internal-postcss-fix-relative-paths:
- specifier: workspace:^
- version: link:../internal-postcss-fix-relative-paths
vite:
specifier: 'catalog:'
version: 5.4.0(@types/node@20.14.13)(lightningcss@1.26.0(patch_hash=5hwfyehqvg5wjb7mwtdvubqbl4))(terser@5.31.6)
packages/internal-example-plugin: {}
- packages/internal-postcss-fix-relative-paths:
- devDependencies:
- '@types/node':
- specifier: 'catalog:'
- version: 20.14.13
- '@types/postcss-import':
- specifier: ^14.0.3
- version: 14.0.3
- postcss:
- specifier: 8.4.41
- version: 8.4.41
- postcss-import:
- specifier: ^16.1.0
- version: 16.1.0(postcss@8.4.41)
-
packages/tailwindcss:
devDependencies:
'@tailwindcss/oxide':
@@ -374,6 +334,9 @@ importers:
'@types/node':
specifier: 'catalog:'
version: 20.14.13
+ dedent:
+ specifier: 1.5.3
+ version: 1.5.3
lightningcss:
specifier: 'catalog:'
version: 1.26.0(patch_hash=5hwfyehqvg5wjb7mwtdvubqbl4)
@@ -1055,7 +1018,6 @@ packages:
'@parcel/watcher-darwin-arm64@2.4.2-alpha.0':
resolution: {integrity: sha512-2xH4Ve7OKjIh+4YRfTN3HGJa2W8KTPLOALHZj5fxcbTPwaVxdpIRItDrcikUx2u3AzGAFme7F+AZZXHnf0F15Q==}
engines: {node: '>= 10.0.0'}
- cpu: [arm64]
os: [darwin]
'@parcel/watcher-darwin-x64@2.4.1':
@@ -1067,7 +1029,6 @@ packages:
'@parcel/watcher-darwin-x64@2.4.2-alpha.0':
resolution: {integrity: sha512-xtjmXUH4YZVah5+7Q0nb+fpRP5qZn9cFfuPuZ4k77UfUGVwhacgZyIRQgIOwMP3GkgW4TsrKQaw1KIe7L1ZqcQ==}
engines: {node: '>= 10.0.0'}
- cpu: [x64]
os: [darwin]
'@parcel/watcher-freebsd-x64@2.4.1':
@@ -1091,7 +1052,6 @@ packages:
'@parcel/watcher-linux-arm64-glibc@2.4.2-alpha.0':
resolution: {integrity: sha512-vIIOcZf+fgsRReIK3Fw0WINvGo9UwiXfisnqYRzfpNByRZvkEPkGTIVe8iiDp72NhPTVmwIvBqM6yKDzIaw8GQ==}
engines: {node: '>= 10.0.0'}
- cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-arm64-musl@2.4.1':
@@ -1103,7 +1063,6 @@ packages:
'@parcel/watcher-linux-arm64-musl@2.4.2-alpha.0':
resolution: {integrity: sha512-gXqEAoLG9bBCbQNUgqjSOxHcjpmCZmYT9M8UvrdTMgMYgXgiWcR8igKlPRd40mCIRZSkMpN2ScSy2WjQ0bQZnQ==}
engines: {node: '>= 10.0.0'}
- cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-x64-glibc@2.4.1':
@@ -1115,7 +1074,6 @@ packages:
'@parcel/watcher-linux-x64-glibc@2.4.2-alpha.0':
resolution: {integrity: sha512-/WJJ3Y46ubwQW+Z+mzpzK3pvqn/AT7MA63NB0+k9GTLNxJQZNREensMtpJ/FJ+LVIiraEHTY22KQrsx9+DeNbw==}
engines: {node: '>= 10.0.0'}
- cpu: [x64]
os: [linux]
'@parcel/watcher-linux-x64-musl@2.4.1':
@@ -1127,7 +1085,6 @@ packages:
'@parcel/watcher-linux-x64-musl@2.4.2-alpha.0':
resolution: {integrity: sha512-1dz4fTM5HaANk3RSRmdhALT+bNqTHawVDL1D77HwV/FuF/kSjlM3rGrJuFaCKwQ5E8CInHCcobqMN8Jh8LYaRg==}
engines: {node: '>= 10.0.0'}
- cpu: [x64]
os: [linux]
'@parcel/watcher-win32-arm64@2.4.1':
@@ -1151,7 +1108,6 @@ packages:
'@parcel/watcher-win32-x64@2.4.2-alpha.0':
resolution: {integrity: sha512-U2abMKF7JUiIxQkos19AvTLFcnl2Xn8yIW1kzu+7B0Lux4Gkuu/BUDBroaM1s6+hwgK63NOLq9itX2Y3GwUThg==}
engines: {node: '>= 10.0.0'}
- cpu: [x64]
os: [win32]
'@parcel/watcher@2.4.1':
@@ -1487,13 +1443,11 @@ packages:
bun@1.1.22:
resolution: {integrity: sha512-G2HCPhzhjDc2jEDkZsO9vwPlpHrTm7a8UVwx9oNS5bZqo5OcSK5GPuWYDWjj7+37bRk5OVLfeIvUMtSrbKeIjQ==}
- cpu: [arm64, x64]
os: [darwin, linux, win32]
hasBin: true
bun@1.1.26:
resolution: {integrity: sha512-dWSewAqE7sVbYmflJxgG47dW4vmsbar7VAnQ4ao45y3ulr3n7CwdsMLFnzd28jhPRtF+rsaVK2y4OLIkP3OD4A==}
- cpu: [arm64, x64]
os: [darwin, linux, win32]
hasBin: true
@@ -2260,13 +2214,11 @@ 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:
@@ -2284,25 +2236,21 @@ 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:
@@ -2314,7 +2262,6 @@ 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:
@@ -4442,7 +4389,7 @@ snapshots:
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0)
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0)
eslint-plugin-react: 7.35.0(eslint@8.57.0)
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
@@ -4466,7 +4413,7 @@ snapshots:
enhanced-resolve: 5.17.1
eslint: 8.57.0
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.6
is-core-module: 2.15.0
@@ -4488,7 +4435,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
+ eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
dependencies:
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.5