Repository: tailwindlabs/tailwindcss (TypeScript)

Issue:
Enabling JIT breaks sourcemaps in webpack + PostCSS project
### What version of Tailwind CSS are you using?

2.2.6

### What build tool (or framework if it abstracts the build tool) are you using?

webpack 5.46.0, postcss 8.3.6

### What version of Node.js are you using?

14.17.0

### What browser are you using?

Chrome

### What operating system are you using?

Ubuntu

### Reproduction repository

⠀

### Describe your issue

When using Tailwind's JIT option for processing PostCSS in the webpack project, source maps are not being shown in the Chrome DevTools (but still generated in the output CSS file). The value of `devtool` option in the webpack config doesn't matter, the result is the same.

With config like this:
```
  purge: [
    './src/**/*.tsx',
  ],
  mode: 'jit',
```
Source maps don't get processed:
![Screenshot from 2021-07-22 16-36-57](https://user-images.githubusercontent.com/3055750/126648794-ffeed31f-df8c-4cf5-a6b7-20c2ac834799.png)

When disabling the JIT option:
```
  purge: false,
```
Source maps are being processed properly, and the original file is shown in Chrome DevTools:
![Screenshot from 2021-07-22 16-30-51](https://user-images.githubusercontent.com/3055750/126649229-f6154cf6-42b1-4b5f-9d6b-35366f31de8f.png)

I'll bootstrap a repro repo if a more thorough investigation is needed.
Add a `from` when postcss parsing preflight
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new features.**

It's never a fun experience to have your pull request declined after investing a lot of time and effort into a new feature. To avoid this from happening, we request that contributors create an issue to first discuss any significant new features. This includes things like adding new utilities, creating new at-rules, or adding new component examples to the documentation.

https://github.com/tailwindcss/tailwindcss/blob/master/.github/CONTRIBUTING.md

-->
Hi, I was tracking down today why builds for some of our apps were not deterministic. I believe it's because no `from` is provided to `postcss.parse`, so it generates a random ID every compilation. I am out of my depth with frontend tooling, so I wasn't sure how this `from` value is used and if what I've done is appropriate. Regardless, I believe it should be something stable and non-null.

See https://github.com/postcss/postcss/issues/1512 and https://github.com/tailwindlabs/tailwindcss/pull/3356 for a related issue and discussion.

Fixes #7583 


Hints:
**here is a workaround until JIT and sourcemaps work ( it is breaking as of v2.2.7) **

- need two config files (tailwind.config.js) one file with JIT enabled the other disabled. 
- tailwind.css <  @tailwind components; @tailwind utilities; 
- styles.scss < @tailwind base;  import xyz // and Load SCSS via globing / scss can have  @apply 
- and run two tasks to process style sheets

`  return src(${options.paths.src.css}/tailwind.css)
    .pipe(postcss([
      require('postcss-import'),
      tailwindcss(options.config.tailwindjs)
    ]))`

`  return src(${options.paths.src.css}/**styles.scss**)
    .pipe(sourcemaps.init()) 
    .pipe(sass().on('error', sass.logError))
    .pipe(postcss([
      tailwindcss(options.config.tailwindnoJIT)
    ]))`

and voila sourcemaps are working working with JIT and  is blazing fast, styles sheets small as well :)



@adamwathan FYI, sourcemaps still not working with JIT enabled in `2.2.7`.
![Screenshot from 2021-08-04 11-53-26](https://user-images.githubusercontent.com/3055750/128153175-4e19701c-9d06-4f4f-82f3-832caf38a382.png)
![Screenshot from 2021-08-04 11-55-11](https://user-images.githubusercontent.com/3055750/128153176-674af11f-e275-452f-a526-09a138269358.png)

Same here. Source maps won't work in JIT mode. I just tried to make a new simple project with webpack and tailwind just to try it out. If I dont use JIT then the source maps works. If I use the mode JIT then the source maps wont work.

I use Tailwind together with some CMS and some elements I cant add css classes to, so I have to use @apply to style those elements.

I added some screenshots down below to show my project and how it looks in firefox dev tools.


**working source maps in firefox dev tools (JIT disabled)**
![working source maps in firefox dev tools (JIT disabled)](https://user-images.githubusercontent.com/43706427/133685149-91b845ed-1da5-447d-8839-5bf691f76a86.jpg)


**broken source maps in firefox dev tools (JIT enabled)**
![broken source maps in firefox dev tools (JIT enabled)](https://user-images.githubusercontent.com/43706427/133684908-94e542da-65cb-4b63-b438-b4a66faed743.jpg)


**error message in firefox dev tools (JIT enabled)**
![error message in friefox dev tools (JIT enabled)](https://user-images.githubusercontent.com/43706427/133684978-95795b5b-c1b0-4f89-bdba-0325276a0ab1.jpg)


**how I import tailwind files to my css**
![how I import tailwind to my css](https://user-images.githubusercontent.com/43706427/133685039-c4586cf2-b47f-42ef-8745-d454795f1857.jpg)


**index.js in vscode**
![index js in vscode](https://user-images.githubusercontent.com/43706427/133685082-0343fd06-e715-4def-84b2-47f84f9374fa.jpg)


**typography.css in vscode**
![typography css in vscode](https://user-images.githubusercontent.com/43706427/133685119-44ce11bf-a043-4ec9-8410-0b859f9cbfe4.jpg)









Same here.
Is there any prospect of fixing this problem. We would not like to do without the JIT, because we now also use arbitrary value support that only works with the JIT.
But working without SourceMap is also tedious.
Same here too,
I tried some different configurations with different builder or method but I not found any solution and my comprehension about how jit works with sourcemaps is so limited that i cannot deep dive into code to inspect and get a solution to help :( 
Adding any regular css rule or even a simple comment will generate sourcemaps correctly.

```html
<div class="with-sourcemap"></div>
<div class="with-another-sourcemap"></div>
<div class="no-sourcemap-here"></div>
```

```pcss
.with-sourcemap {
  background-color: red;
  @apply h-4 w-4 bg-green-500;
}

.with-another-sourcemap {
  /* sourcemap will work here too */
  @apply h-4 w-4 bg-red-500;
}

.no-sourcemap-here {
  @apply h-4 w-4 bg-black;
}
```

![image](https://user-images.githubusercontent.com/5835044/151717106-64a76fe6-8f28-4052-95e3-2ade4f71c1d4.png)
![image](https://user-images.githubusercontent.com/5835044/151717114-b88e7e6d-0597-4624-9758-0ec267fc7752.png)
![image](https://user-images.githubusercontent.com/5835044/151717126-b132a0be-c401-43df-9543-24995b6efb9f.png)
Source maps are also not working properly in 3.0.19.

`src/css/app.css`
```
@tailwind base;
@tailwind components;
@tailwind utilities;
```

`src/js/app.js`
```
import '../css/app.css'
```

`webpack.config.js`
```
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

module.exports = (env, argv) =>
{

  process.env.NODE_ENV = argv.mode

  return {
    devtool: 'source-map',
    entry: {
      app: '/src/js/app.js'
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: 'babel-loader'
        },
        {
          test: /\.css$/,
          exclude: /node_modules/,
          use: [
            MiniCssExtractPlugin.loader,
            'css-loader',
            'postcss-loader',
          ],
        }
      ]
    },
    optimization: {
      minimize: true,
      minimizer: [
        `...`,
        new CssMinimizerPlugin(),
      ],
    },
    output: {
      chunkFilename: '[name].[contenthash].js',
      clean: true,
      filename: '[name].js',
      path: path.resolve(__dirname, 'public/dist')
    },
    plugins: [
      new MiniCssExtractPlugin({filename: '[name].css'})
    ]
  }

}
```

The generated `public/dist/app.css.map` includes this section:
```
"sources":["webpack://@test/test/./src/css/app.css","webpack://@test/test/./src/css/%3Cinput%20css%20doRbU6%3E","webpack://@test/test/<no source>"]
```

Using Chrome DevTools all the Tailwind CSS rules show as `<no source>:1`
Thanks! Gonna take a look at this either today or tomorrow as I'll be making a few fixes to source map generation.

Patch:
diff --git a/src/lib/expandApplyAtRules.js b/src/lib/expandApplyAtRules.js
--- a/src/lib/expandApplyAtRules.js
+++ b/src/lib/expandApplyAtRules.js
@@ -140,7 +140,7 @@ function processApply(root, context) {
     for (let apply of applies) {
       let candidates = perParentApplies.get(apply.parent) || []
 
-      perParentApplies.set(apply.parent, candidates)
+      perParentApplies.set(apply.parent, [candidates, apply.source])
 
       let [applyCandidates, important] = extractApplyCandidates(apply.params)
 
@@ -178,7 +178,7 @@ function processApply(root, context) {
       }
     }
 
-    for (const [parent, candidates] of perParentApplies) {
+    for (const [parent, [candidates, atApplySource]] of perParentApplies) {
       let siblings = []
 
       for (let [applyCandidate, important, rules] of candidates) {
@@ -220,6 +220,12 @@ function processApply(root, context) {
           }
 
           let root = postcss.root({ nodes: [node.clone()] })
+
+          // Make sure every node in the entire tree points back at the @apply rule that generated it
+          root.walk((node) => {
+            node.source = atApplySource
+          })
+
           let canRewriteSelector =
             node.type !== 'atrule' || (node.type === 'atrule' && node.name !== 'keyframes')
 
diff --git a/src/lib/resolveDefaultsAtRules.js b/src/lib/resolveDefaultsAtRules.js
--- a/src/lib/resolveDefaultsAtRules.js
+++ b/src/lib/resolveDefaultsAtRules.js
@@ -120,7 +120,9 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
         }
 
         for (let [, selectors] of selectorGroups) {
-          let universalRule = postcss.rule()
+          let universalRule = postcss.rule({
+            source: universal.source,
+          })
 
           universalRule.selectors = [...selectors]
 
@@ -128,7 +130,9 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
           universal.before(universalRule)
         }
       } else {
-        let universalRule = postcss.rule()
+        let universalRule = postcss.rule({
+          source: universal.source,
+        })
 
         universalRule.selectors = ['*', '::before', '::after']
 
diff --git a/src/util/cloneNodes.js b/src/util/cloneNodes.js
--- a/src/util/cloneNodes.js
+++ b/src/util/cloneNodes.js
@@ -4,6 +4,12 @@ export default function cloneNodes(nodes, source) {
 
     if (source !== undefined) {
       cloned.source = source
+
+      if ('walk' in cloned) {
+        cloned.walk((child) => {
+          child.source = source
+        })
+      }
     }
 
     return cloned


Test patch:
diff --git a/tests/source-maps.test.js b/tests/source-maps.test.js
new file mode 100644
--- /dev/null
+++ b/tests/source-maps.test.js
@@ -0,0 +1,409 @@
+import { runWithSourceMaps as run, html, css } from './util/run'
+import { parseSourceMaps } from './util/source-maps'
+
+it('apply generates source maps', async () => {
+  let config = {
+    content: [
+      {
+        raw: html`
+          <div class="with-declaration"></div>
+          <div class="with-comment"></div>
+          <div class="just-apply"></div>
+        `,
+      },
+    ],
+    corePlugins: { preflight: false },
+  }
+
+  let input = css`
+    .with-declaration {
+      background-color: red;
+      @apply h-4 w-4 bg-green-500;
+    }
+
+    .with-comment {
+      /* sourcemap will work here too */
+      @apply h-4 w-4 bg-red-500;
+    }
+
+    .just-apply {
+      @apply h-4 w-4 bg-black;
+    }
+  `
+
+  let result = await run(input, config)
+  let { sources, annotations } = parseSourceMaps(result)
+
+  // All CSS generated by Tailwind CSS should be annotated with source maps
+  // And always be able to point to the original source file
+  expect(sources).not.toContain('<no source>')
+  expect(sources.length).toBe(1)
+
+  // It would make the tests nicer to read and write
+  expect(annotations).toStrictEqual([
+    '2:4 -> 2:4',
+    '3:6-27 -> 3:6-27',
+    '4:6-33 -> 4:6-18',
+    '4:6-33 -> 5:6-17',
+    '4:6-33 -> 6:6-24',
+    '4:6-33 -> 7:6-61',
+    '5:4 -> 8:4',
+    '7:4 -> 10:4',
+    '8:6-39 -> 11:6-39',
+    '9:6-31 -> 12:6-18',
+    '9:6-31 -> 13:6-17',
+    '9:6-31 -> 14:6-24',
+    '9:6-31 -> 15:6-61',
+    '10:4 -> 16:4',
+    '13:6 -> 18:4',
+    '13:6-29 -> 19:6-18',
+    '13:6-29 -> 20:6-17',
+    '13:6-29 -> 21:6-24',
+    '13:6 -> 22:6',
+    '13:29 -> 23:0',
+  ])
+})
+
+it('preflight + base have source maps', async () => {
+  let config = {
+    content: [],
+  }
+
+  let input = css`
+    @tailwind base;
+  `
+
+  let result = await run(input, config)
+  let { sources, annotations } = parseSourceMaps(result)
+
+  // All CSS generated by Tailwind CSS should be annotated with source maps
+  // And always be able to point to the original source file
+  expect(sources).not.toContain('<no source>')
+  expect(sources.length).toBe(1)
+
+  // It would make the tests nicer to read and write
+  expect(annotations).toStrictEqual([
+    '2:4 -> 1:0',
+    '2:18-4 -> 3:1-2',
+    '2:18 -> 6:1',
+    '2:4 -> 8:0',
+    '2:4-18 -> 11:2-32',
+    '2:4-18 -> 12:2-25',
+    '2:4-18 -> 13:2-29',
+    '2:4-18 -> 14:2-31',
+    '2:18 -> 15:0',
+    '2:4 -> 17:0',
+    '2:4-18 -> 19:2-18',
+    '2:18 -> 20:0',
+    '2:4 -> 22:0',
+    '2:18 -> 27:1',
+    '2:4 -> 29:0',
+    '2:4-18 -> 30:2-26',
+    '2:4-18 -> 31:2-40',
+    '2:4-18 -> 32:2-26',
+    '2:4-18 -> 33:2-21',
+    '2:4-18 -> 34:2-230',
+    '2:18 -> 35:0',
+    '2:4 -> 37:0',
+    '2:18 -> 40:1',
+    '2:4 -> 42:0',
+    '2:4-18 -> 43:2-19',
+    '2:4-18 -> 44:2-30',
+    '2:18 -> 45:0',
+    '2:4 -> 47:0',
+    '2:18 -> 51:1',
+    '2:4 -> 53:0',
+    '2:4-18 -> 54:2-19',
+    '2:4-18 -> 55:2-24',
+    '2:4-18 -> 56:2-31',
+    '2:18 -> 57:0',
+    '2:4 -> 59:0',
+    '2:18 -> 61:1',
+    '2:4 -> 63:0',
+    '2:4-18 -> 64:2-35',
+    '2:18 -> 65:0',
+    '2:4 -> 67:0',
+    '2:18 -> 69:1',
+    '2:4 -> 71:0',
+    '2:4-18 -> 77:2-20',
+    '2:4-18 -> 78:2-22',
+    '2:18 -> 79:0',
+    '2:4 -> 81:0',
+    '2:18 -> 83:1',
+    '2:4 -> 85:0',
+    '2:4-18 -> 86:2-16',
+    '2:4-18 -> 87:2-26',
+    '2:18 -> 88:0',
+    '2:4 -> 90:0',
+    '2:18 -> 92:1',
+    '2:4 -> 94:0',
+    '2:4-18 -> 96:2-21',
+    '2:18 -> 97:0',
+    '2:4 -> 99:0',
+    '2:18 -> 102:1',
+    '2:4 -> 104:0',
+    '2:4-18 -> 108:2-121',
+    '2:4-18 -> 109:2-24',
+    '2:18 -> 110:0',
+    '2:4 -> 112:0',
+    '2:18 -> 114:1',
+    '2:4 -> 116:0',
+    '2:4-18 -> 117:2-16',
+    '2:18 -> 118:0',
+    '2:4 -> 120:0',
+    '2:18 -> 122:1',
+    '2:4 -> 124:0',
+    '2:4-18 -> 126:2-16',
+    '2:4-18 -> 127:2-16',
+    '2:4-18 -> 128:2-20',
+    '2:4-18 -> 129:2-26',
+    '2:18 -> 130:0',
+    '2:4 -> 132:0',
+    '2:4-18 -> 133:2-17',
+    '2:18 -> 134:0',
+    '2:4 -> 136:0',
+    '2:4-18 -> 137:2-13',
+    '2:18 -> 138:0',
+    '2:4 -> 140:0',
+    '2:18 -> 144:1',
+    '2:4 -> 146:0',
+    '2:4-18 -> 147:2-24',
+    '2:4-18 -> 148:2-31',
+    '2:4-18 -> 149:2-35',
+    '2:18 -> 150:0',
+    '2:4 -> 152:0',
+    '2:18 -> 156:1',
+    '2:4 -> 158:0',
+    '2:4-18 -> 163:2-30',
+    '2:4-18 -> 164:2-25',
+    '2:4-18 -> 165:2-30',
+    '2:4-18 -> 166:2-24',
+    '2:4-18 -> 167:2-19',
+    '2:4-18 -> 168:2-20',
+    '2:18 -> 169:0',
+    '2:4 -> 171:0',
+    '2:18 -> 173:1',
+    '2:4 -> 175:0',
+    '2:4-18 -> 177:2-22',
+    '2:18 -> 178:0',
+    '2:4 -> 180:0',
+    '2:18 -> 183:1',
+    '2:4 -> 185:0',
+    '2:4-18 -> 189:2-36',
+    '2:4-18 -> 190:2-39',
+    '2:4-18 -> 191:2-32',
+    '2:18 -> 192:0',
+    '2:4 -> 194:0',
+    '2:18 -> 196:1',
+    '2:4 -> 198:0',
+    '2:4-18 -> 199:2-15',
+    '2:18 -> 200:0',
+    '2:4 -> 202:0',
+    '2:18 -> 204:1',
+    '2:4 -> 206:0',
+    '2:4-18 -> 207:2-18',
+    '2:18 -> 208:0',
+    '2:4 -> 210:0',
+    '2:18 -> 212:1',
+    '2:4 -> 214:0',
+    '2:4-18 -> 215:2-26',
+    '2:18 -> 216:0',
+    '2:4 -> 218:0',
+    '2:18 -> 220:1',
+    '2:4 -> 222:0',
+    '2:4-18 -> 224:2-14',
+    '2:18 -> 225:0',
+    '2:4 -> 227:0',
+    '2:18 -> 230:1',
+    '2:4 -> 232:0',
+    '2:4-18 -> 233:2-39',
+    '2:4-18 -> 234:2-30',
+    '2:18 -> 235:0',
+    '2:4 -> 237:0',
+    '2:18 -> 239:1',
+    '2:4 -> 241:0',
+    '2:4-18 -> 242:2-26',
+    '2:18 -> 243:0',
+    '2:4 -> 245:0',
+    '2:18 -> 248:1',
+    '2:4 -> 250:0',
+    '2:4-18 -> 251:2-36',
+    '2:4-18 -> 252:2-23',
+    '2:18 -> 253:0',
+    '2:4 -> 255:0',
+    '2:18 -> 257:1',
+    '2:4 -> 259:0',
+    '2:4-18 -> 260:2-20',
+    '2:18 -> 261:0',
+    '2:4 -> 263:0',
+    '2:18 -> 265:1',
+    '2:4 -> 267:0',
+    '2:4-18 -> 280:2-11',
+    '2:18 -> 281:0',
+    '2:4 -> 283:0',
+    '2:4-18 -> 284:2-11',
+    '2:4-18 -> 285:2-12',
+    '2:18 -> 286:0',
+    '2:4 -> 288:0',
+    '2:4-18 -> 289:2-12',
+    '2:18 -> 290:0',
+    '2:4 -> 292:0',
+    '2:4-18 -> 295:2-18',
+    '2:4-18 -> 296:2-11',
+    '2:4-18 -> 297:2-12',
+    '2:18 -> 298:0',
+    '2:4 -> 300:0',
+    '2:18 -> 302:1',
+    '2:4 -> 304:0',
+    '2:4-18 -> 305:2-18',
+    '2:18 -> 306:0',
+    '2:4 -> 308:0',
+    '2:18 -> 311:1',
+    '2:4 -> 313:0',
+    '2:4-18 -> 315:2-20',
+    '2:4-18 -> 316:2-24',
+    '2:18 -> 317:0',
+    '2:4 -> 319:0',
+    '2:18 -> 321:1',
+    '2:4 -> 323:0',
+    '2:4-18 -> 325:2-17',
+    '2:18 -> 326:0',
+    '2:4 -> 328:0',
+    '2:18 -> 330:1',
+    '2:4 -> 331:0',
+    '2:4-18 -> 332:2-17',
+    '2:18 -> 333:0',
+    '2:4 -> 335:0',
+    '2:18 -> 339:1',
+    '2:4 -> 341:0',
+    '2:4-18 -> 349:2-24',
+    '2:4-18 -> 350:2-32',
+    '2:18 -> 351:0',
+    '2:4 -> 353:0',
+    '2:18 -> 355:1',
+    '2:4 -> 357:0',
+    '2:4-18 -> 359:2-17',
+    '2:4-18 -> 360:2-14',
+    '2:18 -> 361:0',
+    '2:4 -> 363:0',
+    '2:18 -> 365:1',
+    '2:4 -> 367:0',
+    '2:4-18 -> 368:2-15',
+    '2:18 -> 369:0',
+    '2:4 -> 371:0',
+    '2:4-18 -> 372:2-21',
+    '2:4-18 -> 373:2-21',
+    '2:4-18 -> 374:2-16',
+    '2:4-18 -> 375:2-16',
+    '2:4-18 -> 376:2-16',
+    '2:4-18 -> 377:2-17',
+    '2:4-18 -> 378:2-17',
+    '2:4-18 -> 379:2-15',
+    '2:4-18 -> 380:2-15',
+    '2:4-18 -> 381:2-20',
+    '2:4-18 -> 382:2-40',
+    '2:4-18 -> 383:2-17',
+    '2:4-18 -> 384:2-22',
+    '2:4-18 -> 385:2-24',
+    '2:4-18 -> 386:2-25',
+    '2:4-18 -> 387:2-26',
+    '2:4-18 -> 388:2-20',
+    '2:4-18 -> 389:2-29',
+    '2:4-18 -> 390:2-30',
+    '2:4-18 -> 391:2-40',
+    '2:4-18 -> 392:2-36',
+    '2:4-18 -> 393:2-29',
+    '2:4-18 -> 394:2-24',
+    '2:4-18 -> 395:2-32',
+    '2:4-18 -> 396:2-14',
+    '2:4-18 -> 397:2-20',
+    '2:4-18 -> 398:2-18',
+    '2:4-18 -> 399:2-19',
+    '2:4-18 -> 400:2-20',
+    '2:4-18 -> 401:2-16',
+    '2:4-18 -> 402:2-18',
+    '2:4-18 -> 403:2-15',
+    '2:4-18 -> 404:2-21',
+    '2:4-18 -> 405:2-23',
+    '2:4-18 -> 406:2-29',
+    '2:4-18 -> 407:2-27',
+    '2:4-18 -> 408:2-28',
+    '2:4-18 -> 409:2-29',
+    '2:4-18 -> 410:2-25',
+    '2:4-18 -> 411:2-26',
+    '2:4-18 -> 412:2-27',
+    '2:4 -> 413:2',
+    '2:18 -> 414:0',
+  ])
+})
+
+it('utilities have source maps', async () => {
+  let config = {
+    content: [{ raw: `text-red-500` }],
+  }
+
+  let input = css`
+    @tailwind utilities;
+  `
+
+  let result = await run(input, config)
+  let { sources, annotations } = parseSourceMaps(result)
+
+  // All CSS generated by Tailwind CSS should be annotated with source maps
+  // And always be able to point to the original source file
+  expect(sources).not.toContain('<no source>')
+  expect(sources.length).toBe(1)
+
+  // It would make the tests nicer to read and write
+  expect(annotations).toStrictEqual(['2:4 -> 1:0', '2:4-23 -> 2:4-24', '2:4 -> 3:4', '2:23 -> 4:0'])
+})
+
+it('components have source maps', async () => {
+  let config = {
+    content: [{ raw: `container` }],
+  }
+
+  let input = css`
+    @tailwind components;
+  `
+
+  let result = await run(input, config)
+  let { sources, annotations } = parseSourceMaps(result)
+
+  // All CSS generated by Tailwind CSS should be annotated with source maps
+  // And always be able to point to the original source file
+  expect(sources).not.toContain('<no source>')
+  expect(sources.length).toBe(1)
+
+  // It would make the tests nicer to read and write
+  expect(annotations).toStrictEqual([
+    '2:4 -> 1:0',
+    '2:4 -> 2:4',
+    '2:24 -> 3:0',
+    '2:4 -> 4:0',
+    '2:4 -> 5:4',
+    '2:4 -> 6:8',
+    '2:24 -> 7:4',
+    '2:24 -> 8:0',
+    '2:4 -> 9:0',
+    '2:4 -> 10:4',
+    '2:4 -> 11:8',
+    '2:24 -> 12:4',
+    '2:24 -> 13:0',
+    '2:4 -> 14:0',
+    '2:4 -> 15:4',
+    '2:4 -> 16:8',
+    '2:24 -> 17:4',
+    '2:24 -> 18:0',
+    '2:4 -> 19:0',
+    '2:4 -> 20:4',
+    '2:4 -> 21:8',
+    '2:24 -> 22:4',
+    '2:24 -> 23:0',
+    '2:4 -> 24:0',
+    '2:4 -> 25:4',
+    '2:4 -> 26:8',
+    '2:24 -> 27:4',
+    '2:24 -> 28:0',
+  ])
+})
diff --git a/tests/util/run.js b/tests/util/run.js
--- a/tests/util/run.js
+++ b/tests/util/run.js
@@ -5,6 +5,14 @@ import tailwind from '../../src'
 export * from './strings'
 export * from './defaults'
 
+let map = JSON.stringify({
+  version: 3,
+  file: null,
+  sources: [],
+  names: [],
+  mappings: '',
+})
+
 export function run(input, config, plugin = tailwind) {
   let { currentTestName } = expect.getState()
 
@@ -12,3 +20,14 @@ export function run(input, config, plugin = tailwind) {
     from: `${path.resolve(__filename)}?test=${currentTestName}`,
   })
 }
+
+export function runWithSourceMaps(input, config, plugin = tailwind) {
+  let { currentTestName } = expect.getState()
+
+  return postcss(plugin(config)).process(input, {
+    from: `${path.resolve(__filename)}?test=${currentTestName}`,
+    map: {
+      prev: map,
+    },
+  })
+}
diff --git a/tests/util/source-maps.js b/tests/util/source-maps.js
new file mode 100644
--- /dev/null
+++ b/tests/util/source-maps.js
@@ -0,0 +1,79 @@
+import { SourceMapConsumer } from 'source-map-js'
+
+/**
+ * Parse the source maps from a PostCSS result
+ *
+ * @param {import('postcss').Result} result
+ */
+export function parseSourceMaps(result) {
+  const map = result.map.toJSON()
+
+  return {
+    sources: map.sources,
+    annotations: annotatedMappings(map),
+  }
+}
+
+/**
+ * An string annotation that represents a source map
+ *
+ * It's not meant to be exhaustive just enough to
+ * verify that the source map is working and that
+ * lines are mapped back to the original source
+ *
+ * Including when using @apply with multiple classes
+ *
+ * @param {import('source-map-js').RawSourceMap} map
+ */
+function annotatedMappings(map) {
+  const smc = new SourceMapConsumer(map)
+  const annotations = {}
+
+  smc.eachMapping((mapping) => {
+    let annotation = (annotations[mapping.generatedLine] = annotations[mapping.generatedLine] || {
+      ...mapping,
+
+      original: {
+        start: [mapping.originalLine, mapping.originalColumn],
+        end: [mapping.originalLine, mapping.originalColumn],
+      },
+
+      generated: {
+        start: [mapping.generatedLine, mapping.generatedColumn],
+        end: [mapping.generatedLine, mapping.generatedColumn],
+      },
+    })
+
+    annotation.generated.end[0] = mapping.generatedLine
+    annotation.generated.end[1] = mapping.generatedColumn
+
+    annotation.original.end[0] = mapping.originalLine
+    annotation.original.end[1] = mapping.originalColumn
+  })
+
+  return Object.values(annotations).map((annotation) => {
+    return `${formatRange(annotation.original)} -> ${formatRange(annotation.generated)}`
+  })
+}
+
+/**
+ * @param {object} range
+ * @param {[number, number]} range.start
+ * @param {[number, number]} range.end
+ */
+function formatRange(range) {
+  if (range.start[0] === range.end[0]) {
+    // This range is on the same line
+    // and the columns are the same
+    if (range.start[1] === range.end[1]) {
+      return `${range.start[0]}:${range.start[1]}`
+    }
+
+    // This range is on the same line
+    // but the columns are different
+    return `${range.start[0]}:${range.start[1]}-${range.end[1]}`
+  }
+
+  // This range spans multiple lines
+  return `${range.start[0]}:${range.start[1]}-${range.end[0]}:${range.end[1]}`
+}