From 26337bc39f96a49d1759004e1e9782d1b30d7723 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Fri, 20 Dec 2019 10:53:27 -0500 Subject: [PATCH] Add new `plugin` and `plugin.withOptions` functions for creating plugins --- __tests__/processPlugins.test.js | 283 +++++++++++++++++++++++++++++++ plugin.js | 3 + src/util/createPlugin.js | 21 +++ src/util/processPlugins.js | 4 + src/util/resolveConfig.js | 3 + 5 files changed, 314 insertions(+) create mode 100644 plugin.js create mode 100644 src/util/createPlugin.js diff --git a/__tests__/processPlugins.test.js b/__tests__/processPlugins.test.js index f70c12e6f9ed..576a6463aa25 100644 --- a/__tests__/processPlugins.test.js +++ b/__tests__/processPlugins.test.js @@ -1,6 +1,8 @@ import _ from 'lodash' import _postcss from 'postcss' +import tailwind from '../src/index' import processPlugins from '../src/util/processPlugins' +import createPlugin from '../src/util/createPlugin' function css(nodes) { return _postcss.root({ nodes }).toString() @@ -1287,3 +1289,284 @@ test('plugins can provide a config but no handler', () => { } `) }) + +test('plugins can be created using the `createPlugin` function', () => { + const plugin = createPlugin( + function({ addUtilities, theme, variants }) { + const utilities = _.fromPairs( + _.toPairs(theme('testPlugin')).map(([k, v]) => [`.test-${k}`, { testProperty: v }]) + ) + + addUtilities(utilities, variants('testPlugin')) + }, + { + theme: { + testPlugin: { + sm: '1rem', + md: '2rem', + lg: '3rem', + }, + }, + variants: { + testPlugin: ['responsive', 'hover'], + }, + } + ) + + return _postcss([ + tailwind({ + corePlugins: [], + theme: { + screens: { + sm: '400px', + }, + }, + plugins: [plugin], + }), + ]) + .process( + ` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + { from: undefined } + ) + .then(result => { + const expected = ` + .test-sm { + test-property: 1rem + } + .test-md { + test-property: 2rem + } + .test-lg { + test-property: 3rem + } + .hover\\:test-sm:hover { + test-property: 1rem + } + .hover\\:test-md:hover { + test-property: 2rem + } + .hover\\:test-lg:hover { + test-property: 3rem + } + + @media (min-width: 400px) { + .sm\\:test-sm { + test-property: 1rem + } + .sm\\:test-md { + test-property: 2rem + } + .sm\\:test-lg { + test-property: 3rem + } + .sm\\:hover\\:test-sm:hover { + test-property: 1rem + } + .sm\\:hover\\:test-md:hover { + test-property: 2rem + } + .sm\\:hover\\:test-lg:hover { + test-property: 3rem + } + } + ` + + expect(result.css).toMatchCss(expected) + }) +}) + +test('plugins with extra options can be created using the `createPlugin.withOptions` function', () => { + const plugin = createPlugin.withOptions( + function({ className }) { + return function({ addUtilities, theme, variants }) { + const utilities = _.fromPairs( + _.toPairs(theme('testPlugin')).map(([k, v]) => [ + `.${className}-${k}`, + { testProperty: v }, + ]) + ) + + addUtilities(utilities, variants('testPlugin')) + } + }, + function() { + return { + theme: { + testPlugin: { + sm: '1rem', + md: '2rem', + lg: '3rem', + }, + }, + variants: { + testPlugin: ['responsive', 'hover'], + }, + } + } + ) + + return _postcss([ + tailwind({ + corePlugins: [], + theme: { + screens: { + sm: '400px', + }, + }, + plugins: [plugin({ className: 'banana' })], + }), + ]) + .process( + ` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + { from: undefined } + ) + .then(result => { + const expected = ` + .banana-sm { + test-property: 1rem + } + .banana-md { + test-property: 2rem + } + .banana-lg { + test-property: 3rem + } + .hover\\:banana-sm:hover { + test-property: 1rem + } + .hover\\:banana-md:hover { + test-property: 2rem + } + .hover\\:banana-lg:hover { + test-property: 3rem + } + + @media (min-width: 400px) { + .sm\\:banana-sm { + test-property: 1rem + } + .sm\\:banana-md { + test-property: 2rem + } + .sm\\:banana-lg { + test-property: 3rem + } + .sm\\:hover\\:banana-sm:hover { + test-property: 1rem + } + .sm\\:hover\\:banana-md:hover { + test-property: 2rem + } + .sm\\:hover\\:banana-lg:hover { + test-property: 3rem + } + } + ` + + expect(result.css).toMatchCss(expected) + }) +}) + +test('plugins created using `createPlugin.withOptions` do not need to be invoked if the user wants to use the default options', () => { + const plugin = createPlugin.withOptions( + function({ className } = { className: 'banana' }) { + return function({ addUtilities, theme, variants }) { + const utilities = _.fromPairs( + _.toPairs(theme('testPlugin')).map(([k, v]) => [ + `.${className}-${k}`, + { testProperty: v }, + ]) + ) + + addUtilities(utilities, variants('testPlugin')) + } + }, + function() { + return { + theme: { + testPlugin: { + sm: '1rem', + md: '2rem', + lg: '3rem', + }, + }, + variants: { + testPlugin: ['responsive', 'hover'], + }, + } + } + ) + + return _postcss([ + tailwind({ + corePlugins: [], + theme: { + screens: { + sm: '400px', + }, + }, + plugins: [plugin], + }), + ]) + .process( + ` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + { from: undefined } + ) + .then(result => { + const expected = ` + .banana-sm { + test-property: 1rem + } + .banana-md { + test-property: 2rem + } + .banana-lg { + test-property: 3rem + } + .hover\\:banana-sm:hover { + test-property: 1rem + } + .hover\\:banana-md:hover { + test-property: 2rem + } + .hover\\:banana-lg:hover { + test-property: 3rem + } + + @media (min-width: 400px) { + .sm\\:banana-sm { + test-property: 1rem + } + .sm\\:banana-md { + test-property: 2rem + } + .sm\\:banana-lg { + test-property: 3rem + } + .sm\\:hover\\:banana-sm:hover { + test-property: 1rem + } + .sm\\:hover\\:banana-md:hover { + test-property: 2rem + } + .sm\\:hover\\:banana-lg:hover { + test-property: 3rem + } + } + ` + + expect(result.css).toMatchCss(expected) + }) +}) diff --git a/plugin.js b/plugin.js new file mode 100644 index 000000000000..ec0f03b89c3f --- /dev/null +++ b/plugin.js @@ -0,0 +1,3 @@ +const createPlugin = require('./lib/util/createPlugin').default + +module.exports = createPlugin diff --git a/src/util/createPlugin.js b/src/util/createPlugin.js new file mode 100644 index 000000000000..e61478684097 --- /dev/null +++ b/src/util/createPlugin.js @@ -0,0 +1,21 @@ +function createPlugin(plugin, config) { + return { + handler: plugin, + config, + } +} + +createPlugin.withOptions = function(pluginFunction, configFunction) { + const optionsFunction = function(options) { + return { + handler: pluginFunction(options), + config: configFunction(options), + } + } + + optionsFunction.__isOptionsFunction = true + + return optionsFunction +} + +export default createPlugin diff --git a/src/util/processPlugins.js b/src/util/processPlugins.js index 1dcd7f62735c..c0404a2d9107 100644 --- a/src/util/processPlugins.js +++ b/src/util/processPlugins.js @@ -29,6 +29,10 @@ export default function(plugins, config) { const getConfigValue = (path, defaultValue) => _.get(config, path, defaultValue) plugins.forEach(plugin => { + if (plugin.__isOptionsFunction) { + plugin = plugin() + } + const handler = isFunction(plugin) ? plugin : _.get(plugin, 'handler', () => {}) handler({ diff --git a/src/util/resolveConfig.js b/src/util/resolveConfig.js index d3bf1f2c3e1a..7c43d72752bb 100644 --- a/src/util/resolveConfig.js +++ b/src/util/resolveConfig.js @@ -107,6 +107,9 @@ function extractPluginConfigs(configs) { } plugins.forEach(plugin => { + if (plugin.__isOptionsFunction) { + plugin = plugin() + } allConfigs = [...allConfigs, ...extractPluginConfigs([get(plugin, 'config', {})])] }) })