summaryrefslogtreecommitdiff
path: root/node_modules/tailwindcss/src/lib/expandTailwindAtRules.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/tailwindcss/src/lib/expandTailwindAtRules.js')
-rw-r--r--node_modules/tailwindcss/src/lib/expandTailwindAtRules.js297
1 files changed, 297 insertions, 0 deletions
diff --git a/node_modules/tailwindcss/src/lib/expandTailwindAtRules.js b/node_modules/tailwindcss/src/lib/expandTailwindAtRules.js
new file mode 100644
index 0000000..2933d6f
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/expandTailwindAtRules.js
@@ -0,0 +1,297 @@
+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 {
+ /** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */
+ let regexParserContent = []
+
+ for (let item of context.changedContent) {
+ let transformer = getTransformer(context.tailwindConfig, item.extension)
+ let extractor = getExtractor(context, item.extension)
+ regexParserContent.push([item, { transformer, extractor }])
+ }
+
+ const BATCH_SIZE = 500
+
+ for (let i = 0; i < regexParserContent.length; i += BATCH_SIZE) {
+ let batch = regexParserContent.slice(i, i + BATCH_SIZE)
+ await Promise.all(
+ batch.map(async ([{ file, content }, { transformer, extractor }]) => {
+ 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()
+ }
+ })
+ }
+}