204 lines
5.1 KiB
JavaScript
204 lines
5.1 KiB
JavaScript
|
import extractClassNames from './extractClassNames'
|
||
|
import Hook from './hook'
|
||
|
import dlv from 'dlv'
|
||
|
import dset from 'dset'
|
||
|
import resolveFrom from 'resolve-from'
|
||
|
import importFrom from 'import-from'
|
||
|
import chokidar from 'chokidar'
|
||
|
import semver from 'semver'
|
||
|
import invariant from 'tiny-invariant'
|
||
|
import getPlugins from './getPlugins'
|
||
|
import getVariants from './getVariants'
|
||
|
import resolveConfig from './resolveConfig'
|
||
|
import * as path from 'path'
|
||
|
import * as fs from 'fs'
|
||
|
import { getUtilityConfigMap } from './getUtilityConfigMap'
|
||
|
import glob from 'fast-glob'
|
||
|
import normalizePath from 'normalize-path'
|
||
|
|
||
|
function arraysEqual(arr1, arr2) {
|
||
|
return (
|
||
|
JSON.stringify(arr1.concat([]).sort()) ===
|
||
|
JSON.stringify(arr2.concat([]).sort())
|
||
|
)
|
||
|
}
|
||
|
|
||
|
const CONFIG_GLOB =
|
||
|
'**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js'
|
||
|
|
||
|
export default async function getClassNames(
|
||
|
cwd = process.cwd(),
|
||
|
{ onChange = () => {} } = {}
|
||
|
) {
|
||
|
async function run() {
|
||
|
let postcss
|
||
|
let tailwindcss
|
||
|
let browserslistModule
|
||
|
let version
|
||
|
let featureFlags = { future: [], experimental: [] }
|
||
|
|
||
|
const configPaths = (
|
||
|
await glob(CONFIG_GLOB, {
|
||
|
cwd,
|
||
|
ignore: ['**/node_modules'],
|
||
|
onlyFiles: true,
|
||
|
absolute: true,
|
||
|
suppressErrors: true,
|
||
|
})
|
||
|
)
|
||
|
.map(normalizePath)
|
||
|
.sort((a, b) => a.split('/').length - b.split('/').length)
|
||
|
.map(path.normalize)
|
||
|
|
||
|
invariant(configPaths.length > 0, 'No Tailwind CSS config found.')
|
||
|
const configPath = configPaths[0]
|
||
|
const configDir = path.dirname(configPath)
|
||
|
const tailwindBase = path.dirname(
|
||
|
resolveFrom(configDir, 'tailwindcss/package.json')
|
||
|
)
|
||
|
postcss = importFrom(tailwindBase, 'postcss')
|
||
|
tailwindcss = importFrom(configDir, 'tailwindcss')
|
||
|
version = importFrom(configDir, 'tailwindcss/package.json').version
|
||
|
|
||
|
try {
|
||
|
// this is not required
|
||
|
browserslistModule = importFrom(tailwindBase, 'browserslist')
|
||
|
} catch (_) {}
|
||
|
|
||
|
try {
|
||
|
featureFlags = importFrom(tailwindBase, './lib/featureFlags.js').default
|
||
|
} catch (_) {}
|
||
|
|
||
|
const sepLocation = semver.gte(version, '0.99.0')
|
||
|
? ['separator']
|
||
|
: ['options', 'separator']
|
||
|
let userSeperator
|
||
|
let userPurge
|
||
|
let hook = Hook(fs.realpathSync(configPath), (exports) => {
|
||
|
userSeperator = dlv(exports, sepLocation)
|
||
|
userPurge = exports.purge
|
||
|
dset(exports, sepLocation, '__TAILWIND_SEPARATOR__')
|
||
|
exports.purge = {}
|
||
|
return exports
|
||
|
})
|
||
|
|
||
|
hook.watch()
|
||
|
let config
|
||
|
try {
|
||
|
config = __non_webpack_require__(configPath)
|
||
|
} catch (error) {
|
||
|
hook.unwatch()
|
||
|
hook.unhook()
|
||
|
throw error
|
||
|
}
|
||
|
|
||
|
hook.unwatch()
|
||
|
|
||
|
let postcssResult
|
||
|
|
||
|
try {
|
||
|
postcssResult = await Promise.all(
|
||
|
[
|
||
|
semver.gte(version, '0.99.0') ? 'base' : 'preflight',
|
||
|
'components',
|
||
|
'utilities',
|
||
|
].map((group) =>
|
||
|
postcss([tailwindcss(configPath)]).process(`@tailwind ${group};`, {
|
||
|
from: undefined,
|
||
|
})
|
||
|
)
|
||
|
)
|
||
|
} catch (error) {
|
||
|
throw error
|
||
|
} finally {
|
||
|
hook.unhook()
|
||
|
}
|
||
|
|
||
|
const [base, components, utilities] = postcssResult
|
||
|
|
||
|
if (typeof userSeperator !== 'undefined') {
|
||
|
dset(config, sepLocation, userSeperator)
|
||
|
} else {
|
||
|
delete config[sepLocation]
|
||
|
}
|
||
|
if (typeof userPurge !== 'undefined') {
|
||
|
config.purge = userPurge
|
||
|
} else {
|
||
|
delete config.purge
|
||
|
}
|
||
|
|
||
|
const resolvedConfig = resolveConfig({ cwd: configDir, config })
|
||
|
const browserslist = browserslistModule
|
||
|
? browserslistModule(undefined, {
|
||
|
path: configDir,
|
||
|
})
|
||
|
: []
|
||
|
|
||
|
return {
|
||
|
version,
|
||
|
configPath,
|
||
|
config: resolvedConfig,
|
||
|
separator: typeof userSeperator === 'undefined' ? ':' : userSeperator,
|
||
|
classNames: await extractClassNames([
|
||
|
{ root: base.root, source: 'base' },
|
||
|
{ root: components.root, source: 'components' },
|
||
|
{ root: utilities.root, source: 'utilities' },
|
||
|
]),
|
||
|
dependencies: hook.deps,
|
||
|
plugins: getPlugins(config),
|
||
|
variants: getVariants({ config, version, postcss, browserslist }),
|
||
|
utilityConfigMap: await getUtilityConfigMap({
|
||
|
cwd: configDir,
|
||
|
resolvedConfig,
|
||
|
postcss,
|
||
|
browserslist,
|
||
|
}),
|
||
|
modules: {
|
||
|
tailwindcss,
|
||
|
postcss,
|
||
|
},
|
||
|
featureFlags,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let watcher
|
||
|
function watch(files = []) {
|
||
|
unwatch()
|
||
|
watcher = chokidar
|
||
|
.watch(files, { cwd })
|
||
|
.on('change', handleChange)
|
||
|
.on('unlink', handleChange)
|
||
|
}
|
||
|
function unwatch() {
|
||
|
if (watcher) {
|
||
|
watcher.close()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async function handleChange() {
|
||
|
const prevDeps = result ? [result.configPath, ...result.dependencies] : []
|
||
|
try {
|
||
|
result = await run()
|
||
|
} catch (error) {
|
||
|
onChange({ error })
|
||
|
return
|
||
|
}
|
||
|
const newDeps = [result.configPath, ...result.dependencies]
|
||
|
if (!arraysEqual(prevDeps, newDeps)) {
|
||
|
watch(newDeps)
|
||
|
}
|
||
|
onChange(result)
|
||
|
}
|
||
|
|
||
|
let result
|
||
|
try {
|
||
|
result = await run()
|
||
|
} catch (_) {
|
||
|
return null
|
||
|
}
|
||
|
|
||
|
watch([result.configPath, ...result.dependencies])
|
||
|
|
||
|
return result
|
||
|
}
|