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

290 lines
7.3 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'
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'
2020-08-23 15:02:58 +00:00
import { withUserEnvironment } from './environment'
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,cjs}'
2020-04-22 19:29:36 +00:00
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() {
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-11-24 13:27:19 +00:00
const {
version,
featureFlags = { future: [], experimental: [] },
tailwindBase,
} = loadMeta(configDir, cwd)
2020-04-11 21:20:45 +00:00
console.log(`Found tailwindcss v${version}: ${tailwindBase}`)
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()
2020-11-24 13:27:19 +00:00
const {
base,
components,
utilities,
resolvedConfig,
browserslist,
postcss,
} = await withPackages(
2020-08-23 15:02:58 +00:00
configDir,
cwd,
async ({
postcss,
tailwindcss,
browserslistCommand,
browserslistArgs,
}) => {
2020-08-23 15:02:58 +00:00
let postcssResult
try {
postcssResult = await Promise.all(
2020-11-24 13:27:19 +00:00
[
semver.gte(version, '0.99.0') ? 'base' : 'preflight',
'components',
'utilities',
].map((group) =>
postcss([tailwindcss(configPath)]).process(
`@tailwind ${group};`,
{
from: undefined,
}
)
2020-08-23 15:02:58 +00:00
)
)
2020-08-27 12:31:41 +00:00
} catch (error) {
throw error
2020-08-23 15:02:58 +00:00
} finally {
hook.unhook()
}
2020-08-23 15:02:58 +00:00
const [base, components, utilities] = postcssResult
2020-04-11 21:20:45 +00:00
2020-08-23 15:02:58 +00:00
if (typeof userSeperator !== 'undefined') {
dset(config, sepLocation, userSeperator)
} else {
delete config[sepLocation]
}
if (typeof userPurge !== 'undefined') {
config.purge = userPurge
} else {
delete config.purge
}
2020-04-11 21:20:45 +00:00
const resolvedConfig = resolveConfig({
base: configDir,
root: cwd,
config,
})
2020-04-11 21:20:45 +00:00
let browserslist = []
if (
browserslistCommand &&
semver.gte(version, '1.4.0') &&
semver.lte(version, '1.99.0')
) {
try {
const { stdout } = await execa(
browserslistCommand,
browserslistArgs,
{
preferLocal: true,
localDir: configDir,
cwd: configDir,
}
)
browserslist = stdout.split('\n')
} catch (error) {
console.error('Failed to load browserslist:', error)
}
}
2020-08-23 15:02:58 +00:00
return {
base,
components,
utilities,
resolvedConfig,
postcss,
browserslist,
}
}
2020-08-23 15:02:58 +00:00
)
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({
base: configDir,
root: cwd,
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: {
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
}
2020-08-23 15:02:58 +00:00
function loadMeta(configDir, root) {
return withUserEnvironment(configDir, root, ({ require, resolve }) => {
const tailwindBase = path.dirname(resolve('tailwindcss/package.json'))
2020-08-23 15:02:58 +00:00
const version = require('tailwindcss/package.json').version
let featureFlags
try {
2020-08-27 12:39:00 +00:00
featureFlags = require('./lib/featureFlags.js', tailwindBase).default
2020-08-23 15:02:58 +00:00
} catch (_) {}
return { version, featureFlags, tailwindBase }
2020-08-23 15:02:58 +00:00
})
}
function withPackages(configDir, root, cb) {
return withUserEnvironment(
configDir,
root,
async ({ isPnP, require, resolve }) => {
const tailwindBase = path.dirname(resolve('tailwindcss/package.json'))
const postcss = require('postcss', tailwindBase)
const tailwindcss = require('tailwindcss')
let browserslistCommand
let browserslistArgs = []
try {
const browserslistBin = resolve(
path.join(
'browserslist',
require('browserslist/package.json', tailwindBase).bin.browserslist
),
tailwindBase
)
if (isPnP) {
browserslistCommand = 'yarn'
browserslistArgs = ['node', browserslistBin]
} else {
browserslistCommand = process.execPath
browserslistArgs = [browserslistBin]
}
} catch (_) {}
2020-08-23 15:02:58 +00:00
return cb({ postcss, tailwindcss, browserslistCommand, browserslistArgs })
}
)
2020-08-23 15:02:58 +00:00
}