Skip to content
This repository was archived by the owner on Apr 6, 2021. It is now read-only.

Commit 24fb855

Browse files
committed
Add basic support for classic plugin API
1 parent bfe57b4 commit 24fb855

File tree

7 files changed

+346
-9
lines changed

7 files changed

+346
-9
lines changed

TODO.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,26 @@
5858
- [x] Test with Laravel mix
5959
- [x] Unify variants and screen variants
6060

61+
#### Feb 28
62+
63+
- [x] Basic @apply support
64+
65+
### Mar 1
66+
67+
- [x] Fix bug with focus:ring-2, we need to add the universal selector still not just strip it out, otherwise it's never added
68+
- [x] Fix cache miss when fetching context for config
69+
- [x] Make existing official plugins work
70+
- [x] Add support for classic plugin API
71+
6172
#### Next
6273

63-
- [ ] Fix bug with focus:ring-2, we need to add the universal selector still not just strip it out, otherwise it's never added
64-
- [ ] Add support for plugin API
65-
- [ ] Make existing official plugins work
6674
- [ ] Rebuild when config dependencies change
6775
- [ ] Support container configuration options
6876
- [ ] Support complex screens configuration
6977
- [ ] Make prefixes work
7078
- [ ] Make important work
7179
- [ ] Make separator work
72-
- [ ] Make @apply work
80+
- [ ] Support @apply with custom CSS
7381
- [ ] Add support for custom CSS that supports variants (anything in @layer?)
7482
- [ ] Support square brackets for arbitrary values
7583
- [ ] Support purge safelist (just add entries to candidate list, regexes will be harder though)

package-lock.json

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
},
1414
"dependencies": {
1515
"chokidar": "^3.5.1",
16+
"dlv": "^1.1.3",
1617
"fast-glob": "^3.2.5",
18+
"lodash.topath": "^4.5.2",
1719
"object-hash": "^2.1.1",
1820
"postcss-selector-parser": "^6.0.4",
1921
"quick-lru": "^5.1.1",
@@ -24,6 +26,7 @@
2426
"postcss": "^8.2.6"
2527
},
2628
"devDependencies": {
29+
"@tailwindcss/aspect-ratio": "^0.2.0",
2730
"autoprefixer": "^10.2.4",
2831
"jest": "^26.6.3",
2932
"postcss": "^8.2.6",

src/index.js

Lines changed: 195 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@ const chokidar = require('chokidar')
77
const fastGlob = require('fast-glob')
88
const hash = require('object-hash')
99
const LRU = require('quick-lru')
10+
const dlv = require('dlv')
11+
const Node = require('postcss/lib/node')
12+
const selectorParser = require('postcss-selector-parser')
1013

1114
const resolveConfig = require('tailwindcss/resolveConfig')
1215
const escape = require('tailwindcss/lib/util/escapeClassName').default
1316
const evaluateTailwindFunctions = require('tailwindcss/lib/lib/evaluateTailwindFunctions').default
17+
const parseObjectStyles = require('tailwindcss/lib/util/parseObjectStyles').default
18+
const transformThemeValue = require('tailwindcss/lib/util/transformThemeValue').default
19+
const prefixSelector = require('tailwindcss/lib/util/prefixSelector').default
1420

1521
const corePlugins = require('./corePlugins')
1622

@@ -342,13 +348,175 @@ function rebootTemplateWatcher(context) {
342348
}
343349
}
344350

351+
function parseLegacyStyles(styles) {
352+
if (!Array.isArray(styles)) {
353+
return parseLegacyStyles([styles])
354+
}
355+
356+
return styles.flatMap((style) => (style instanceof Node ? style : parseObjectStyles(style)))
357+
}
358+
359+
function getClasses(selector) {
360+
let parser = selectorParser((selectors) => {
361+
let allClasses = []
362+
selectors.walkClasses((classNode) => {
363+
allClasses.push(classNode.value)
364+
})
365+
return allClasses
366+
})
367+
return parser.transformSync(selector)
368+
}
369+
370+
function toPath(value) {
371+
if (Array.isArray(value)) {
372+
return value
373+
}
374+
375+
let inBrackets = false
376+
let parts = []
377+
let chunk = ''
378+
379+
for (let i = 0; i < value.length; i++) {
380+
let char = value[i]
381+
if (char === '[') {
382+
inBrackets = true
383+
parts.push(chunk)
384+
chunk = ''
385+
continue
386+
}
387+
if (char === ']' && inBrackets) {
388+
inBrackets = false
389+
parts.push(chunk)
390+
chunk = ''
391+
continue
392+
}
393+
if (char === '.' && !inBrackets && chunk.length > 0) {
394+
parts.push(chunk)
395+
chunk = ''
396+
continue
397+
}
398+
chunk = chunk + char
399+
}
400+
401+
if (chunk.length > 0) {
402+
parts.push(chunk)
403+
}
404+
405+
return parts
406+
}
407+
408+
// { .foo { color: black } }
409+
// => [ ['foo', ['.foo', { color: 'black' }] ]
410+
function toStaticRuleArray(legacyStyles) {
411+
return parseLegacyStyles(legacyStyles).flatMap((node) => {
412+
let nodeMap = new Map()
413+
let classes = getClasses(node.selector)
414+
return classes.map((c) => {
415+
if (!nodeMap.has(node)) {
416+
let decls = Object.fromEntries(
417+
node.nodes.map(({ prop, value }) => {
418+
return [prop, value]
419+
})
420+
)
421+
nodeMap.set(node, [node.selector, decls])
422+
}
423+
return [c, nodeMap.get(node)]
424+
})
425+
})
426+
}
427+
345428
function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offsets }) {
429+
function getConfigValue(path, defaultValue) {
430+
return path ? dlv(tailwindConfig, path, defaultValue) : tailwindConfig
431+
}
432+
433+
function applyConfiguredPrefix(selector) {
434+
return prefixSelector(config.prefix, selector)
435+
}
436+
346437
return {
347-
addBase(nodes) {
438+
// Classic plugin API
439+
addVariant() {
440+
throw new Error(`Variant plugins aren't supported yet`)
441+
},
442+
postcss,
443+
prefix: applyConfiguredPrefix,
444+
e: escape,
445+
config: getConfigValue,
446+
theme(path, defaultValue) {
447+
const [pathRoot, ...subPaths] = toPath(path)
448+
const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue)
449+
return transformThemeValue(pathRoot)(value)
450+
},
451+
corePlugins: (path) => {
452+
if (Array.isArray(tailwindConfig.corePlugins)) {
453+
return tailwindConfig.corePlugins.includes(path)
454+
}
455+
456+
return getConfigValue(['corePlugins', path], true)
457+
},
458+
variants: (path, defaultValue) => {
459+
if (Array.isArray(tailwindConfig.variants)) {
460+
return tailwindConfig.variants
461+
}
462+
463+
return getConfigValue(['variants', path], defaultValue)
464+
},
465+
addBase(styles) {
466+
let nodes = parseLegacyStyles(styles)
348467
for (let node of nodes) {
349468
context.baseRules.add(node)
350469
}
351470
},
471+
addComponents(components, options) {
472+
let defaultOptions = {
473+
variants: [],
474+
respectPrefix: true,
475+
respectImportant: false,
476+
respectVariants: true,
477+
}
478+
479+
options = Object.assign(
480+
{},
481+
defaultOptions,
482+
Array.isArray(options) ? { variants: options } : options
483+
)
484+
485+
for (let [identifier, tuple] of toStaticRuleArray(components)) {
486+
let offset = offsets.components++
487+
488+
if (context.componentMap.has(identifier)) {
489+
context.componentMap.get(identifier).push([offset, tuple])
490+
} else {
491+
context.componentMap.set(identifier, [[offset, tuple]])
492+
}
493+
}
494+
},
495+
addUtilities(utilities, options) {
496+
let defaultOptions = {
497+
variants: [],
498+
respectPrefix: true,
499+
respectImportant: true,
500+
respectVariants: true,
501+
}
502+
503+
options = Object.assign(
504+
{},
505+
defaultOptions,
506+
Array.isArray(options) ? { variants: options } : options
507+
)
508+
509+
for (let [identifier, tuple] of toStaticRuleArray(utilities)) {
510+
let offset = offsets.utilities++
511+
512+
if (context.utilityMap.has(identifier)) {
513+
context.utilityMap.get(identifier).push([offset, tuple])
514+
} else {
515+
context.utilityMap.set(identifier, [[offset, tuple]])
516+
}
517+
}
518+
},
519+
// ---
352520
jit: {
353521
e: escape,
354522
config: tailwindConfig,
@@ -451,8 +619,7 @@ function registerPlugins(tailwindConfig, plugins, context) {
451619
offsets,
452620
})
453621

454-
for (let pluginName in plugins) {
455-
let plugin = corePlugins[pluginName]
622+
for (let plugin of plugins) {
456623
if (Array.isArray(plugin)) {
457624
for (let pluginItem of plugin) {
458625
pluginItem(pluginApi)
@@ -585,11 +752,34 @@ function setupContext(tailwindConfig, configHash, configPath) {
585752
screens: [],
586753
},
587754
}
588-
589755
contextMap.set(configHash, context)
590756

591757
rebootTemplateWatcher(context)
592-
registerPlugins(context.tailwindConfig, corePlugins, context)
758+
759+
let corePluginList = Object.entries(corePlugins)
760+
.map(([name, plugin]) => {
761+
// TODO: Make variants a real core plugin so we don't special case it
762+
if (name === 'variants') {
763+
return plugin
764+
}
765+
766+
if (!tailwindConfig.corePlugins.includes(name)) {
767+
return null
768+
}
769+
770+
return plugin
771+
})
772+
.filter(Boolean)
773+
774+
let userPlugins = tailwindConfig.plugins.map((plugin) => {
775+
if (plugin.__isOptionsFunction) {
776+
plugin = plugin()
777+
}
778+
779+
return typeof plugin === 'function' ? plugin : plugin.handler
780+
})
781+
782+
registerPlugins(context.tailwindConfig, [...corePluginList, ...userPlugins], context)
593783

594784
return context
595785
}

0 commit comments

Comments
 (0)