-
Notifications
You must be signed in to change notification settings - Fork 127
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add preliminary support for v4 (#249)
* Refactor * Add support for loading v4 * Update changelog
- Loading branch information
1 parent
1fa24c2
commit 81c446e
Showing
7 changed files
with
149 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,12 @@ | ||
// @ts-check | ||
import * as fs from 'fs/promises' | ||
import { createRequire } from 'module' | ||
import * as path from 'path' | ||
import clearModule from 'clear-module' | ||
import escalade from 'escalade/sync' | ||
import * as path from 'path' | ||
import postcss from 'postcss' | ||
import postcssImport from 'postcss-import' | ||
import prettier from 'prettier' | ||
import resolveFrom from 'resolve-from' | ||
// @ts-ignore | ||
import { generateRules as generateRulesFallback } from 'tailwindcss/lib/lib/generateRules' | ||
// @ts-ignore | ||
|
@@ -12,6 +15,8 @@ import loadConfigFallback from 'tailwindcss/loadConfig' | |
import resolveConfigFallback from 'tailwindcss/resolveConfig' | ||
import { expiringMap } from './expiring-map.js' | ||
|
||
let localRequire = createRequire(import.meta.url) | ||
|
||
/** @typedef {import('prettier').ParserOptions} ParserOptions **/ | ||
/** @typedef {import('./types.js').ContextContainer} ContextContainer **/ | ||
|
||
|
@@ -24,6 +29,9 @@ import { expiringMap } from './expiring-map.js' | |
/** @type {Map<string, string | null>} */ | ||
let sourceToPathMap = new Map() | ||
|
||
/** @type {Map<string, string | null>} */ | ||
let sourceToEntryMap = new Map() | ||
|
||
/** @type {ExpiringMap<string | null, ContextContainer>} */ | ||
let pathToContextMap = expiringMap(10_000) | ||
|
||
|
@@ -35,7 +43,7 @@ let prettierConfigCache = expiringMap(10_000) | |
* @returns {Promise<ContextContainer>} | ||
*/ | ||
export async function getTailwindConfig(options) { | ||
let key = `${options.filepath}:${options.tailwindConfig ?? ''}` | ||
let key = `${options.filepath}:${options.tailwindConfig ?? ''}:${options.tailwindEntryPoint ?? ''}` | ||
let baseDir = await getBaseDir(options) | ||
|
||
// Map the source file to it's associated Tailwind config file | ||
|
@@ -45,16 +53,23 @@ export async function getTailwindConfig(options) { | |
sourceToPathMap.set(key, configPath) | ||
} | ||
|
||
let entryPoint = sourceToEntryMap.get(key) | ||
if (entryPoint === undefined) { | ||
entryPoint = getEntryPoint(options, baseDir) | ||
sourceToEntryMap.set(key, entryPoint) | ||
} | ||
|
||
// Now see if we've loaded the Tailwind config file before (and it's still valid) | ||
let existing = pathToContextMap.get(configPath) | ||
let contextKey = `${configPath}:${entryPoint}` | ||
let existing = pathToContextMap.get(contextKey) | ||
if (existing) { | ||
return existing | ||
} | ||
|
||
// By this point we know we need to load the Tailwind config file | ||
let result = loadTailwindConfig(baseDir, configPath) | ||
let result = await loadTailwindConfig(baseDir, configPath, entryPoint) | ||
|
||
pathToContextMap.set(configPath, result) | ||
pathToContextMap.set(contextKey, result) | ||
|
||
return result | ||
} | ||
|
@@ -88,38 +103,51 @@ async function getBaseDir(options) { | |
return prettierConfigPath ? path.dirname(prettierConfigPath) : process.cwd() | ||
} | ||
|
||
if (options.tailwindEntryPoint) { | ||
return prettierConfigPath ? path.dirname(prettierConfigPath) : process.cwd() | ||
} | ||
|
||
return prettierConfigPath | ||
? path.dirname(prettierConfigPath) | ||
: options.filepath | ||
? path.dirname(options.filepath) | ||
: process.cwd() | ||
? path.dirname(options.filepath) | ||
: process.cwd() | ||
} | ||
|
||
/** | ||
* | ||
* @param {string} baseDir | ||
* @param {string | null} tailwindConfigPath | ||
* @returns {ContextContainer} | ||
* @param {string | null} entryPoint | ||
* @returns {Promise<ContextContainer>} | ||
*/ | ||
function loadTailwindConfig(baseDir, tailwindConfigPath) { | ||
async function loadTailwindConfig(baseDir, tailwindConfigPath, entryPoint) { | ||
let createContext = createContextFallback | ||
let generateRules = generateRulesFallback | ||
let resolveConfig = resolveConfigFallback | ||
let loadConfig = loadConfigFallback | ||
let tailwindConfig = {} | ||
|
||
try { | ||
let pkgDir = path.dirname(resolveFrom(baseDir, 'tailwindcss/package.json')) | ||
let pkgFile = localRequire.resolve('tailwindcss/package.json', { | ||
paths: [baseDir], | ||
}) | ||
|
||
let pkgDir = path.dirname(pkgFile) | ||
|
||
try { | ||
let v4 = await loadV4(baseDir, pkgDir, entryPoint) | ||
if (v4) { | ||
return v4 | ||
} | ||
} catch {} | ||
|
||
resolveConfig = require(path.join(pkgDir, 'resolveConfig')) | ||
createContext = require(path.join( | ||
pkgDir, | ||
'lib/lib/setupContextUtils', | ||
)).createContext | ||
generateRules = require(path.join( | ||
pkgDir, | ||
'lib/lib/generateRules', | ||
)).generateRules | ||
createContext = require( | ||
path.join(pkgDir, 'lib/lib/setupContextUtils'), | ||
).createContext | ||
generateRules = require( | ||
path.join(pkgDir, 'lib/lib/generateRules'), | ||
).generateRules | ||
|
||
// Prior to `[email protected]` this won't exist so we load it last | ||
loadConfig = require(path.join(pkgDir, 'loadConfig')) | ||
|
@@ -139,11 +167,53 @@ function loadTailwindConfig(baseDir, tailwindConfigPath) { | |
|
||
return { | ||
context, | ||
tailwindConfig, | ||
generateRules, | ||
} | ||
} | ||
|
||
/** | ||
* @param {string} baseDir | ||
* @param {string} pkgDir | ||
* @param {string | null} entryPoint | ||
*/ | ||
async function loadV4(baseDir, pkgDir, entryPoint) { | ||
// Import Tailwind — if this is v4 it'll have APIs we can use directly | ||
let pkgPath = localRequire.resolve('tailwindcss', { | ||
paths: [baseDir], | ||
}) | ||
|
||
let tw = await import(pkgPath) | ||
|
||
// This is not Tailwind v4 | ||
if (!tw.loadDesignSystem) { | ||
return null | ||
} | ||
|
||
// If the user doesn't define an entrypoint then we use the default theme | ||
entryPoint = entryPoint ?? `${pkgDir}/theme.css` | ||
|
||
// Resolve imports in the entrypoint to a flat CSS tree | ||
let css = await fs.readFile(entryPoint, 'utf-8') | ||
let resolveImports = postcss([postcssImport()]) | ||
let result = await resolveImports.process(css, { from: entryPoint }) | ||
|
||
// Load the design system and set up a compatible context object that is | ||
// usable by the rest of the plugin | ||
let design = tw.loadDesignSystem(result.css) | ||
|
||
return { | ||
context: { | ||
/** | ||
* @param {string[]} classList | ||
*/ | ||
getClassOrder: (classList) => design.getClassOrder(classList), | ||
}, | ||
|
||
// Stubs that are not needed for v4 | ||
generateRules: () => [], | ||
} | ||
} | ||
|
||
/** | ||
* @param {ParserOptions} options | ||
* @param {string} baseDir | ||
|
@@ -178,3 +248,16 @@ function getConfigPath(options, baseDir) { | |
|
||
return null | ||
} | ||
|
||
/** | ||
* @param {ParserOptions} options | ||
* @param {string} baseDir | ||
* @returns {string | null} | ||
*/ | ||
function getEntryPoint(options, baseDir) { | ||
if (options.tailwindEntryPoint) { | ||
return path.resolve(baseDir, options.tailwindEntryPoint) | ||
} | ||
|
||
return null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters