Repository: tailwindlabs/tailwindcss

Problem statement:
Triggering rebuild on config.js change
Enjoying working with the release, good work!

I'd like to propose triggering a rebuild when the config file (`tailwind-config.js`) is updated if a build tool is being used – ie. webpack. 

Basically in the same way as a new build is triggered when you update a css/sass/js file. It would mean that modifications can be made to the config file without having to halt and restart `npm watch` for instance.


Hints:
I do not believe it's possible as the config is loaded into memory on the webpack side, and webpack doesn't watch it's own config files as it can't restart itself.
How about an abstraction that watches for Laravel Mix (in my case) config changes or tailwind changes and restarts the watch command?
This is a super annoying problem and is hard to solve nicely for the reasons @joshmanders mentioned. The best I've figured is to use [chokidar-cli](https://github.com/kimmobrunfeldt/chokidar-cli) to watch the config and restart `watch` when the config file changes.

Here's an example command you can add to your `package.json` scripts:

```
"watch-tailwind": "chokidar 'tailwind.js' --initial=true -c 'npm run watch'",
```

You'll have to install chokidar-cli first to get that working:

```
npm install chokidar-cli --save-dev
```

Changes to your config will result in much slower rebuilds than changes to just your CSS since it has to restart the entire watcher, but it's the best I've come up with so far.

I've also had a few weird situations where it feels like it's building stale versions of things in weird ways and I've had to restart the command manually, but I can't reproduce it reliably and haven't noticed any patterns.

In general, I would recommend trying to avoid editing your config file; ideally you want your config file figured out and more or less "done" at some point, it should be a super low churn file. The workflow shouldn't be "go to config, add utility size I need, go to DOM, add new class"; you want everything already there, otherwise you're just writing CSS again.

One Tailwind anti-pattern that I'd like to warn against in the documentation is starting with a really small/light config file and adding values as you need them. This totally kills the workflow of just designing in the browser with a comprehensive set of utilities, all in the name of prematurely trying to keep your CSS small.

It's better to have more than you need and never have to edit that file and introduce new values into your CSS, then optimize for filesize later IMO. Even then, Tailwind out of the box with a million fucking colors and 5 breakpoints is only 27kb minified and gzipped, which is smaller than just about every image you'll ever serve 😄 
I'm going to close this as an issue just because there's really nothing we can do about it; we don't have our own watching build tool or anything, we're just the CSS framework.

Happy to keep discussing this though, and we can definitely add some stuff to the documentation if we can come up with some good recommended approaches for people using different tools.
> It's better to have more than you need and never have to edit that file and introduce new values into your CSS, then optimize for filesize later IMO. Even then, Tailwind out of the box with a million fucking colors and 5 breakpoints is only 27kb minified and gzipped, which is smaller than just about every image you'll ever serve 😄

This right here is why I was so excited for tailwind. Tachyons naming didn't really sit well with me. I really enjoy your naming of every thing having the same name, just prefixed with a size, like `tablet:bg-blue` instead of something like `bg-sm-blue` or something.
> In general, I would recommend trying to avoid editing your config file; ideally you want your config file figured out and more or less "done" at some point, it should be a super low churn file. The workflow shouldn't be "go to config, add utility size I need, go to DOM, add new class"; you want everything already there, otherwise you're just writing CSS again.

I completely agree, having the ability without reducing the rebuild time would be pretty handy though. 

Happy to continue the discussion. Closing the issue makes sense.

For Webpack (with postcss-loader), the config file can be added as a dependency to the messages array. postcss-loader will then add this to Webpack via `addDependency` ([for reference](https://webpack.github.io/docs/loaders.html#adddependency)).

I'm doing something similar to the below in another project:
 
```javascript
module.exports = postcss.plugin('loadconfig', function (configFile) {
  return function (css, opts) {
      if (fs.existsSync(configFile)) {
        opts.messages = [{
          type: 'dependency',
          file: configFile,
          parent: css.source.input.file,
        }];
      }
    });
  }
});
```

This will trigger a rebuild if the config file is updated/changed in the exact same manner as a sass/css/js file. I've not noticed any real performance hits in rebuilds with this approach in the past.

I haven't attempted an implementation with gulp though. Normally you specify the files you want watch for changes and the subsequent behaviours manually. Happy to investigate further though.

In the meantime I can create a fork for testing the above with webpack (which will work with laravel mix @m1guelpf)?
@davidianbonner Oh wow that's great news! I had thought this was a lost cause from a pure Webpack point of view.

So I've got things rebuilding now whenever the config changes, but the rebuilds don't seem to take into account the new config 🤔 

So for example if I add a new color and save the config file, I can see Webpack retriggering in the terminal, but the CSS output doesn't actually contain the new color.

Going to keep hacking on it and try a few things, but if you've got any ideas I'd love to heard them. I'm a complete Webpack novice, so lots of trial and error at the moment, haha...
Alright so I've actually got this figured it out and it was a change needed in Tailwind! Going to PR a solution for this soon and probably get it out in a release today.

Patch:
diff --git a/docs/tailwind.js b/docs/tailwind.js
--- a/docs/tailwind.js
+++ b/docs/tailwind.js
@@ -1,4 +1,4 @@
-var config = require('../defaultConfig')
+var config = require('../lib/index').defaultConfig()
 
 config.colors = {
   ...config.colors,
diff --git a/src/index.js b/src/index.js
--- a/src/index.js
+++ b/src/index.js
@@ -5,6 +5,7 @@ import _ from 'lodash'
 import postcss from 'postcss'
 import stylefmt from 'stylefmt'
 
+import registerConfigAsDependency from './lib/registerConfigAsDependency'
 import substitutePreflightAtRule from './lib/substitutePreflightAtRule'
 import evaluateTailwindFunctions from './lib/evaluateTailwindFunctions'
 import generateUtilities from './lib/generateUtilities'
@@ -15,23 +16,30 @@ import substituteScreenAtRules from './lib/substituteScreenAtRules'
 import substituteClassApplyAtRules from './lib/substituteClassApplyAtRules'
 
 const plugin = postcss.plugin('tailwind', (config) => {
-  if (_.isUndefined(config)) {
-    config = require('../defaultConfig')
+  const plugins = []
+
+  if (! _.isUndefined(config)) {
+    plugins.push(registerConfigAsDependency(path.resolve(config)))
   }
 
-  if (_.isString(config)) {
-    config = require(path.resolve(config))
+  const lazyConfig = () => {
+    if (_.isUndefined(config)) {
+      return require('../defaultConfig')
+    }
+
+    delete require.cache[require.resolve(path.resolve(config))]
+    return require(path.resolve(config))
   }
 
-  return postcss([
-    substitutePreflightAtRule(config),
-    evaluateTailwindFunctions(config),
-    generateUtilities(config),
-    substituteHoverableAtRules(config),
-    substituteFocusableAtRules(config),
-    substituteResponsiveAtRules(config),
-    substituteScreenAtRules(config),
-    substituteClassApplyAtRules(config),
+  return postcss(...plugins, ...[
+    substitutePreflightAtRule(lazyConfig),
+    evaluateTailwindFunctions(lazyConfig),
+    generateUtilities(lazyConfig),
+    substituteHoverableAtRules(lazyConfig),
+    substituteFocusableAtRules(lazyConfig),
+    substituteResponsiveAtRules(lazyConfig),
+    substituteScreenAtRules(lazyConfig),
+    substituteClassApplyAtRules(lazyConfig),
     stylefmt,
   ])
 })
diff --git a/src/lib/evaluateTailwindFunctions.js b/src/lib/evaluateTailwindFunctions.js
--- a/src/lib/evaluateTailwindFunctions.js
+++ b/src/lib/evaluateTailwindFunctions.js
@@ -1,7 +1,9 @@
 import _ from 'lodash'
 import functions from 'postcss-functions'
 
-export default function(options) {
+export default function(config) {
+  const options = config()
+
   return functions({
     functions: {
       config: function (path) {
diff --git a/src/lib/generateUtilities.js b/src/lib/generateUtilities.js
--- a/src/lib/generateUtilities.js
+++ b/src/lib/generateUtilities.js
@@ -37,8 +37,10 @@ import verticalAlign from '../generators/verticalAlign'
 import visibility from '../generators/visibility'
 import zIndex from '../generators/zIndex'
 
-export default function(options) {
+export default function(config) {
   return function(css) {
+    const options = config()
+
     css.walkAtRules('tailwind', atRule => {
       if (atRule.params === 'utilities') {
         const utilities = _.flatten([
diff --git a/src/lib/registerConfigAsDependency.js b/src/lib/registerConfigAsDependency.js
new file mode 100644
--- /dev/null
+++ b/src/lib/registerConfigAsDependency.js
@@ -0,0 +1,15 @@
+import fs from 'fs'
+
+export default function(configFile) {
+  if (! fs.existsSync(configFile)) {
+    throw new Error(`Specified Tailwind config file "${configFile}" doesn't exist.`)
+  }
+
+  return function (css, opts) {
+    opts.messages = [{
+      type: 'dependency',
+      file: configFile,
+      parent: css.source.input.file,
+    }]
+  }
+}
diff --git a/src/lib/substituteClassApplyAtRules.js b/src/lib/substituteClassApplyAtRules.js
--- a/src/lib/substituteClassApplyAtRules.js
+++ b/src/lib/substituteClassApplyAtRules.js
@@ -9,8 +9,9 @@ function normalizeClassNames(classNames) {
   })
 }
 
-export default postcss.plugin('tailwind-apply', function(css) {
-  return function(css) {
+export default function(config) {
+  return function (css) {
+    const options = config()
     css.walkRules(function(rule) {
       rule.walkAtRules('apply', atRule => {
         const mixins = normalizeClassNames(postcss.list.space(atRule.params))
@@ -42,4 +43,4 @@ export default postcss.plugin('tailwind-apply', function(css) {
       })
     })
   }
-})
+}
diff --git a/src/lib/substituteFocusableAtRules.js b/src/lib/substituteFocusableAtRules.js
--- a/src/lib/substituteFocusableAtRules.js
+++ b/src/lib/substituteFocusableAtRules.js
@@ -2,8 +2,10 @@ import _ from 'lodash'
 import postcss from 'postcss'
 import cloneNodes from '../util/cloneNodes'
 
-export default function(options) {
-  return function(css) {
+export default function(config) {
+  return function (css) {
+    const options = config()
+
     css.walkAtRules('focusable', atRule => {
 
       atRule.walkRules(rule => {
diff --git a/src/lib/substituteHoverableAtRules.js b/src/lib/substituteHoverableAtRules.js
--- a/src/lib/substituteHoverableAtRules.js
+++ b/src/lib/substituteHoverableAtRules.js
@@ -2,8 +2,10 @@ import _ from 'lodash'
 import postcss from 'postcss'
 import cloneNodes from '../util/cloneNodes'
 
-export default function(options) {
-  return function(css) {
+export default function(config) {
+  return function (css) {
+    const options = config()
+
     css.walkAtRules('hoverable', atRule => {
 
       atRule.walkRules(rule => {
diff --git a/src/lib/substitutePreflightAtRule.js b/src/lib/substitutePreflightAtRule.js
--- a/src/lib/substitutePreflightAtRule.js
+++ b/src/lib/substitutePreflightAtRule.js
@@ -1,8 +1,10 @@
 import fs from 'fs'
 import postcss from 'postcss'
 
-export default function(options) {
+export default function(config) {
   return function (css) {
+    const options = config()
+
     css.walkAtRules('tailwind', atRule => {
       if (atRule.params === 'preflight') {
           atRule.before(postcss.parse(fs.readFileSync(`${__dirname}/../../css/preflight.css`, 'utf8')))
diff --git a/src/lib/substituteResponsiveAtRules.js b/src/lib/substituteResponsiveAtRules.js
--- a/src/lib/substituteResponsiveAtRules.js
+++ b/src/lib/substituteResponsiveAtRules.js
@@ -3,8 +3,10 @@ import postcss from 'postcss'
 import cloneNodes from '../util/cloneNodes'
 import buildMediaQuery from '../util/buildMediaQuery'
 
-export default function({ screens }) {
-  return function(css) {
+
+export default function(config) {
+  return function (css) {
+    const screens = config().screens
     const rules = []
 
     css.walkAtRules('responsive', atRule => {
diff --git a/src/lib/substituteScreenAtRules.js b/src/lib/substituteScreenAtRules.js
--- a/src/lib/substituteScreenAtRules.js
+++ b/src/lib/substituteScreenAtRules.js
@@ -3,8 +3,9 @@ import postcss from 'postcss'
 import cloneNodes from '../util/cloneNodes'
 import buildMediaQuery from '../util/buildMediaQuery'
 
-export default function(options) {
+export default function(config) {
   return function(css) {
+    const options = config()
     const rules = []
 
     css.walkAtRules('screen', atRule => {


Test patch:
diff --git a/__tests__/applyAtRule.test.js b/__tests__/applyAtRule.test.js
--- a/__tests__/applyAtRule.test.js
+++ b/__tests__/applyAtRule.test.js
@@ -1,14 +1,14 @@
 import postcss from 'postcss'
 import plugin from '../src/lib/substituteClassApplyAtRules'
 
-function run(input, opts) {
+function run(input, opts = () => {}) {
     return postcss([plugin(opts)]).process(input)
 }
 
 test("it copies a class's declarations into itself", () => {
     const output = '.a { color: red; } .b { color: red; }'
 
-    return run('.a { color: red; } .b { @apply .a; }', {}).then(result => {
+    return run('.a { color: red; } .b { @apply .a; }').then(result => {
         expect(result.css).toEqual(output)
         expect(result.warnings().length).toBe(0)
     })
@@ -38,16 +38,15 @@ test("it doesn't copy a media query definition into itself", () => {
 
         .b {
             @apply .a;
-        }`,
-        {}
-    ).then(result => {
+        }`)
+    .then(result => {
         expect(result.css).toEqual(output)
         expect(result.warnings().length).toBe(0)
     })
 })
 
 test('it fails if the class does not exist', () => {
-    run('.b { @apply .a; }', {}).catch(error => {
+    run('.b { @apply .a; }').catch(error => {
         expect(error.reason).toEqual('No .a class found.')
     })
 })
diff --git a/__tests__/focusableAtRule.test.js b/__tests__/focusableAtRule.test.js
--- a/__tests__/focusableAtRule.test.js
+++ b/__tests__/focusableAtRule.test.js
@@ -1,7 +1,7 @@
 import postcss from 'postcss'
 import plugin from '../src/lib/substituteFocusableAtRules'
 
-function run(input, opts = {}) {
+function run(input, opts = () => {}) {
   return postcss([plugin(opts)]).process(input)
 }
 
@@ -18,7 +18,7 @@ test("it adds a focusable variant to each nested class definition", () => {
       .chocolate, .focus\\:chocolate:focus { color: brown; }
   `
 
-  return run(input, {}).then(result => {
+  return run(input).then(result => {
     expect(result.css).toEqual(output)
     expect(result.warnings().length).toBe(0)
   })
diff --git a/__tests__/hoverableAtRule.test.js b/__tests__/hoverableAtRule.test.js
--- a/__tests__/hoverableAtRule.test.js
+++ b/__tests__/hoverableAtRule.test.js
@@ -1,7 +1,7 @@
 import postcss from 'postcss'
 import plugin from '../src/lib/substituteHoverableAtRules'
 
-function run(input, opts = {}) {
+function run(input, opts = () => {}) {
   return postcss([plugin(opts)]).process(input)
 }
 
@@ -18,7 +18,7 @@ test("it adds a hoverable variant to each nested class definition", () => {
       .chocolate, .hover\\:chocolate:hover { color: brown; }
   `
 
-  return run(input, {}).then(result => {
+  return run(input).then(result => {
     expect(result.css).toEqual(output)
     expect(result.warnings().length).toBe(0)
   })