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

213 lines
5.4 KiB
JavaScript
Raw Normal View History

2020-05-03 14:57:15 +00:00
import extractClassNames from './extractClassNames'
import Hook from './hook'
2020-04-11 21:20:45 +00:00
import dlv from 'dlv'
import dset from 'dset'
2020-06-16 09:41:26 +00:00
import resolveFrom from 'resolve-from'
2020-04-11 21:20:45 +00:00
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'
2020-04-25 21:41:17 +00:00
import { getUtilityConfigMap } from './getUtilityConfigMap'
2020-06-24 10:55:17 +00:00
import glob from 'fast-glob'
import normalizePath from 'normalize-path'
import execa from 'execa'
2020-04-23 18:54:01 +00:00
2020-04-11 21:20:45 +00:00
function arraysEqual(arr1, arr2) {
2020-04-19 16:30:25 +00:00
return (
JSON.stringify(arr1.concat([]).sort()) ===
JSON.stringify(arr2.concat([]).sort())
)
2020-04-11 21:20:45 +00:00
}
2020-04-22 19:29:36 +00:00
const CONFIG_GLOB =
'**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js'
2020-04-11 21:20:45 +00:00
export default async function getClassNames(
cwd = process.cwd(),
{ onChange = () => {} } = {}
) {
2020-04-22 19:29:36 +00:00
async function run() {
let postcss
let tailwindcss
let version
let featureFlags = { future: [], experimental: [] }
2020-04-11 21:20:45 +00:00
2020-06-24 10:55:17 +00:00
const configPaths = (
await glob(CONFIG_GLOB, {
cwd,
ignore: ['**/node_modules'],
onlyFiles: true,
absolute: true,
2020-08-27 11:16:42 +00:00
suppressErrors: true,
2020-06-24 10:55:17 +00:00
})
)
.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]
2020-12-01 19:03:31 +00:00
console.log(`Found Tailwind config file: ${configPath}`)
const configDir = path.dirname(configPath)
2020-06-16 09:41:26 +00:00
const tailwindBase = path.dirname(
resolveFrom(configDir, 'tailwindcss/package.json')
)
postcss = importFrom(tailwindBase, 'postcss')
tailwindcss = importFrom(configDir, 'tailwindcss')
version = importFrom(configDir, 'tailwindcss/package.json').version
2020-12-01 19:03:31 +00:00
console.log(`Found tailwindcss v${version}: ${tailwindBase}`)
2020-04-11 21:20:45 +00:00
try {
featureFlags = importFrom(tailwindBase, './lib/featureFlags.js').default
} catch (_) {}
2020-04-11 21:20:45 +00:00
const sepLocation = semver.gte(version, '0.99.0')
? ['separator']
: ['options', 'separator']
let userSeperator
2020-06-20 18:05:36 +00:00
let userPurge
let hook = Hook(fs.realpathSync(configPath), (exports) => {
2020-04-11 21:20:45 +00:00
userSeperator = dlv(exports, sepLocation)
2020-06-20 18:05:36 +00:00
userPurge = exports.purge
2020-04-11 21:20:45 +00:00
dset(exports, sepLocation, '__TAILWIND_SEPARATOR__')
2020-06-20 18:05:36 +00:00
exports.purge = {}
2020-04-11 21:20:45 +00:00
return exports
})
hook.watch()
2020-04-23 18:54:01 +00:00
let config
try {
config = __non_webpack_require__(configPath)
} catch (error) {
hook.unwatch()
hook.unhook()
throw error
2020-04-23 18:54:01 +00:00
}
2020-04-11 21:20:45 +00:00
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,
})
)
2020-05-10 12:06:22 +00:00
)
} catch (error) {
throw error
} finally {
hook.unhook()
}
2020-04-11 21:20:45 +00:00
const [base, components, utilities] = postcssResult
2020-04-11 21:20:45 +00:00
if (typeof userSeperator !== 'undefined') {
dset(config, sepLocation, userSeperator)
} else {
delete config[sepLocation]
}
2020-06-20 18:05:36 +00:00
if (typeof userPurge !== 'undefined') {
config.purge = userPurge
} else {
delete config.purge
}
2020-04-11 21:20:45 +00:00
const resolvedConfig = resolveConfig({ cwd: configDir, config })
let browserslist = []
try {
const { stdout, stderr } = await execa('browserslist', [], {
preferLocal: true,
localDir: configDir,
cwd: configDir,
})
if (stderr) {
throw Error(stderr)
}
browserslist = stdout.split('\n')
} catch (error) {
console.error('Failed to load browserslist:', error)
}
2020-04-25 21:41:17 +00:00
2020-04-11 21:20:45 +00:00
return {
version,
2020-04-22 19:29:36 +00:00
configPath,
2020-04-25 21:41:17 +00:00
config: resolvedConfig,
2020-04-11 21:20:45 +00:00
separator: typeof userSeperator === 'undefined' ? ':' : userSeperator,
2020-05-10 12:06:22 +00:00
classNames: await extractClassNames([
2020-05-10 12:19:17 +00:00
{ root: base.root, source: 'base' },
2020-05-10 12:06:22 +00:00
{ root: components.root, source: 'components' },
{ root: utilities.root, source: 'utilities' },
]),
2020-04-22 19:29:36 +00:00
dependencies: hook.deps,
2020-04-11 21:20:45 +00:00
plugins: getPlugins(config),
2020-05-03 18:37:37 +00:00
variants: getVariants({ config, version, postcss, browserslist }),
2020-04-25 21:41:17 +00:00
utilityConfigMap: await getUtilityConfigMap({
cwd: configDir,
2020-04-25 21:41:17 +00:00
resolvedConfig,
postcss,
2020-05-03 18:37:37 +00:00
browserslist,
2020-04-25 21:41:17 +00:00
}),
2020-06-17 17:34:53 +00:00
modules: {
tailwindcss,
postcss,
},
featureFlags,
2020-04-11 21:20:45 +00:00
}
}
let watcher
2020-04-22 19:29:36 +00:00
function watch(files = []) {
2020-05-03 13:45:36 +00:00
unwatch()
2020-04-11 21:20:45 +00:00
watcher = chokidar
2020-05-03 13:45:36 +00:00
.watch(files, { cwd })
2020-04-11 21:20:45 +00:00
.on('change', handleChange)
.on('unlink', handleChange)
}
2020-05-03 13:45:36 +00:00
function unwatch() {
if (watcher) {
watcher.close()
}
}
2020-04-11 21:20:45 +00:00
async function handleChange() {
2020-05-03 13:45:36 +00:00
const prevDeps = result ? [result.configPath, ...result.dependencies] : []
2020-04-22 19:29:36 +00:00
try {
result = await run()
2020-04-23 18:54:01 +00:00
} catch (error) {
onChange({ error })
2020-04-22 19:29:36 +00:00
return
}
2020-05-03 13:45:36 +00:00
const newDeps = [result.configPath, ...result.dependencies]
if (!arraysEqual(prevDeps, newDeps)) {
watch(newDeps)
2020-04-11 21:20:45 +00:00
}
onChange(result)
}
2020-04-22 19:29:36 +00:00
let result
try {
result = await run()
2020-12-01 19:03:31 +00:00
console.log('Initialised successfully.')
} catch (error) {
console.error('Failed to initialise:', error)
2020-04-22 19:29:36 +00:00
return null
}
2020-05-03 13:45:36 +00:00
watch([result.configPath, ...result.dependencies])
2020-04-22 19:29:36 +00:00
2020-04-11 21:20:45 +00:00
return result
}