tailwind-ctp-intellisense/packages/tailwindcss-class-names/src/index.js

153 lines
3.7 KiB
JavaScript

import extractClassNames from './extractClassNames.mjs'
import Hook from './hook.mjs'
import dlv from 'dlv'
import dset from 'dset'
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 util from 'util'
import * as path from 'path'
import { glob } from './glob'
import { getUtilityConfigMap } from './getUtilityConfigMap'
function TailwindConfigError(error) {
Error.call(this)
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = error.message
this.stack = error.stack
}
util.inherits(TailwindConfigError, Error)
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 configPath
let postcss
let tailwindcss
let version
configPath = await glob(CONFIG_GLOB, {
cwd,
ignore: '**/node_modules/**',
max: 1,
})
invariant(configPath.length === 1, 'No Tailwind CSS config found.')
configPath = configPath[0]
const configDir = path.dirname(configPath)
postcss = importFrom(configDir, 'postcss')
tailwindcss = importFrom(configDir, 'tailwindcss')
version = importFrom(configDir, 'tailwindcss/package.json').version
const sepLocation = semver.gte(version, '0.99.0')
? ['separator']
: ['options', 'separator']
let userSeperator
let hook = Hook(configPath, (exports) => {
userSeperator = dlv(exports, sepLocation)
dset(exports, sepLocation, '__TAILWIND_SEPARATOR__')
return exports
})
hook.watch()
let config
try {
config = __non_webpack_require__(configPath)
} catch (error) {
throw new TailwindConfigError(error)
}
hook.unwatch()
const ast = await postcss([tailwindcss(configPath)]).process(
`
@tailwind components;
@tailwind utilities;
`,
{ from: undefined }
)
hook.unhook()
if (typeof userSeperator !== 'undefined') {
dset(config, sepLocation, userSeperator)
} else {
delete config[sepLocation]
}
const resolvedConfig = resolveConfig({ cwd: configDir, config })
return {
version,
configPath,
config: resolvedConfig,
separator: typeof userSeperator === 'undefined' ? ':' : userSeperator,
classNames: await extractClassNames(ast),
dependencies: hook.deps,
plugins: getPlugins(config),
variants: getVariants({ config, version, postcss }),
utilityConfigMap: await getUtilityConfigMap({
cwd: configDir,
resolvedConfig,
postcss,
}),
}
}
let watcher
function watch(files = []) {
if (watcher) watcher.close()
watcher = chokidar
.watch([CONFIG_GLOB, ...files], { cwd })
.on('change', handleChange)
.on('unlink', handleChange)
}
async function handleChange() {
const prevDeps = result ? result.dependencies : []
try {
result = await run()
} catch (error) {
if (error instanceof TailwindConfigError) {
onChange({ error })
} else {
onChange(null)
}
return
}
if (!arraysEqual(prevDeps, result.dependencies)) {
watch(result.dependencies)
}
onChange(result)
}
let result
try {
result = await run()
} catch (_) {
watch()
return null
}
watch(result.dependencies)
return result
}