Updated node modules
This commit is contained in:
52
node_modules/tailwindcss/src/lib/cacheInvalidation.js
generated
vendored
Normal file
52
node_modules/tailwindcss/src/lib/cacheInvalidation.js
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
import crypto from 'crypto'
|
||||
import * as sharedState from './sharedState'
|
||||
|
||||
/**
|
||||
* Calculate the hash of a string.
|
||||
*
|
||||
* This doesn't need to be cryptographically secure or
|
||||
* anything like that since it's used only to detect
|
||||
* when the CSS changes to invalidate the context.
|
||||
*
|
||||
* This is wrapped in a try/catch because it's really dependent
|
||||
* on how Node itself is build and the environment and OpenSSL
|
||||
* version / build that is installed on the user's machine.
|
||||
*
|
||||
* Based on the environment this can just outright fail.
|
||||
*
|
||||
* See https://github.com/nodejs/node/issues/40455
|
||||
*
|
||||
* @param {string} str
|
||||
*/
|
||||
function getHash(str) {
|
||||
try {
|
||||
return crypto.createHash('md5').update(str, 'utf-8').digest('binary')
|
||||
} catch (err) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the CSS tree is different from the
|
||||
* previous version for the given `sourcePath`.
|
||||
*
|
||||
* @param {string} sourcePath
|
||||
* @param {import('postcss').Node} root
|
||||
*/
|
||||
export function hasContentChanged(sourcePath, root) {
|
||||
let css = root.toString()
|
||||
|
||||
// We only care about files with @tailwind directives
|
||||
// Other files use an existing context
|
||||
if (!css.includes('@tailwind')) {
|
||||
return false
|
||||
}
|
||||
|
||||
let existingHash = sharedState.sourceHashMap.get(sourcePath)
|
||||
let rootHash = getHash(css)
|
||||
let didChange = existingHash !== rootHash
|
||||
|
||||
sharedState.sourceHashMap.set(sourcePath, rootHash)
|
||||
|
||||
return didChange
|
||||
}
|
58
node_modules/tailwindcss/src/lib/collapseAdjacentRules.js
generated
vendored
Normal file
58
node_modules/tailwindcss/src/lib/collapseAdjacentRules.js
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
let comparisonMap = {
|
||||
atrule: ['name', 'params'],
|
||||
rule: ['selector'],
|
||||
}
|
||||
let types = new Set(Object.keys(comparisonMap))
|
||||
|
||||
export default function collapseAdjacentRules() {
|
||||
function collapseRulesIn(root) {
|
||||
let currentRule = null
|
||||
root.each((node) => {
|
||||
if (!types.has(node.type)) {
|
||||
currentRule = null
|
||||
return
|
||||
}
|
||||
|
||||
if (currentRule === null) {
|
||||
currentRule = node
|
||||
return
|
||||
}
|
||||
|
||||
let properties = comparisonMap[node.type]
|
||||
|
||||
if (node.type === 'atrule' && node.name === 'font-face') {
|
||||
currentRule = node
|
||||
} else if (
|
||||
properties.every(
|
||||
(property) =>
|
||||
(node[property] ?? '').replace(/\s+/g, ' ') ===
|
||||
(currentRule[property] ?? '').replace(/\s+/g, ' ')
|
||||
)
|
||||
) {
|
||||
// An AtRule may not have children (for example if we encounter duplicate @import url(…) rules)
|
||||
if (node.nodes) {
|
||||
currentRule.append(node.nodes)
|
||||
}
|
||||
|
||||
node.remove()
|
||||
} else {
|
||||
currentRule = node
|
||||
}
|
||||
})
|
||||
|
||||
// After we've collapsed adjacent rules & at-rules, we need to collapse
|
||||
// adjacent rules & at-rules that are children of at-rules.
|
||||
// We do not care about nesting rules because Tailwind CSS
|
||||
// explicitly does not handle rule nesting on its own as
|
||||
// the user is expected to use a nesting plugin
|
||||
root.each((node) => {
|
||||
if (node.type === 'atrule') {
|
||||
collapseRulesIn(node)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (root) => {
|
||||
collapseRulesIn(root)
|
||||
}
|
||||
}
|
93
node_modules/tailwindcss/src/lib/collapseDuplicateDeclarations.js
generated
vendored
Normal file
93
node_modules/tailwindcss/src/lib/collapseDuplicateDeclarations.js
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
export default function collapseDuplicateDeclarations() {
|
||||
return (root) => {
|
||||
root.walkRules((node) => {
|
||||
let seen = new Map()
|
||||
let droppable = new Set([])
|
||||
let byProperty = new Map()
|
||||
|
||||
node.walkDecls((decl) => {
|
||||
// This could happen if we have nested selectors. In that case the
|
||||
// parent will loop over all its declarations but also the declarations
|
||||
// of nested rules. With this we ensure that we are shallowly checking
|
||||
// declarations.
|
||||
if (decl.parent !== node) {
|
||||
return
|
||||
}
|
||||
|
||||
if (seen.has(decl.prop)) {
|
||||
// Exact same value as what we have seen so far
|
||||
if (seen.get(decl.prop).value === decl.value) {
|
||||
// Keep the last one, drop the one we've seen so far
|
||||
droppable.add(seen.get(decl.prop))
|
||||
// Override the existing one with the new value. This is necessary
|
||||
// so that if we happen to have more than one declaration with the
|
||||
// same value, that we keep removing the previous one. Otherwise we
|
||||
// will only remove the *first* one.
|
||||
seen.set(decl.prop, decl)
|
||||
return
|
||||
}
|
||||
|
||||
// Not the same value, so we need to check if we can merge it so
|
||||
// let's collect it first.
|
||||
if (!byProperty.has(decl.prop)) {
|
||||
byProperty.set(decl.prop, new Set())
|
||||
}
|
||||
|
||||
byProperty.get(decl.prop).add(seen.get(decl.prop))
|
||||
byProperty.get(decl.prop).add(decl)
|
||||
}
|
||||
|
||||
seen.set(decl.prop, decl)
|
||||
})
|
||||
|
||||
// Drop all the duplicate declarations with the exact same value we've
|
||||
// already seen so far.
|
||||
for (let decl of droppable) {
|
||||
decl.remove()
|
||||
}
|
||||
|
||||
// Analyze the declarations based on its unit, drop all the declarations
|
||||
// with the same unit but the last one in the list.
|
||||
for (let declarations of byProperty.values()) {
|
||||
let byUnit = new Map()
|
||||
|
||||
for (let decl of declarations) {
|
||||
let unit = resolveUnit(decl.value)
|
||||
if (unit === null) {
|
||||
// We don't have a unit, so should never try and collapse this
|
||||
// value. This is because we can't know how to do it in a correct
|
||||
// way (e.g.: overrides for older browsers)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!byUnit.has(unit)) {
|
||||
byUnit.set(unit, new Set())
|
||||
}
|
||||
|
||||
byUnit.get(unit).add(decl)
|
||||
}
|
||||
|
||||
for (let declarations of byUnit.values()) {
|
||||
// Get all but the last one
|
||||
let removableDeclarations = Array.from(declarations).slice(0, -1)
|
||||
|
||||
for (let decl of removableDeclarations) {
|
||||
decl.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let UNITLESS_NUMBER = Symbol('unitless-number')
|
||||
|
||||
function resolveUnit(input) {
|
||||
let result = /^-?\d*.?\d+([\w%]+)?$/g.exec(input)
|
||||
|
||||
if (result) {
|
||||
return result[1] ?? UNITLESS_NUMBER
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
208
node_modules/tailwindcss/src/lib/content.js
generated
vendored
Normal file
208
node_modules/tailwindcss/src/lib/content.js
generated
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
// @ts-check
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import isGlob from 'is-glob'
|
||||
import fastGlob from 'fast-glob'
|
||||
import normalizePath from 'normalize-path'
|
||||
import { parseGlob } from '../util/parseGlob'
|
||||
import { env } from './sharedState'
|
||||
|
||||
/** @typedef {import('../../types/config.js').RawFile} RawFile */
|
||||
/** @typedef {import('../../types/config.js').FilePath} FilePath */
|
||||
|
||||
/**
|
||||
* @typedef {object} ContentPath
|
||||
* @property {string} original
|
||||
* @property {string} base
|
||||
* @property {string | null} glob
|
||||
* @property {boolean} ignore
|
||||
* @property {string} pattern
|
||||
*/
|
||||
|
||||
/**
|
||||
* Turn a list of content paths (absolute or not; glob or not) into a list of
|
||||
* absolute file paths that exist on the filesystem
|
||||
*
|
||||
* If there are symlinks in the path then multiple paths will be returned
|
||||
* one for the symlink and one for the actual file
|
||||
*
|
||||
* @param {*} context
|
||||
* @param {import('tailwindcss').Config} tailwindConfig
|
||||
* @returns {ContentPath[]}
|
||||
*/
|
||||
export function parseCandidateFiles(context, tailwindConfig) {
|
||||
let files = tailwindConfig.content.files
|
||||
|
||||
// Normalize the file globs
|
||||
files = files.filter((filePath) => typeof filePath === 'string')
|
||||
files = files.map(normalizePath)
|
||||
|
||||
// Split into included and excluded globs
|
||||
let tasks = fastGlob.generateTasks(files)
|
||||
|
||||
/** @type {ContentPath[]} */
|
||||
let included = []
|
||||
|
||||
/** @type {ContentPath[]} */
|
||||
let excluded = []
|
||||
|
||||
for (const task of tasks) {
|
||||
included.push(...task.positive.map((filePath) => parseFilePath(filePath, false)))
|
||||
excluded.push(...task.negative.map((filePath) => parseFilePath(filePath, true)))
|
||||
}
|
||||
|
||||
let paths = [...included, ...excluded]
|
||||
|
||||
// Resolve paths relative to the config file or cwd
|
||||
paths = resolveRelativePaths(context, paths)
|
||||
|
||||
// Resolve symlinks if possible
|
||||
paths = paths.flatMap(resolvePathSymlinks)
|
||||
|
||||
// Update cached patterns
|
||||
paths = paths.map(resolveGlobPattern)
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} filePath
|
||||
* @param {boolean} ignore
|
||||
* @returns {ContentPath}
|
||||
*/
|
||||
function parseFilePath(filePath, ignore) {
|
||||
let contentPath = {
|
||||
original: filePath,
|
||||
base: filePath,
|
||||
ignore,
|
||||
pattern: filePath,
|
||||
glob: null,
|
||||
}
|
||||
|
||||
if (isGlob(filePath)) {
|
||||
Object.assign(contentPath, parseGlob(filePath))
|
||||
}
|
||||
|
||||
return contentPath
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {ContentPath} contentPath
|
||||
* @returns {ContentPath}
|
||||
*/
|
||||
function resolveGlobPattern(contentPath) {
|
||||
// This is required for Windows support to properly pick up Glob paths.
|
||||
// Afaik, this technically shouldn't be needed but there's probably
|
||||
// some internal, direct path matching with a normalized path in
|
||||
// a package which can't handle mixed directory separators
|
||||
let base = normalizePath(contentPath.base)
|
||||
|
||||
// If the user's file path contains any special characters (like parens) for instance fast-glob
|
||||
// is like "OOOH SHINY" and treats them as such. So we have to escape the base path to fix this
|
||||
base = fastGlob.escapePath(base)
|
||||
|
||||
contentPath.pattern = contentPath.glob ? `${base}/${contentPath.glob}` : base
|
||||
contentPath.pattern = contentPath.ignore ? `!${contentPath.pattern}` : contentPath.pattern
|
||||
|
||||
return contentPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve each path relative to the config file (when possible) if the experimental flag is enabled
|
||||
* Otherwise, resolve relative to the current working directory
|
||||
*
|
||||
* @param {any} context
|
||||
* @param {ContentPath[]} contentPaths
|
||||
* @returns {ContentPath[]}
|
||||
*/
|
||||
function resolveRelativePaths(context, contentPaths) {
|
||||
let resolveFrom = []
|
||||
|
||||
// Resolve base paths relative to the config file (when possible) if the experimental flag is enabled
|
||||
if (context.userConfigPath && context.tailwindConfig.content.relative) {
|
||||
resolveFrom = [path.dirname(context.userConfigPath)]
|
||||
}
|
||||
|
||||
return contentPaths.map((contentPath) => {
|
||||
contentPath.base = path.resolve(...resolveFrom, contentPath.base)
|
||||
|
||||
return contentPath
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the symlink for the base directory / file in each path
|
||||
* These are added as additional dependencies to watch for changes because
|
||||
* some tools (like webpack) will only watch the actual file or directory
|
||||
* but not the symlink itself even in projects that use monorepos.
|
||||
*
|
||||
* @param {ContentPath} contentPath
|
||||
* @returns {ContentPath[]}
|
||||
*/
|
||||
function resolvePathSymlinks(contentPath) {
|
||||
let paths = [contentPath]
|
||||
|
||||
try {
|
||||
let resolvedPath = fs.realpathSync(contentPath.base)
|
||||
if (resolvedPath !== contentPath.base) {
|
||||
paths.push({
|
||||
...contentPath,
|
||||
base: resolvedPath,
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
// TODO: log this?
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} context
|
||||
* @param {ContentPath[]} candidateFiles
|
||||
* @param {Map<string, number>} fileModifiedMap
|
||||
* @returns {[{ content: string, extension: string }[], Map<string, number>]}
|
||||
*/
|
||||
export function resolvedChangedContent(context, candidateFiles, fileModifiedMap) {
|
||||
let changedContent = context.tailwindConfig.content.files
|
||||
.filter((item) => typeof item.raw === 'string')
|
||||
.map(({ raw, extension = 'html' }) => ({ content: raw, extension }))
|
||||
|
||||
let [changedFiles, mTimesToCommit] = resolveChangedFiles(candidateFiles, fileModifiedMap)
|
||||
|
||||
for (let changedFile of changedFiles) {
|
||||
let extension = path.extname(changedFile).slice(1)
|
||||
changedContent.push({ file: changedFile, extension })
|
||||
}
|
||||
|
||||
return [changedContent, mTimesToCommit]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {ContentPath[]} candidateFiles
|
||||
* @param {Map<string, number>} fileModifiedMap
|
||||
* @returns {[Set<string>, Map<string, number>]}
|
||||
*/
|
||||
function resolveChangedFiles(candidateFiles, fileModifiedMap) {
|
||||
let paths = candidateFiles.map((contentPath) => contentPath.pattern)
|
||||
let mTimesToCommit = new Map()
|
||||
|
||||
let changedFiles = new Set()
|
||||
env.DEBUG && console.time('Finding changed files')
|
||||
let files = fastGlob.sync(paths, { absolute: true })
|
||||
for (let file of files) {
|
||||
let prevModified = fileModifiedMap.get(file) || -Infinity
|
||||
let modified = fs.statSync(file).mtimeMs
|
||||
|
||||
if (modified > prevModified) {
|
||||
changedFiles.add(file)
|
||||
mTimesToCommit.set(file, modified)
|
||||
}
|
||||
}
|
||||
env.DEBUG && console.timeEnd('Finding changed files')
|
||||
return [changedFiles, mTimesToCommit]
|
||||
}
|
217
node_modules/tailwindcss/src/lib/defaultExtractor.js
generated
vendored
Normal file
217
node_modules/tailwindcss/src/lib/defaultExtractor.js
generated
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
import { flagEnabled } from '../featureFlags'
|
||||
import * as regex from './regex'
|
||||
|
||||
export function defaultExtractor(context) {
|
||||
let patterns = Array.from(buildRegExps(context))
|
||||
|
||||
/**
|
||||
* @param {string} content
|
||||
*/
|
||||
return (content) => {
|
||||
/** @type {(string|string)[]} */
|
||||
let results = []
|
||||
|
||||
for (let pattern of patterns) {
|
||||
results = [...results, ...(content.match(pattern) ?? [])]
|
||||
}
|
||||
|
||||
return results.filter((v) => v !== undefined).map(clipAtBalancedParens)
|
||||
}
|
||||
}
|
||||
|
||||
function* buildRegExps(context) {
|
||||
let separator = context.tailwindConfig.separator
|
||||
let variantGroupingEnabled = flagEnabled(context.tailwindConfig, 'variantGrouping')
|
||||
let prefix =
|
||||
context.tailwindConfig.prefix !== ''
|
||||
? regex.optional(regex.pattern([/-?/, regex.escape(context.tailwindConfig.prefix)]))
|
||||
: ''
|
||||
|
||||
let utility = regex.any([
|
||||
// Arbitrary properties (without square brackets)
|
||||
/\[[^\s:'"`]+:[^\s\[\]]+\]/,
|
||||
|
||||
// Arbitrary properties with balanced square brackets
|
||||
// This is a targeted fix to continue to allow theme()
|
||||
// with square brackets to work in arbitrary properties
|
||||
// while fixing a problem with the regex matching too much
|
||||
/\[[^\s:'"`]+:[^\s]+?\[[^\s]+\][^\s]+?\]/,
|
||||
|
||||
// Utilities
|
||||
regex.pattern([
|
||||
// Utility Name / Group Name
|
||||
/-?(?:\w+)/,
|
||||
|
||||
// Normal/Arbitrary values
|
||||
regex.optional(
|
||||
regex.any([
|
||||
regex.pattern([
|
||||
// Arbitrary values
|
||||
/-(?:\w+-)*\[[^\s:]+\]/,
|
||||
|
||||
// Not immediately followed by an `{[(`
|
||||
/(?![{([]])/,
|
||||
|
||||
// optionally followed by an opacity modifier
|
||||
/(?:\/[^\s'"`\\><$]*)?/,
|
||||
]),
|
||||
|
||||
regex.pattern([
|
||||
// Arbitrary values
|
||||
/-(?:\w+-)*\[[^\s]+\]/,
|
||||
|
||||
// Not immediately followed by an `{[(`
|
||||
/(?![{([]])/,
|
||||
|
||||
// optionally followed by an opacity modifier
|
||||
/(?:\/[^\s'"`\\$]*)?/,
|
||||
]),
|
||||
|
||||
// Normal values w/o quotes — may include an opacity modifier
|
||||
/[-\/][^\s'"`\\$={><]*/,
|
||||
])
|
||||
),
|
||||
]),
|
||||
])
|
||||
|
||||
let variantPatterns = [
|
||||
// Without quotes
|
||||
regex.any([
|
||||
// This is here to provide special support for the `@` variant
|
||||
regex.pattern([/@\[[^\s"'`]+\](\/[^\s"'`]+)?/, separator]),
|
||||
|
||||
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, separator]),
|
||||
regex.pattern([/[^\s"'`\[\\]+/, separator]),
|
||||
]),
|
||||
|
||||
// With quotes allowed
|
||||
regex.any([
|
||||
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]/, separator]),
|
||||
regex.pattern([/[^\s`\[\\]+/, separator]),
|
||||
]),
|
||||
]
|
||||
|
||||
for (const variantPattern of variantPatterns) {
|
||||
yield regex.pattern([
|
||||
// Variants
|
||||
'((?=((',
|
||||
variantPattern,
|
||||
')+))\\2)?',
|
||||
|
||||
// Important (optional)
|
||||
/!?/,
|
||||
|
||||
prefix,
|
||||
|
||||
variantGroupingEnabled
|
||||
? regex.any([
|
||||
// Or any of those things but grouped separated by commas
|
||||
regex.pattern([/\(/, utility, regex.zeroOrMore([/,/, utility]), /\)/]),
|
||||
|
||||
// Arbitrary properties, constrained utilities, arbitrary values, etc…
|
||||
utility,
|
||||
])
|
||||
: utility,
|
||||
])
|
||||
}
|
||||
|
||||
// 5. Inner matches
|
||||
yield /[^<>"'`\s.(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g
|
||||
}
|
||||
|
||||
// We want to capture any "special" characters
|
||||
// AND the characters immediately following them (if there is one)
|
||||
let SPECIALS = /([\[\]'"`])([^\[\]'"`])?/g
|
||||
let ALLOWED_CLASS_CHARACTERS = /[^"'`\s<>\]]+/
|
||||
|
||||
/**
|
||||
* Clips a string ensuring that parentheses, quotes, etc… are balanced
|
||||
* Used for arbitrary values only
|
||||
*
|
||||
* We will go past the end of the balanced parens until we find a non-class character
|
||||
*
|
||||
* Depth matching behavior:
|
||||
* w-[calc(100%-theme('spacing[some_key][1.5]'))]']
|
||||
* ┬ ┬ ┬┬ ┬ ┬┬ ┬┬┬┬┬┬┬
|
||||
* 1 2 3 4 34 3 210 END
|
||||
* ╰────┴──────────┴────────┴────────┴┴───┴─┴┴┴
|
||||
*
|
||||
* @param {string} input
|
||||
*/
|
||||
function clipAtBalancedParens(input) {
|
||||
// We are care about this for arbitrary values
|
||||
if (!input.includes('-[')) {
|
||||
return input
|
||||
}
|
||||
|
||||
let depth = 0
|
||||
let openStringTypes = []
|
||||
|
||||
// Find all parens, brackets, quotes, etc
|
||||
// Stop when we end at a balanced pair
|
||||
// This is naive and will treat mismatched parens as balanced
|
||||
// This shouldn't be a problem in practice though
|
||||
let matches = input.matchAll(SPECIALS)
|
||||
|
||||
// We can't use lookbehind assertions because we have to support Safari
|
||||
// So, instead, we've emulated it using capture groups and we'll re-work the matches to accommodate
|
||||
matches = Array.from(matches).flatMap((match) => {
|
||||
const [, ...groups] = match
|
||||
|
||||
return groups.map((group, idx) =>
|
||||
Object.assign([], match, {
|
||||
index: match.index + idx,
|
||||
0: group,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
for (let match of matches) {
|
||||
let char = match[0]
|
||||
let inStringType = openStringTypes[openStringTypes.length - 1]
|
||||
|
||||
if (char === inStringType) {
|
||||
openStringTypes.pop()
|
||||
} else if (char === "'" || char === '"' || char === '`') {
|
||||
openStringTypes.push(char)
|
||||
}
|
||||
|
||||
if (inStringType) {
|
||||
continue
|
||||
} else if (char === '[') {
|
||||
depth++
|
||||
continue
|
||||
} else if (char === ']') {
|
||||
depth--
|
||||
continue
|
||||
}
|
||||
|
||||
// We've gone one character past the point where we should stop
|
||||
// This means that there was an extra closing `]`
|
||||
// We'll clip to just before it
|
||||
if (depth < 0) {
|
||||
return input.substring(0, match.index - 1)
|
||||
}
|
||||
|
||||
// We've finished balancing the brackets but there still may be characters that can be included
|
||||
// For example in the class `text-[#336699]/[.35]`
|
||||
// The depth goes to `0` at the closing `]` but goes up again at the `[`
|
||||
|
||||
// If we're at zero and encounter a non-class character then we clip the class there
|
||||
if (depth === 0 && !ALLOWED_CLASS_CHARACTERS.test(char)) {
|
||||
return input.substring(0, match.index)
|
||||
}
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
// Regular utilities
|
||||
// {{modifier}:}*{namespace}{-{suffix}}*{/{opacityModifier}}?
|
||||
|
||||
// Arbitrary values
|
||||
// {{modifier}:}*{namespace}-[{arbitraryValue}]{/{opacityModifier}}?
|
||||
// arbitraryValue: no whitespace, balanced quotes unless within quotes, balanced brackets unless within quotes
|
||||
|
||||
// Arbitrary properties
|
||||
// {{modifier}:}*[{validCssPropertyName}:{arbitraryValue}]
|
47
node_modules/tailwindcss/src/lib/detectNesting.js
generated
vendored
Normal file
47
node_modules/tailwindcss/src/lib/detectNesting.js
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
function isRoot(node) {
|
||||
return node.type === 'root'
|
||||
}
|
||||
|
||||
function isAtLayer(node) {
|
||||
return node.type === 'atrule' && node.name === 'layer'
|
||||
}
|
||||
|
||||
export default function (_context) {
|
||||
return (root, result) => {
|
||||
let found = false
|
||||
|
||||
root.walkAtRules('tailwind', (node) => {
|
||||
if (found) return false
|
||||
|
||||
if (node.parent && !(isRoot(node.parent) || isAtLayer(node.parent))) {
|
||||
found = true
|
||||
node.warn(
|
||||
result,
|
||||
[
|
||||
'Nested @tailwind rules were detected, but are not supported.',
|
||||
"Consider using a prefix to scope Tailwind's classes: https://tailwindcss.com/docs/configuration#prefix",
|
||||
'Alternatively, use the important selector strategy: https://tailwindcss.com/docs/configuration#selector-strategy',
|
||||
].join('\n')
|
||||
)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
root.walkRules((rule) => {
|
||||
if (found) return false
|
||||
|
||||
rule.walkRules((nestedRule) => {
|
||||
found = true
|
||||
nestedRule.warn(
|
||||
result,
|
||||
[
|
||||
'Nested CSS was detected, but CSS nesting has not been configured correctly.',
|
||||
'Please enable a CSS nesting plugin *before* Tailwind in your configuration.',
|
||||
'See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting',
|
||||
].join('\n')
|
||||
)
|
||||
return false
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
272
node_modules/tailwindcss/src/lib/evaluateTailwindFunctions.js
generated
vendored
Normal file
272
node_modules/tailwindcss/src/lib/evaluateTailwindFunctions.js
generated
vendored
Normal file
@@ -0,0 +1,272 @@
|
||||
import dlv from 'dlv'
|
||||
import didYouMean from 'didyoumean'
|
||||
import transformThemeValue from '../util/transformThemeValue'
|
||||
import parseValue from '../value-parser/index'
|
||||
import { normalizeScreens } from '../util/normalizeScreens'
|
||||
import buildMediaQuery from '../util/buildMediaQuery'
|
||||
import { toPath } from '../util/toPath'
|
||||
import { withAlphaValue } from '../util/withAlphaVariable'
|
||||
import { parseColorFormat } from '../util/pluginUtils'
|
||||
import log from '../util/log'
|
||||
|
||||
function isObject(input) {
|
||||
return typeof input === 'object' && input !== null
|
||||
}
|
||||
|
||||
function findClosestExistingPath(theme, path) {
|
||||
let parts = toPath(path)
|
||||
do {
|
||||
parts.pop()
|
||||
|
||||
if (dlv(theme, parts) !== undefined) break
|
||||
} while (parts.length)
|
||||
|
||||
return parts.length ? parts : undefined
|
||||
}
|
||||
|
||||
function pathToString(path) {
|
||||
if (typeof path === 'string') return path
|
||||
return path.reduce((acc, cur, i) => {
|
||||
if (cur.includes('.')) return `${acc}[${cur}]`
|
||||
return i === 0 ? cur : `${acc}.${cur}`
|
||||
}, '')
|
||||
}
|
||||
|
||||
function list(items) {
|
||||
return items.map((key) => `'${key}'`).join(', ')
|
||||
}
|
||||
|
||||
function listKeys(obj) {
|
||||
return list(Object.keys(obj))
|
||||
}
|
||||
|
||||
function validatePath(config, path, defaultValue, themeOpts = {}) {
|
||||
const pathString = Array.isArray(path) ? pathToString(path) : path.replace(/^['"]+|['"]+$/g, '')
|
||||
const pathSegments = Array.isArray(path) ? path : toPath(pathString)
|
||||
const value = dlv(config.theme, pathSegments, defaultValue)
|
||||
|
||||
if (value === undefined) {
|
||||
let error = `'${pathString}' does not exist in your theme config.`
|
||||
const parentSegments = pathSegments.slice(0, -1)
|
||||
const parentValue = dlv(config.theme, parentSegments)
|
||||
|
||||
if (isObject(parentValue)) {
|
||||
const validKeys = Object.keys(parentValue).filter(
|
||||
(key) => validatePath(config, [...parentSegments, key]).isValid
|
||||
)
|
||||
const suggestion = didYouMean(pathSegments[pathSegments.length - 1], validKeys)
|
||||
if (suggestion) {
|
||||
error += ` Did you mean '${pathToString([...parentSegments, suggestion])}'?`
|
||||
} else if (validKeys.length > 0) {
|
||||
error += ` '${pathToString(parentSegments)}' has the following valid keys: ${list(
|
||||
validKeys
|
||||
)}`
|
||||
}
|
||||
} else {
|
||||
const closestPath = findClosestExistingPath(config.theme, pathString)
|
||||
if (closestPath) {
|
||||
const closestValue = dlv(config.theme, closestPath)
|
||||
if (isObject(closestValue)) {
|
||||
error += ` '${pathToString(closestPath)}' has the following keys: ${listKeys(
|
||||
closestValue
|
||||
)}`
|
||||
} else {
|
||||
error += ` '${pathToString(closestPath)}' is not an object.`
|
||||
}
|
||||
} else {
|
||||
error += ` Your theme has the following top-level keys: ${listKeys(config.theme)}`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: false,
|
||||
error,
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!(
|
||||
typeof value === 'string' ||
|
||||
typeof value === 'number' ||
|
||||
typeof value === 'function' ||
|
||||
value instanceof String ||
|
||||
value instanceof Number ||
|
||||
Array.isArray(value)
|
||||
)
|
||||
) {
|
||||
let error = `'${pathString}' was found but does not resolve to a string.`
|
||||
|
||||
if (isObject(value)) {
|
||||
let validKeys = Object.keys(value).filter(
|
||||
(key) => validatePath(config, [...pathSegments, key]).isValid
|
||||
)
|
||||
if (validKeys.length) {
|
||||
error += ` Did you mean something like '${pathToString([...pathSegments, validKeys[0]])}'?`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: false,
|
||||
error,
|
||||
}
|
||||
}
|
||||
|
||||
const [themeSection] = pathSegments
|
||||
|
||||
return {
|
||||
isValid: true,
|
||||
value: transformThemeValue(themeSection)(value, themeOpts),
|
||||
}
|
||||
}
|
||||
|
||||
function extractArgs(node, vNodes, functions) {
|
||||
vNodes = vNodes.map((vNode) => resolveVNode(node, vNode, functions))
|
||||
|
||||
let args = ['']
|
||||
|
||||
for (let vNode of vNodes) {
|
||||
if (vNode.type === 'div' && vNode.value === ',') {
|
||||
args.push('')
|
||||
} else {
|
||||
args[args.length - 1] += parseValue.stringify(vNode)
|
||||
}
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
function resolveVNode(node, vNode, functions) {
|
||||
if (vNode.type === 'function' && functions[vNode.value] !== undefined) {
|
||||
let args = extractArgs(node, vNode.nodes, functions)
|
||||
vNode.type = 'word'
|
||||
vNode.value = functions[vNode.value](node, ...args)
|
||||
}
|
||||
|
||||
return vNode
|
||||
}
|
||||
|
||||
function resolveFunctions(node, input, functions) {
|
||||
let hasAnyFn = Object.keys(functions).some((fn) => input.includes(`${fn}(`))
|
||||
if (!hasAnyFn) return input
|
||||
|
||||
return parseValue(input)
|
||||
.walk((vNode) => {
|
||||
resolveVNode(node, vNode, functions)
|
||||
})
|
||||
.toString()
|
||||
}
|
||||
|
||||
let nodeTypePropertyMap = {
|
||||
atrule: 'params',
|
||||
decl: 'value',
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {Iterable<[path: string, alpha: string|undefined]>}
|
||||
*/
|
||||
function* toPaths(path) {
|
||||
// Strip quotes from beginning and end of string
|
||||
// This allows the alpha value to be present inside of quotes
|
||||
path = path.replace(/^['"]+|['"]+$/g, '')
|
||||
|
||||
let matches = path.match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]+))$/)
|
||||
let alpha = undefined
|
||||
|
||||
yield [path, undefined]
|
||||
|
||||
if (matches) {
|
||||
path = matches[1]
|
||||
alpha = matches[2]
|
||||
|
||||
yield [path, alpha]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} config
|
||||
* @param {string} path
|
||||
* @param {any} defaultValue
|
||||
*/
|
||||
function resolvePath(config, path, defaultValue) {
|
||||
const results = Array.from(toPaths(path)).map(([path, alpha]) => {
|
||||
return Object.assign(validatePath(config, path, defaultValue, { opacityValue: alpha }), {
|
||||
resolvedPath: path,
|
||||
alpha,
|
||||
})
|
||||
})
|
||||
|
||||
return results.find((result) => result.isValid) ?? results[0]
|
||||
}
|
||||
|
||||
export default function (context) {
|
||||
let config = context.tailwindConfig
|
||||
|
||||
let functions = {
|
||||
theme: (node, path, ...defaultValue) => {
|
||||
let { isValid, value, error, alpha } = resolvePath(
|
||||
config,
|
||||
path,
|
||||
defaultValue.length ? defaultValue : undefined
|
||||
)
|
||||
|
||||
if (!isValid) {
|
||||
let parentNode = node.parent
|
||||
let candidate = parentNode?.raws.tailwind?.candidate
|
||||
|
||||
if (parentNode && candidate !== undefined) {
|
||||
// Remove this utility from any caches
|
||||
context.markInvalidUtilityNode(parentNode)
|
||||
|
||||
// Remove the CSS node from the markup
|
||||
parentNode.remove()
|
||||
|
||||
// Show a warning
|
||||
log.warn('invalid-theme-key-in-class', [
|
||||
`The utility \`${candidate}\` contains an invalid theme value and was not generated.`,
|
||||
])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
throw node.error(error)
|
||||
}
|
||||
|
||||
let maybeColor = parseColorFormat(value)
|
||||
let isColorFunction = maybeColor !== undefined && typeof maybeColor === 'function'
|
||||
|
||||
if (alpha !== undefined || isColorFunction) {
|
||||
if (alpha === undefined) {
|
||||
alpha = 1.0
|
||||
}
|
||||
|
||||
value = withAlphaValue(maybeColor, alpha, maybeColor)
|
||||
}
|
||||
|
||||
return value
|
||||
},
|
||||
screen: (node, screen) => {
|
||||
screen = screen.replace(/^['"]+/g, '').replace(/['"]+$/g, '')
|
||||
let screens = normalizeScreens(config.theme.screens)
|
||||
let screenDefinition = screens.find(({ name }) => name === screen)
|
||||
|
||||
if (!screenDefinition) {
|
||||
throw node.error(`The '${screen}' screen does not exist in your theme.`)
|
||||
}
|
||||
|
||||
return buildMediaQuery(screenDefinition)
|
||||
},
|
||||
}
|
||||
return (root) => {
|
||||
root.walk((node) => {
|
||||
let property = nodeTypePropertyMap[node.type]
|
||||
|
||||
if (property === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
node[property] = resolveFunctions(node, node[property], functions)
|
||||
})
|
||||
}
|
||||
}
|
613
node_modules/tailwindcss/src/lib/expandApplyAtRules.js
generated
vendored
Normal file
613
node_modules/tailwindcss/src/lib/expandApplyAtRules.js
generated
vendored
Normal file
@@ -0,0 +1,613 @@
|
||||
import postcss from 'postcss'
|
||||
import parser from 'postcss-selector-parser'
|
||||
|
||||
import { resolveMatches } from './generateRules'
|
||||
import escapeClassName from '../util/escapeClassName'
|
||||
import { applyImportantSelector } from '../util/applyImportantSelector'
|
||||
import { movePseudos } from '../util/pseudoElements'
|
||||
|
||||
/** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */
|
||||
|
||||
function extractClasses(node) {
|
||||
/** @type {Map<string, Set<string>>} */
|
||||
let groups = new Map()
|
||||
|
||||
let container = postcss.root({ nodes: [node.clone()] })
|
||||
|
||||
container.walkRules((rule) => {
|
||||
parser((selectors) => {
|
||||
selectors.walkClasses((classSelector) => {
|
||||
let parentSelector = classSelector.parent.toString()
|
||||
|
||||
let classes = groups.get(parentSelector)
|
||||
if (!classes) {
|
||||
groups.set(parentSelector, (classes = new Set()))
|
||||
}
|
||||
|
||||
classes.add(classSelector.value)
|
||||
})
|
||||
}).processSync(rule.selector)
|
||||
})
|
||||
|
||||
let normalizedGroups = Array.from(groups.values(), (classes) => Array.from(classes))
|
||||
let classes = normalizedGroups.flat()
|
||||
|
||||
return Object.assign(classes, { groups: normalizedGroups })
|
||||
}
|
||||
|
||||
let selectorExtractor = parser()
|
||||
|
||||
/**
|
||||
* @param {string} ruleSelectors
|
||||
*/
|
||||
function extractSelectors(ruleSelectors) {
|
||||
return selectorExtractor.astSync(ruleSelectors)
|
||||
}
|
||||
|
||||
function extractBaseCandidates(candidates, separator) {
|
||||
let baseClasses = new Set()
|
||||
|
||||
for (let candidate of candidates) {
|
||||
baseClasses.add(candidate.split(separator).pop())
|
||||
}
|
||||
|
||||
return Array.from(baseClasses)
|
||||
}
|
||||
|
||||
function prefix(context, selector) {
|
||||
let prefix = context.tailwindConfig.prefix
|
||||
return typeof prefix === 'function' ? prefix(selector) : prefix + selector
|
||||
}
|
||||
|
||||
function* pathToRoot(node) {
|
||||
yield node
|
||||
while (node.parent) {
|
||||
yield node.parent
|
||||
node = node.parent
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only clone the node itself and not its children
|
||||
*
|
||||
* @param {*} node
|
||||
* @param {*} overrides
|
||||
* @returns
|
||||
*/
|
||||
function shallowClone(node, overrides = {}) {
|
||||
let children = node.nodes
|
||||
node.nodes = []
|
||||
|
||||
let tmp = node.clone(overrides)
|
||||
|
||||
node.nodes = children
|
||||
|
||||
return tmp
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone just the nodes all the way to the top that are required to represent
|
||||
* this singular rule in the tree.
|
||||
*
|
||||
* For example, if we have CSS like this:
|
||||
* ```css
|
||||
* @media (min-width: 768px) {
|
||||
* @supports (display: grid) {
|
||||
* .foo {
|
||||
* display: grid;
|
||||
* grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @supports (backdrop-filter: blur(1px)) {
|
||||
* .bar {
|
||||
* backdrop-filter: blur(1px);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* .baz {
|
||||
* color: orange;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* And we're cloning `.bar` it'll return a cloned version of what's required for just that single node:
|
||||
*
|
||||
* ```css
|
||||
* @media (min-width: 768px) {
|
||||
* @supports (backdrop-filter: blur(1px)) {
|
||||
* .bar {
|
||||
* backdrop-filter: blur(1px);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param {import('postcss').Node} node
|
||||
*/
|
||||
function nestedClone(node) {
|
||||
for (let parent of pathToRoot(node)) {
|
||||
if (node === parent) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (parent.type === 'root') {
|
||||
break
|
||||
}
|
||||
|
||||
node = shallowClone(parent, {
|
||||
nodes: [node],
|
||||
})
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('postcss').Root} root
|
||||
*/
|
||||
function buildLocalApplyCache(root, context) {
|
||||
/** @type {ApplyCache} */
|
||||
let cache = new Map()
|
||||
|
||||
root.walkRules((rule) => {
|
||||
// Ignore rules generated by Tailwind
|
||||
for (let node of pathToRoot(rule)) {
|
||||
if (node.raws.tailwind?.layer !== undefined) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Clone what's required to represent this singular rule in the tree
|
||||
let container = nestedClone(rule)
|
||||
let sort = context.offsets.create('user')
|
||||
|
||||
for (let className of extractClasses(rule)) {
|
||||
let list = cache.get(className) || []
|
||||
cache.set(className, list)
|
||||
|
||||
list.push([
|
||||
{
|
||||
layer: 'user',
|
||||
sort,
|
||||
important: false,
|
||||
},
|
||||
container,
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {ApplyCache}
|
||||
*/
|
||||
function buildApplyCache(applyCandidates, context) {
|
||||
for (let candidate of applyCandidates) {
|
||||
if (context.notClassCache.has(candidate) || context.applyClassCache.has(candidate)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (context.classCache.has(candidate)) {
|
||||
context.applyClassCache.set(
|
||||
candidate,
|
||||
context.classCache.get(candidate).map(([meta, rule]) => [meta, rule.clone()])
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
let matches = Array.from(resolveMatches(candidate, context))
|
||||
|
||||
if (matches.length === 0) {
|
||||
context.notClassCache.add(candidate)
|
||||
continue
|
||||
}
|
||||
|
||||
context.applyClassCache.set(candidate, matches)
|
||||
}
|
||||
|
||||
return context.applyClassCache
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a cache only when it's first used
|
||||
*
|
||||
* @param {() => ApplyCache} buildCacheFn
|
||||
* @returns {ApplyCache}
|
||||
*/
|
||||
function lazyCache(buildCacheFn) {
|
||||
let cache = null
|
||||
|
||||
return {
|
||||
get: (name) => {
|
||||
cache = cache || buildCacheFn()
|
||||
|
||||
return cache.get(name)
|
||||
},
|
||||
has: (name) => {
|
||||
cache = cache || buildCacheFn()
|
||||
|
||||
return cache.has(name)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a series of multiple caches and merge
|
||||
* them so they act like one large cache
|
||||
*
|
||||
* @param {ApplyCache[]} caches
|
||||
* @returns {ApplyCache}
|
||||
*/
|
||||
function combineCaches(caches) {
|
||||
return {
|
||||
get: (name) => caches.flatMap((cache) => cache.get(name) || []),
|
||||
has: (name) => caches.some((cache) => cache.has(name)),
|
||||
}
|
||||
}
|
||||
|
||||
function extractApplyCandidates(params) {
|
||||
let candidates = params.split(/[\s\t\n]+/g)
|
||||
|
||||
if (candidates[candidates.length - 1] === '!important') {
|
||||
return [candidates.slice(0, -1), true]
|
||||
}
|
||||
|
||||
return [candidates, false]
|
||||
}
|
||||
|
||||
function processApply(root, context, localCache) {
|
||||
let applyCandidates = new Set()
|
||||
|
||||
// Collect all @apply rules and candidates
|
||||
let applies = []
|
||||
root.walkAtRules('apply', (rule) => {
|
||||
let [candidates] = extractApplyCandidates(rule.params)
|
||||
|
||||
for (let util of candidates) {
|
||||
applyCandidates.add(util)
|
||||
}
|
||||
|
||||
applies.push(rule)
|
||||
})
|
||||
|
||||
// Start the @apply process if we have rules with @apply in them
|
||||
if (applies.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// Fill up some caches!
|
||||
let applyClassCache = combineCaches([localCache, buildApplyCache(applyCandidates, context)])
|
||||
|
||||
/**
|
||||
* When we have an apply like this:
|
||||
*
|
||||
* .abc {
|
||||
* @apply hover:font-bold;
|
||||
* }
|
||||
*
|
||||
* What we essentially will do is resolve to this:
|
||||
*
|
||||
* .abc {
|
||||
* @apply .hover\:font-bold:hover {
|
||||
* font-weight: 500;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Notice that the to-be-applied class is `.hover\:font-bold:hover` and that the utility candidate was `hover:font-bold`.
|
||||
* What happens in this function is that we prepend a `.` and escape the candidate.
|
||||
* This will result in `.hover\:font-bold`
|
||||
* Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover`
|
||||
*
|
||||
* @param {string} selector
|
||||
* @param {string} utilitySelectors
|
||||
* @param {string} candidate
|
||||
*/
|
||||
function replaceSelector(selector, utilitySelectors, candidate) {
|
||||
let selectorList = extractSelectors(selector)
|
||||
let utilitySelectorsList = extractSelectors(utilitySelectors)
|
||||
let candidateList = extractSelectors(`.${escapeClassName(candidate)}`)
|
||||
let candidateClass = candidateList.nodes[0].nodes[0]
|
||||
|
||||
selectorList.each((sel) => {
|
||||
/** @type {Set<import('postcss-selector-parser').Selector>} */
|
||||
let replaced = new Set()
|
||||
|
||||
utilitySelectorsList.each((utilitySelector) => {
|
||||
let hasReplaced = false
|
||||
utilitySelector = utilitySelector.clone()
|
||||
|
||||
utilitySelector.walkClasses((node) => {
|
||||
if (node.value !== candidateClass.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// Don't replace multiple instances of the same class
|
||||
// This is theoretically correct but only partially
|
||||
// We'd need to generate every possible permutation of the replacement
|
||||
// For example with `.foo + .foo { … }` and `section { @apply foo; }`
|
||||
// We'd need to generate all of these:
|
||||
// - `.foo + .foo`
|
||||
// - `.foo + section`
|
||||
// - `section + .foo`
|
||||
// - `section + section`
|
||||
if (hasReplaced) {
|
||||
return
|
||||
}
|
||||
|
||||
// Since you can only `@apply` class names this is sufficient
|
||||
// We want to replace the matched class name with the selector the user is using
|
||||
// Ex: Replace `.text-blue-500` with `.foo.bar:is(.something-cool)`
|
||||
node.replaceWith(...sel.nodes.map((node) => node.clone()))
|
||||
|
||||
// Record that we did something and we want to use this new selector
|
||||
replaced.add(utilitySelector)
|
||||
|
||||
hasReplaced = true
|
||||
})
|
||||
})
|
||||
|
||||
// Sort tag names before class names (but only sort each group (separated by a combinator)
|
||||
// separately and not in total)
|
||||
// This happens when replacing `.bar` in `.foo.bar` with a tag like `section`
|
||||
for (let sel of replaced) {
|
||||
let groups = [[]]
|
||||
for (let node of sel.nodes) {
|
||||
if (node.type === 'combinator') {
|
||||
groups.push(node)
|
||||
groups.push([])
|
||||
} else {
|
||||
let last = groups[groups.length - 1]
|
||||
last.push(node)
|
||||
}
|
||||
}
|
||||
|
||||
sel.nodes = []
|
||||
|
||||
for (let group of groups) {
|
||||
if (Array.isArray(group)) {
|
||||
group.sort((a, b) => {
|
||||
if (a.type === 'tag' && b.type === 'class') {
|
||||
return -1
|
||||
} else if (a.type === 'class' && b.type === 'tag') {
|
||||
return 1
|
||||
} else if (a.type === 'class' && b.type === 'pseudo' && b.value.startsWith('::')) {
|
||||
return -1
|
||||
} else if (a.type === 'pseudo' && a.value.startsWith('::') && b.type === 'class') {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
sel.nodes = sel.nodes.concat(group)
|
||||
}
|
||||
}
|
||||
|
||||
sel.replaceWith(...replaced)
|
||||
})
|
||||
|
||||
return selectorList.toString()
|
||||
}
|
||||
|
||||
let perParentApplies = new Map()
|
||||
|
||||
// Collect all apply candidates and their rules
|
||||
for (let apply of applies) {
|
||||
let [candidates] = perParentApplies.get(apply.parent) || [[], apply.source]
|
||||
|
||||
perParentApplies.set(apply.parent, [candidates, apply.source])
|
||||
|
||||
let [applyCandidates, important] = extractApplyCandidates(apply.params)
|
||||
|
||||
if (apply.parent.type === 'atrule') {
|
||||
if (apply.parent.name === 'screen') {
|
||||
let screenType = apply.parent.params
|
||||
|
||||
throw apply.error(
|
||||
`@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${applyCandidates
|
||||
.map((c) => `${screenType}:${c}`)
|
||||
.join(' ')} instead.`
|
||||
)
|
||||
}
|
||||
|
||||
throw apply.error(
|
||||
`@apply is not supported within nested at-rules like @${apply.parent.name}. You can fix this by un-nesting @${apply.parent.name}.`
|
||||
)
|
||||
}
|
||||
|
||||
for (let applyCandidate of applyCandidates) {
|
||||
if ([prefix(context, 'group'), prefix(context, 'peer')].includes(applyCandidate)) {
|
||||
// TODO: Link to specific documentation page with error code.
|
||||
throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`)
|
||||
}
|
||||
|
||||
if (!applyClassCache.has(applyCandidate)) {
|
||||
throw apply.error(
|
||||
`The \`${applyCandidate}\` class does not exist. If \`${applyCandidate}\` is a custom class, make sure it is defined within a \`@layer\` directive.`
|
||||
)
|
||||
}
|
||||
|
||||
let rules = applyClassCache.get(applyCandidate)
|
||||
|
||||
candidates.push([applyCandidate, important, rules])
|
||||
}
|
||||
}
|
||||
|
||||
for (let [parent, [candidates, atApplySource]] of perParentApplies) {
|
||||
let siblings = []
|
||||
|
||||
for (let [applyCandidate, important, rules] of candidates) {
|
||||
let potentialApplyCandidates = [
|
||||
applyCandidate,
|
||||
...extractBaseCandidates([applyCandidate], context.tailwindConfig.separator),
|
||||
]
|
||||
|
||||
for (let [meta, node] of rules) {
|
||||
let parentClasses = extractClasses(parent)
|
||||
let nodeClasses = extractClasses(node)
|
||||
|
||||
// When we encounter a rule like `.dark .a, .b { … }` we only want to be left with `[.dark, .a]` if the base applyCandidate is `.a` or with `[.b]` if the base applyCandidate is `.b`
|
||||
// So we've split them into groups
|
||||
nodeClasses = nodeClasses.groups
|
||||
.filter((classList) =>
|
||||
classList.some((className) => potentialApplyCandidates.includes(className))
|
||||
)
|
||||
.flat()
|
||||
|
||||
// Add base utility classes from the @apply node to the list of
|
||||
// classes to check whether it intersects and therefore results in a
|
||||
// circular dependency or not.
|
||||
//
|
||||
// E.g.:
|
||||
// .foo {
|
||||
// @apply hover:a; // This applies "a" but with a modifier
|
||||
// }
|
||||
//
|
||||
// We only have to do that with base classes of the `node`, not of the `parent`
|
||||
// E.g.:
|
||||
// .hover\:foo {
|
||||
// @apply bar;
|
||||
// }
|
||||
// .bar {
|
||||
// @apply foo;
|
||||
// }
|
||||
//
|
||||
// This should not result in a circular dependency because we are
|
||||
// just applying `.foo` and the rule above is `.hover\:foo` which is
|
||||
// unrelated. However, if we were to apply `hover:foo` then we _did_
|
||||
// have to include this one.
|
||||
nodeClasses = nodeClasses.concat(
|
||||
extractBaseCandidates(nodeClasses, context.tailwindConfig.separator)
|
||||
)
|
||||
|
||||
let intersects = parentClasses.some((selector) => nodeClasses.includes(selector))
|
||||
if (intersects) {
|
||||
throw node.error(
|
||||
`You cannot \`@apply\` the \`${applyCandidate}\` utility here because it creates a circular dependency.`
|
||||
)
|
||||
}
|
||||
|
||||
let root = postcss.root({ nodes: [node.clone()] })
|
||||
|
||||
// Make sure every node in the entire tree points back at the @apply rule that generated it
|
||||
root.walk((node) => {
|
||||
node.source = atApplySource
|
||||
})
|
||||
|
||||
let canRewriteSelector =
|
||||
node.type !== 'atrule' || (node.type === 'atrule' && node.name !== 'keyframes')
|
||||
|
||||
if (canRewriteSelector) {
|
||||
root.walkRules((rule) => {
|
||||
// Let's imagine you have the following structure:
|
||||
//
|
||||
// .foo {
|
||||
// @apply bar;
|
||||
// }
|
||||
//
|
||||
// @supports (a: b) {
|
||||
// .bar {
|
||||
// color: blue
|
||||
// }
|
||||
//
|
||||
// .something-unrelated {}
|
||||
// }
|
||||
//
|
||||
// In this case we want to apply `.bar` but it happens to be in
|
||||
// an atrule node. We clone that node instead of the nested one
|
||||
// because we still want that @supports rule to be there once we
|
||||
// applied everything.
|
||||
//
|
||||
// However it happens to be that the `.something-unrelated` is
|
||||
// also in that same shared @supports atrule. This is not good,
|
||||
// and this should not be there. The good part is that this is
|
||||
// a clone already and it can be safely removed. The question is
|
||||
// how do we know we can remove it. Basically what we can do is
|
||||
// match it against the applyCandidate that you want to apply. If
|
||||
// it doesn't match the we can safely delete it.
|
||||
//
|
||||
// If we didn't do this, then the `replaceSelector` function
|
||||
// would have replaced this with something that didn't exist and
|
||||
// therefore it removed the selector altogether. In this specific
|
||||
// case it would result in `{}` instead of `.something-unrelated {}`
|
||||
if (!extractClasses(rule).some((candidate) => candidate === applyCandidate)) {
|
||||
rule.remove()
|
||||
return
|
||||
}
|
||||
|
||||
// Strip the important selector from the parent selector if at the beginning
|
||||
let importantSelector =
|
||||
typeof context.tailwindConfig.important === 'string'
|
||||
? context.tailwindConfig.important
|
||||
: null
|
||||
|
||||
// We only want to move the "important" selector if this is a Tailwind-generated utility
|
||||
// We do *not* want to do this for user CSS that happens to be structured the same
|
||||
let isGenerated = parent.raws.tailwind !== undefined
|
||||
|
||||
let parentSelector =
|
||||
isGenerated && importantSelector && parent.selector.indexOf(importantSelector) === 0
|
||||
? parent.selector.slice(importantSelector.length)
|
||||
: parent.selector
|
||||
|
||||
rule.selector = replaceSelector(parentSelector, rule.selector, applyCandidate)
|
||||
|
||||
// And then re-add it if it was removed
|
||||
if (importantSelector && parentSelector !== parent.selector) {
|
||||
rule.selector = applyImportantSelector(rule.selector, importantSelector)
|
||||
}
|
||||
|
||||
rule.walkDecls((d) => {
|
||||
d.important = meta.important || important
|
||||
})
|
||||
|
||||
// Move pseudo elements to the end of the selector (if necessary)
|
||||
let selector = parser().astSync(rule.selector)
|
||||
selector.each((sel) => movePseudos(sel))
|
||||
rule.selector = selector.toString()
|
||||
})
|
||||
}
|
||||
|
||||
// It could be that the node we were inserted was removed because the class didn't match
|
||||
// If that was the *only* rule in the parent, then we have nothing add so we skip it
|
||||
if (!root.nodes[0]) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Insert it
|
||||
siblings.push([meta.sort, root.nodes[0]])
|
||||
}
|
||||
}
|
||||
|
||||
// Inject the rules, sorted, correctly
|
||||
let nodes = context.offsets.sort(siblings).map((s) => s[1])
|
||||
|
||||
// `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
|
||||
parent.after(nodes)
|
||||
}
|
||||
|
||||
for (let apply of applies) {
|
||||
// If there are left-over declarations, just remove the @apply
|
||||
if (apply.parent.nodes.length > 1) {
|
||||
apply.remove()
|
||||
} else {
|
||||
// The node is empty, drop the full node
|
||||
apply.parent.remove()
|
||||
}
|
||||
}
|
||||
|
||||
// Do it again, in case we have other `@apply` rules
|
||||
processApply(root, context, localCache)
|
||||
}
|
||||
|
||||
export default function expandApplyAtRules(context) {
|
||||
return (root) => {
|
||||
// Build a cache of the user's CSS so we can use it to resolve classes used by @apply
|
||||
let localCache = lazyCache(() => buildLocalApplyCache(root, context))
|
||||
|
||||
processApply(root, context, localCache)
|
||||
}
|
||||
}
|
285
node_modules/tailwindcss/src/lib/expandTailwindAtRules.js
generated
vendored
Normal file
285
node_modules/tailwindcss/src/lib/expandTailwindAtRules.js
generated
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
import fs from 'fs'
|
||||
import LRU from '@alloc/quick-lru'
|
||||
import * as sharedState from './sharedState'
|
||||
import { generateRules } from './generateRules'
|
||||
import log from '../util/log'
|
||||
import cloneNodes from '../util/cloneNodes'
|
||||
import { defaultExtractor } from './defaultExtractor'
|
||||
|
||||
let env = sharedState.env
|
||||
|
||||
const builtInExtractors = {
|
||||
DEFAULT: defaultExtractor,
|
||||
}
|
||||
|
||||
const builtInTransformers = {
|
||||
DEFAULT: (content) => content,
|
||||
svelte: (content) => content.replace(/(?:^|\s)class:/g, ' '),
|
||||
}
|
||||
|
||||
function getExtractor(context, fileExtension) {
|
||||
let extractors = context.tailwindConfig.content.extract
|
||||
|
||||
return (
|
||||
extractors[fileExtension] ||
|
||||
extractors.DEFAULT ||
|
||||
builtInExtractors[fileExtension] ||
|
||||
builtInExtractors.DEFAULT(context)
|
||||
)
|
||||
}
|
||||
|
||||
function getTransformer(tailwindConfig, fileExtension) {
|
||||
let transformers = tailwindConfig.content.transform
|
||||
|
||||
return (
|
||||
transformers[fileExtension] ||
|
||||
transformers.DEFAULT ||
|
||||
builtInTransformers[fileExtension] ||
|
||||
builtInTransformers.DEFAULT
|
||||
)
|
||||
}
|
||||
|
||||
let extractorCache = new WeakMap()
|
||||
|
||||
// Scans template contents for possible classes. This is a hot path on initial build but
|
||||
// not too important for subsequent builds. The faster the better though — if we can speed
|
||||
// up these regexes by 50% that could cut initial build time by like 20%.
|
||||
function getClassCandidates(content, extractor, candidates, seen) {
|
||||
if (!extractorCache.has(extractor)) {
|
||||
extractorCache.set(extractor, new LRU({ maxSize: 25000 }))
|
||||
}
|
||||
|
||||
for (let line of content.split('\n')) {
|
||||
line = line.trim()
|
||||
|
||||
if (seen.has(line)) {
|
||||
continue
|
||||
}
|
||||
seen.add(line)
|
||||
|
||||
if (extractorCache.get(extractor).has(line)) {
|
||||
for (let match of extractorCache.get(extractor).get(line)) {
|
||||
candidates.add(match)
|
||||
}
|
||||
} else {
|
||||
let extractorMatches = extractor(line).filter((s) => s !== '!*')
|
||||
let lineMatchesSet = new Set(extractorMatches)
|
||||
|
||||
for (let match of lineMatchesSet) {
|
||||
candidates.add(match)
|
||||
}
|
||||
|
||||
extractorCache.get(extractor).set(line, lineMatchesSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {[import('./offsets.js').RuleOffset, import('postcss').Node][]} rules
|
||||
* @param {*} context
|
||||
*/
|
||||
function buildStylesheet(rules, context) {
|
||||
let sortedRules = context.offsets.sort(rules)
|
||||
|
||||
let returnValue = {
|
||||
base: new Set(),
|
||||
defaults: new Set(),
|
||||
components: new Set(),
|
||||
utilities: new Set(),
|
||||
variants: new Set(),
|
||||
}
|
||||
|
||||
for (let [sort, rule] of sortedRules) {
|
||||
returnValue[sort.layer].add(rule)
|
||||
}
|
||||
|
||||
return returnValue
|
||||
}
|
||||
|
||||
export default function expandTailwindAtRules(context) {
|
||||
return async (root) => {
|
||||
let layerNodes = {
|
||||
base: null,
|
||||
components: null,
|
||||
utilities: null,
|
||||
variants: null,
|
||||
}
|
||||
|
||||
root.walkAtRules((rule) => {
|
||||
// Make sure this file contains Tailwind directives. If not, we can save
|
||||
// a lot of work and bail early. Also we don't have to register our touch
|
||||
// file as a dependency since the output of this CSS does not depend on
|
||||
// the source of any templates. Think Vue <style> blocks for example.
|
||||
if (rule.name === 'tailwind') {
|
||||
if (Object.keys(layerNodes).includes(rule.params)) {
|
||||
layerNodes[rule.params] = rule
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (Object.values(layerNodes).every((n) => n === null)) {
|
||||
return root
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
// Find potential rules in changed files
|
||||
let candidates = new Set([...(context.candidates ?? []), sharedState.NOT_ON_DEMAND])
|
||||
let seen = new Set()
|
||||
|
||||
env.DEBUG && console.time('Reading changed files')
|
||||
|
||||
if (__OXIDE__) {
|
||||
// TODO: Pass through or implement `extractor`
|
||||
for (let candidate of require('@tailwindcss/oxide').parseCandidateStringsFromFiles(
|
||||
context.changedContent
|
||||
// Object.assign({}, builtInTransformers, context.tailwindConfig.content.transform)
|
||||
)) {
|
||||
candidates.add(candidate)
|
||||
}
|
||||
|
||||
// for (let { file, content, extension } of context.changedContent) {
|
||||
// let transformer = getTransformer(context.tailwindConfig, extension)
|
||||
// let extractor = getExtractor(context, extension)
|
||||
// getClassCandidatesOxide(file, transformer(content), extractor, candidates, seen)
|
||||
// }
|
||||
} else {
|
||||
await Promise.all(
|
||||
context.changedContent.map(async ({ file, content, extension }) => {
|
||||
let transformer = getTransformer(context.tailwindConfig, extension)
|
||||
let extractor = getExtractor(context, extension)
|
||||
content = file ? await fs.promises.readFile(file, 'utf8') : content
|
||||
getClassCandidates(transformer(content), extractor, candidates, seen)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
env.DEBUG && console.timeEnd('Reading changed files')
|
||||
|
||||
// ---
|
||||
|
||||
// Generate the actual CSS
|
||||
let classCacheCount = context.classCache.size
|
||||
|
||||
env.DEBUG && console.time('Generate rules')
|
||||
env.DEBUG && console.time('Sorting candidates')
|
||||
let sortedCandidates = __OXIDE__
|
||||
? candidates
|
||||
: new Set(
|
||||
[...candidates].sort((a, z) => {
|
||||
if (a === z) return 0
|
||||
if (a < z) return -1
|
||||
return 1
|
||||
})
|
||||
)
|
||||
env.DEBUG && console.timeEnd('Sorting candidates')
|
||||
generateRules(sortedCandidates, context)
|
||||
env.DEBUG && console.timeEnd('Generate rules')
|
||||
|
||||
// We only ever add to the classCache, so if it didn't grow, there is nothing new.
|
||||
env.DEBUG && console.time('Build stylesheet')
|
||||
if (context.stylesheetCache === null || context.classCache.size !== classCacheCount) {
|
||||
context.stylesheetCache = buildStylesheet([...context.ruleCache], context)
|
||||
}
|
||||
env.DEBUG && console.timeEnd('Build stylesheet')
|
||||
|
||||
let {
|
||||
defaults: defaultNodes,
|
||||
base: baseNodes,
|
||||
components: componentNodes,
|
||||
utilities: utilityNodes,
|
||||
variants: screenNodes,
|
||||
} = context.stylesheetCache
|
||||
|
||||
// ---
|
||||
|
||||
// Replace any Tailwind directives with generated CSS
|
||||
|
||||
if (layerNodes.base) {
|
||||
layerNodes.base.before(
|
||||
cloneNodes([...baseNodes, ...defaultNodes], layerNodes.base.source, {
|
||||
layer: 'base',
|
||||
})
|
||||
)
|
||||
layerNodes.base.remove()
|
||||
}
|
||||
|
||||
if (layerNodes.components) {
|
||||
layerNodes.components.before(
|
||||
cloneNodes([...componentNodes], layerNodes.components.source, {
|
||||
layer: 'components',
|
||||
})
|
||||
)
|
||||
layerNodes.components.remove()
|
||||
}
|
||||
|
||||
if (layerNodes.utilities) {
|
||||
layerNodes.utilities.before(
|
||||
cloneNodes([...utilityNodes], layerNodes.utilities.source, {
|
||||
layer: 'utilities',
|
||||
})
|
||||
)
|
||||
layerNodes.utilities.remove()
|
||||
}
|
||||
|
||||
// We do post-filtering to not alter the emitted order of the variants
|
||||
const variantNodes = Array.from(screenNodes).filter((node) => {
|
||||
const parentLayer = node.raws.tailwind?.parentLayer
|
||||
|
||||
if (parentLayer === 'components') {
|
||||
return layerNodes.components !== null
|
||||
}
|
||||
|
||||
if (parentLayer === 'utilities') {
|
||||
return layerNodes.utilities !== null
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if (layerNodes.variants) {
|
||||
layerNodes.variants.before(
|
||||
cloneNodes(variantNodes, layerNodes.variants.source, {
|
||||
layer: 'variants',
|
||||
})
|
||||
)
|
||||
layerNodes.variants.remove()
|
||||
} else if (variantNodes.length > 0) {
|
||||
root.append(
|
||||
cloneNodes(variantNodes, root.source, {
|
||||
layer: 'variants',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// If we've got a utility layer and no utilities are generated there's likely something wrong
|
||||
const hasUtilityVariants = variantNodes.some(
|
||||
(node) => node.raws.tailwind?.parentLayer === 'utilities'
|
||||
)
|
||||
|
||||
if (layerNodes.utilities && utilityNodes.size === 0 && !hasUtilityVariants) {
|
||||
log.warn('content-problems', [
|
||||
'No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.',
|
||||
'https://tailwindcss.com/docs/content-configuration',
|
||||
])
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
if (env.DEBUG) {
|
||||
console.log('Potential classes: ', candidates.size)
|
||||
console.log('Active contexts: ', sharedState.contextSourcesMap.size)
|
||||
}
|
||||
|
||||
// Clear the cache for the changed files
|
||||
context.changedContent = []
|
||||
|
||||
// Cleanup any leftover @layer atrules
|
||||
root.walkAtRules('layer', (rule) => {
|
||||
if (Object.keys(layerNodes).includes(rule.params)) {
|
||||
rule.remove()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
48
node_modules/tailwindcss/src/lib/findAtConfigPath.js
generated
vendored
Normal file
48
node_modules/tailwindcss/src/lib/findAtConfigPath.js
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
/**
|
||||
* Find the @config at-rule in the given CSS AST and return the relative path to the config file
|
||||
*
|
||||
* @param {import('postcss').Root} root
|
||||
* @param {import('postcss').Result} result
|
||||
*/
|
||||
export function findAtConfigPath(root, result) {
|
||||
let configPath = null
|
||||
let relativeTo = null
|
||||
|
||||
root.walkAtRules('config', (rule) => {
|
||||
relativeTo = rule.source?.input.file ?? result.opts.from ?? null
|
||||
|
||||
if (relativeTo === null) {
|
||||
throw rule.error(
|
||||
'The `@config` directive cannot be used without setting `from` in your PostCSS config.'
|
||||
)
|
||||
}
|
||||
|
||||
if (configPath) {
|
||||
throw rule.error('Only one `@config` directive is allowed per file.')
|
||||
}
|
||||
|
||||
let matches = rule.params.match(/(['"])(.*?)\1/)
|
||||
if (!matches) {
|
||||
throw rule.error('A path is required when using the `@config` directive.')
|
||||
}
|
||||
|
||||
let inputPath = matches[2]
|
||||
if (path.isAbsolute(inputPath)) {
|
||||
throw rule.error('The `@config` directive cannot be used with an absolute path.')
|
||||
}
|
||||
|
||||
configPath = path.resolve(path.dirname(relativeTo), inputPath)
|
||||
if (!fs.existsSync(configPath)) {
|
||||
throw rule.error(
|
||||
`The config file at "${inputPath}" does not exist. Make sure the path is correct and the file exists.`
|
||||
)
|
||||
}
|
||||
|
||||
rule.remove()
|
||||
})
|
||||
|
||||
return configPath ? configPath : null
|
||||
}
|
934
node_modules/tailwindcss/src/lib/generateRules.js
generated
vendored
Normal file
934
node_modules/tailwindcss/src/lib/generateRules.js
generated
vendored
Normal file
@@ -0,0 +1,934 @@
|
||||
import postcss from 'postcss'
|
||||
import selectorParser from 'postcss-selector-parser'
|
||||
import parseObjectStyles from '../util/parseObjectStyles'
|
||||
import isPlainObject from '../util/isPlainObject'
|
||||
import prefixSelector from '../util/prefixSelector'
|
||||
import { updateAllClasses, getMatchingTypes } from '../util/pluginUtils'
|
||||
import log from '../util/log'
|
||||
import * as sharedState from './sharedState'
|
||||
import {
|
||||
formatVariantSelector,
|
||||
finalizeSelector,
|
||||
eliminateIrrelevantSelectors,
|
||||
} from '../util/formatVariantSelector'
|
||||
import { asClass } from '../util/nameClass'
|
||||
import { normalize } from '../util/dataTypes'
|
||||
import { isValidVariantFormatString, parseVariant, INTERNAL_FEATURES } from './setupContextUtils'
|
||||
import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue'
|
||||
import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js'
|
||||
import { flagEnabled } from '../featureFlags'
|
||||
import { applyImportantSelector } from '../util/applyImportantSelector'
|
||||
|
||||
let classNameParser = selectorParser((selectors) => {
|
||||
return selectors.first.filter(({ type }) => type === 'class').pop().value
|
||||
})
|
||||
|
||||
export function getClassNameFromSelector(selector) {
|
||||
return classNameParser.transformSync(selector)
|
||||
}
|
||||
|
||||
// Generate match permutations for a class candidate, like:
|
||||
// ['ring-offset-blue', '100']
|
||||
// ['ring-offset', 'blue-100']
|
||||
// ['ring', 'offset-blue-100']
|
||||
// Example with dynamic classes:
|
||||
// ['grid-cols', '[[linename],1fr,auto]']
|
||||
// ['grid', 'cols-[[linename],1fr,auto]']
|
||||
function* candidatePermutations(candidate) {
|
||||
let lastIndex = Infinity
|
||||
|
||||
while (lastIndex >= 0) {
|
||||
let dashIdx
|
||||
let wasSlash = false
|
||||
|
||||
if (lastIndex === Infinity && candidate.endsWith(']')) {
|
||||
let bracketIdx = candidate.indexOf('[')
|
||||
|
||||
// If character before `[` isn't a dash or a slash, this isn't a dynamic class
|
||||
// eg. string[]
|
||||
if (candidate[bracketIdx - 1] === '-') {
|
||||
dashIdx = bracketIdx - 1
|
||||
} else if (candidate[bracketIdx - 1] === '/') {
|
||||
dashIdx = bracketIdx - 1
|
||||
wasSlash = true
|
||||
} else {
|
||||
dashIdx = -1
|
||||
}
|
||||
} else if (lastIndex === Infinity && candidate.includes('/')) {
|
||||
dashIdx = candidate.lastIndexOf('/')
|
||||
wasSlash = true
|
||||
} else {
|
||||
dashIdx = candidate.lastIndexOf('-', lastIndex)
|
||||
}
|
||||
|
||||
if (dashIdx < 0) {
|
||||
break
|
||||
}
|
||||
|
||||
let prefix = candidate.slice(0, dashIdx)
|
||||
let modifier = candidate.slice(wasSlash ? dashIdx : dashIdx + 1)
|
||||
|
||||
lastIndex = dashIdx - 1
|
||||
|
||||
// TODO: This feels a bit hacky
|
||||
if (prefix === '' || modifier === '/') {
|
||||
continue
|
||||
}
|
||||
|
||||
yield [prefix, modifier]
|
||||
}
|
||||
}
|
||||
|
||||
function applyPrefix(matches, context) {
|
||||
if (matches.length === 0 || context.tailwindConfig.prefix === '') {
|
||||
return matches
|
||||
}
|
||||
|
||||
for (let match of matches) {
|
||||
let [meta] = match
|
||||
if (meta.options.respectPrefix) {
|
||||
let container = postcss.root({ nodes: [match[1].clone()] })
|
||||
let classCandidate = match[1].raws.tailwind.classCandidate
|
||||
|
||||
container.walkRules((r) => {
|
||||
// If this is a negative utility with a dash *before* the prefix we
|
||||
// have to ensure that the generated selector matches the candidate
|
||||
|
||||
// Not doing this will cause `-tw-top-1` to generate the class `.tw--top-1`
|
||||
// The disconnect between candidate <-> class can cause @apply to hard crash.
|
||||
let shouldPrependNegative = classCandidate.startsWith('-')
|
||||
|
||||
r.selector = prefixSelector(
|
||||
context.tailwindConfig.prefix,
|
||||
r.selector,
|
||||
shouldPrependNegative
|
||||
)
|
||||
})
|
||||
|
||||
match[1] = container.nodes[0]
|
||||
}
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
function applyImportant(matches, classCandidate) {
|
||||
if (matches.length === 0) {
|
||||
return matches
|
||||
}
|
||||
|
||||
let result = []
|
||||
|
||||
for (let [meta, rule] of matches) {
|
||||
let container = postcss.root({ nodes: [rule.clone()] })
|
||||
|
||||
container.walkRules((r) => {
|
||||
let ast = selectorParser().astSync(r.selector)
|
||||
|
||||
// Remove extraneous selectors that do not include the base candidate
|
||||
ast.each((sel) => eliminateIrrelevantSelectors(sel, classCandidate))
|
||||
|
||||
// Update all instances of the base candidate to include the important marker
|
||||
updateAllClasses(ast, (className) =>
|
||||
className === classCandidate ? `!${className}` : className
|
||||
)
|
||||
|
||||
r.selector = ast.toString()
|
||||
|
||||
r.walkDecls((d) => (d.important = true))
|
||||
})
|
||||
|
||||
result.push([{ ...meta, important: true }, container.nodes[0]])
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Takes a list of rule tuples and applies a variant like `hover`, sm`,
|
||||
// whatever to it. We used to do some extra caching here to avoid generating
|
||||
// a variant of the same rule more than once, but this was never hit because
|
||||
// we cache at the entire selector level further up the tree.
|
||||
//
|
||||
// Technically you can get a cache hit if you have `hover:focus:text-center`
|
||||
// and `focus:hover:text-center` in the same project, but it doesn't feel
|
||||
// worth the complexity for that case.
|
||||
|
||||
function applyVariant(variant, matches, context) {
|
||||
if (matches.length === 0) {
|
||||
return matches
|
||||
}
|
||||
|
||||
/** @type {{modifier: string | null, value: string | null}} */
|
||||
let args = { modifier: null, value: sharedState.NONE }
|
||||
|
||||
// Retrieve "modifier"
|
||||
{
|
||||
let [baseVariant, ...modifiers] = splitAtTopLevelOnly(variant, '/')
|
||||
|
||||
// This is a hack to support variants with `/` in them, like `ar-1/10/20:text-red-500`
|
||||
// In this case 1/10 is a value but /20 is a modifier
|
||||
if (modifiers.length > 1) {
|
||||
baseVariant = baseVariant + '/' + modifiers.slice(0, -1).join('/')
|
||||
modifiers = modifiers.slice(-1)
|
||||
}
|
||||
|
||||
if (modifiers.length && !context.variantMap.has(variant)) {
|
||||
variant = baseVariant
|
||||
args.modifier = modifiers[0]
|
||||
|
||||
if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve "arbitrary value"
|
||||
if (variant.endsWith(']') && !variant.startsWith('[')) {
|
||||
// We either have:
|
||||
// @[200px]
|
||||
// group-[:hover]
|
||||
//
|
||||
// But we don't want:
|
||||
// @-[200px] (`-` is incorrect)
|
||||
// group[:hover] (`-` is missing)
|
||||
let match = /(.)(-?)\[(.*)\]/g.exec(variant)
|
||||
if (match) {
|
||||
let [, char, separator, value] = match
|
||||
// @-[200px] case
|
||||
if (char === '@' && separator === '-') return []
|
||||
// group[:hover] case
|
||||
if (char !== '@' && separator === '') return []
|
||||
|
||||
variant = variant.replace(`${separator}[${value}]`, '')
|
||||
args.value = value
|
||||
}
|
||||
}
|
||||
|
||||
// Register arbitrary variants
|
||||
if (isArbitraryValue(variant) && !context.variantMap.has(variant)) {
|
||||
let sort = context.offsets.recordVariant(variant)
|
||||
|
||||
let selector = normalize(variant.slice(1, -1))
|
||||
let selectors = splitAtTopLevelOnly(selector, ',')
|
||||
|
||||
// We do not support multiple selectors for arbitrary variants
|
||||
if (selectors.length > 1) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (!selectors.every(isValidVariantFormatString)) {
|
||||
return []
|
||||
}
|
||||
|
||||
let records = selectors.map((sel, idx) => [
|
||||
context.offsets.applyParallelOffset(sort, idx),
|
||||
parseVariant(sel.trim()),
|
||||
])
|
||||
|
||||
context.variantMap.set(variant, records)
|
||||
}
|
||||
|
||||
if (context.variantMap.has(variant)) {
|
||||
let isArbitraryVariant = isArbitraryValue(variant)
|
||||
let internalFeatures = context.variantOptions.get(variant)?.[INTERNAL_FEATURES] ?? {}
|
||||
let variantFunctionTuples = context.variantMap.get(variant).slice()
|
||||
let result = []
|
||||
|
||||
let respectPrefix = (() => {
|
||||
if (isArbitraryVariant) return false
|
||||
if (internalFeatures.respectPrefix === false) return false
|
||||
return true
|
||||
})()
|
||||
|
||||
for (let [meta, rule] of matches) {
|
||||
// Don't generate variants for user css
|
||||
if (meta.layer === 'user') {
|
||||
continue
|
||||
}
|
||||
|
||||
let container = postcss.root({ nodes: [rule.clone()] })
|
||||
|
||||
for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples) {
|
||||
let clone = (containerFromArray ?? container).clone()
|
||||
let collectedFormats = []
|
||||
|
||||
function prepareBackup() {
|
||||
// Already prepared, chicken out
|
||||
if (clone.raws.neededBackup) {
|
||||
return
|
||||
}
|
||||
clone.raws.neededBackup = true
|
||||
clone.walkRules((rule) => (rule.raws.originalSelector = rule.selector))
|
||||
}
|
||||
|
||||
function modifySelectors(modifierFunction) {
|
||||
prepareBackup()
|
||||
clone.each((rule) => {
|
||||
if (rule.type !== 'rule') {
|
||||
return
|
||||
}
|
||||
|
||||
rule.selectors = rule.selectors.map((selector) => {
|
||||
return modifierFunction({
|
||||
get className() {
|
||||
return getClassNameFromSelector(selector)
|
||||
},
|
||||
selector,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
let ruleWithVariant = variantFunction({
|
||||
// Public API
|
||||
get container() {
|
||||
prepareBackup()
|
||||
return clone
|
||||
},
|
||||
separator: context.tailwindConfig.separator,
|
||||
modifySelectors,
|
||||
|
||||
// Private API for now
|
||||
wrap(wrapper) {
|
||||
let nodes = clone.nodes
|
||||
clone.removeAll()
|
||||
wrapper.append(nodes)
|
||||
clone.append(wrapper)
|
||||
},
|
||||
format(selectorFormat) {
|
||||
collectedFormats.push({
|
||||
format: selectorFormat,
|
||||
respectPrefix,
|
||||
})
|
||||
},
|
||||
args,
|
||||
})
|
||||
|
||||
// It can happen that a list of format strings is returned from within the function. In that
|
||||
// case, we have to process them as well. We can use the existing `variantSort`.
|
||||
if (Array.isArray(ruleWithVariant)) {
|
||||
for (let [idx, variantFunction] of ruleWithVariant.entries()) {
|
||||
// This is a little bit scary since we are pushing to an array of items that we are
|
||||
// currently looping over. However, you can also think of it like a processing queue
|
||||
// where you keep handling jobs until everything is done and each job can queue more
|
||||
// jobs if needed.
|
||||
variantFunctionTuples.push([
|
||||
context.offsets.applyParallelOffset(variantSort, idx),
|
||||
variantFunction,
|
||||
|
||||
// If the clone has been modified we have to pass that back
|
||||
// though so each rule can use the modified container
|
||||
clone.clone(),
|
||||
])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (typeof ruleWithVariant === 'string') {
|
||||
collectedFormats.push({
|
||||
format: ruleWithVariant,
|
||||
respectPrefix,
|
||||
})
|
||||
}
|
||||
|
||||
if (ruleWithVariant === null) {
|
||||
continue
|
||||
}
|
||||
|
||||
// We had to backup selectors, therefore we assume that somebody touched
|
||||
// `container` or `modifySelectors`. Let's see if they did, so that we
|
||||
// can restore the selectors, and collect the format strings.
|
||||
if (clone.raws.neededBackup) {
|
||||
delete clone.raws.neededBackup
|
||||
clone.walkRules((rule) => {
|
||||
let before = rule.raws.originalSelector
|
||||
if (!before) return
|
||||
delete rule.raws.originalSelector
|
||||
if (before === rule.selector) return // No mutation happened
|
||||
|
||||
let modified = rule.selector
|
||||
|
||||
// Rebuild the base selector, this is what plugin authors would do
|
||||
// as well. E.g.: `${variant}${separator}${className}`.
|
||||
// However, plugin authors probably also prepend or append certain
|
||||
// classes, pseudos, ids, ...
|
||||
let rebuiltBase = selectorParser((selectors) => {
|
||||
selectors.walkClasses((classNode) => {
|
||||
classNode.value = `${variant}${context.tailwindConfig.separator}${classNode.value}`
|
||||
})
|
||||
}).processSync(before)
|
||||
|
||||
// Now that we know the original selector, the new selector, and
|
||||
// the rebuild part in between, we can replace the part that plugin
|
||||
// authors need to rebuild with `&`, and eventually store it in the
|
||||
// collectedFormats. Similar to what `format('...')` would do.
|
||||
//
|
||||
// E.g.:
|
||||
// variant: foo
|
||||
// selector: .markdown > p
|
||||
// modified (by plugin): .foo .foo\\:markdown > p
|
||||
// rebuiltBase (internal): .foo\\:markdown > p
|
||||
// format: .foo &
|
||||
collectedFormats.push({
|
||||
format: modified.replace(rebuiltBase, '&'),
|
||||
respectPrefix,
|
||||
})
|
||||
rule.selector = before
|
||||
})
|
||||
}
|
||||
|
||||
// This tracks the originating layer for the variant
|
||||
// For example:
|
||||
// .sm:underline {} is a variant of something in the utilities layer
|
||||
// .sm:container {} is a variant of the container component
|
||||
clone.nodes[0].raws.tailwind = { ...clone.nodes[0].raws.tailwind, parentLayer: meta.layer }
|
||||
|
||||
let withOffset = [
|
||||
{
|
||||
...meta,
|
||||
sort: context.offsets.applyVariantOffset(
|
||||
meta.sort,
|
||||
variantSort,
|
||||
Object.assign(args, context.variantOptions.get(variant))
|
||||
),
|
||||
collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
|
||||
},
|
||||
clone.nodes[0],
|
||||
]
|
||||
result.push(withOffset)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
function parseRules(rule, cache, options = {}) {
|
||||
// PostCSS node
|
||||
if (!isPlainObject(rule) && !Array.isArray(rule)) {
|
||||
return [[rule], options]
|
||||
}
|
||||
|
||||
// Tuple
|
||||
if (Array.isArray(rule)) {
|
||||
return parseRules(rule[0], cache, rule[1])
|
||||
}
|
||||
|
||||
// Simple object
|
||||
if (!cache.has(rule)) {
|
||||
cache.set(rule, parseObjectStyles(rule))
|
||||
}
|
||||
|
||||
return [cache.get(rule), options]
|
||||
}
|
||||
|
||||
const IS_VALID_PROPERTY_NAME = /^[a-z_-]/
|
||||
|
||||
function isValidPropName(name) {
|
||||
return IS_VALID_PROPERTY_NAME.test(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} declaration
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function looksLikeUri(declaration) {
|
||||
// Quick bailout for obvious non-urls
|
||||
// This doesn't support schemes that don't use a leading // but that's unlikely to be a problem
|
||||
if (!declaration.includes('://')) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(declaration)
|
||||
return url.scheme !== '' && url.host !== ''
|
||||
} catch (err) {
|
||||
// Definitely not a valid url
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function isParsableNode(node) {
|
||||
let isParsable = true
|
||||
|
||||
node.walkDecls((decl) => {
|
||||
if (!isParsableCssValue(decl.prop, decl.value)) {
|
||||
isParsable = false
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
return isParsable
|
||||
}
|
||||
|
||||
function isParsableCssValue(property, value) {
|
||||
// We don't want to to treat [https://example.com] as a custom property
|
||||
// Even though, according to the CSS grammar, it's a totally valid CSS declaration
|
||||
// So we short-circuit here by checking if the custom property looks like a url
|
||||
if (looksLikeUri(`${property}:${value}`)) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
postcss.parse(`a{${property}:${value}}`).toResult()
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function extractArbitraryProperty(classCandidate, context) {
|
||||
let [, property, value] = classCandidate.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/) ?? []
|
||||
|
||||
if (value === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!isValidPropName(property)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!isValidArbitraryValue(value)) {
|
||||
return null
|
||||
}
|
||||
|
||||
let normalized = normalize(value)
|
||||
|
||||
if (!isParsableCssValue(property, normalized)) {
|
||||
return null
|
||||
}
|
||||
|
||||
let sort = context.offsets.arbitraryProperty()
|
||||
|
||||
return [
|
||||
[
|
||||
{ sort, layer: 'utilities' },
|
||||
() => ({
|
||||
[asClass(classCandidate)]: {
|
||||
[property]: normalized,
|
||||
},
|
||||
}),
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
function* resolveMatchedPlugins(classCandidate, context) {
|
||||
if (context.candidateRuleMap.has(classCandidate)) {
|
||||
yield [context.candidateRuleMap.get(classCandidate), 'DEFAULT']
|
||||
}
|
||||
|
||||
yield* (function* (arbitraryPropertyRule) {
|
||||
if (arbitraryPropertyRule !== null) {
|
||||
yield [arbitraryPropertyRule, 'DEFAULT']
|
||||
}
|
||||
})(extractArbitraryProperty(classCandidate, context))
|
||||
|
||||
let candidatePrefix = classCandidate
|
||||
let negative = false
|
||||
|
||||
const twConfigPrefix = context.tailwindConfig.prefix
|
||||
|
||||
const twConfigPrefixLen = twConfigPrefix.length
|
||||
|
||||
const hasMatchingPrefix =
|
||||
candidatePrefix.startsWith(twConfigPrefix) || candidatePrefix.startsWith(`-${twConfigPrefix}`)
|
||||
|
||||
if (candidatePrefix[twConfigPrefixLen] === '-' && hasMatchingPrefix) {
|
||||
negative = true
|
||||
candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1)
|
||||
}
|
||||
|
||||
if (negative && context.candidateRuleMap.has(candidatePrefix)) {
|
||||
yield [context.candidateRuleMap.get(candidatePrefix), '-DEFAULT']
|
||||
}
|
||||
|
||||
for (let [prefix, modifier] of candidatePermutations(candidatePrefix)) {
|
||||
if (context.candidateRuleMap.has(prefix)) {
|
||||
yield [context.candidateRuleMap.get(prefix), negative ? `-${modifier}` : modifier]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function splitWithSeparator(input, separator) {
|
||||
if (input === sharedState.NOT_ON_DEMAND) {
|
||||
return [sharedState.NOT_ON_DEMAND]
|
||||
}
|
||||
|
||||
return splitAtTopLevelOnly(input, separator)
|
||||
}
|
||||
|
||||
function* recordCandidates(matches, classCandidate) {
|
||||
for (const match of matches) {
|
||||
match[1].raws.tailwind = {
|
||||
...match[1].raws.tailwind,
|
||||
classCandidate,
|
||||
preserveSource: match[0].options?.preserveSource ?? false,
|
||||
}
|
||||
|
||||
yield match
|
||||
}
|
||||
}
|
||||
|
||||
function* resolveMatches(candidate, context, original = candidate) {
|
||||
let separator = context.tailwindConfig.separator
|
||||
let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse()
|
||||
let important = false
|
||||
|
||||
if (classCandidate.startsWith('!')) {
|
||||
important = true
|
||||
classCandidate = classCandidate.slice(1)
|
||||
}
|
||||
|
||||
if (flagEnabled(context.tailwindConfig, 'variantGrouping')) {
|
||||
if (classCandidate.startsWith('(') && classCandidate.endsWith(')')) {
|
||||
let base = variants.slice().reverse().join(separator)
|
||||
for (let part of splitAtTopLevelOnly(classCandidate.slice(1, -1), ',')) {
|
||||
yield* resolveMatches(base + separator + part, context, original)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Reintroduce this in ways that doesn't break on false positives
|
||||
// function sortAgainst(toSort, against) {
|
||||
// return toSort.slice().sort((a, z) => {
|
||||
// return bigSign(against.get(a)[0] - against.get(z)[0])
|
||||
// })
|
||||
// }
|
||||
// let sorted = sortAgainst(variants, context.variantMap)
|
||||
// if (sorted.toString() !== variants.toString()) {
|
||||
// let corrected = sorted.reverse().concat(classCandidate).join(':')
|
||||
// throw new Error(`Class ${candidate} should be written as ${corrected}`)
|
||||
// }
|
||||
|
||||
for (let matchedPlugins of resolveMatchedPlugins(classCandidate, context)) {
|
||||
let matches = []
|
||||
let typesByMatches = new Map()
|
||||
|
||||
let [plugins, modifier] = matchedPlugins
|
||||
let isOnlyPlugin = plugins.length === 1
|
||||
|
||||
for (let [sort, plugin] of plugins) {
|
||||
let matchesPerPlugin = []
|
||||
|
||||
if (typeof plugin === 'function') {
|
||||
for (let ruleSet of [].concat(plugin(modifier, { isOnlyPlugin }))) {
|
||||
let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
|
||||
for (let rule of rules) {
|
||||
matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only process static plugins on exact matches
|
||||
else if (modifier === 'DEFAULT' || modifier === '-DEFAULT') {
|
||||
let ruleSet = plugin
|
||||
let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
|
||||
for (let rule of rules) {
|
||||
matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
|
||||
}
|
||||
}
|
||||
|
||||
if (matchesPerPlugin.length > 0) {
|
||||
let matchingTypes = Array.from(
|
||||
getMatchingTypes(
|
||||
sort.options?.types ?? [],
|
||||
modifier,
|
||||
sort.options ?? {},
|
||||
context.tailwindConfig
|
||||
)
|
||||
).map(([_, type]) => type)
|
||||
|
||||
if (matchingTypes.length > 0) {
|
||||
typesByMatches.set(matchesPerPlugin, matchingTypes)
|
||||
}
|
||||
|
||||
matches.push(matchesPerPlugin)
|
||||
}
|
||||
}
|
||||
|
||||
if (isArbitraryValue(modifier)) {
|
||||
if (matches.length > 1) {
|
||||
// Partition plugins in 2 categories so that we can start searching in the plugins that
|
||||
// don't have `any` as a type first.
|
||||
let [withAny, withoutAny] = matches.reduce(
|
||||
(group, plugin) => {
|
||||
let hasAnyType = plugin.some(([{ options }]) =>
|
||||
options.types.some(({ type }) => type === 'any')
|
||||
)
|
||||
|
||||
if (hasAnyType) {
|
||||
group[0].push(plugin)
|
||||
} else {
|
||||
group[1].push(plugin)
|
||||
}
|
||||
return group
|
||||
},
|
||||
[[], []]
|
||||
)
|
||||
|
||||
function findFallback(matches) {
|
||||
// If only a single plugin matches, let's take that one
|
||||
if (matches.length === 1) {
|
||||
return matches[0]
|
||||
}
|
||||
|
||||
// Otherwise, find the plugin that creates a valid rule given the arbitrary value, and
|
||||
// also has the correct type which preferOnConflicts the plugin in case of clashes.
|
||||
return matches.find((rules) => {
|
||||
let matchingTypes = typesByMatches.get(rules)
|
||||
return rules.some(([{ options }, rule]) => {
|
||||
if (!isParsableNode(rule)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return options.types.some(
|
||||
({ type, preferOnConflict }) => matchingTypes.includes(type) && preferOnConflict
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Try to find a fallback plugin, because we already know that multiple plugins matched for
|
||||
// the given arbitrary value.
|
||||
let fallback = findFallback(withoutAny) ?? findFallback(withAny)
|
||||
if (fallback) {
|
||||
matches = [fallback]
|
||||
}
|
||||
|
||||
// We couldn't find a fallback plugin which means that there are now multiple plugins that
|
||||
// generated css for the current candidate. This means that the result is ambiguous and this
|
||||
// should not happen. We won't generate anything right now, so let's report this to the user
|
||||
// by logging some options about what they can do.
|
||||
else {
|
||||
let typesPerPlugin = matches.map(
|
||||
(match) => new Set([...(typesByMatches.get(match) ?? [])])
|
||||
)
|
||||
|
||||
// Remove duplicates, so that we can detect proper unique types for each plugin.
|
||||
for (let pluginTypes of typesPerPlugin) {
|
||||
for (let type of pluginTypes) {
|
||||
let removeFromOwnGroup = false
|
||||
|
||||
for (let otherGroup of typesPerPlugin) {
|
||||
if (pluginTypes === otherGroup) continue
|
||||
|
||||
if (otherGroup.has(type)) {
|
||||
otherGroup.delete(type)
|
||||
removeFromOwnGroup = true
|
||||
}
|
||||
}
|
||||
|
||||
if (removeFromOwnGroup) pluginTypes.delete(type)
|
||||
}
|
||||
}
|
||||
|
||||
let messages = []
|
||||
|
||||
for (let [idx, group] of typesPerPlugin.entries()) {
|
||||
for (let type of group) {
|
||||
let rules = matches[idx]
|
||||
.map(([, rule]) => rule)
|
||||
.flat()
|
||||
.map((rule) =>
|
||||
rule
|
||||
.toString()
|
||||
.split('\n')
|
||||
.slice(1, -1) // Remove selector and closing '}'
|
||||
.map((line) => line.trim())
|
||||
.map((x) => ` ${x}`) // Re-indent
|
||||
.join('\n')
|
||||
)
|
||||
.join('\n\n')
|
||||
|
||||
messages.push(
|
||||
` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
log.warn([
|
||||
`The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
|
||||
...messages,
|
||||
`If this is content and not a class, replace it with \`${candidate
|
||||
.replace('[', '[')
|
||||
.replace(']', ']')}\` to silence this warning.`,
|
||||
])
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
matches = matches.map((list) => list.filter((match) => isParsableNode(match[1])))
|
||||
}
|
||||
|
||||
matches = matches.flat()
|
||||
matches = Array.from(recordCandidates(matches, classCandidate))
|
||||
matches = applyPrefix(matches, context)
|
||||
|
||||
if (important) {
|
||||
matches = applyImportant(matches, classCandidate)
|
||||
}
|
||||
|
||||
for (let variant of variants) {
|
||||
matches = applyVariant(variant, matches, context)
|
||||
}
|
||||
|
||||
for (let match of matches) {
|
||||
match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate }
|
||||
|
||||
// Apply final format selector
|
||||
match = applyFinalFormat(match, { context, candidate, original })
|
||||
|
||||
// Skip rules with invalid selectors
|
||||
// This will cause the candidate to be added to the "not class"
|
||||
// cache skipping it entirely for future builds
|
||||
if (match === null) {
|
||||
continue
|
||||
}
|
||||
|
||||
yield match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyFinalFormat(match, { context, candidate, original }) {
|
||||
if (!match[0].collectedFormats) {
|
||||
return match
|
||||
}
|
||||
|
||||
let isValid = true
|
||||
let finalFormat
|
||||
|
||||
try {
|
||||
finalFormat = formatVariantSelector(match[0].collectedFormats, {
|
||||
context,
|
||||
candidate,
|
||||
})
|
||||
} catch {
|
||||
// The format selector we produced is invalid
|
||||
// This could be because:
|
||||
// - A bug exists
|
||||
// - A plugin introduced an invalid variant selector (ex: `addVariant('foo', '&;foo')`)
|
||||
// - The user used an invalid arbitrary variant (ex: `[&;foo]:underline`)
|
||||
// Either way the build will fail because of this
|
||||
// We would rather that the build pass "silently" given that this could
|
||||
// happen because of picking up invalid things when scanning content
|
||||
// So we'll throw out the candidate instead
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
let container = postcss.root({ nodes: [match[1].clone()] })
|
||||
|
||||
container.walkRules((rule) => {
|
||||
if (inKeyframes(rule)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
rule.selector = finalizeSelector(rule.selector, finalFormat, {
|
||||
candidate: original,
|
||||
context,
|
||||
})
|
||||
} catch {
|
||||
// If this selector is invalid we also want to skip it
|
||||
// But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content
|
||||
isValid = false
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (!isValid) {
|
||||
return null
|
||||
}
|
||||
|
||||
match[1] = container.nodes[0]
|
||||
|
||||
return match
|
||||
}
|
||||
|
||||
function inKeyframes(rule) {
|
||||
return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
|
||||
}
|
||||
|
||||
function getImportantStrategy(important) {
|
||||
if (important === true) {
|
||||
return (rule) => {
|
||||
if (inKeyframes(rule)) {
|
||||
return
|
||||
}
|
||||
|
||||
rule.walkDecls((d) => {
|
||||
if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
|
||||
d.important = true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof important === 'string') {
|
||||
return (rule) => {
|
||||
if (inKeyframes(rule)) {
|
||||
return
|
||||
}
|
||||
|
||||
rule.selectors = rule.selectors.map((selector) => {
|
||||
return applyImportantSelector(selector, important)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateRules(candidates, context) {
|
||||
let allRules = []
|
||||
let strategy = getImportantStrategy(context.tailwindConfig.important)
|
||||
|
||||
for (let candidate of candidates) {
|
||||
if (context.notClassCache.has(candidate)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (context.candidateRuleCache.has(candidate)) {
|
||||
allRules = allRules.concat(Array.from(context.candidateRuleCache.get(candidate)))
|
||||
continue
|
||||
}
|
||||
|
||||
let matches = Array.from(resolveMatches(candidate, context))
|
||||
|
||||
if (matches.length === 0) {
|
||||
context.notClassCache.add(candidate)
|
||||
continue
|
||||
}
|
||||
|
||||
context.classCache.set(candidate, matches)
|
||||
|
||||
let rules = context.candidateRuleCache.get(candidate) ?? new Set()
|
||||
context.candidateRuleCache.set(candidate, rules)
|
||||
|
||||
for (const match of matches) {
|
||||
let [{ sort, options }, rule] = match
|
||||
|
||||
if (options.respectImportant && strategy) {
|
||||
let container = postcss.root({ nodes: [rule.clone()] })
|
||||
container.walkRules(strategy)
|
||||
rule = container.nodes[0]
|
||||
}
|
||||
|
||||
let newEntry = [sort, rule]
|
||||
rules.add(newEntry)
|
||||
context.ruleCache.add(newEntry)
|
||||
allRules.push(newEntry)
|
||||
}
|
||||
}
|
||||
|
||||
return allRules
|
||||
}
|
||||
|
||||
function isArbitraryValue(input) {
|
||||
return input.startsWith('[') && input.endsWith(']')
|
||||
}
|
||||
|
||||
export { resolveMatches, generateRules }
|
79
node_modules/tailwindcss/src/lib/getModuleDependencies.js
generated
vendored
Normal file
79
node_modules/tailwindcss/src/lib/getModuleDependencies.js
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
let jsExtensions = ['.js', '.cjs', '.mjs']
|
||||
|
||||
// Given the current file `a.ts`, we want to make sure that when importing `b` that we resolve
|
||||
// `b.ts` before `b.js`
|
||||
//
|
||||
// E.g.:
|
||||
//
|
||||
// a.ts
|
||||
// b // .ts
|
||||
// c // .ts
|
||||
// a.js
|
||||
// b // .js or .ts
|
||||
|
||||
let jsResolutionOrder = ['', '.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.jsx', '.tsx']
|
||||
let tsResolutionOrder = ['', '.ts', '.cts', '.mts', '.tsx', '.js', '.cjs', '.mjs', '.jsx']
|
||||
|
||||
function resolveWithExtension(file, extensions) {
|
||||
// Try to find `./a.ts`, `./a.ts`, ... from `./a`
|
||||
for (let ext of extensions) {
|
||||
let full = `${file}${ext}`
|
||||
if (fs.existsSync(full) && fs.statSync(full).isFile()) {
|
||||
return full
|
||||
}
|
||||
}
|
||||
|
||||
// Try to find `./a/index.js` from `./a`
|
||||
for (let ext of extensions) {
|
||||
let full = `${file}/index${ext}`
|
||||
if (fs.existsSync(full)) {
|
||||
return full
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function* _getModuleDependencies(filename, base, seen, ext = path.extname(filename)) {
|
||||
// Try to find the file
|
||||
let absoluteFile = resolveWithExtension(
|
||||
path.resolve(base, filename),
|
||||
jsExtensions.includes(ext) ? jsResolutionOrder : tsResolutionOrder
|
||||
)
|
||||
if (absoluteFile === null) return // File doesn't exist
|
||||
|
||||
// Prevent infinite loops when there are circular dependencies
|
||||
if (seen.has(absoluteFile)) return // Already seen
|
||||
seen.add(absoluteFile)
|
||||
|
||||
// Mark the file as a dependency
|
||||
yield absoluteFile
|
||||
|
||||
// Resolve new base for new imports/requires
|
||||
base = path.dirname(absoluteFile)
|
||||
ext = path.extname(absoluteFile)
|
||||
|
||||
let contents = fs.readFileSync(absoluteFile, 'utf-8')
|
||||
|
||||
// Find imports/requires
|
||||
for (let match of [
|
||||
...contents.matchAll(/import[\s\S]*?['"](.{3,}?)['"]/gi),
|
||||
...contents.matchAll(/import[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi),
|
||||
...contents.matchAll(/require\(['"`](.+)['"`]\)/gi),
|
||||
]) {
|
||||
// Bail out if it's not a relative file
|
||||
if (!match[1].startsWith('.')) continue
|
||||
|
||||
yield* _getModuleDependencies(match[1], base, seen, ext)
|
||||
}
|
||||
}
|
||||
|
||||
export default function getModuleDependencies(absoluteFilePath) {
|
||||
if (absoluteFilePath === null) return new Set()
|
||||
return new Set(
|
||||
_getModuleDependencies(absoluteFilePath, path.dirname(absoluteFilePath), new Set())
|
||||
)
|
||||
}
|
31
node_modules/tailwindcss/src/lib/load-config.ts
generated
vendored
Normal file
31
node_modules/tailwindcss/src/lib/load-config.ts
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import jitiFactory from 'jiti'
|
||||
import { transform } from 'sucrase'
|
||||
|
||||
import { Config } from '../../types/config'
|
||||
|
||||
let jiti: ReturnType<typeof jitiFactory> | null = null
|
||||
function lazyJiti() {
|
||||
return (
|
||||
jiti ??
|
||||
(jiti = jitiFactory(__filename, {
|
||||
interopDefault: true,
|
||||
transform: (opts) => {
|
||||
return transform(opts.source, {
|
||||
transforms: ['typescript', 'imports'],
|
||||
})
|
||||
},
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
export function loadConfig(path: string): Config {
|
||||
let config = (function () {
|
||||
try {
|
||||
return path ? require(path) : {}
|
||||
} catch {
|
||||
return lazyJiti()(path)
|
||||
}
|
||||
})()
|
||||
|
||||
return config.default ?? config
|
||||
}
|
84
node_modules/tailwindcss/src/lib/normalizeTailwindDirectives.js
generated
vendored
Normal file
84
node_modules/tailwindcss/src/lib/normalizeTailwindDirectives.js
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
import log from '../util/log'
|
||||
|
||||
export default function normalizeTailwindDirectives(root) {
|
||||
let tailwindDirectives = new Set()
|
||||
let layerDirectives = new Set()
|
||||
let applyDirectives = new Set()
|
||||
|
||||
root.walkAtRules((atRule) => {
|
||||
if (atRule.name === 'apply') {
|
||||
applyDirectives.add(atRule)
|
||||
}
|
||||
|
||||
if (atRule.name === 'import') {
|
||||
if (atRule.params === '"tailwindcss/base"' || atRule.params === "'tailwindcss/base'") {
|
||||
atRule.name = 'tailwind'
|
||||
atRule.params = 'base'
|
||||
} else if (
|
||||
atRule.params === '"tailwindcss/components"' ||
|
||||
atRule.params === "'tailwindcss/components'"
|
||||
) {
|
||||
atRule.name = 'tailwind'
|
||||
atRule.params = 'components'
|
||||
} else if (
|
||||
atRule.params === '"tailwindcss/utilities"' ||
|
||||
atRule.params === "'tailwindcss/utilities'"
|
||||
) {
|
||||
atRule.name = 'tailwind'
|
||||
atRule.params = 'utilities'
|
||||
} else if (
|
||||
atRule.params === '"tailwindcss/screens"' ||
|
||||
atRule.params === "'tailwindcss/screens'" ||
|
||||
atRule.params === '"tailwindcss/variants"' ||
|
||||
atRule.params === "'tailwindcss/variants'"
|
||||
) {
|
||||
atRule.name = 'tailwind'
|
||||
atRule.params = 'variants'
|
||||
}
|
||||
}
|
||||
|
||||
if (atRule.name === 'tailwind') {
|
||||
if (atRule.params === 'screens') {
|
||||
atRule.params = 'variants'
|
||||
}
|
||||
tailwindDirectives.add(atRule.params)
|
||||
}
|
||||
|
||||
if (['layer', 'responsive', 'variants'].includes(atRule.name)) {
|
||||
if (['responsive', 'variants'].includes(atRule.name)) {
|
||||
log.warn(`${atRule.name}-at-rule-deprecated`, [
|
||||
`The \`@${atRule.name}\` directive has been deprecated in Tailwind CSS v3.0.`,
|
||||
`Use \`@layer utilities\` or \`@layer components\` instead.`,
|
||||
'https://tailwindcss.com/docs/upgrade-guide#replace-variants-with-layer',
|
||||
])
|
||||
}
|
||||
layerDirectives.add(atRule)
|
||||
}
|
||||
})
|
||||
|
||||
if (
|
||||
!tailwindDirectives.has('base') ||
|
||||
!tailwindDirectives.has('components') ||
|
||||
!tailwindDirectives.has('utilities')
|
||||
) {
|
||||
for (let rule of layerDirectives) {
|
||||
if (rule.name === 'layer' && ['base', 'components', 'utilities'].includes(rule.params)) {
|
||||
if (!tailwindDirectives.has(rule.params)) {
|
||||
throw rule.error(
|
||||
`\`@layer ${rule.params}\` is used but no matching \`@tailwind ${rule.params}\` directive is present.`
|
||||
)
|
||||
}
|
||||
} else if (rule.name === 'responsive') {
|
||||
if (!tailwindDirectives.has('utilities')) {
|
||||
throw rule.error('`@responsive` is used but `@tailwind utilities` is missing.')
|
||||
}
|
||||
} else if (rule.name === 'variants') {
|
||||
if (!tailwindDirectives.has('utilities')) {
|
||||
throw rule.error('`@variants` is used but `@tailwind utilities` is missing.')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { tailwindDirectives, applyDirectives }
|
||||
}
|
373
node_modules/tailwindcss/src/lib/offsets.js
generated
vendored
Normal file
373
node_modules/tailwindcss/src/lib/offsets.js
generated
vendored
Normal file
@@ -0,0 +1,373 @@
|
||||
// @ts-check
|
||||
|
||||
import bigSign from '../util/bigSign'
|
||||
import { remapBitfield } from './remap-bitfield.js'
|
||||
|
||||
/**
|
||||
* @typedef {'base' | 'defaults' | 'components' | 'utilities' | 'variants' | 'user'} Layer
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} VariantOption
|
||||
* @property {number} id An unique identifier to identify `matchVariant`
|
||||
* @property {function | undefined} sort The sort function
|
||||
* @property {string|null} value The value we want to compare
|
||||
* @property {string|null} modifier The modifier that was used (if any)
|
||||
* @property {bigint} variant The variant bitmask
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} RuleOffset
|
||||
* @property {Layer} layer The layer that this rule belongs to
|
||||
* @property {Layer} parentLayer The layer that this rule originally belonged to. Only different from layer if this is a variant.
|
||||
* @property {bigint} arbitrary 0n if false, 1n if true
|
||||
* @property {bigint} variants Dynamic size. 1 bit per registered variant. 0n means no variants
|
||||
* @property {bigint} parallelIndex Rule index for the parallel variant. 0 if not applicable.
|
||||
* @property {bigint} index Index of the rule / utility in it's given *parent* layer. Monotonically increasing.
|
||||
* @property {VariantOption[]} options Some information on how we can sort arbitrary variants
|
||||
*/
|
||||
|
||||
export class Offsets {
|
||||
constructor() {
|
||||
/**
|
||||
* Offsets for the next rule in a given layer
|
||||
*
|
||||
* @type {Record<Layer, bigint>}
|
||||
*/
|
||||
this.offsets = {
|
||||
defaults: 0n,
|
||||
base: 0n,
|
||||
components: 0n,
|
||||
utilities: 0n,
|
||||
variants: 0n,
|
||||
user: 0n,
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions for a given layer
|
||||
*
|
||||
* @type {Record<Layer, bigint>}
|
||||
*/
|
||||
this.layerPositions = {
|
||||
defaults: 0n,
|
||||
base: 1n,
|
||||
components: 2n,
|
||||
utilities: 3n,
|
||||
|
||||
// There isn't technically a "user" layer, but we need to give it a position
|
||||
// Because it's used for ordering user-css from @apply
|
||||
user: 4n,
|
||||
|
||||
variants: 5n,
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of functions currently registered across all variants (including arbitrary variants)
|
||||
*
|
||||
* @type {bigint}
|
||||
*/
|
||||
this.reservedVariantBits = 0n
|
||||
|
||||
/**
|
||||
* Positions for a given variant
|
||||
*
|
||||
* @type {Map<string, bigint>}
|
||||
*/
|
||||
this.variantOffsets = new Map()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Layer} layer
|
||||
* @returns {RuleOffset}
|
||||
*/
|
||||
create(layer) {
|
||||
return {
|
||||
layer,
|
||||
parentLayer: layer,
|
||||
arbitrary: 0n,
|
||||
variants: 0n,
|
||||
parallelIndex: 0n,
|
||||
index: this.offsets[layer]++,
|
||||
options: [],
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {RuleOffset}
|
||||
*/
|
||||
arbitraryProperty() {
|
||||
return {
|
||||
...this.create('utilities'),
|
||||
arbitrary: 1n,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the offset for a variant
|
||||
*
|
||||
* @param {string} variant
|
||||
* @param {number} index
|
||||
* @returns {RuleOffset}
|
||||
*/
|
||||
forVariant(variant, index = 0) {
|
||||
let offset = this.variantOffsets.get(variant)
|
||||
if (offset === undefined) {
|
||||
throw new Error(`Cannot find offset for unknown variant ${variant}`)
|
||||
}
|
||||
|
||||
return {
|
||||
...this.create('variants'),
|
||||
variants: offset << BigInt(index),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {RuleOffset} rule
|
||||
* @param {RuleOffset} variant
|
||||
* @param {VariantOption} options
|
||||
* @returns {RuleOffset}
|
||||
*/
|
||||
applyVariantOffset(rule, variant, options) {
|
||||
options.variant = variant.variants
|
||||
|
||||
return {
|
||||
...rule,
|
||||
layer: 'variants',
|
||||
parentLayer: rule.layer === 'variants' ? rule.parentLayer : rule.layer,
|
||||
variants: rule.variants | variant.variants,
|
||||
options: options.sort ? [].concat(options, rule.options) : rule.options,
|
||||
|
||||
// TODO: Technically this is wrong. We should be handling parallel index on a per variant basis.
|
||||
// We'll take the max of all the parallel indexes for now.
|
||||
// @ts-ignore
|
||||
parallelIndex: max([rule.parallelIndex, variant.parallelIndex]),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {RuleOffset} offset
|
||||
* @param {number} parallelIndex
|
||||
* @returns {RuleOffset}
|
||||
*/
|
||||
applyParallelOffset(offset, parallelIndex) {
|
||||
return {
|
||||
...offset,
|
||||
parallelIndex: BigInt(parallelIndex),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Each variant gets 1 bit per function / rule registered.
|
||||
* This is because multiple variants can be applied to a single rule and we need to know which ones are present and which ones are not.
|
||||
* Additionally, every unique group of variants is grouped together in the stylesheet.
|
||||
*
|
||||
* This grouping is order-independent. For instance, we do not differentiate between `hover:focus` and `focus:hover`.
|
||||
*
|
||||
* @param {string[]} variants
|
||||
* @param {(name: string) => number} getLength
|
||||
*/
|
||||
recordVariants(variants, getLength) {
|
||||
for (let variant of variants) {
|
||||
this.recordVariant(variant, getLength(variant))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as `recordVariants` but for a single arbitrary variant at runtime.
|
||||
* @param {string} variant
|
||||
* @param {number} fnCount
|
||||
*
|
||||
* @returns {RuleOffset} The highest offset for this variant
|
||||
*/
|
||||
recordVariant(variant, fnCount = 1) {
|
||||
this.variantOffsets.set(variant, 1n << this.reservedVariantBits)
|
||||
|
||||
// Ensure space is reserved for each "function" in the parallel variant
|
||||
// by offsetting the next variant by the number of parallel variants
|
||||
// in the one we just added.
|
||||
|
||||
// Single functions that return parallel variants are NOT handled separately here
|
||||
// They're offset by 1 (or the number of functions) as usual
|
||||
// And each rule returned is tracked separately since the functions are evaluated lazily.
|
||||
// @see `RuleOffset.parallelIndex`
|
||||
this.reservedVariantBits += BigInt(fnCount)
|
||||
|
||||
return {
|
||||
...this.create('variants'),
|
||||
variants: this.variantOffsets.get(variant),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {RuleOffset} a
|
||||
* @param {RuleOffset} b
|
||||
* @returns {bigint}
|
||||
*/
|
||||
compare(a, b) {
|
||||
// Sort layers together
|
||||
if (a.layer !== b.layer) {
|
||||
return this.layerPositions[a.layer] - this.layerPositions[b.layer]
|
||||
}
|
||||
|
||||
// When sorting the `variants` layer, we need to sort based on the parent layer as well within
|
||||
// this variants layer.
|
||||
if (a.parentLayer !== b.parentLayer) {
|
||||
return this.layerPositions[a.parentLayer] - this.layerPositions[b.parentLayer]
|
||||
}
|
||||
|
||||
// Sort based on the sorting function
|
||||
for (let aOptions of a.options) {
|
||||
for (let bOptions of b.options) {
|
||||
if (aOptions.id !== bOptions.id) continue
|
||||
if (!aOptions.sort || !bOptions.sort) continue
|
||||
|
||||
let maxFnVariant = max([aOptions.variant, bOptions.variant]) ?? 0n
|
||||
|
||||
// Create a mask of 0s from bits 1..N where N represents the mask of the Nth bit
|
||||
let mask = ~(maxFnVariant | (maxFnVariant - 1n))
|
||||
let aVariantsAfterFn = a.variants & mask
|
||||
let bVariantsAfterFn = b.variants & mask
|
||||
|
||||
// If the variants the same, we _can_ sort them
|
||||
if (aVariantsAfterFn !== bVariantsAfterFn) {
|
||||
continue
|
||||
}
|
||||
|
||||
let result = aOptions.sort(
|
||||
{
|
||||
value: aOptions.value,
|
||||
modifier: aOptions.modifier,
|
||||
},
|
||||
{
|
||||
value: bOptions.value,
|
||||
modifier: bOptions.modifier,
|
||||
}
|
||||
)
|
||||
if (result !== 0) return result
|
||||
}
|
||||
}
|
||||
|
||||
// Sort variants in the order they were registered
|
||||
if (a.variants !== b.variants) {
|
||||
return a.variants - b.variants
|
||||
}
|
||||
|
||||
// Make sure each rule returned by a parallel variant is sorted in ascending order
|
||||
if (a.parallelIndex !== b.parallelIndex) {
|
||||
return a.parallelIndex - b.parallelIndex
|
||||
}
|
||||
|
||||
// Always sort arbitrary properties after other utilities
|
||||
if (a.arbitrary !== b.arbitrary) {
|
||||
return a.arbitrary - b.arbitrary
|
||||
}
|
||||
|
||||
// Sort utilities, components, etc… in the order they were registered
|
||||
return a.index - b.index
|
||||
}
|
||||
|
||||
/**
|
||||
* Arbitrary variants are recorded in the order they're encountered.
|
||||
* This means that the order is not stable between environments and sets of content files.
|
||||
*
|
||||
* In order to make the order stable, we need to remap the arbitrary variant offsets to
|
||||
* be in alphabetical order starting from the offset of the first arbitrary variant.
|
||||
*/
|
||||
recalculateVariantOffsets() {
|
||||
// Sort the variants by their name
|
||||
let variants = Array.from(this.variantOffsets.entries())
|
||||
.filter(([v]) => v.startsWith('['))
|
||||
.sort(([a], [z]) => fastCompare(a, z))
|
||||
|
||||
// Sort the list of offsets
|
||||
// This is not necessarily a discrete range of numbers which is why
|
||||
// we're using sort instead of creating a range from min/max
|
||||
let newOffsets = variants.map(([, offset]) => offset).sort((a, z) => bigSign(a - z))
|
||||
|
||||
// Create a map from the old offsets to the new offsets in the new sort order
|
||||
/** @type {[bigint, bigint][]} */
|
||||
let mapping = variants.map(([, oldOffset], i) => [oldOffset, newOffsets[i]])
|
||||
|
||||
// Remove any variants that will not move letting us skip
|
||||
// remapping if everything happens to be in order
|
||||
return mapping.filter(([a, z]) => a !== z)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {[RuleOffset, T][]} list
|
||||
* @returns {[RuleOffset, T][]}
|
||||
*/
|
||||
remapArbitraryVariantOffsets(list) {
|
||||
let mapping = this.recalculateVariantOffsets()
|
||||
|
||||
// No arbitrary variants? Nothing to do.
|
||||
// Everyhing already in order? Nothing to do.
|
||||
if (mapping.length === 0) {
|
||||
return list
|
||||
}
|
||||
|
||||
// Remap every variant offset in the list
|
||||
return list.map((item) => {
|
||||
let [offset, rule] = item
|
||||
|
||||
offset = {
|
||||
...offset,
|
||||
variants: remapBitfield(offset.variants, mapping),
|
||||
}
|
||||
|
||||
return [offset, rule]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {[RuleOffset, T][]} list
|
||||
* @returns {[RuleOffset, T][]}
|
||||
*/
|
||||
sort(list) {
|
||||
list = this.remapArbitraryVariantOffsets(list)
|
||||
|
||||
return list.sort(([a], [b]) => bigSign(this.compare(a, b)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {bigint[]} nums
|
||||
* @returns {bigint|null}
|
||||
*/
|
||||
function max(nums) {
|
||||
let max = null
|
||||
|
||||
for (const num of nums) {
|
||||
max = max ?? num
|
||||
max = max > num ? max : num
|
||||
}
|
||||
|
||||
return max
|
||||
}
|
||||
|
||||
/**
|
||||
* A fast ASCII order string comparison function.
|
||||
*
|
||||
* Using `.sort()` without a custom compare function is faster
|
||||
* But you can only use that if you're sorting an array of
|
||||
* only strings. If you're sorting strings inside objects
|
||||
* or arrays, you need must use a custom compare function.
|
||||
*
|
||||
* @param {string} a
|
||||
* @param {string} b
|
||||
*/
|
||||
function fastCompare(a, b) {
|
||||
let aLen = a.length
|
||||
let bLen = b.length
|
||||
let minLen = aLen < bLen ? aLen : bLen
|
||||
|
||||
for (let i = 0; i < minLen; i++) {
|
||||
let cmp = a.charCodeAt(i) - b.charCodeAt(i)
|
||||
if (cmp !== 0) return cmp
|
||||
}
|
||||
|
||||
return aLen - bLen
|
||||
}
|
52
node_modules/tailwindcss/src/lib/partitionApplyAtRules.js
generated
vendored
Normal file
52
node_modules/tailwindcss/src/lib/partitionApplyAtRules.js
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
function partitionRules(root) {
|
||||
if (!root.walkAtRules) return
|
||||
|
||||
let applyParents = new Set()
|
||||
|
||||
root.walkAtRules('apply', (rule) => {
|
||||
applyParents.add(rule.parent)
|
||||
})
|
||||
|
||||
if (applyParents.size === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
for (let rule of applyParents) {
|
||||
let nodeGroups = []
|
||||
let lastGroup = []
|
||||
|
||||
for (let node of rule.nodes) {
|
||||
if (node.type === 'atrule' && node.name === 'apply') {
|
||||
if (lastGroup.length > 0) {
|
||||
nodeGroups.push(lastGroup)
|
||||
lastGroup = []
|
||||
}
|
||||
nodeGroups.push([node])
|
||||
} else {
|
||||
lastGroup.push(node)
|
||||
}
|
||||
}
|
||||
|
||||
if (lastGroup.length > 0) {
|
||||
nodeGroups.push(lastGroup)
|
||||
}
|
||||
|
||||
if (nodeGroups.length === 1) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (let group of [...nodeGroups].reverse()) {
|
||||
let clone = rule.clone({ nodes: [] })
|
||||
clone.append(group)
|
||||
rule.after(clone)
|
||||
}
|
||||
|
||||
rule.remove()
|
||||
}
|
||||
}
|
||||
|
||||
export default function expandApplyAtRules() {
|
||||
return (root) => {
|
||||
partitionRules(root)
|
||||
}
|
||||
}
|
74
node_modules/tailwindcss/src/lib/regex.js
generated
vendored
Normal file
74
node_modules/tailwindcss/src/lib/regex.js
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
const REGEX_SPECIAL = /[\\^$.*+?()[\]{}|]/g
|
||||
const REGEX_HAS_SPECIAL = RegExp(REGEX_SPECIAL.source)
|
||||
|
||||
/**
|
||||
* @param {string|RegExp|Array<string|RegExp>} source
|
||||
*/
|
||||
function toSource(source) {
|
||||
source = Array.isArray(source) ? source : [source]
|
||||
|
||||
source = source.map((item) => (item instanceof RegExp ? item.source : item))
|
||||
|
||||
return source.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|RegExp|Array<string|RegExp>} source
|
||||
*/
|
||||
export function pattern(source) {
|
||||
return new RegExp(toSource(source), 'g')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|RegExp|Array<string|RegExp>} source
|
||||
*/
|
||||
export function withoutCapturing(source) {
|
||||
return new RegExp(`(?:${toSource(source)})`, 'g')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<string|RegExp>} sources
|
||||
*/
|
||||
export function any(sources) {
|
||||
return `(?:${sources.map(toSource).join('|')})`
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|RegExp} source
|
||||
*/
|
||||
export function optional(source) {
|
||||
return `(?:${toSource(source)})?`
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|RegExp|Array<string|RegExp>} source
|
||||
*/
|
||||
export function zeroOrMore(source) {
|
||||
return `(?:${toSource(source)})*`
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a RegExp that matches balanced brackets for a given depth
|
||||
* We have to specify a depth because JS doesn't support recursive groups using ?R
|
||||
*
|
||||
* Based on https://stackoverflow.com/questions/17759004/how-to-match-string-within-parentheses-nested-in-java/17759264#17759264
|
||||
*
|
||||
* @param {string|RegExp|Array<string|RegExp>} source
|
||||
*/
|
||||
export function nestedBrackets(open, close, depth = 1) {
|
||||
return withoutCapturing([
|
||||
escape(open),
|
||||
/[^\s]*/,
|
||||
depth === 1
|
||||
? `[^${escape(open)}${escape(close)}\s]*`
|
||||
: any([`[^${escape(open)}${escape(close)}\s]*`, nestedBrackets(open, close, depth - 1)]),
|
||||
/[^\s]*/,
|
||||
escape(close),
|
||||
])
|
||||
}
|
||||
|
||||
export function escape(string) {
|
||||
return string && REGEX_HAS_SPECIAL.test(string)
|
||||
? string.replace(REGEX_SPECIAL, '\\$&')
|
||||
: string || ''
|
||||
}
|
82
node_modules/tailwindcss/src/lib/remap-bitfield.js
generated
vendored
Normal file
82
node_modules/tailwindcss/src/lib/remap-bitfield.js
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* We must remap all the old bits to new bits for each set variant
|
||||
* Only arbitrary variants are considered as those are the only
|
||||
* ones that need to be re-sorted at this time
|
||||
*
|
||||
* An iterated process that removes and sets individual bits simultaneously
|
||||
* will not work because we may have a new bit that is also a later old bit
|
||||
* This means that we would be removing a previously set bit which we don't
|
||||
* want to do
|
||||
*
|
||||
* For example (assume `bN` = `1<<N`)
|
||||
* Given the "total" mapping `[[b1, b3], [b2, b4], [b3, b1], [b4, b2]]`
|
||||
* The mapping is "total" because:
|
||||
* 1. Every input and output is accounted for
|
||||
* 2. All combinations are unique
|
||||
* 3. No one input maps to multiple outputs and vice versa
|
||||
* And, given an offset with all bits set:
|
||||
* V = b1 | b2 | b3 | b4
|
||||
*
|
||||
* Let's explore the issue with removing and setting bits simultaneously:
|
||||
* V & ~b1 | b3 = b2 | b3 | b4
|
||||
* V & ~b2 | b4 = b3 | b4
|
||||
* V & ~b3 | b1 = b1 | b4
|
||||
* V & ~b4 | b2 = b1 | b2
|
||||
*
|
||||
* As you can see, we end up with the wrong result.
|
||||
* This is because we're removing a bit that was previously set.
|
||||
* And, thus the final result is missing b3 and b4.
|
||||
*
|
||||
* Now, let's explore the issue with removing the bits first:
|
||||
* V & ~b1 = b2 | b3 | b4
|
||||
* V & ~b2 = b3 | b4
|
||||
* V & ~b3 = b4
|
||||
* V & ~b4 = 0
|
||||
*
|
||||
* And then setting the bits:
|
||||
* V | b3 = b3
|
||||
* V | b4 = b3 | b4
|
||||
* V | b1 = b1 | b3 | b4
|
||||
* V | b2 = b1 | b2 | b3 | b4
|
||||
*
|
||||
* We get the correct result because we're not removing any bits that were
|
||||
* previously set thus properly remapping the bits to the new order
|
||||
*
|
||||
* To collect this into a single operation that can be done simultaneously
|
||||
* we must first create a mask for the old bits that are set and a mask for
|
||||
* the new bits that are set. Then we can remove the old bits and set the new
|
||||
* bits simultaneously in a "single" operation like so:
|
||||
* OldMask = b1 | b2 | b3 | b4
|
||||
* NewMask = b3 | b4 | b1 | b2
|
||||
*
|
||||
* So this:
|
||||
* V & ~oldMask | newMask
|
||||
*
|
||||
* Expands to this:
|
||||
* V & ~b1 & ~b2 & ~b3 & ~b4 | b3 | b4 | b1 | b2
|
||||
*
|
||||
* Which becomes this:
|
||||
* b1 | b2 | b3 | b4
|
||||
*
|
||||
* Which is the correct result!
|
||||
*
|
||||
* @param {bigint} num
|
||||
* @param {[bigint, bigint][]} mapping
|
||||
*/
|
||||
export function remapBitfield(num, mapping) {
|
||||
// Create masks for the old and new bits that are set
|
||||
let oldMask = 0n
|
||||
let newMask = 0n
|
||||
for (let [oldBit, newBit] of mapping) {
|
||||
if (num & oldBit) {
|
||||
oldMask = oldMask | oldBit
|
||||
newMask = newMask | newBit
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all old bits
|
||||
// Set all new bits
|
||||
return (num & ~oldMask) | newMask
|
||||
}
|
163
node_modules/tailwindcss/src/lib/resolveDefaultsAtRules.js
generated
vendored
Normal file
163
node_modules/tailwindcss/src/lib/resolveDefaultsAtRules.js
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
import postcss from 'postcss'
|
||||
import selectorParser from 'postcss-selector-parser'
|
||||
import { flagEnabled } from '../featureFlags'
|
||||
|
||||
let getNode = {
|
||||
id(node) {
|
||||
return selectorParser.attribute({
|
||||
attribute: 'id',
|
||||
operator: '=',
|
||||
value: node.value,
|
||||
quoteMark: '"',
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
function minimumImpactSelector(nodes) {
|
||||
let rest = nodes
|
||||
.filter((node) => {
|
||||
// Keep non-pseudo nodes
|
||||
if (node.type !== 'pseudo') return true
|
||||
|
||||
// Keep pseudo nodes that have subnodes
|
||||
// E.g.: `:not()` contains subnodes inside the parentheses
|
||||
if (node.nodes.length > 0) return true
|
||||
|
||||
// Keep pseudo `elements`
|
||||
// This implicitly means that we ignore pseudo `classes`
|
||||
return (
|
||||
node.value.startsWith('::') ||
|
||||
[':before', ':after', ':first-line', ':first-letter'].includes(node.value)
|
||||
)
|
||||
})
|
||||
.reverse()
|
||||
|
||||
let searchFor = new Set(['tag', 'class', 'id', 'attribute'])
|
||||
|
||||
let splitPointIdx = rest.findIndex((n) => searchFor.has(n.type))
|
||||
if (splitPointIdx === -1) return rest.reverse().join('').trim()
|
||||
|
||||
let node = rest[splitPointIdx]
|
||||
let bestNode = getNode[node.type] ? getNode[node.type](node) : node
|
||||
|
||||
rest = rest.slice(0, splitPointIdx)
|
||||
|
||||
let combinatorIdx = rest.findIndex((n) => n.type === 'combinator' && n.value === '>')
|
||||
if (combinatorIdx !== -1) {
|
||||
rest.splice(0, combinatorIdx)
|
||||
rest.unshift(selectorParser.universal())
|
||||
}
|
||||
|
||||
return [bestNode, ...rest.reverse()].join('').trim()
|
||||
}
|
||||
|
||||
export let elementSelectorParser = selectorParser((selectors) => {
|
||||
return selectors.map((s) => {
|
||||
let nodes = s.split((n) => n.type === 'combinator' && n.value === ' ').pop()
|
||||
return minimumImpactSelector(nodes)
|
||||
})
|
||||
})
|
||||
|
||||
let cache = new Map()
|
||||
|
||||
function extractElementSelector(selector) {
|
||||
if (!cache.has(selector)) {
|
||||
cache.set(selector, elementSelectorParser.transformSync(selector))
|
||||
}
|
||||
|
||||
return cache.get(selector)
|
||||
}
|
||||
|
||||
export default function resolveDefaultsAtRules({ tailwindConfig }) {
|
||||
return (root) => {
|
||||
let variableNodeMap = new Map()
|
||||
|
||||
/** @type {Set<import('postcss').AtRule>} */
|
||||
let universals = new Set()
|
||||
|
||||
root.walkAtRules('defaults', (rule) => {
|
||||
if (rule.nodes && rule.nodes.length > 0) {
|
||||
universals.add(rule)
|
||||
return
|
||||
}
|
||||
|
||||
let variable = rule.params
|
||||
if (!variableNodeMap.has(variable)) {
|
||||
variableNodeMap.set(variable, new Set())
|
||||
}
|
||||
|
||||
variableNodeMap.get(variable).add(rule.parent)
|
||||
|
||||
rule.remove()
|
||||
})
|
||||
|
||||
if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) {
|
||||
for (let universal of universals) {
|
||||
/** @type {Map<string, Set<string>>} */
|
||||
let selectorGroups = new Map()
|
||||
|
||||
let rules = variableNodeMap.get(universal.params) ?? []
|
||||
|
||||
for (let rule of rules) {
|
||||
for (let selector of extractElementSelector(rule.selector)) {
|
||||
// If selector contains a vendor prefix after a pseudo element or class,
|
||||
// we consider them separately because merging the declarations into
|
||||
// a single rule will cause browsers that do not understand the
|
||||
// vendor prefix to throw out the whole rule
|
||||
let selectorGroupName =
|
||||
selector.includes(':-') || selector.includes('::-') ? selector : '__DEFAULT__'
|
||||
|
||||
let selectors = selectorGroups.get(selectorGroupName) ?? new Set()
|
||||
selectorGroups.set(selectorGroupName, selectors)
|
||||
|
||||
selectors.add(selector)
|
||||
}
|
||||
}
|
||||
|
||||
if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) {
|
||||
if (selectorGroups.size === 0) {
|
||||
universal.remove()
|
||||
continue
|
||||
}
|
||||
|
||||
for (let [, selectors] of selectorGroups) {
|
||||
let universalRule = postcss.rule({
|
||||
source: universal.source,
|
||||
})
|
||||
|
||||
universalRule.selectors = [...selectors]
|
||||
|
||||
universalRule.append(universal.nodes.map((node) => node.clone()))
|
||||
universal.before(universalRule)
|
||||
}
|
||||
}
|
||||
|
||||
universal.remove()
|
||||
}
|
||||
} else if (universals.size) {
|
||||
let universalRule = postcss.rule({
|
||||
selectors: ['*', '::before', '::after'],
|
||||
})
|
||||
|
||||
for (let universal of universals) {
|
||||
universalRule.append(universal.nodes)
|
||||
|
||||
if (!universalRule.parent) {
|
||||
universal.before(universalRule)
|
||||
}
|
||||
|
||||
if (!universalRule.source) {
|
||||
universalRule.source = universal.source
|
||||
}
|
||||
|
||||
universal.remove()
|
||||
}
|
||||
|
||||
let backdropRule = universalRule.clone({
|
||||
selectors: ['::backdrop'],
|
||||
})
|
||||
|
||||
universalRule.after(backdropRule)
|
||||
}
|
||||
}
|
||||
}
|
1340
node_modules/tailwindcss/src/lib/setupContextUtils.js
generated
vendored
Normal file
1340
node_modules/tailwindcss/src/lib/setupContextUtils.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
169
node_modules/tailwindcss/src/lib/setupTrackingContext.js
generated
vendored
Normal file
169
node_modules/tailwindcss/src/lib/setupTrackingContext.js
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
// @ts-check
|
||||
|
||||
import fs from 'fs'
|
||||
import LRU from '@alloc/quick-lru'
|
||||
|
||||
import hash from '../util/hashConfig'
|
||||
import resolveConfig from '../public/resolve-config'
|
||||
import resolveConfigPath from '../util/resolveConfigPath'
|
||||
import { getContext, getFileModifiedMap } from './setupContextUtils'
|
||||
import parseDependency from '../util/parseDependency'
|
||||
import { validateConfig } from '../util/validateConfig.js'
|
||||
import { parseCandidateFiles, resolvedChangedContent } from './content.js'
|
||||
import { loadConfig } from '../lib/load-config'
|
||||
import getModuleDependencies from './getModuleDependencies'
|
||||
|
||||
let configPathCache = new LRU({ maxSize: 100 })
|
||||
|
||||
let candidateFilesCache = new WeakMap()
|
||||
|
||||
function getCandidateFiles(context, tailwindConfig) {
|
||||
if (candidateFilesCache.has(context)) {
|
||||
return candidateFilesCache.get(context)
|
||||
}
|
||||
|
||||
let candidateFiles = parseCandidateFiles(context, tailwindConfig)
|
||||
|
||||
return candidateFilesCache.set(context, candidateFiles).get(context)
|
||||
}
|
||||
|
||||
// Get the config object based on a path
|
||||
function getTailwindConfig(configOrPath) {
|
||||
let userConfigPath = resolveConfigPath(configOrPath)
|
||||
|
||||
if (userConfigPath !== null) {
|
||||
let [prevConfig, prevConfigHash, prevDeps, prevModified] =
|
||||
configPathCache.get(userConfigPath) || []
|
||||
|
||||
let newDeps = getModuleDependencies(userConfigPath)
|
||||
|
||||
let modified = false
|
||||
let newModified = new Map()
|
||||
for (let file of newDeps) {
|
||||
let time = fs.statSync(file).mtimeMs
|
||||
newModified.set(file, time)
|
||||
if (!prevModified || !prevModified.has(file) || time > prevModified.get(file)) {
|
||||
modified = true
|
||||
}
|
||||
}
|
||||
|
||||
// It hasn't changed (based on timestamps)
|
||||
if (!modified) {
|
||||
return [prevConfig, userConfigPath, prevConfigHash, prevDeps]
|
||||
}
|
||||
|
||||
// It has changed (based on timestamps), or first run
|
||||
for (let file of newDeps) {
|
||||
delete require.cache[file]
|
||||
}
|
||||
let newConfig = validateConfig(resolveConfig(loadConfig(userConfigPath)))
|
||||
let newHash = hash(newConfig)
|
||||
configPathCache.set(userConfigPath, [newConfig, newHash, newDeps, newModified])
|
||||
return [newConfig, userConfigPath, newHash, newDeps]
|
||||
}
|
||||
|
||||
// It's a plain object, not a path
|
||||
let newConfig = resolveConfig(configOrPath?.config ?? configOrPath ?? {})
|
||||
|
||||
newConfig = validateConfig(newConfig)
|
||||
|
||||
return [newConfig, null, hash(newConfig), []]
|
||||
}
|
||||
|
||||
// DISABLE_TOUCH = TRUE
|
||||
|
||||
// Retrieve an existing context from cache if possible (since contexts are unique per
|
||||
// source path), or set up a new one (including setting up watchers and registering
|
||||
// plugins) then return it
|
||||
export default function setupTrackingContext(configOrPath) {
|
||||
return ({ tailwindDirectives, registerDependency }) => {
|
||||
return (root, result) => {
|
||||
let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] =
|
||||
getTailwindConfig(configOrPath)
|
||||
|
||||
let contextDependencies = new Set(configDependencies)
|
||||
|
||||
// If there are no @tailwind or @apply rules, we don't consider this CSS
|
||||
// file or its dependencies to be dependencies of the context. Can reuse
|
||||
// the context even if they change. We may want to think about `@layer`
|
||||
// being part of this trigger too, but it's tough because it's impossible
|
||||
// for a layer in one file to end up in the actual @tailwind rule in
|
||||
// another file since independent sources are effectively isolated.
|
||||
if (tailwindDirectives.size > 0) {
|
||||
// Add current css file as a context dependencies.
|
||||
contextDependencies.add(result.opts.from)
|
||||
|
||||
// Add all css @import dependencies as context dependencies.
|
||||
for (let message of result.messages) {
|
||||
if (message.type === 'dependency') {
|
||||
contextDependencies.add(message.file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let [context, , mTimesToCommit] = getContext(
|
||||
root,
|
||||
result,
|
||||
tailwindConfig,
|
||||
userConfigPath,
|
||||
tailwindConfigHash,
|
||||
contextDependencies
|
||||
)
|
||||
|
||||
let fileModifiedMap = getFileModifiedMap(context)
|
||||
|
||||
let candidateFiles = getCandidateFiles(context, tailwindConfig)
|
||||
|
||||
// If there are no @tailwind or @apply rules, we don't consider this CSS file or it's
|
||||
// dependencies to be dependencies of the context. Can reuse the context even if they change.
|
||||
// We may want to think about `@layer` being part of this trigger too, but it's tough
|
||||
// because it's impossible for a layer in one file to end up in the actual @tailwind rule
|
||||
// in another file since independent sources are effectively isolated.
|
||||
if (tailwindDirectives.size > 0) {
|
||||
// Add template paths as postcss dependencies.
|
||||
for (let contentPath of candidateFiles) {
|
||||
for (let dependency of parseDependency(contentPath)) {
|
||||
registerDependency(dependency)
|
||||
}
|
||||
}
|
||||
|
||||
let [changedContent, contentMTimesToCommit] = resolvedChangedContent(
|
||||
context,
|
||||
candidateFiles,
|
||||
fileModifiedMap
|
||||
)
|
||||
|
||||
for (let content of changedContent) {
|
||||
context.changedContent.push(content)
|
||||
}
|
||||
|
||||
// Add the mtimes of the content files to the commit list
|
||||
// We can overwrite the existing values because unconditionally
|
||||
// This is because:
|
||||
// 1. Most of the files here won't be in the map yet
|
||||
// 2. If they are that means it's a context dependency
|
||||
// and we're reading this after the context. This means
|
||||
// that the mtime we just read is strictly >= the context
|
||||
// mtime. Unless the user / os is doing something weird
|
||||
// in which the mtime would be going backwards. If that
|
||||
// happens there's already going to be problems.
|
||||
for (let [path, mtime] of contentMTimesToCommit.entries()) {
|
||||
mTimesToCommit.set(path, mtime)
|
||||
}
|
||||
}
|
||||
|
||||
for (let file of configDependencies) {
|
||||
registerDependency({ type: 'dependency', file })
|
||||
}
|
||||
|
||||
// "commit" the new modified time for all context deps
|
||||
// We do this here because we want content tracking to
|
||||
// read the "old" mtime even when it's a context dependency.
|
||||
for (let [path, mtime] of mTimesToCommit.entries()) {
|
||||
fileModifiedMap.set(path, mtime)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
}
|
61
node_modules/tailwindcss/src/lib/sharedState.js
generated
vendored
Normal file
61
node_modules/tailwindcss/src/lib/sharedState.js
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
import pkg from '../../package.json'
|
||||
|
||||
export const env =
|
||||
typeof process !== 'undefined'
|
||||
? {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
DEBUG: resolveDebug(process.env.DEBUG),
|
||||
ENGINE: pkg.tailwindcss.engine,
|
||||
}
|
||||
: {
|
||||
NODE_ENV: 'production',
|
||||
DEBUG: false,
|
||||
ENGINE: pkg.tailwindcss.engine,
|
||||
}
|
||||
|
||||
export const contextMap = new Map()
|
||||
export const configContextMap = new Map()
|
||||
export const contextSourcesMap = new Map()
|
||||
export const sourceHashMap = new Map()
|
||||
export const NOT_ON_DEMAND = new String('*')
|
||||
|
||||
export const NONE = Symbol('__NONE__')
|
||||
|
||||
export function resolveDebug(debug) {
|
||||
if (debug === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Environment variables are strings, so convert to boolean
|
||||
if (debug === 'true' || debug === '1') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (debug === 'false' || debug === '0') {
|
||||
return false
|
||||
}
|
||||
|
||||
// Keep the debug convention into account:
|
||||
// DEBUG=* -> This enables all debug modes
|
||||
// DEBUG=projectA,projectB,projectC -> This enables debug for projectA, projectB and projectC
|
||||
// DEBUG=projectA:* -> This enables all debug modes for projectA (if you have sub-types)
|
||||
// DEBUG=projectA,-projectB -> This enables debug for projectA and explicitly disables it for projectB
|
||||
|
||||
if (debug === '*') {
|
||||
return true
|
||||
}
|
||||
|
||||
let debuggers = debug.split(',').map((d) => d.split(':')[0])
|
||||
|
||||
// Ignoring tailwindcss
|
||||
if (debuggers.includes('-tailwindcss')) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Including tailwindcss
|
||||
if (debuggers.includes('tailwindcss')) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
19
node_modules/tailwindcss/src/lib/substituteScreenAtRules.js
generated
vendored
Normal file
19
node_modules/tailwindcss/src/lib/substituteScreenAtRules.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { normalizeScreens } from '../util/normalizeScreens'
|
||||
import buildMediaQuery from '../util/buildMediaQuery'
|
||||
|
||||
export default function ({ tailwindConfig: { theme } }) {
|
||||
return function (css) {
|
||||
css.walkAtRules('screen', (atRule) => {
|
||||
let screen = atRule.params
|
||||
let screens = normalizeScreens(theme.screens)
|
||||
let screenDefinition = screens.find(({ name }) => name === screen)
|
||||
|
||||
if (!screenDefinition) {
|
||||
throw atRule.error(`No \`${screen}\` screen found.`)
|
||||
}
|
||||
|
||||
atRule.name = 'media'
|
||||
atRule.params = buildMediaQuery(screenDefinition)
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user