diff options
| author | Philipp Tanlak <philipp.tanlak@gmail.com> | 2025-11-24 20:54:57 +0100 |
|---|---|---|
| committer | Philipp Tanlak <philipp.tanlak@gmail.com> | 2025-11-24 20:57:48 +0100 |
| commit | b1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c (patch) | |
| tree | 49d360fd6cbc6a2754efe93524ac47ff0fbe0f7d /node_modules/tailwindcss/src/lib/setupTrackingContext.js | |
Docs
Diffstat (limited to 'node_modules/tailwindcss/src/lib/setupTrackingContext.js')
| -rw-r--r-- | node_modules/tailwindcss/src/lib/setupTrackingContext.js | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/node_modules/tailwindcss/src/lib/setupTrackingContext.js b/node_modules/tailwindcss/src/lib/setupTrackingContext.js new file mode 100644 index 0000000..70e7cb6 --- /dev/null +++ b/node_modules/tailwindcss/src/lib/setupTrackingContext.js @@ -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 + } + } +} |