Skip to content

Commit 1293a08

Browse files
author
Brad Cornes
committed
Merge branch 'next' into config-explorer
2 parents c103881 + 2de4816 commit 1293a08

File tree

12 files changed

+302
-109
lines changed

12 files changed

+302
-109
lines changed

packages/tailwindcss-class-names/src/getPlugins.js

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
import * as path from 'path'
22
import stackTrace from 'stack-trace'
33
import pkgUp from 'pkg-up'
4+
import { glob } from './glob'
5+
import { isObject } from './isObject'
46

5-
function isObject(variable) {
6-
return Object.prototype.toString.call(variable) === '[object Object]'
7+
export async function getBuiltInPlugins(cwd) {
8+
try {
9+
return (
10+
await glob(path.resolve(cwd, 'node_modules/tailwindcss/lib/plugins/*.js'))
11+
)
12+
.map((x) => {
13+
try {
14+
const mod = __non_webpack_require__(x)
15+
return mod.default ? mod.default() : mod()
16+
} catch (_) {}
17+
})
18+
.filter(Boolean)
19+
} catch (_) {
20+
return []
21+
}
722
}
823

924
export default function getPlugins(config) {
@@ -13,7 +28,7 @@ export default function getPlugins(config) {
1328
return []
1429
}
1530

16-
return plugins.map(plugin => {
31+
return plugins.map((plugin) => {
1732
let pluginConfig = plugin.config
1833
if (!isObject(pluginConfig)) {
1934
pluginConfig = {}
@@ -25,7 +40,7 @@ export default function getPlugins(config) {
2540
: [],
2641
variants: isObject(pluginConfig.variants)
2742
? Object.keys(pluginConfig.variants)
28-
: []
43+
: [],
2944
}
3045

3146
const fn = plugin.handler || plugin
@@ -40,32 +55,32 @@ export default function getPlugins(config) {
4055
const trace = stackTrace.parse(e)
4156
if (trace.length === 0)
4257
return {
43-
name: fnName
58+
name: fnName,
4459
}
4560
const file = trace[0].fileName
4661
const dir = path.dirname(file)
4762
let pkg = pkgUp.sync({ cwd: dir })
4863
if (!pkg)
4964
return {
50-
name: fnName
65+
name: fnName,
5166
}
5267
try {
5368
pkg = __non_webpack_require__(pkg)
5469
} catch (_) {
5570
return {
56-
name: fnName
71+
name: fnName,
5772
}
5873
}
5974
if (pkg.name && path.resolve(dir, pkg.main || 'index.js') === file) {
6075
return {
6176
name: pkg.name,
6277
homepage: pkg.homepage,
63-
contributes
78+
contributes,
6479
}
6580
}
6681
}
6782
return {
68-
name: fnName
83+
name: fnName,
6984
}
7085
})
7186
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { runPlugin } from './runPlugin'
2+
import { getBuiltInPlugins } from './getPlugins'
3+
import { isObject } from './isObject'
4+
5+
const proxyHandler = (base = []) => ({
6+
get(target, key) {
7+
if (isObject(target[key])) {
8+
return new Proxy(target[key], proxyHandler([...base, key]))
9+
} else {
10+
if (
11+
[...base, key].every((x) => typeof x === 'string') &&
12+
target.hasOwnProperty(key)
13+
) {
14+
return '$dep$' + [...base, key].join('.')
15+
}
16+
return target[key]
17+
}
18+
},
19+
})
20+
21+
export async function getUtilityConfigMap({ cwd, resolvedConfig, postcss }) {
22+
const builtInPlugins = await getBuiltInPlugins(cwd)
23+
const userPlugins = Array.isArray(resolvedConfig.plugins)
24+
? resolvedConfig.plugins
25+
: []
26+
27+
try {
28+
const classNameConfigMap = {}
29+
const proxiedConfig = new Proxy(resolvedConfig, proxyHandler())
30+
31+
;[...builtInPlugins, ...userPlugins].forEach((plugin) => {
32+
runPlugin(plugin, {
33+
postcss,
34+
config: proxiedConfig,
35+
addUtilities: (utilities) => {
36+
Object.keys(utilities).forEach((util) => {
37+
let props = Object.keys(utilities[util])
38+
if (
39+
props.length === 1 &&
40+
/^\.[^\s]+$/.test(util) &&
41+
typeof utilities[util][props[0]] === 'string' &&
42+
utilities[util][props[0]].substr(0, 5) === '$dep$'
43+
) {
44+
classNameConfigMap[util.substr(1)] = utilities[util][
45+
props[0]
46+
].substr(5)
47+
}
48+
})
49+
},
50+
})
51+
})
52+
53+
return classNameConfigMap
54+
} catch (_) {
55+
return {}
56+
}
57+
}

packages/tailwindcss-class-names/src/getVariants.js

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import semver from 'semver'
2-
import dlv from 'dlv'
2+
import { runPlugin } from './runPlugin'
33

44
export default function getVariants({ config, version, postcss }) {
55
let variants = ['responsive', 'hover']
@@ -9,31 +9,18 @@ export default function getVariants({ config, version, postcss }) {
99
semver.gte(version, '1.0.0-beta.1') && variants.push('default')
1010
semver.gte(version, '1.1.0') &&
1111
variants.push('first', 'last', 'odd', 'even', 'disabled', 'visited')
12+
semver.gte(version, '1.3.0') && variants.push('group-focus')
13+
14+
let plugins = Array.isArray(config.plugins) ? config.plugins : []
1215

13-
let plugins = config.plugins
14-
if (!Array.isArray(plugins)) {
15-
plugins = []
16-
}
1716
plugins.forEach((plugin) => {
18-
try {
19-
;(plugin.handler || plugin)({
20-
addUtilities: () => {},
21-
addComponents: () => {},
22-
addBase: () => {},
23-
addVariant: (name) => {
24-
variants.push(name)
25-
},
26-
e: (x) => x,
27-
prefix: (x) => x,
28-
theme: (path, defaultValue) =>
29-
dlv(config, `theme.${path}`, defaultValue),
30-
variants: () => [],
31-
config: (path, defaultValue) => dlv(config, path, defaultValue),
32-
postcss,
33-
})
34-
} catch (_) {
35-
console.error(_)
36-
}
17+
runPlugin(plugin, {
18+
postcss,
19+
config,
20+
addVariant: (name) => {
21+
variants.push(name)
22+
},
23+
})
3724
})
3825

3926
return variants
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import nodeGlob from 'glob'
2+
import dlv from 'dlv'
3+
import * as path from 'path'
4+
5+
export function glob(pattern, options = {}) {
6+
return new Promise((resolve, reject) => {
7+
let g = new nodeGlob.Glob(pattern, options)
8+
let matches = []
9+
let max = dlv(options, 'max', Infinity)
10+
g.on('match', (match) => {
11+
matches.push(path.resolve(options.cwd || process.cwd(), match))
12+
if (matches.length === max) {
13+
g.abort()
14+
resolve(matches)
15+
}
16+
})
17+
g.on('end', () => {
18+
resolve(matches)
19+
})
20+
g.on('error', reject)
21+
})
22+
}

packages/tailwindcss-class-names/src/index.js

Lines changed: 66 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,81 +3,75 @@ import Hook from './hook.mjs'
33
import dlv from 'dlv'
44
import dset from 'dset'
55
import importFrom from 'import-from'
6-
import nodeGlob from 'glob'
7-
import * as path from 'path'
86
import chokidar from 'chokidar'
97
import semver from 'semver'
108
import invariant from 'tiny-invariant'
119
import getPlugins from './getPlugins'
1210
import getVariants from './getVariants'
1311
import resolveConfig from './resolveConfig'
12+
import * as util from 'util'
13+
import { glob } from './glob'
14+
import { getUtilityConfigMap } from './getUtilityConfigMap'
1415

15-
function glob(pattern, options = {}) {
16-
return new Promise((resolve, reject) => {
17-
let g = new nodeGlob.Glob(pattern, options)
18-
let matches = []
19-
let max = dlv(options, 'max', Infinity)
20-
g.on('match', match => {
21-
matches.push(path.resolve(options.cwd || process.cwd(), match))
22-
if (matches.length === max) {
23-
g.abort()
24-
resolve(matches)
25-
}
26-
})
27-
g.on('end', () => {
28-
resolve(matches)
29-
})
30-
g.on('error', reject)
31-
})
16+
function TailwindConfigError(error) {
17+
Error.call(this)
18+
Error.captureStackTrace(this, this.constructor)
19+
20+
this.name = this.constructor.name
21+
this.message = error.message
22+
this.stack = error.stack
3223
}
3324

25+
util.inherits(TailwindConfigError, Error)
26+
3427
function arraysEqual(arr1, arr2) {
3528
return (
3629
JSON.stringify(arr1.concat([]).sort()) ===
3730
JSON.stringify(arr2.concat([]).sort())
3831
)
3932
}
4033

34+
const CONFIG_GLOB =
35+
'**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js'
36+
4137
export default async function getClassNames(
4238
cwd = process.cwd(),
4339
{ onChange = () => {} } = {}
4440
) {
45-
let configPath
46-
let postcss
47-
let tailwindcss
48-
let version
41+
async function run() {
42+
let configPath
43+
let postcss
44+
let tailwindcss
45+
let version
4946

50-
try {
51-
configPath = await glob(
52-
'**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js',
53-
{
54-
cwd,
55-
ignore: '**/node_modules/**',
56-
max: 1
57-
}
58-
)
47+
configPath = await glob(CONFIG_GLOB, {
48+
cwd,
49+
ignore: '**/node_modules/**',
50+
max: 1,
51+
})
5952
invariant(configPath.length === 1, 'No Tailwind CSS config found.')
6053
configPath = configPath[0]
6154
postcss = importFrom(cwd, 'postcss')
6255
tailwindcss = importFrom(cwd, 'tailwindcss')
6356
version = importFrom(cwd, 'tailwindcss/package.json').version
64-
} catch (_) {
65-
return null
66-
}
6757

68-
async function run() {
6958
const sepLocation = semver.gte(version, '0.99.0')
7059
? ['separator']
7160
: ['options', 'separator']
7261
let userSeperator
73-
let hook = Hook(configPath, exports => {
62+
let hook = Hook(configPath, (exports) => {
7463
userSeperator = dlv(exports, sepLocation)
7564
dset(exports, sepLocation, '__TAILWIND_SEPARATOR__')
7665
return exports
7766
})
7867

7968
hook.watch()
80-
const config = __non_webpack_require__(configPath)
69+
let config
70+
try {
71+
config = __non_webpack_require__(configPath)
72+
} catch (error) {
73+
throw new TailwindConfigError(error)
74+
}
8175
hook.unwatch()
8276

8377
const ast = await postcss([tailwindcss(configPath)]).process(
@@ -96,37 +90,61 @@ export default async function getClassNames(
9690
delete config[sepLocation]
9791
}
9892

93+
const resolvedConfig = resolveConfig({ cwd, config })
94+
9995
return {
100-
config: resolveConfig({ cwd, config }),
96+
configPath,
97+
config: resolvedConfig,
10198
separator: typeof userSeperator === 'undefined' ? ':' : userSeperator,
10299
classNames: await extractClassNames(ast),
103-
dependencies: [configPath, ...hook.deps],
100+
dependencies: hook.deps,
104101
plugins: getPlugins(config),
105-
variants: getVariants({ config, version, postcss })
102+
variants: getVariants({ config, version, postcss }),
103+
utilityConfigMap: await getUtilityConfigMap({
104+
cwd,
105+
resolvedConfig,
106+
postcss,
107+
}),
106108
}
107109
}
108110

109111
let watcher
110-
function watch(files) {
112+
function watch(files = []) {
111113
if (watcher) watcher.close()
112114
watcher = chokidar
113-
.watch(files)
115+
.watch([CONFIG_GLOB, ...files])
114116
.on('change', handleChange)
115117
.on('unlink', handleChange)
116118
}
117119

118-
let result = await run()
119-
watch(result.dependencies)
120-
121120
async function handleChange() {
122-
const prevDeps = result.dependencies
123-
result = await run()
121+
const prevDeps = result ? result.dependencies : []
122+
try {
123+
result = await run()
124+
} catch (error) {
125+
if (error instanceof TailwindConfigError) {
126+
onChange({ error })
127+
} else {
128+
onChange(null)
129+
}
130+
return
131+
}
124132
if (!arraysEqual(prevDeps, result.dependencies)) {
125133
watch(result.dependencies)
126134
}
127135
onChange(result)
128136
}
129137

138+
let result
139+
try {
140+
result = await run()
141+
} catch (_) {
142+
watch()
143+
return null
144+
}
145+
146+
watch(result.dependencies)
147+
130148
return result
131149
}
132150

0 commit comments

Comments
 (0)