add utility config map

master
Brad Cornes 2020-04-25 22:41:17 +01:00
parent d89883b658
commit 2de4816e3c
7 changed files with 145 additions and 53 deletions

View File

@ -1,9 +1,24 @@
import * as path from 'path' import * as path from 'path'
import stackTrace from 'stack-trace' import stackTrace from 'stack-trace'
import pkgUp from 'pkg-up' import pkgUp from 'pkg-up'
import { glob } from './glob'
import { isObject } from './isObject'
function isObject(variable) { export async function getBuiltInPlugins(cwd) {
return Object.prototype.toString.call(variable) === '[object Object]' try {
return (
await glob(path.resolve(cwd, 'node_modules/tailwindcss/lib/plugins/*.js'))
)
.map((x) => {
try {
const mod = __non_webpack_require__(x)
return mod.default ? mod.default() : mod()
} catch (_) {}
})
.filter(Boolean)
} catch (_) {
return []
}
} }
export default function getPlugins(config) { export default function getPlugins(config) {
@ -13,7 +28,7 @@ export default function getPlugins(config) {
return [] return []
} }
return plugins.map(plugin => { return plugins.map((plugin) => {
let pluginConfig = plugin.config let pluginConfig = plugin.config
if (!isObject(pluginConfig)) { if (!isObject(pluginConfig)) {
pluginConfig = {} pluginConfig = {}
@ -25,7 +40,7 @@ export default function getPlugins(config) {
: [], : [],
variants: isObject(pluginConfig.variants) variants: isObject(pluginConfig.variants)
? Object.keys(pluginConfig.variants) ? Object.keys(pluginConfig.variants)
: [] : [],
} }
const fn = plugin.handler || plugin const fn = plugin.handler || plugin
@ -40,32 +55,32 @@ export default function getPlugins(config) {
const trace = stackTrace.parse(e) const trace = stackTrace.parse(e)
if (trace.length === 0) if (trace.length === 0)
return { return {
name: fnName name: fnName,
} }
const file = trace[0].fileName const file = trace[0].fileName
const dir = path.dirname(file) const dir = path.dirname(file)
let pkg = pkgUp.sync({ cwd: dir }) let pkg = pkgUp.sync({ cwd: dir })
if (!pkg) if (!pkg)
return { return {
name: fnName name: fnName,
} }
try { try {
pkg = __non_webpack_require__(pkg) pkg = __non_webpack_require__(pkg)
} catch (_) { } catch (_) {
return { return {
name: fnName name: fnName,
} }
} }
if (pkg.name && path.resolve(dir, pkg.main || 'index.js') === file) { if (pkg.name && path.resolve(dir, pkg.main || 'index.js') === file) {
return { return {
name: pkg.name, name: pkg.name,
homepage: pkg.homepage, homepage: pkg.homepage,
contributes contributes,
} }
} }
} }
return { return {
name: fnName name: fnName,
} }
}) })
} }

View File

@ -0,0 +1,57 @@
import { runPlugin } from './runPlugin'
import { getBuiltInPlugins } from './getPlugins'
import { isObject } from './isObject'
const proxyHandler = (base = []) => ({
get(target, key) {
if (isObject(target[key])) {
return new Proxy(target[key], proxyHandler([...base, key]))
} else {
if (
[...base, key].every((x) => typeof x === 'string') &&
target.hasOwnProperty(key)
) {
return '$dep$' + [...base, key].join('.')
}
return target[key]
}
},
})
export async function getUtilityConfigMap({ cwd, resolvedConfig, postcss }) {
const builtInPlugins = await getBuiltInPlugins(cwd)
const userPlugins = Array.isArray(resolvedConfig.plugins)
? resolvedConfig.plugins
: []
try {
const classNameConfigMap = {}
const proxiedConfig = new Proxy(resolvedConfig, proxyHandler())
;[...builtInPlugins, ...userPlugins].forEach((plugin) => {
runPlugin(plugin, {
postcss,
config: proxiedConfig,
addUtilities: (utilities) => {
Object.keys(utilities).forEach((util) => {
let props = Object.keys(utilities[util])
if (
props.length === 1 &&
/^\.[^\s]+$/.test(util) &&
typeof utilities[util][props[0]] === 'string' &&
utilities[util][props[0]].substr(0, 5) === '$dep$'
) {
classNameConfigMap[util.substr(1)] = utilities[util][
props[0]
].substr(5)
}
})
},
})
})
return classNameConfigMap
} catch (_) {
return {}
}
}

View File

@ -1,5 +1,5 @@
import semver from 'semver' import semver from 'semver'
import dlv from 'dlv' import { runPlugin } from './runPlugin'
export default function getVariants({ config, version, postcss }) { export default function getVariants({ config, version, postcss }) {
let variants = ['responsive', 'hover'] let variants = ['responsive', 'hover']
@ -11,28 +11,16 @@ export default function getVariants({ config, version, postcss }) {
variants.push('first', 'last', 'odd', 'even', 'disabled', 'visited') variants.push('first', 'last', 'odd', 'even', 'disabled', 'visited')
semver.gte(version, '1.3.0') && variants.push('group-focus') semver.gte(version, '1.3.0') && variants.push('group-focus')
let plugins = config.plugins let plugins = Array.isArray(config.plugins) ? config.plugins : []
if (!Array.isArray(plugins)) {
plugins = []
}
plugins.forEach((plugin) => { plugins.forEach((plugin) => {
try { runPlugin(plugin, {
;(plugin.handler || plugin)({ postcss,
addUtilities: () => {}, config,
addComponents: () => {}, addVariant: (name) => {
addBase: () => {}, variants.push(name)
addVariant: (name) => { },
variants.push(name) })
},
e: (x) => x,
prefix: (x) => x,
theme: (path, defaultValue) =>
dlv(config, `theme.${path}`, defaultValue),
variants: () => [],
config: (path, defaultValue) => dlv(config, path, defaultValue),
postcss,
})
} catch (_) {}
}) })
return variants return variants

View File

@ -0,0 +1,22 @@
import nodeGlob from 'glob'
import dlv from 'dlv'
import * as path from 'path'
export function glob(pattern, options = {}) {
return new Promise((resolve, reject) => {
let g = new nodeGlob.Glob(pattern, options)
let matches = []
let max = dlv(options, 'max', Infinity)
g.on('match', (match) => {
matches.push(path.resolve(options.cwd || process.cwd(), match))
if (matches.length === max) {
g.abort()
resolve(matches)
}
})
g.on('end', () => {
resolve(matches)
})
g.on('error', reject)
})
}

View File

@ -3,8 +3,6 @@ import Hook from './hook.mjs'
import dlv from 'dlv' import dlv from 'dlv'
import dset from 'dset' import dset from 'dset'
import importFrom from 'import-from' import importFrom from 'import-from'
import nodeGlob from 'glob'
import * as path from 'path'
import chokidar from 'chokidar' import chokidar from 'chokidar'
import semver from 'semver' import semver from 'semver'
import invariant from 'tiny-invariant' import invariant from 'tiny-invariant'
@ -12,6 +10,8 @@ import getPlugins from './getPlugins'
import getVariants from './getVariants' import getVariants from './getVariants'
import resolveConfig from './resolveConfig' import resolveConfig from './resolveConfig'
import * as util from 'util' import * as util from 'util'
import { glob } from './glob'
import { getUtilityConfigMap } from './getUtilityConfigMap'
function TailwindConfigError(error) { function TailwindConfigError(error) {
Error.call(this) Error.call(this)
@ -24,25 +24,6 @@ function TailwindConfigError(error) {
util.inherits(TailwindConfigError, Error) util.inherits(TailwindConfigError, Error)
function glob(pattern, options = {}) {
return new Promise((resolve, reject) => {
let g = new nodeGlob.Glob(pattern, options)
let matches = []
let max = dlv(options, 'max', Infinity)
g.on('match', (match) => {
matches.push(path.resolve(options.cwd || process.cwd(), match))
if (matches.length === max) {
g.abort()
resolve(matches)
}
})
g.on('end', () => {
resolve(matches)
})
g.on('error', reject)
})
}
function arraysEqual(arr1, arr2) { function arraysEqual(arr1, arr2) {
return ( return (
JSON.stringify(arr1.concat([]).sort()) === JSON.stringify(arr1.concat([]).sort()) ===
@ -109,14 +90,21 @@ export default async function getClassNames(
delete config[sepLocation] delete config[sepLocation]
} }
const resolvedConfig = resolveConfig({ cwd, config })
return { return {
configPath, configPath,
config: resolveConfig({ cwd, config }), config: resolvedConfig,
separator: typeof userSeperator === 'undefined' ? ':' : userSeperator, separator: typeof userSeperator === 'undefined' ? ':' : userSeperator,
classNames: await extractClassNames(ast), classNames: await extractClassNames(ast),
dependencies: hook.deps, dependencies: hook.deps,
plugins: getPlugins(config), plugins: getPlugins(config),
variants: getVariants({ config, version, postcss }), variants: getVariants({ config, version, postcss }),
utilityConfigMap: await getUtilityConfigMap({
cwd,
resolvedConfig,
postcss,
}),
} }
} }

View File

@ -0,0 +1,3 @@
export function isObject(thing) {
return Object.prototype.toString.call(thing) === '[object Object]'
}

View File

@ -0,0 +1,19 @@
import dlv from 'dlv'
export function runPlugin(plugin, params = {}) {
const { config, ...rest } = params
try {
;(plugin.handler || plugin)({
addUtilities: () => {},
addComponents: () => {},
addBase: () => {},
addVariant: () => {},
e: (x) => x,
prefix: (x) => x,
theme: (path, defaultValue) => dlv(config, `theme.${path}`, defaultValue),
variants: () => [],
config: (path, defaultValue) => dlv(config, path, defaultValue),
...rest,
})
} catch (_) {}
}