diff --git a/__tests__/fixtures/purge-example.html b/__tests__/fixtures/purge-example.html
new file mode 100644
index 000000000..5e07ba579
--- /dev/null
+++ b/__tests__/fixtures/purge-example.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/__tests__/fixtures/tailwind-output-purged.css b/__tests__/fixtures/tailwind-output-purged.css
new file mode 100644
index 000000000..d1ee8726a
--- /dev/null
+++ b/__tests__/fixtures/tailwind-output-purged.css
@@ -0,0 +1,628 @@
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+/* Document
+ ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
+ */
+
+html {
+ line-height: 1.15; /* 1 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/* Sections
+ ========================================================================== */
+
+/**
+ * Remove the margin in all browsers.
+ */
+
+body {
+ margin: 0;
+}
+
+/**
+ * Render the `main` element consistently in IE.
+ */
+
+main {
+ display: block;
+}
+
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+hr {
+ box-sizing: content-box; /* 1 */
+ height: 0; /* 1 */
+ overflow: visible; /* 2 */
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+pre {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * Remove the gray background on active links in IE 10.
+ */
+
+a {
+ background-color: transparent;
+}
+
+/**
+ * 1. Remove the bottom border in Chrome 57-
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+abbr[title] {
+ border-bottom: none; /* 1 */
+ text-decoration: underline; /* 2 */
+ text-decoration: underline dotted; /* 2 */
+}
+
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+code,
+kbd,
+samp {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/**
+ * Add the correct font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Remove the border on images inside links in IE 10.
+ */
+
+img {
+ border-style: none;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * 1. Change the font styles in all browsers.
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 1 */
+ line-height: 1.15; /* 1 */
+ margin: 0; /* 2 */
+}
+
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+button,
+input { /* 1 */
+ overflow: visible;
+}
+
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+button,
+select { /* 1 */
+ text-transform: none;
+}
+
+/**
+ * Correct the inability to style clickable types in iOS and Safari.
+ */
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+ -webkit-appearance: button;
+}
+
+/**
+ * Remove the inner border and padding in Firefox.
+ */
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
+}
+
+/**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+ outline: 1px dotted ButtonText;
+}
+
+/**
+ * Correct the padding in Firefox.
+ */
+
+fieldset {
+ padding: 0.35em 0.75em 0.625em;
+}
+
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ * `fieldset` elements in all browsers.
+ */
+
+legend {
+ box-sizing: border-box; /* 1 */
+ color: inherit; /* 2 */
+ display: table; /* 1 */
+ max-width: 100%; /* 1 */
+ padding: 0; /* 3 */
+ white-space: normal; /* 1 */
+}
+
+/**
+ * Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+progress {
+ vertical-align: baseline;
+}
+
+/**
+ * Remove the default vertical scrollbar in IE 10+.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * 1. Add the correct box sizing in IE 10.
+ * 2. Remove the padding in IE 10.
+ */
+
+[type="checkbox"],
+[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ outline-offset: -2px; /* 2 */
+}
+
+/**
+ * Remove the inner padding in Chrome and Safari on macOS.
+ */
+
+[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button; /* 1 */
+ font: inherit; /* 2 */
+}
+
+/* Interactive
+ ========================================================================== */
+
+/*
+ * Add the correct display in Edge, IE 10+, and Firefox.
+ */
+
+details {
+ display: block;
+}
+
+/*
+ * Add the correct display in all browsers.
+ */
+
+summary {
+ display: list-item;
+}
+
+/* Misc
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 10+.
+ */
+
+template {
+ display: none;
+}
+
+/**
+ * Add the correct display in IE 10.
+ */
+
+[hidden] {
+ display: none;
+}
+
+/**
+ * Manually forked from SUIT CSS Base: https://github.com/suitcss/base
+ * A thin layer on top of normalize.css that provides a starting point more
+ * suitable for web applications.
+ */
+
+/**
+ * Removes the default spacing and border for appropriate elements.
+ */
+
+blockquote,
+dl,
+dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
+p,
+pre {
+ margin: 0;
+}
+
+button {
+ background-color: transparent;
+ background-image: none;
+ padding: 0;
+}
+
+/**
+ * Work around a Firefox/IE bug where the transparent `button` background
+ * results in a loss of the default `button` focus styles.
+ */
+
+button:focus {
+ outline: 1px dotted;
+ outline: 5px auto -webkit-focus-ring-color;
+}
+
+fieldset {
+ margin: 0;
+ padding: 0;
+}
+
+ol,
+ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+/**
+ * Tailwind custom reset styles
+ */
+
+/**
+ * 1. Use the user's configured `sans` font-family (with Tailwind's default
+ * sans-serif font stack as a fallback) as a sane default.
+ * 2. Use Tailwind's default "normal" line-height so the user isn't forced
+ * to override it to ensure consistency even when using the default theme.
+ */
+
+html {
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 1 */
+ line-height: 1.5; /* 2 */
+}
+
+/**
+ * 1. Prevent padding and border from affecting element width.
+ *
+ * We used to set this in the html element and inherit from
+ * the parent element for everything else. This caused issues
+ * in shadow-dom-enhanced elements like where the content
+ * is wrapped by a div with box-sizing set to `content-box`.
+ *
+ * https://github.com/mozdevs/cssremedy/issues/4
+ *
+ *
+ * 2. Allow adding a border to an element by just adding a border-width.
+ *
+ * By default, the way the browser specifies that an element should have no
+ * border is by setting it's border-style to `none` in the user-agent
+ * stylesheet.
+ *
+ * In order to easily add borders to elements by just setting the `border-width`
+ * property, we change the default border-style for all elements to `solid`, and
+ * use border-width to hide them instead. This way our `border` utilities only
+ * need to set the `border-width` property instead of the entire `border`
+ * shorthand, making our border utilities much more straightforward to compose.
+ *
+ * https://github.com/tailwindcss/tailwindcss/pull/116
+ */
+
+*,
+::before,
+::after {
+ box-sizing: border-box; /* 1 */
+ border-width: 0; /* 2 */
+ border-style: solid; /* 2 */
+ border-color: #e2e8f0; /* 2 */
+}
+
+/*
+ * Ensure horizontal rules are visible by default
+ */
+
+hr {
+ border-top-width: 1px;
+}
+
+/**
+ * Undo the `border-style: none` reset that Normalize applies to images so that
+ * our `border-{width}` utilities have the expected effect.
+ *
+ * The Normalize reset is unnecessary for us since we default the border-width
+ * to 0 on all elements.
+ *
+ * https://github.com/tailwindcss/tailwindcss/issues/362
+ */
+
+img {
+ border-style: solid;
+}
+
+textarea {
+ resize: vertical;
+}
+
+input::placeholder,
+textarea::placeholder {
+ color: #a0aec0;
+}
+
+button,
+[role="button"] {
+ cursor: pointer;
+}
+
+table {
+ border-collapse: collapse;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-size: inherit;
+ font-weight: inherit;
+}
+
+/**
+ * Reset links to optimize for opt-in styling instead of
+ * opt-out.
+ */
+
+a {
+ color: inherit;
+ text-decoration: inherit;
+}
+
+/**
+ * Reset form element properties that are easy to forget to
+ * style explicitly so you don't inadvertently introduce
+ * styles that deviate from your design system. These styles
+ * supplement a partial reset that is already applied by
+ * normalize.css.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ padding: 0;
+ line-height: inherit;
+ color: inherit;
+}
+
+/**
+ * Use the configured 'mono' font family for elements that
+ * are expected to be rendered with a monospace font, falling
+ * back to the system monospace stack if there is no configured
+ * 'mono' font family.
+ */
+
+pre,
+code,
+kbd,
+samp {
+ font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+}
+
+/**
+ * Make replaced elements `display: block` by default as that's
+ * the behavior you want almost all of the time. Inspired by
+ * CSS Remedy, with `svg` added as well.
+ *
+ * https://github.com/mozdevs/cssremedy/issues/14
+ */
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+ display: block;
+ vertical-align: middle;
+}
+
+/**
+ * Constrain images and videos to the parent width and preserve
+ * their instrinsic aspect ratio.
+ *
+ * https://github.com/mozdevs/cssremedy/issues/14
+ */
+
+img,
+video {
+ max-width: 100%;
+ height: auto;
+}
+
+.bg-red-500 {
+ --bg-opacity: 1;
+ background-color: #f56565;
+ background-color: rgba(245, 101, 101, var(--bg-opacity));
+}
+
+.bg-black\! {
+ --bg-opacity: 1;
+ background-color: #000;
+ background-color: rgba(0, 0, 0, var(--bg-opacity));
+}
+
+.block {
+ display: block;
+}
+
+.font-\%\#\$\@ {
+ font-family: Comic Sans;
+}
+
+.h-screen {
+ height: 100vh;
+}
+
+.w-\(1\/2\+8\) {
+ width: calc(50% + 2rem);
+}
+
+.w-1\/2 {
+ width: 50%;
+}
+
+.example {
+ font-weight: 700;
+ color: #f56565;
+}
+
+@media (min-width: 768px) {
+ .md\:bg-blue-300 {
+ --bg-opacity: 1;
+ background-color: #90cdf4;
+ background-color: rgba(144, 205, 244, var(--bg-opacity));
+ }
+
+ .md\:flow-root {
+ display: flow-root;
+ }
+}
diff --git a/__tests__/responsiveAtRule.test.js b/__tests__/responsiveAtRule.test.js
index 63c6319a4..a59dff31f 100644
--- a/__tests__/responsiveAtRule.test.js
+++ b/__tests__/responsiveAtRule.test.js
@@ -12,6 +12,8 @@ test('it can generate responsive variants', () => {
.banana { color: yellow; }
.chocolate { color: brown; }
}
+
+ @tailwind screens;
`
const output = `
@@ -52,6 +54,8 @@ test('it can generate responsive variants with a custom separator', () => {
.banana { color: yellow; }
.chocolate { color: brown; }
}
+
+ @tailwind screens;
`
const output = `
@@ -92,6 +96,8 @@ test('it can generate responsive variants when classes have non-standard charact
.hover\\:banana { color: yellow; }
.chocolate-2\\.5 { color: brown; }
}
+
+ @tailwind screens;
`
const output = `
@@ -137,6 +143,8 @@ test('responsive variants are grouped', () => {
@responsive {
.chocolate { color: brown; }
}
+
+ @tailwind screens;
`
const output = `
@@ -181,6 +189,8 @@ test('it can generate responsive variants for nested at-rules', () => {
.grid\\:banana { color: blue; }
}
}
+
+ @tailwind screens;
`
const output = `
@@ -244,6 +254,8 @@ test('it can generate responsive variants for deeply nested at-rules', () => {
}
}
}
+
+ @tailwind screens;
`
const output = `
@@ -307,6 +319,8 @@ test('screen prefix is only applied to the last class in a selector', () => {
@responsive {
.banana li * .sandwich #foo > div { color: yellow; }
}
+
+ @tailwind screens;
`
const output = `
@@ -342,6 +356,8 @@ test('responsive variants are generated for all selectors in a rule', () => {
@responsive {
.foo, .bar { color: yellow; }
}
+
+ @tailwind screens;
`
const output = `
@@ -377,6 +393,8 @@ test('selectors with no classes cannot be made responsive', () => {
@responsive {
div { color: yellow; }
}
+
+ @tailwind screens;
`
expect.assertions(1)
return run(input, {
@@ -398,6 +416,8 @@ test('all selectors in a rule must contain classes', () => {
@responsive {
.foo, div { color: yellow; }
}
+
+ @tailwind screens;
`
expect.assertions(1)
return run(input, {
diff --git a/__tests__/sanity.test.js b/__tests__/sanity.test.js
index b27f1aae3..dd12478b6 100644
--- a/__tests__/sanity.test.js
+++ b/__tests__/sanity.test.js
@@ -77,3 +77,123 @@ it('generates the right CSS with implicit screen utilities', () => {
expect(result.css).toBe(expected)
})
})
+
+it('generates the right CSS when "important" is enabled', () => {
+ const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
+ const input = fs.readFileSync(inputPath, 'utf8')
+
+ return postcss([tailwind({ ...config, important: true })])
+ .process(input, { from: inputPath })
+ .then(result => {
+ const expected = fs.readFileSync(
+ path.resolve(`${__dirname}/fixtures/tailwind-output-important.css`),
+ 'utf8'
+ )
+
+ expect(result.css).toBe(expected)
+ })
+})
+
+it('purges unused classes', () => {
+ const OLD_NODE_ENV = process.env.NODE_ENV
+ process.env.NODE_ENV = 'production'
+ const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
+ const input = fs.readFileSync(inputPath, 'utf8')
+
+ return postcss([
+ tailwind({
+ ...config,
+ theme: {
+ extend: {
+ colors: {
+ 'black!': '#000',
+ },
+ spacing: {
+ '(1/2+8)': 'calc(50% + 2rem)',
+ },
+ minHeight: {
+ '(screen-100)': 'calc(100vh - 1rem)',
+ },
+ fontFamily: {
+ '%#$@': 'Comic Sans',
+ },
+ },
+ },
+ purge: [path.resolve(`${__dirname}/fixtures/**/*.html`)],
+ }),
+ ])
+ .process(input, { from: inputPath })
+ .then(result => {
+ process.env.NODE_ENV = OLD_NODE_ENV
+ const expected = fs.readFileSync(
+ path.resolve(`${__dirname}/fixtures/tailwind-output-purged.css`),
+ 'utf8'
+ )
+
+ expect(result.css).toBe(expected)
+ })
+})
+
+it('does not purge except in production', () => {
+ const OLD_NODE_ENV = process.env.NODE_ENV
+ process.env.NODE_ENV = 'development'
+ const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
+ const input = fs.readFileSync(inputPath, 'utf8')
+
+ return postcss([
+ tailwind({
+ ...config,
+ purge: [path.resolve(`${__dirname}/fixtures/**/*.html`)],
+ }),
+ ])
+ .process(input, { from: inputPath })
+ .then(result => {
+ process.env.NODE_ENV = OLD_NODE_ENV
+ const expected = fs.readFileSync(
+ path.resolve(`${__dirname}/fixtures/tailwind-output.css`),
+ 'utf8'
+ )
+
+ expect(result.css).toBe(expected)
+ })
+})
+
+it('purges outside of production if explicitly enabled', () => {
+ const OLD_NODE_ENV = process.env.NODE_ENV
+ process.env.NODE_ENV = 'development'
+ const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`)
+ const input = fs.readFileSync(inputPath, 'utf8')
+
+ return postcss([
+ tailwind({
+ ...config,
+ theme: {
+ extend: {
+ colors: {
+ 'black!': '#000',
+ },
+ spacing: {
+ '(1/2+8)': 'calc(50% + 2rem)',
+ },
+ minHeight: {
+ '(screen-100)': 'calc(100vh - 1rem)',
+ },
+ fontFamily: {
+ '%#$@': 'Comic Sans',
+ },
+ },
+ },
+ purge: { enabled: true, paths: [path.resolve(`${__dirname}/fixtures/**/*.html`)] },
+ }),
+ ])
+ .process(input, { from: inputPath })
+ .then(result => {
+ process.env.NODE_ENV = OLD_NODE_ENV
+ const expected = fs.readFileSync(
+ path.resolve(`${__dirname}/fixtures/tailwind-output-purged.css`),
+ 'utf8'
+ )
+
+ expect(result.css).toBe(expected)
+ })
+})
diff --git a/package.json b/package.json
index f89bd7319..b5bc5f105 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
"rimraf": "^3.0.0"
},
"dependencies": {
+ "@fullhuman/postcss-purgecss": "^2.1.2",
"autoprefixer": "^9.4.5",
"bytes": "^3.0.0",
"chalk": "^4.0.0",
diff --git a/scripts/rebuildFixtures.js b/scripts/rebuildFixtures.js
index 707b9920b..071c3664d 100644
--- a/scripts/rebuildFixtures.js
+++ b/scripts/rebuildFixtures.js
@@ -39,6 +39,29 @@ Promise.all([
to: '__tests__/fixtures/tailwind-output-important.css',
config: { important: true },
}),
+ build({
+ from: '__tests__/fixtures/tailwind-input.css',
+ to: '__tests__/fixtures/tailwind-output-purged.css',
+ config: {
+ theme: {
+ extend: {
+ colors: {
+ 'black!': '#000',
+ },
+ spacing: {
+ '(1/2+8)': 'calc(50% + 2rem)',
+ },
+ minHeight: {
+ '(screen-100)': 'calc(100vh - 1rem)',
+ },
+ fontFamily: {
+ '%#$@': 'Comic Sans',
+ },
+ },
+ },
+ purge: { enabled: true, paths: ['./__tests__/fixtures/**/*.html'] },
+ },
+ }),
]).then(() => {
console.log('\nFinished rebuilding fixtures.')
console.log(
diff --git a/src/lib/purgeUnusedUtilities.js b/src/lib/purgeUnusedUtilities.js
new file mode 100644
index 000000000..1715d94e9
--- /dev/null
+++ b/src/lib/purgeUnusedUtilities.js
@@ -0,0 +1,56 @@
+import _ from 'lodash'
+import postcss from 'postcss'
+import purgecss from '@fullhuman/postcss-purgecss'
+
+export default function purgeUnusedUtilities(config) {
+ const purgeEnabled =
+ _.get(config, 'purge.enabled', false) === true ||
+ (config.purge !== undefined && process.env.NODE_ENV === 'production')
+
+ if (!purgeEnabled) {
+ return function(css) {
+ css.walkComments(comment => {
+ switch (comment.text.trim()) {
+ case 'tailwind start components':
+ case 'tailwind start utilities':
+ case 'tailwind start screens':
+ case 'tailwind end components':
+ case 'tailwind end utilities':
+ case 'tailwind end screens':
+ comment.remove()
+ break
+ default:
+ break
+ }
+ })
+ }
+ }
+
+ return postcss([
+ function(css) {
+ css.prepend(postcss.comment({ text: 'purgecss start ignore' }))
+ css.append(postcss.comment({ text: 'purgecss end ignore' }))
+
+ css.walkComments(comment => {
+ switch (comment.text.trim()) {
+ case 'tailwind start components':
+ case 'tailwind start utilities':
+ case 'tailwind start screens':
+ comment.text = 'purgecss end ignore'
+ break
+ case 'tailwind end components':
+ case 'tailwind end utilities':
+ case 'tailwind end screens':
+ comment.text = 'purgecss start ignore'
+ break
+ default:
+ break
+ }
+ })
+ },
+ purgecss({
+ content: Array.isArray(config.purge) ? config.purge : config.purge.paths,
+ defaultExtractor: content => content.match(/[^<>"'`\s]+(? i.nodes.length !== 0)
- if (!hasScreenRules) {
- return
- }
-
- let includesScreensExplicitly = false
-
css.walkAtRules('tailwind', atRule => {
- if (atRule.params === 'screens') {
- atRule.replaceWith(finalRules)
- includesScreensExplicitly = true
+ if (atRule.params !== 'screens') {
+ return
}
- })
- if (!includesScreensExplicitly) {
- css.append(finalRules)
- return
- }
+ if (hasScreenRules) {
+ atRule.before(finalRules)
+ }
+
+ atRule.remove()
+ })
}
}
diff --git a/src/lib/substituteTailwindAtRules.js b/src/lib/substituteTailwindAtRules.js
index d82a28692..1607f388b 100644
--- a/src/lib/substituteTailwindAtRules.js
+++ b/src/lib/substituteTailwindAtRules.js
@@ -40,6 +40,8 @@ export default function(
}
})
+ let includesScreensExplicitly = false
+
css.walkAtRules('tailwind', atRule => {
if (atRule.params === 'preflight') {
// prettier-ignore
@@ -52,14 +54,32 @@ export default function(
}
if (atRule.params === 'components') {
+ atRule.before(postcss.comment({ text: 'tailwind start components' }))
atRule.before(updateSource(pluginComponents, atRule.source))
+ atRule.after(postcss.comment({ text: 'tailwind end components' }))
atRule.remove()
}
if (atRule.params === 'utilities') {
+ atRule.before(postcss.comment({ text: 'tailwind start utilities' }))
atRule.before(updateSource(pluginUtilities, atRule.source))
+ atRule.after(postcss.comment({ text: 'tailwind end utilities' }))
atRule.remove()
}
+
+ if (atRule.params === 'screens') {
+ includesScreensExplicitly = true
+ atRule.before(postcss.comment({ text: 'tailwind start screens' }))
+ atRule.after(postcss.comment({ text: 'tailwind end screens' }))
+ }
})
+
+ if (!includesScreensExplicitly) {
+ css.append([
+ postcss.comment({ text: 'tailwind start screens' }),
+ postcss.atRule({ name: 'tailwind', params: 'screens' }),
+ postcss.comment({ text: 'tailwind end screens' }),
+ ])
+ }
}
}
diff --git a/src/processTailwindFeatures.js b/src/processTailwindFeatures.js
index 61fecaf12..2059e8c9a 100644
--- a/src/processTailwindFeatures.js
+++ b/src/processTailwindFeatures.js
@@ -7,6 +7,7 @@ import substituteVariantsAtRules from './lib/substituteVariantsAtRules'
import substituteResponsiveAtRules from './lib/substituteResponsiveAtRules'
import substituteScreenAtRules from './lib/substituteScreenAtRules'
import substituteClassApplyAtRules from './lib/substituteClassApplyAtRules'
+import purgeUnusedUtilities from './lib/purgeUnusedUtilities'
import corePlugins from './corePlugins'
import processPlugins from './util/processPlugins'
@@ -23,6 +24,7 @@ export default function(getConfig) {
substituteResponsiveAtRules(config),
substituteScreenAtRules(config),
substituteClassApplyAtRules(config, processedPlugins.utilities),
+ purgeUnusedUtilities(config),
]).process(css, { from: _.get(css, 'source.input.file') })
}
}
diff --git a/yarn.lock b/yarn.lock
index 238bd50b8..e2d50ac93 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -845,6 +845,14 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
+"@fullhuman/postcss-purgecss@^2.1.2":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/@fullhuman/postcss-purgecss/-/postcss-purgecss-2.1.2.tgz#8fe4d4ae2b58214b5452cb490a31c7146517442f"
+ integrity sha512-Jf34YVBK9GtXTblpu0svNUJdA7rTQoRMz+yEJe6mwTnXDIGipWLzaX/VgU/x6IPC6WvU5SY/XlawwqhxoyFPTg==
+ dependencies:
+ postcss "7.0.27"
+ purgecss "^2.1.2"
+
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b"
@@ -1715,6 +1723,11 @@ commander@^4.0.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-4.0.1.tgz#b67622721785993182e807f4883633e6401ba53c"
integrity sha512-IPF4ouhCP+qdlcmCedhxX4xiGBPyigb8v5NeUp+0LyhwLgxMqyp3S0vl7TAPfS/hiP7FC3caI/PB9lTmP8r1NA==
+commander@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
+ integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
+
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -4266,6 +4279,15 @@ postcss-value-parser@^4.0.3:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d"
integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==
+postcss@7.0.27, postcss@^7.0.11, postcss@^7.0.18, postcss@^7.0.21, postcss@^7.0.27:
+ version "7.0.27"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9"
+ integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==
+ dependencies:
+ chalk "^2.4.2"
+ source-map "^0.6.1"
+ supports-color "^6.1.0"
+
postcss@^6.0.9:
version "6.0.23"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
@@ -4275,15 +4297,6 @@ postcss@^6.0.9:
source-map "^0.6.1"
supports-color "^5.4.0"
-postcss@^7.0.11, postcss@^7.0.18, postcss@^7.0.21, postcss@^7.0.27:
- version "7.0.27"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9"
- integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==
- dependencies:
- chalk "^2.4.2"
- source-map "^0.6.1"
- supports-color "^6.1.0"
-
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -4362,6 +4375,16 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+purgecss@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-2.1.2.tgz#96f666d04c56705208aaa1a544b5f22e13828955"
+ integrity sha512-5oDBxiT9VonwKmEMohPFRFZrj8fdSVKxHPwq7G5Rx/2pXicZFJu+D4m5bb3NuV0sSK3ooNxq5jFIwwHzifP5FA==
+ dependencies:
+ commander "^5.0.0"
+ glob "^7.0.0"
+ postcss "7.0.27"
+ postcss-selector-parser "^6.0.2"
+
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"