Repository: tailwindlabs/tailwindcss (TypeScript)
Date: 2017-11-03 18:47:33+00:00
Task type: Feature

Issue:
Screen-Utilities – problem with other postcss plugins
Hey there,

I love my utilities to be marked as ```!important``` because i've seen some people overwriting utilities (which is more than bad). If specitifity of e.g. a bem-item is higher the utility gets overridden.

## Example:

```css
.mx-auto { … }

/* this is not good, but happens from time to time. If i mark my utilities as important, this will not work and the user MUST remove the class mx-auto from the html */
.card > .card__image {
    margin-left: 10px;
} 
```

```html
<div class="card">
    <div class="card__image mx-auto">
        <img … />
    </div>
</div>
```

I've written a tiny postcss plugin for marking utilities important.
https://www.npmjs.com/package/postcss-important-startstop

## Problem

The plugin is based on comments in the css.
It's not the only postcss plugin which does so.

If i try to use it with tailwindcss, the ```@tailwind utilities``` can be marked important by using

```css
/* @important(start) */
@tailwind utilities;
/* @important(stop) */
```

This works like a charm.
The problem is, that the **custom utilities** are added to the end of the css-file **magically**.

So this happens:

```css
/* @important(start) */
@tailwind utilities;
/* @important(stop) */

/**
 * Here you would add any custom utilities you need that don't come out of the box with Tailwind.
 */
.bg-hero-image {
    background-image: url('/some/image/file.png');
}
```

results in this:

<img width="1145" alt="bildschirmfoto 2017-11-01 um 13 20 22" src="https://user-images.githubusercontent.com/1016798/32274587-c6e39bf0-bf07-11e7-9d43-58f48738dcb8.png">

I have no chance to modify the "custom utilities" with my comment-based plugin.

Is it somehow possible to "remove the magic" and render the custom utilities with something like that?

```css
@tailwind custom-utilities;
```

WDYT?
Screen-Utilities – problem with other postcss plugins
Hey there,

I love my utilities to be marked as ```!important``` because i've seen some people overwriting utilities (which is more than bad). If specitifity of e.g. a bem-item is higher the utility gets overridden.

## Example:

```css
.mx-auto { … }

/* this is not good, but happens from time to time. If i mark my utilities as important, this will not work and the user MUST remove the class mx-auto from the html */
.card > .card__image {
    margin-left: 10px;
} 
```

```html
<div class="card">
    <div class="card__image mx-auto">
        <img … />
    </div>
</div>
```

I've written a tiny postcss plugin for marking utilities important.
https://www.npmjs.com/package/postcss-important-startstop

## Problem

The plugin is based on comments in the css.
It's not the only postcss plugin which does so.

If i try to use it with tailwindcss, the ```@tailwind utilities``` can be marked important by using

```css
/* @important(start) */
@tailwind utilities;
/* @important(stop) */
```

This works like a charm.
The problem is, that the **custom utilities** are added to the end of the css-file **magically**.

So this happens:

```css
/* @important(start) */
@tailwind utilities;
/* @important(stop) */

/**
 * Here you would add any custom utilities you need that don't come out of the box with Tailwind.
 */
.bg-hero-image {
    background-image: url('/some/image/file.png');
}
```

results in this:

<img width="1145" alt="bildschirmfoto 2017-11-01 um 13 20 22" src="https://user-images.githubusercontent.com/1016798/32274587-c6e39bf0-bf07-11e7-9d43-58f48738dcb8.png">

I have no chance to modify the "custom utilities" with my comment-based plugin.

Is it somehow possible to "remove the magic" and render the custom utilities with something like that?

```css
@tailwind custom-utilities;
```

WDYT?


Issue discussion:
I think this is a reasonable idea.

What if we did something like this:

- Add an optional marker like `@tailwind screen-utilities` and in the processing phase where we generate those utilities, we first looked for that marker and if it was present, output the screen specific utilities there

- If that marker is not present anywhere in the file, default to outputting the utilities at the end of the CSS file

This way you could have finer control over it if needed, and the 95% of people who want the default behavior can just pretend the option doesn't exist.

Would that solve your issue?
Yeah, this would definitly solve my issue and makes perfect sense to me.
Haha, i just removed my stop tag for now.
Feels nasty and it is.

```css

/**
 * Here you would add any custom utilities you need that don't come out of the box with Tailwind.
 */
.bg-hero-image {
    background-image: url('/some/image/file.png');
}

/* @important(start) */
@tailwind utilities;
```

Does *temporarily* solve the problem.

@adamwathan  If you have a hint for me where I could start to adjust this (just a quickstart) I can 
 try to give it a shot.
@psren This is the line that dumps it at the end of the stylesheet:

https://github.com/tailwindcss/tailwindcss/blob/master/src/lib/substituteResponsiveAtRules.js#L32

So instead we'd just want to look for some marker at-rule to replace instead if it was present.

The tricky thing is coming up with a name I don't hate for the at-rule 😄 but feel free to start a PR using `@tailwind screen-utilities` and we can easily change it if I can come up with something I actually like.
@adamwathan i like the name, because is is equal to the helper https://tailwindcss.com/docs/functions-and-directives/#screen
Aaaaand one more question.
We have two variants to test now.

With screen-utils rendered by ```@tailwind screen-utilities``` and with implicitly rendered screen-utils.

We have to keep implicit rendering (of cause) to provide BC and the "ease of use".
Later we will maybe have a third variant with the planned (or at least mentioned) [modularization](https://github.com/tailwindcss/tailwindcss/issues/27#issuecomment-341186641
).

So my question before i start to implement this is:
How'd you go with tests?

I would copy the [tailwind-input.css](https://github.com/tailwindcss/tailwindcss/blob/master/__tests__/fixtures/tailwind-input.css) to another file for the different test and expect exactly the same output.

Every possible change to this block https://github.com/tailwindcss/tailwindcss/blob/master/__tests__/fixtures/tailwind-input.css#L5-L9 must be done twice from then on.

BUUUT: I don't think thats a problem because this part most likely won't change in any minor version.

@adamwathan WDYT?
I think this is a reasonable idea.

What if we did something like this:

- Add an optional marker like `@tailwind screen-utilities` and in the processing phase where we generate those utilities, we first looked for that marker and if it was present, output the screen specific utilities there

- If that marker is not present anywhere in the file, default to outputting the utilities at the end of the CSS file

This way you could have finer control over it if needed, and the 95% of people who want the default behavior can just pretend the option doesn't exist.

Would that solve your issue?
Yeah, this would definitly solve my issue and makes perfect sense to me.
Haha, i just removed my stop tag for now.
Feels nasty and it is.

```css

/**
 * Here you would add any custom utilities you need that don't come out of the box with Tailwind.
 */
.bg-hero-image {
    background-image: url('/some/image/file.png');
}

/* @important(start) */
@tailwind utilities;
```

Does *temporarily* solve the problem.

@adamwathan  If you have a hint for me where I could start to adjust this (just a quickstart) I can 
 try to give it a shot.
@psren This is the line that dumps it at the end of the stylesheet:

https://github.com/tailwindcss/tailwindcss/blob/master/src/lib/substituteResponsiveAtRules.js#L32

So instead we'd just want to look for some marker at-rule to replace instead if it was present.

The tricky thing is coming up with a name I don't hate for the at-rule 😄 but feel free to start a PR using `@tailwind screen-utilities` and we can easily change it if I can come up with something I actually like.
@adamwathan i like the name, because is is equal to the helper https://tailwindcss.com/docs/functions-and-directives/#screen
Aaaaand one more question.
We have two variants to test now.

With screen-utils rendered by ```@tailwind screen-utilities``` and with implicitly rendered screen-utils.

We have to keep implicit rendering (of cause) to provide BC and the "ease of use".
Later we will maybe have a third variant with the planned (or at least mentioned) [modularization](https://github.com/tailwindcss/tailwindcss/issues/27#issuecomment-341186641
).

So my question before i start to implement this is:
How'd you go with tests?

I would copy the [tailwind-input.css](https://github.com/tailwindcss/tailwindcss/blob/master/__tests__/fixtures/tailwind-input.css) to another file for the different test and expect exactly the same output.

Every possible change to this block https://github.com/tailwindcss/tailwindcss/blob/master/__tests__/fixtures/tailwind-input.css#L5-L9 must be done twice from then on.

BUUUT: I don't think thats a problem because this part most likely won't change in any minor version.

@adamwathan WDYT?

Solution patch:
diff --git a/docs/source/docs/functions-and-directives.blade.md b/docs/source/docs/functions-and-directives.blade.md
--- a/docs/source/docs/functions-and-directives.blade.md
+++ b/docs/source/docs/functions-and-directives.blade.md
@@ -8,7 +8,7 @@ Tailwind exposes a few custom CSS functions and directives that can be used in y
 
 ### `@@tailwind`
 
-Use the `@@tailwind` directive to insert Tailwind's `preflight` and `utilities` styles into your CSS. Here's a full example of how you might do this:
+Use the `@@tailwind` directive to insert Tailwind's `preflight`, `utilities` and `screen` styles into your CSS. Here's a full example of how you might do this:
 
 ```less
 /**
@@ -25,6 +25,13 @@ Use the `@@tailwind` directive to insert Tailwind's `preflight` and `utilities`
  * config file.
  */
 @@tailwind utilities;
+
+/**
+ * (Optional)
+ * This injects the utility classes and styles wrapped by the @@responsive directive.
+ * These will be appended at the end of the stylesheet if the `@@tailwind screens` directive is not used.
+ */
+ @@tailwind screens;
 ```
 
 ### `@@apply`
diff --git a/src/lib/substituteResponsiveAtRules.js b/src/lib/substituteResponsiveAtRules.js
--- a/src/lib/substituteResponsiveAtRules.js
+++ b/src/lib/substituteResponsiveAtRules.js
@@ -6,11 +6,12 @@ import buildMediaQuery from '../util/buildMediaQuery'
 export default function(config) {
   return function(css) {
     const screens = config().screens
-    const rules = []
+    const responsiveRules = []
+    let finalRules = []
 
     css.walkAtRules('responsive', atRule => {
       const nodes = atRule.nodes
-      rules.push(...cloneNodes(nodes))
+      responsiveRules.push(...cloneNodes(nodes))
       atRule.before(nodes)
       atRule.remove()
     })
@@ -22,15 +23,33 @@ export default function(config) {
       })
 
       mediaQuery.append(
-        rules.map(rule => {
+        responsiveRules.map(rule => {
           const cloned = rule.clone()
           cloned.selectors = _.map(rule.selectors, selector => `.${screen}\\:${selector.slice(1)}`)
           return cloned
         })
       )
 
-      if (mediaQuery.nodes.length) {
-        css.append(mediaQuery)
+      finalRules.push(mediaQuery)
+    })
+
+    const hasScreenRules = finalRules.some(i => i.nodes.length !== 0)
+    if (!hasScreenRules) {
+      return
+    }
+
+    const includesScreensExplicitly = css.some(
+      rule => rule.type === 'atrule' && rule.params === 'screens'
+    )
+
+    if (!includesScreensExplicitly) {
+      css.append(finalRules)
+      return
+    }
+
+    css.walkAtRules('tailwind', atRule => {
+      if (atRule.params === 'screens') {
+        atRule.replaceWith(finalRules)
       }
     })
   }


Test patch:
diff --git a/__tests__/fixtures/tailwind-input-with-explicit-screen-utilities.css b/__tests__/fixtures/tailwind-input-with-explicit-screen-utilities.css
new file mode 100644
--- /dev/null
+++ b/__tests__/fixtures/tailwind-input-with-explicit-screen-utilities.css
@@ -0,0 +1,11 @@
+@responsive {
+    .example {
+        color: red;
+    }
+}
+
+@tailwind screens;
+
+.john {
+    content: "wick";
+}
diff --git a/__tests__/fixtures/tailwind-output-with-explicit-screen-utilities.css b/__tests__/fixtures/tailwind-output-with-explicit-screen-utilities.css
new file mode 100644
--- /dev/null
+++ b/__tests__/fixtures/tailwind-output-with-explicit-screen-utilities.css
@@ -0,0 +1,31 @@
+.example {
+  color: red;
+}
+
+@media (min-width: 576px) {
+  .sm\:example {
+    color: red;
+  }
+}
+
+@media (min-width: 768px) {
+  .md\:example {
+    color: red;
+  }
+}
+
+@media (min-width: 992px) {
+  .lg\:example {
+    color: red;
+  }
+}
+
+@media (min-width: 1200px) {
+  .xl\:example {
+    color: red;
+  }
+}
+
+.john {
+  content: "wick";
+}
diff --git a/__tests__/sanity.test.js b/__tests__/sanity.test.js
--- a/__tests__/sanity.test.js
+++ b/__tests__/sanity.test.js
@@ -28,3 +28,21 @@ it('does not add any CSS if no Tailwind features are used', () => {
       expect(result.css).toBe('')
     })
 })
+
+it('generates the right CSS with implicit screen utilities', () => {
+  const input = fs.readFileSync(
+    path.resolve(`${__dirname}/fixtures/tailwind-input-with-explicit-screen-utilities.css`),
+    'utf8'
+  )
+
+  return postcss([tailwind()])
+    .process(input)
+    .then(result => {
+      const expected = fs.readFileSync(
+        path.resolve(`${__dirname}/fixtures/tailwind-output-with-explicit-screen-utilities.css`),
+        'utf8'
+      )
+
+      expect(result.css).toBe(expected)
+    })
+})