Repository: tailwindlabs/tailwindcss
Pull request #2680 (2020-10-26 13:22:22)

Support defining presets as functions
_Description by @adamwathan_

---

This PR adds support for defining presets as functions in case users need to accept options in their presets. We don't actually provide anything to these functions, we just notice if you've given us a function and invoke it with no arguments on your behalf. This lets your preset be used like this:

```js
module.exports = {
  // ...
  presets: [
    // Require it with no options
    require('@acmecorp/tailwind-base'),

    // Invoke it and pass options to your own options API
    require('@acmecorp/tailwind-base')({
      colorPalette: 'legacy',
    })
  ]
}
```

We need this internally for the Tailwind UI plugin which we're currently converting to a preset for Tailwind 2.0.

codecov-io: # [Codecov](https://codecov.io/gh/tailwindlabs/tailwindcss/pull/2680?src=pr&el=h1) Report
> Merging [#2680](https://codecov.io/gh/tailwindlabs/tailwindcss/pull/2680?src=pr&el=desc) into [master](https://codecov.io/gh/tailwindlabs/tailwindcss/commit/3c9c46c9a8ca2260096ddae11e02c859362aeca6?el=desc) will **increase** coverage by `0.01%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/tailwindlabs/tailwindcss/pull/2680/graphs/tree.svg?width=650&height=150&src=pr&token=bY4d7WZ54r)](https://codecov.io/gh/tailwindlabs/tailwindcss/pull/2680?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #2680      +/-   ##
==========================================
+ Coverage   92.99%   93.00%   +0.01%     
==========================================
  Files         172      172              
  Lines        1727     1730       +3     
  Branches      304      306       +2     
==========================================
+ Hits         1606     1609       +3     
  Misses        104      104              
  Partials       17       17              
```


| [Impacted Files](https://codecov.io/gh/tailwindlabs/tailwindcss/pull/2680?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/util/getAllConfigs.js](https://codecov.io/gh/tailwindlabs/tailwindcss/pull/2680/diff?src=pr&el=tree#diff-c3JjL3V0aWwvZ2V0QWxsQ29uZmlncy5qcw==) | `75.00% <100.00%> (ø)` | |
| [src/util/resolveConfig.js](https://codecov.io/gh/tailwindlabs/tailwindcss/pull/2680/diff?src=pr&el=tree#diff-c3JjL3V0aWwvcmVzb2x2ZUNvbmZpZy5qcw==) | `98.16% <100.00%> (+0.05%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tailwindlabs/tailwindcss/pull/2680?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/tailwindlabs/tailwindcss/pull/2680?src=pr&el=footer). Last update [3c9c46c...4a8439c](https://codecov.io/gh/tailwindlabs/tailwindcss/pull/2680?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/util/getAllConfigs.js b/src/util/getAllConfigs.js
index 5602c7e6..3e65d5db 100644
--- a/src/util/getAllConfigs.js
+++ b/src/util/getAllConfigs.js
@@ -1,10 +1,10 @@
 import defaultConfig from '../../stubs/defaultConfig.stub.js'
 import { flagEnabled } from '../featureFlags'
-import { flatMap, get } from 'lodash'
+import { flatMap, get, isFunction } from 'lodash'
 
 export default function getAllConfigs(config) {
   const configs = flatMap([...get(config, 'presets', [defaultConfig])].reverse(), (preset) => {
-    return getAllConfigs(preset)
+    return getAllConfigs(isFunction(preset) ? preset() : preset)
   })
 
   const features = {


Test patch:
diff --git a/__tests__/customConfig.test.js b/__tests__/customConfig.test.js
index 308cb873..0cc662c2 100644
--- a/__tests__/customConfig.test.js
+++ b/__tests__/customConfig.test.js
@@ -261,6 +261,56 @@ test('the default config can be overridden using the presets key', () => {
     })
 })
 
+test('presets can be functions', () => {
+  return postcss([
+    tailwind({
+      presets: [
+        () => ({
+          theme: {
+            extend: {
+              minHeight: {
+                24: '24px',
+              },
+            },
+          },
+          corePlugins: ['minHeight'],
+          variants: { minHeight: [] },
+        }),
+      ],
+      theme: {
+        extend: { minHeight: { 48: '48px' } },
+      },
+    }),
+  ])
+    .process(
+      `
+        @tailwind utilities
+      `,
+      { from: undefined }
+    )
+    .then((result) => {
+      const expected = `
+        .min-h-0 {
+          min-height: 0;
+        }
+        .min-h-24 {
+          min-height: 24px;
+        }
+        .min-h-48 {
+          min-height: 48px;
+        }
+        .min-h-full {
+          min-height: 100%;
+        }
+        .min-h-screen {
+          min-height: 100vh;
+        }
+      `
+
+      expect(result.css).toMatchCss(expected)
+    })
+})
+
 test('the default config can be removed by using an empty presets key in a preset', () => {
   return postcss([
     tailwind({
@@ -367,3 +417,68 @@ test('presets can have their own presets', () => {
       expect(result.css).toMatchCss(expected)
     })
 })
+
+test('function presets can be mixed with object presets', () => {
+  return postcss([
+    tailwind({
+      presets: [
+        () => ({
+          presets: [],
+          theme: {
+            colors: { red: '#dd0000' },
+          },
+        }),
+        {
+          presets: [
+            () => ({
+              presets: [],
+              theme: {
+                colors: {
+                  transparent: 'transparent',
+                  red: '#ff0000',
+                },
+              },
+            }),
+          ],
+          theme: {
+            extend: {
+              colors: {
+                black: 'black',
+                red: '#ee0000',
+              },
+              backgroundColor: (theme) => theme('colors'),
+            },
+          },
+          corePlugins: ['backgroundColor'],
+        },
+      ],
+      theme: {
+        extend: { colors: { white: 'white' } },
+      },
+    }),
+  ])
+    .process(
+      `
+        @tailwind utilities
+      `,
+      { from: undefined }
+    )
+    .then((result) => {
+      const expected = `
+        .bg-transparent {
+          background-color: transparent;
+        }
+        .bg-red {
+          background-color: #ee0000;
+        }
+        .bg-black {
+          background-color: black;
+        }
+        .bg-white {
+          background-color: white;
+        }
+      `
+
+      expect(result.css).toMatchCss(expected)
+    })
+})