import { createRequire } from 'node:module' import fs from 'fs-extra' import path from 'pathe' import postcss from 'postcss' import { loadConfig } from 'tailwindcss-config' type NodeRequire = ReturnType export interface TailwindBuildOptions { cwd: string config?: string majorVersion: 2 | 3 | 4 postcssPlugin?: string } function createCwdRequire(cwd: string) { return createRequire(path.join(cwd, 'package.json')) } function resolveModuleEntry(id: string, moduleRequire: NodeRequire) { return path.isAbsolute(id) ? id : moduleRequire.resolve(id) } function resolvePackageRootFromEntry(entry: string) { let current = path.dirname(entry) while (current && current !== path.dirname(current)) { const packageJsonPath = path.join(current, 'package.json') if (fs.pathExistsSync(packageJsonPath)) { return current } current = path.dirname(current) } return undefined } function resolveCacheKey(moduleRequire: NodeRequire, entry: string) { try { return moduleRequire.resolve(entry) } catch { return entry } } function clearTailwindV3RuntimeState(pluginName: string, moduleRequire: NodeRequire) { try { const entry = resolveModuleEntry(pluginName, moduleRequire) const root = resolvePackageRootFromEntry(entry) if (!root) { return } const sharedStatePath = path.join(root, 'lib/lib/sharedState.js') if (!fs.pathExistsSync(sharedStatePath)) { return } const sharedStateKey = resolveCacheKey(moduleRequire, sharedStatePath) const sharedState = moduleRequire.cache[sharedStateKey]?.exports as | { contextMap?: Map configContextMap?: Map contextSourcesMap?: Map sourceHashMap?: Map } | undefined sharedState?.contextMap?.clear() sharedState?.configContextMap?.clear() sharedState?.contextSourcesMap?.clear() sharedState?.sourceHashMap?.clear() for (const candidate of ['lib/plugin.js', 'lib/index.js']) { const runtimeEntry = path.join(root, candidate) if (!fs.pathExistsSync(runtimeEntry)) { continue } const runtimeKey = resolveCacheKey(moduleRequire, runtimeEntry) const runtimeModule = moduleRequire.cache[runtimeKey]?.exports as | { contextRef?: { value?: unknown[] } } | undefined runtimeModule?.contextRef?.value?.splice(0, runtimeModule.contextRef.value.length) break } } catch { // best-effort cleanup for Tailwind v3 runtime state } } async function resolveConfigPath(options: TailwindBuildOptions) { if (options.config && path.isAbsolute(options.config)) { return options.config } const result = await loadConfig({ cwd: options.cwd }) if (!result) { throw new Error(`Unable to locate Tailwind CSS config from ${options.cwd}`) } return result.filepath } export async function runTailwindBuild(options: TailwindBuildOptions) { const configPath = await resolveConfigPath(options) const pluginName = options.postcssPlugin ?? (options.majorVersion === 4 ? '@tailwindcss/postcss' : 'tailwindcss') const moduleRequire = createCwdRequire(options.cwd) if (options.majorVersion === 3) { clearTailwindV3RuntimeState(pluginName, moduleRequire) } if (options.majorVersion === 4) { return postcss([ moduleRequire(pluginName)({ config: configPath, }), ]).process('@import \'tailwindcss\';', { from: undefined, }) } return postcss([ moduleRequire(pluginName)({ config: configPath, }), ]).process('@tailwind base;@tailwind components;@tailwind utilities;', { from: undefined, }) }