From 6bd3327d8e58689658414b4eb671e0d3f36807ac Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 20 Sep 2022 10:05:56 -0400 Subject: [PATCH] wip wip wip wip --- .../tailwindcss-cli/tests/cli.test.js | 1 + src/cli.js | 59 +++++++++++++++---- src/featureFlags.js | 2 - src/lib/content.js | 5 +- src/lib/setupContextUtils.js | 8 ++- src/lib/setupTrackingContext.js | 21 +++---- src/util/resolveConfigPath.js | 22 ++++--- 7 files changed, 71 insertions(+), 47 deletions(-) diff --git a/integrations/tailwindcss-cli/tests/cli.test.js b/integrations/tailwindcss-cli/tests/cli.test.js index 9ebcd3012..e23b6c36d 100644 --- a/integrations/tailwindcss-cli/tests/cli.test.js +++ b/integrations/tailwindcss-cli/tests/cli.test.js @@ -459,6 +459,7 @@ describe('Build command', () => { -m, --minify Minify the output -c, --config Path to a custom config file --no-autoprefixer Disable autoprefixer + --experimental-contextual-paths Enable experimental contextual path resolution -h, --help Display usage information `) ) diff --git a/src/cli.js b/src/cli.js index 2851a79f1..5f4bad74a 100644 --- a/src/cli.js +++ b/src/cli.js @@ -19,6 +19,8 @@ import packageJson from '../package.json' import normalizePath from 'normalize-path' import micromatch from 'micromatch' import { validateConfig } from './util/validateConfig.js' +import { parseCandidateFiles } from './lib/content.js' +import resolveConfigPath from './util/resolveConfigPath.js' let env = { DEBUG: process.env.DEBUG !== undefined && process.env.DEBUG !== '0', @@ -219,6 +221,10 @@ let commands = { type: Boolean, description: 'Disable autoprefixer', }, + '--experimental-contextual-paths': { + type: Boolean, + description: 'Enable experimental contextual path resolution', + }, '-c': '--config', '-i': '--input', '-o': '--output', @@ -429,11 +435,31 @@ async function build() { process.exit(9) } - let configPath = args['--config'] - ? args['--config'] - : ((defaultPath) => (fs.existsSync(defaultPath) ? defaultPath : null))( - path.resolve(`./${configs.tailwind}`) - ) + let configPath + + if (args['--config']) { + // The user has specified an exact config file to use + // So we want to use that one and only that one + configPath = path.resolve(args['--config']) + } else { + let postcssOptions = { + config: `./${configs.tailwind}`, + experimental: { + contextualPaths: args['--experimental-contextual-paths'], + }, + } + + // The user has not specified an exact config file to use + // So we want to find the closest one to the input file + // when experimental contextual paths are enabled + let maybeConfigPath = resolveConfigPath(postcssOptions, input ? path.resolve(input) : null) + + if (fs.existsSync(maybeConfigPath)) { + configPath = maybeConfigPath + } else { + configPath = null + } + } async function loadPostCssPlugins() { let customPostCssPath = typeof args['--postcss'] === 'string' ? args['--postcss'] : undefined @@ -551,14 +577,20 @@ async function build() { } function extractFileGlobs(config) { - return config.content.files - .filter((file) => { - // Strings in this case are files / globs. If it is something else, - // like an object it's probably a raw content object. But this object - // is not watchable, so let's remove it. - return typeof file === 'string' - }) - .map((glob) => normalizePath(glob)) + let context = { + userConfigPath: configPath, + postcssOptions: args['--experimental-contextual-paths'] + ? { + experimental: { + contextualPaths: true, + }, + } + : {}, + } + + let contentPaths = parseCandidateFiles(context, config) + + return contentPaths.map((contentPath) => contentPath.pattern) } function extractRawContent(config) { @@ -845,6 +877,7 @@ async function build() { * @return {{all: string[], dynamic: string[], static: string[]}} **/ function refreshContentPatterns(config) { + // TODO: This can be optimized since we're using `fastGlob.generateTasks` indirectly in extractFileGlobs let globs = extractFileGlobs(config) let tasks = fastGlob.generateTasks(globs, { absolute: true }) let dynamicPatterns = tasks.filter((task) => task.dynamic).flatMap((task) => task.patterns) diff --git a/src/featureFlags.js b/src/featureFlags.js index 7616dbd64..bd75108ea 100644 --- a/src/featureFlags.js +++ b/src/featureFlags.js @@ -15,8 +15,6 @@ let featureFlags = { 'optimizeUniversalDefaults', 'matchVariant', // 'variantGrouping', - 'resolveContentRelativeToConfig', - 'resolveConfigRelativeToInput', ], } diff --git a/src/lib/content.js b/src/lib/content.js index da153dddc..f0ceefa9f 100644 --- a/src/lib/content.js +++ b/src/lib/content.js @@ -116,10 +116,7 @@ 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 && - flagEnabled(context.tailwindConfig, 'resolveContentRelativeToConfig') - ) { + if (context.userConfigPath && context.postcssOptions?.experimental?.contextualPaths) { resolveFrom = [path.dirname(context.userConfigPath)] } diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 67587459b..ddaaa481c 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -940,7 +940,8 @@ export function getContext( tailwindConfig, userConfigPath, tailwindConfigHash, - contextDependencies + contextDependencies, + postcssOptions ) { let sourcePath = result.opts.from let isConfigFile = userConfigPath !== null @@ -1000,7 +1001,10 @@ export function getContext( let context = createContext(tailwindConfig, [], root) - Object.assign(context, { userConfigPath }) + Object.assign(context, { + userConfigPath, + postcssOptions, + }) trackModified([...contextDependencies], getFileModifiedMap(context)) diff --git a/src/lib/setupTrackingContext.js b/src/lib/setupTrackingContext.js index 0f1526f23..eae375e98 100644 --- a/src/lib/setupTrackingContext.js +++ b/src/lib/setupTrackingContext.js @@ -1,19 +1,10 @@ import fs from 'fs' -import path from 'path' - -import fastGlob from 'fast-glob' import LRU from 'quick-lru' -import normalizePath from 'normalize-path' import hash from '../util/hashConfig' import getModuleDependencies from '../lib/getModuleDependencies' - import resolveConfig from '../public/resolve-config' - import resolveConfigPath from '../util/resolveConfigPath' - -import { env } from './sharedState' - import { getContext, getFileModifiedMap } from './setupContextUtils' import parseDependency from '../util/parseDependency' import { validateConfig } from '../util/validateConfig.js' @@ -36,6 +27,7 @@ function getCandidateFiles(context, tailwindConfig) { // Get the config object based on a path function getTailwindConfig(configOrPath, inputPath) { let userConfigPath = resolveConfigPath(configOrPath, inputPath) + let postcssOptions = typeof configOrPath === 'object' && configOrPath !== null ? configOrPath : {} if (userConfigPath !== null) { let [prevConfig, prevConfigHash, prevDeps, prevModified] = @@ -55,7 +47,7 @@ function getTailwindConfig(configOrPath, inputPath) { // It hasn't changed (based on timestamps) if (!modified) { - return [prevConfig, userConfigPath, prevConfigHash, prevDeps] + return [prevConfig, userConfigPath, prevConfigHash, prevDeps, postcssOptions] } // It has changed (based on timestamps), or first run @@ -66,7 +58,7 @@ function getTailwindConfig(configOrPath, inputPath) { newConfig = validateConfig(newConfig) let newHash = hash(newConfig) configPathCache.set(userConfigPath, [newConfig, newHash, newDeps, newModified]) - return [newConfig, userConfigPath, newHash, newDeps] + return [newConfig, userConfigPath, newHash, newDeps, postcssOptions] } // It's a plain object, not a path @@ -76,7 +68,7 @@ function getTailwindConfig(configOrPath, inputPath) { newConfig = validateConfig(newConfig) - return [newConfig, null, hash(newConfig), []] + return [newConfig, null, hash(newConfig), [], postcssOptions] } // DISABLE_TOUCH = TRUE @@ -87,7 +79,7 @@ function getTailwindConfig(configOrPath, inputPath) { export default function setupTrackingContext(configOrPath) { return ({ tailwindDirectives, registerDependency }) => { return (root, result) => { - let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] = + let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies, postcssOptions] = getTailwindConfig(configOrPath, result.opts.from) let contextDependencies = new Set(configDependencies) @@ -116,7 +108,8 @@ export default function setupTrackingContext(configOrPath) { tailwindConfig, userConfigPath, tailwindConfigHash, - contextDependencies + contextDependencies, + postcssOptions ) let candidateFiles = getCandidateFiles(context, tailwindConfig) diff --git a/src/util/resolveConfigPath.js b/src/util/resolveConfigPath.js index f622f75f0..c2b8e862b 100644 --- a/src/util/resolveConfigPath.js +++ b/src/util/resolveConfigPath.js @@ -34,16 +34,10 @@ function pickResolvedPath(configPath, inputPath) { if (inputPath) { try { // Use require.resolve so we can find config file in parent directories - let resolvedPath = require.resolve(configPath, { - paths: [inputPath], + return require.resolve(configPath, { + paths: [path.dirname(inputPath)], }) - - let maybeConfig = require(resolvedPath) - - if (typeof maybeConfig === 'object' && flagEnabled(maybeConfig, 'resolveConfigRelativeToInput')) { - return resolvedPath - } - } catch (e) {} + } catch {} } return path.resolve(configPath) @@ -61,7 +55,11 @@ export default function resolveConfigPath(pathOrConfig, inputPath) { pathOrConfig.config !== undefined && isString(pathOrConfig.config) ) { - return pickResolvedPath(pathOrConfig.config, inputPath) + if (pathOrConfig.experimental?.contextualPaths) { + return pickResolvedPath(pathOrConfig.config, inputPath) + } + + return path.resolve(pathOrConfig.config) } // require('tailwindcss')({ config: { theme: ..., variants: ... } }) @@ -75,13 +73,13 @@ export default function resolveConfigPath(pathOrConfig, inputPath) { // require('tailwindcss')('custom-config.js') if (isString(pathOrConfig)) { - return pickResolvedPath(pathOrConfig, inputPath) + return path.resolve(pathOrConfig) } // require('tailwindcss') for (const configFile of ['./tailwind.config.js', './tailwind.config.cjs']) { try { - const configPath = pickResolvedPath(configFile, inputPath) + const configPath = path.resolve(configFile) fs.accessSync(configPath) return configPath } catch (err) {}