Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
adamwathan committed May 25, 2021
1 parent babe7aa commit ce8556b
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 297 deletions.
188 changes: 188 additions & 0 deletions src/jit/lib/setupContextUtils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import fs from 'fs'
import url from 'url'
import path from 'path'
import postcss from 'postcss'
import dlv from 'dlv'
import selectorParser from 'postcss-selector-parser'
import LRU from 'quick-lru'
import normalizePath from 'normalize-path'

import hash from '../../util/hashConfig'
import getModuleDependencies from '../../lib/getModuleDependencies'

import resolveConfig from '../../../resolveConfig'

import resolveConfigPath from '../../util/resolveConfigPath'

import transformThemeValue from '../../util/transformThemeValue'
import parseObjectStyles from '../../util/parseObjectStyles'
Expand All @@ -12,6 +22,8 @@ import escapeClassName from '../../util/escapeClassName'
import nameClass from '../../util/nameClass'
import { coerceValue } from '../../util/pluginUtils'
import corePlugins from '../corePlugins'
import * as sharedState from './sharedState'
import { env } from './sharedState'

function toPath(value) {
if (Array.isArray(value)) {
Expand Down Expand Up @@ -456,3 +468,179 @@ export function registerPlugins(plugins, context) {
context.variantMap.set(variantName, [sort, variantFunction])
}
}

let contextMap = sharedState.contextMap
let configContextMap = sharedState.configContextMap
let contextSourcesMap = sharedState.contextSourcesMap

function cleanupContext(context) {
if (context.watcher) {
context.watcher.close()
}
}

let configPathCache = new LRU({ maxSize: 100 })

// 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).map((dep) => dep.file)

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 = resolveConfig(require(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 === undefined ? configOrPath : configOrPath.config
)

return [newConfig, null, hash(newConfig), []]
}

export function getContext(configOrPath, tailwindDirectives, registerDependency, root, result) {
let sourcePath = result.opts.from
let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] =
getTailwindConfig(configOrPath)
let isConfigFile = userConfigPath !== null

let contextDependencies = new Set(configDependencies)

// If there are no @tailwind 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 current css file as a context dependencies.
contextDependencies.add(sourcePath)

// Add all css @import dependencies as context dependencies.
for (let message of result.messages) {
if (message.type === 'dependency') {
contextDependencies.add(message.file)
}
}
}

for (let file of configDependencies) {
registerDependency(file)
}

let contextDependenciesChanged = trackModified([...contextDependencies])

env.DEBUG && console.log('Source path:', sourcePath)

if (!contextDependenciesChanged) {
// If this file already has a context in the cache and we don't need to
// reset the context, return the cached context.
if (isConfigFile && contextMap.has(sourcePath)) {
return [contextMap.get(sourcePath), false]
}

// If the config used already exists in the cache, return that.
if (configContextMap.has(tailwindConfigHash)) {
let context = configContextMap.get(tailwindConfigHash)
contextSourcesMap.get(context).add(sourcePath)
contextMap.set(sourcePath, context)

return [context, false]
}
}

// If this source is in the context map, get the old context.
// Remove this source from the context sources for the old context,
// and clean up that context if no one else is using it. This can be
// called by many processes in rapid succession, so we check for presence
// first because the first process to run this code will wipe it out first.
if (contextMap.has(sourcePath)) {
let oldContext = contextMap.get(sourcePath)
if (contextSourcesMap.has(oldContext)) {
contextSourcesMap.get(oldContext).delete(sourcePath)
if (contextSourcesMap.get(oldContext).size === 0) {
contextSourcesMap.delete(oldContext)
cleanupContext(oldContext)
}
}
}

env.DEBUG && console.log('Setting up new context...')

let purgeContent = Array.isArray(tailwindConfig.purge)
? tailwindConfig.purge
: tailwindConfig.purge.content

let context = {
changedFiles: new Set(),
ruleCache: new Set(),
watcher: null,
touchFile: null,
classCache: new Map(),
applyClassCache: new Map(),
notClassCache: new Set(),
postCssNodeCache: new Map(),
candidateRuleMap: new Map(),
configPath: userConfigPath,
tailwindConfig: tailwindConfig,
configDependencies: new Set(),
candidateFiles: purgeContent
.filter((item) => typeof item === 'string')
.map((purgePath) =>
normalizePath(
path.resolve(
userConfigPath === null ? process.cwd() : path.dirname(userConfigPath),
purgePath
)
)
),
rawContent: purgeContent
.filter((item) => typeof item.raw === 'string')
.map(({ raw, extension }) => ({ content: raw, extension })),
variantMap: new Map(),
stylesheetCache: null,
fileModifiedMap: new Map(),
}

// ---

// Update all context tracking state

configContextMap.set(tailwindConfigHash, context)
contextMap.set(sourcePath, context)

if (!contextSourcesMap.has(context)) {
contextSourcesMap.set(context, new Set())
}

contextSourcesMap.get(context).add(sourcePath)

registerPlugins(resolvePlugins(context, tailwindDirectives, root), context)

return [context, true]
}
153 changes: 9 additions & 144 deletions src/jit/lib/setupTrackingContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import path from 'path'
import fastGlob from 'fast-glob'
import parseGlob from 'parse-glob'
import LRU from 'quick-lru'
import normalizePath from 'normalize-path'

import hash from '../../util/hashConfig'
import getModuleDependencies from '../../lib/getModuleDependencies'
Expand All @@ -13,20 +12,9 @@ import resolveConfig from '../../../resolveConfig'

import resolveConfigPath from '../../util/resolveConfigPath'

import * as sharedState from './sharedState'
import { env } from './sharedState'

import { trackModified, resolvePlugins, registerPlugins } from './setupContextUtils'

let contextMap = sharedState.contextMap
let configContextMap = sharedState.configContextMap
let contextSourcesMap = sharedState.contextSourcesMap

function cleanupContext(context) {
if (context.watcher) {
context.watcher.close()
}
}
import { getContext } from './setupContextUtils'

let configPathCache = new LRU({ maxSize: 100 })

Expand Down Expand Up @@ -92,144 +80,21 @@ function resolveChangedFiles(context) {
return changedFiles
}

function getContext(configOrPath, tailwindDirectives, registerDependency, root, result) {
let sourcePath = result.opts.from
let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] = getTailwindConfig(
configOrPath
)
let isConfigFile = userConfigPath !== null

let contextDependencies = new Set(configDependencies)

// If there are no @tailwind 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 current css file as a context dependencies.
contextDependencies.add(sourcePath)

// Add all css @import dependencies as context dependencies.
for (let message of result.messages) {
if (message.type === 'dependency') {
contextDependencies.add(message.file)
}
}
}

for (let file of configDependencies) {
registerDependency(file)
}

// ---

let contextDependenciesChanged = trackModified([...contextDependencies])

env.DEBUG && console.log('Source path:', sourcePath)

if (!contextDependenciesChanged) {
// If this file already has a context in the cache and we don't need to
// reset the context, return the cached context.
if (isConfigFile && contextMap.has(sourcePath)) {
return contextMap.get(sourcePath)
}

// If the config used already exists in the cache, return that.
if (configContextMap.has(tailwindConfigHash)) {
let context = configContextMap.get(tailwindConfigHash)
contextSourcesMap.get(context).add(sourcePath)
contextMap.set(sourcePath, context)

return context
}
}

// If this source is in the context map, get the old context.
// Remove this source from the context sources for the old context,
// and clean up that context if no one else is using it. This can be
// called by many processes in rapid succession, so we check for presence
// first because the first process to run this code will wipe it out first.
if (contextMap.has(sourcePath)) {
let oldContext = contextMap.get(sourcePath)
if (contextSourcesMap.has(oldContext)) {
contextSourcesMap.get(oldContext).delete(sourcePath)
if (contextSourcesMap.get(oldContext).size === 0) {
contextSourcesMap.delete(oldContext)
cleanupContext(oldContext)
}
}
}

env.DEBUG && console.log('Setting up new context...')

let purgeContent = Array.isArray(tailwindConfig.purge)
? tailwindConfig.purge
: tailwindConfig.purge.content

let context = {
changedFiles: new Set(),
ruleCache: new Set(),
watcher: null,
touchFile: null,
classCache: new Map(),
applyClassCache: new Map(),
notClassCache: new Set(),
postCssNodeCache: new Map(),
candidateRuleMap: new Map(),
configPath: userConfigPath,
tailwindConfig: tailwindConfig,
configDependencies: new Set(),
candidateFiles: purgeContent
.filter((item) => typeof item === 'string')
.map((purgePath) =>
normalizePath(
path.resolve(
userConfigPath === null ? process.cwd() : path.dirname(userConfigPath),
purgePath
)
)
),
rawContent: purgeContent
.filter((item) => typeof item.raw === 'string')
.map(({ raw, extension }) => ({ content: raw, extension })),
variantMap: new Map(),
stylesheetCache: null,
fileModifiedMap: new Map(),
}

// ---

// Update all context tracking state

configContextMap.set(tailwindConfigHash, context)
contextMap.set(sourcePath, context)

if (!contextSourcesMap.has(context)) {
contextSourcesMap.set(context, new Set())
}

contextSourcesMap.get(context).add(sourcePath)

// ---

// getModuleDependencies

// ---

registerPlugins(resolvePlugins(context, tailwindDirectives, root), context)

return context
}

// 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, tailwindDirectives, registerDependency) {
return (result, root) => {
let context = getContext(configOrPath, tailwindDirectives, registerDependency, root, result)
let [context] = getContext(
configOrPath,
tailwindDirectives,
registerDependency,
root,
result,
getTailwindConfig
)

// If there are no @tailwind 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.
Expand Down
Loading

0 comments on commit ce8556b

Please sign in to comment.