Skip to content

Commit 293900a

Browse files
authored
Merge pull request #1268 from tailwindcss/plugin-function
Add new `plugin` and `plugin.withOptions` functions for creating plugins
2 parents 569e74b + 26337bc commit 293900a

File tree

5 files changed

+314
-0
lines changed

5 files changed

+314
-0
lines changed

__tests__/processPlugins.test.js

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import _ from 'lodash'
22
import _postcss from 'postcss'
3+
import tailwind from '../src/index'
34
import processPlugins from '../src/util/processPlugins'
5+
import createPlugin from '../src/util/createPlugin'
46

57
function css(nodes) {
68
return _postcss.root({ nodes }).toString()
@@ -1287,3 +1289,284 @@ test('plugins can provide a config but no handler', () => {
12871289
}
12881290
`)
12891291
})
1292+
1293+
test('plugins can be created using the `createPlugin` function', () => {
1294+
const plugin = createPlugin(
1295+
function({ addUtilities, theme, variants }) {
1296+
const utilities = _.fromPairs(
1297+
_.toPairs(theme('testPlugin')).map(([k, v]) => [`.test-${k}`, { testProperty: v }])
1298+
)
1299+
1300+
addUtilities(utilities, variants('testPlugin'))
1301+
},
1302+
{
1303+
theme: {
1304+
testPlugin: {
1305+
sm: '1rem',
1306+
md: '2rem',
1307+
lg: '3rem',
1308+
},
1309+
},
1310+
variants: {
1311+
testPlugin: ['responsive', 'hover'],
1312+
},
1313+
}
1314+
)
1315+
1316+
return _postcss([
1317+
tailwind({
1318+
corePlugins: [],
1319+
theme: {
1320+
screens: {
1321+
sm: '400px',
1322+
},
1323+
},
1324+
plugins: [plugin],
1325+
}),
1326+
])
1327+
.process(
1328+
`
1329+
@tailwind base;
1330+
@tailwind components;
1331+
@tailwind utilities;
1332+
`,
1333+
{ from: undefined }
1334+
)
1335+
.then(result => {
1336+
const expected = `
1337+
.test-sm {
1338+
test-property: 1rem
1339+
}
1340+
.test-md {
1341+
test-property: 2rem
1342+
}
1343+
.test-lg {
1344+
test-property: 3rem
1345+
}
1346+
.hover\\:test-sm:hover {
1347+
test-property: 1rem
1348+
}
1349+
.hover\\:test-md:hover {
1350+
test-property: 2rem
1351+
}
1352+
.hover\\:test-lg:hover {
1353+
test-property: 3rem
1354+
}
1355+
1356+
@media (min-width: 400px) {
1357+
.sm\\:test-sm {
1358+
test-property: 1rem
1359+
}
1360+
.sm\\:test-md {
1361+
test-property: 2rem
1362+
}
1363+
.sm\\:test-lg {
1364+
test-property: 3rem
1365+
}
1366+
.sm\\:hover\\:test-sm:hover {
1367+
test-property: 1rem
1368+
}
1369+
.sm\\:hover\\:test-md:hover {
1370+
test-property: 2rem
1371+
}
1372+
.sm\\:hover\\:test-lg:hover {
1373+
test-property: 3rem
1374+
}
1375+
}
1376+
`
1377+
1378+
expect(result.css).toMatchCss(expected)
1379+
})
1380+
})
1381+
1382+
test('plugins with extra options can be created using the `createPlugin.withOptions` function', () => {
1383+
const plugin = createPlugin.withOptions(
1384+
function({ className }) {
1385+
return function({ addUtilities, theme, variants }) {
1386+
const utilities = _.fromPairs(
1387+
_.toPairs(theme('testPlugin')).map(([k, v]) => [
1388+
`.${className}-${k}`,
1389+
{ testProperty: v },
1390+
])
1391+
)
1392+
1393+
addUtilities(utilities, variants('testPlugin'))
1394+
}
1395+
},
1396+
function() {
1397+
return {
1398+
theme: {
1399+
testPlugin: {
1400+
sm: '1rem',
1401+
md: '2rem',
1402+
lg: '3rem',
1403+
},
1404+
},
1405+
variants: {
1406+
testPlugin: ['responsive', 'hover'],
1407+
},
1408+
}
1409+
}
1410+
)
1411+
1412+
return _postcss([
1413+
tailwind({
1414+
corePlugins: [],
1415+
theme: {
1416+
screens: {
1417+
sm: '400px',
1418+
},
1419+
},
1420+
plugins: [plugin({ className: 'banana' })],
1421+
}),
1422+
])
1423+
.process(
1424+
`
1425+
@tailwind base;
1426+
@tailwind components;
1427+
@tailwind utilities;
1428+
`,
1429+
{ from: undefined }
1430+
)
1431+
.then(result => {
1432+
const expected = `
1433+
.banana-sm {
1434+
test-property: 1rem
1435+
}
1436+
.banana-md {
1437+
test-property: 2rem
1438+
}
1439+
.banana-lg {
1440+
test-property: 3rem
1441+
}
1442+
.hover\\:banana-sm:hover {
1443+
test-property: 1rem
1444+
}
1445+
.hover\\:banana-md:hover {
1446+
test-property: 2rem
1447+
}
1448+
.hover\\:banana-lg:hover {
1449+
test-property: 3rem
1450+
}
1451+
1452+
@media (min-width: 400px) {
1453+
.sm\\:banana-sm {
1454+
test-property: 1rem
1455+
}
1456+
.sm\\:banana-md {
1457+
test-property: 2rem
1458+
}
1459+
.sm\\:banana-lg {
1460+
test-property: 3rem
1461+
}
1462+
.sm\\:hover\\:banana-sm:hover {
1463+
test-property: 1rem
1464+
}
1465+
.sm\\:hover\\:banana-md:hover {
1466+
test-property: 2rem
1467+
}
1468+
.sm\\:hover\\:banana-lg:hover {
1469+
test-property: 3rem
1470+
}
1471+
}
1472+
`
1473+
1474+
expect(result.css).toMatchCss(expected)
1475+
})
1476+
})
1477+
1478+
test('plugins created using `createPlugin.withOptions` do not need to be invoked if the user wants to use the default options', () => {
1479+
const plugin = createPlugin.withOptions(
1480+
function({ className } = { className: 'banana' }) {
1481+
return function({ addUtilities, theme, variants }) {
1482+
const utilities = _.fromPairs(
1483+
_.toPairs(theme('testPlugin')).map(([k, v]) => [
1484+
`.${className}-${k}`,
1485+
{ testProperty: v },
1486+
])
1487+
)
1488+
1489+
addUtilities(utilities, variants('testPlugin'))
1490+
}
1491+
},
1492+
function() {
1493+
return {
1494+
theme: {
1495+
testPlugin: {
1496+
sm: '1rem',
1497+
md: '2rem',
1498+
lg: '3rem',
1499+
},
1500+
},
1501+
variants: {
1502+
testPlugin: ['responsive', 'hover'],
1503+
},
1504+
}
1505+
}
1506+
)
1507+
1508+
return _postcss([
1509+
tailwind({
1510+
corePlugins: [],
1511+
theme: {
1512+
screens: {
1513+
sm: '400px',
1514+
},
1515+
},
1516+
plugins: [plugin],
1517+
}),
1518+
])
1519+
.process(
1520+
`
1521+
@tailwind base;
1522+
@tailwind components;
1523+
@tailwind utilities;
1524+
`,
1525+
{ from: undefined }
1526+
)
1527+
.then(result => {
1528+
const expected = `
1529+
.banana-sm {
1530+
test-property: 1rem
1531+
}
1532+
.banana-md {
1533+
test-property: 2rem
1534+
}
1535+
.banana-lg {
1536+
test-property: 3rem
1537+
}
1538+
.hover\\:banana-sm:hover {
1539+
test-property: 1rem
1540+
}
1541+
.hover\\:banana-md:hover {
1542+
test-property: 2rem
1543+
}
1544+
.hover\\:banana-lg:hover {
1545+
test-property: 3rem
1546+
}
1547+
1548+
@media (min-width: 400px) {
1549+
.sm\\:banana-sm {
1550+
test-property: 1rem
1551+
}
1552+
.sm\\:banana-md {
1553+
test-property: 2rem
1554+
}
1555+
.sm\\:banana-lg {
1556+
test-property: 3rem
1557+
}
1558+
.sm\\:hover\\:banana-sm:hover {
1559+
test-property: 1rem
1560+
}
1561+
.sm\\:hover\\:banana-md:hover {
1562+
test-property: 2rem
1563+
}
1564+
.sm\\:hover\\:banana-lg:hover {
1565+
test-property: 3rem
1566+
}
1567+
}
1568+
`
1569+
1570+
expect(result.css).toMatchCss(expected)
1571+
})
1572+
})

plugin.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const createPlugin = require('./lib/util/createPlugin').default
2+
3+
module.exports = createPlugin

src/util/createPlugin.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
function createPlugin(plugin, config) {
2+
return {
3+
handler: plugin,
4+
config,
5+
}
6+
}
7+
8+
createPlugin.withOptions = function(pluginFunction, configFunction) {
9+
const optionsFunction = function(options) {
10+
return {
11+
handler: pluginFunction(options),
12+
config: configFunction(options),
13+
}
14+
}
15+
16+
optionsFunction.__isOptionsFunction = true
17+
18+
return optionsFunction
19+
}
20+
21+
export default createPlugin

src/util/processPlugins.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ export default function(plugins, config) {
2929
const getConfigValue = (path, defaultValue) => _.get(config, path, defaultValue)
3030

3131
plugins.forEach(plugin => {
32+
if (plugin.__isOptionsFunction) {
33+
plugin = plugin()
34+
}
35+
3236
const handler = isFunction(plugin) ? plugin : _.get(plugin, 'handler', () => {})
3337

3438
handler({

src/util/resolveConfig.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ function extractPluginConfigs(configs) {
107107
}
108108

109109
plugins.forEach(plugin => {
110+
if (plugin.__isOptionsFunction) {
111+
plugin = plugin()
112+
}
110113
allConfigs = [...allConfigs, ...extractPluginConfigs([get(plugin, 'config', {})])]
111114
})
112115
})

0 commit comments

Comments
 (0)