%PDF- %PDF-
Direktori : /www/loslex_o/demo/node_modules/tailwindcss/src/lib/ |
Current File : /www/loslex_o/demo/node_modules/tailwindcss/src/lib/expandTailwindAtRules.js |
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') /** @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 = 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', }) ) } // TODO: Why is the root node having no source location for `end` possible? root.source.end = root.source.end ?? root.source.start // 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() } }) } }