Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
tailwindcss/nesting
plugin (#4673)
* add nesting plugin * rename @tailwindcss/nesting to tailwindcss/nesting * ignore the built `nesting` plugin * add a postcss7 compat version * include `nesting` plugin when publishing * add `build-plugins` script This will allow us to keep the plugins in their dedicated folders + tests + postcss7 compatibility files. However, when we copy over the plugins to the root. For example `plugins/nesting/` -> `nesting/` we skip files like `.test.js` and `.postcss7.js`. * build plugins when running `prepublishOnly` * improve compat mode We will use a glob so that we can move all *.postcss7.* files to just *.* likewise we will also backup to *.* to *.postcss8.* for restoring purposes. Concrete example: - Current state: - index.js // PostCSS 8 implementation - index.postcss7.js // PostCSS 7 implementation - Run "compat" - index.js // PostCSS 7 implementation - index.postcss7.js // PostCSS 7 implementation - index.postcss8.js // PostCSS 8 implementation (Backup of original) - Run "compat:restore" - index.js // PostCSS 8 implementation - index.postcss7.js // PostCSS 7 implementation - X index.postcss8.js // PostCSS 8 implementation (Removed) * Update README.md * ensure we `npm install` before publishing Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
- Loading branch information
1 parent
243e881
commit f63b453
Showing
9 changed files
with
373 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,5 +10,8 @@ index.html | |
yarn.lock | ||
yarn-error.log | ||
|
||
# "External" plugins | ||
/nesting | ||
|
||
# Perf related files | ||
isolate*.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# tailwindcss/nesting | ||
|
||
This is a PostCSS plugin that wraps [postcss-nested](https://github.com/postcss/postcss-nested) or [postcss-nesting](https://github.com/jonathantneal/postcss-nesting) and acts as a compatibility layer to make sure your nesting plugin of choice properly understands Tailwind's custom syntax like `@apply` and `@screen`. | ||
|
||
Add it to your PostCSS configuration, somewhere before Tailwind itself: | ||
|
||
```js | ||
// postcss.config.js | ||
module.exports = { | ||
plugins: [ | ||
require('postcss-import'), | ||
require('tailwindcss/nesting'), | ||
require('tailwindcss'), | ||
require('autoprefixer'), | ||
] | ||
} | ||
``` | ||
|
||
By default, it uses the [postcss-nested](https://github.com/postcss/postcss-nested) plugin under the hood, which uses a Sass-like syntax and is the plugin that powers nesting support in the [Tailwind CSS plugin API](https://tailwindcss.com/docs/plugins#css-in-js-syntax). | ||
|
||
If you'd rather use [postcss-nesting](https://github.com/jonathantneal/postcss-nesting) (which is based on the work-in-progress [CSS Nesting](https://drafts.csswg.org/css-nesting-1/) specification), first install the plugin alongside: | ||
|
||
```shell | ||
npm install postcss-nesting | ||
``` | ||
|
||
Then pass the plugin itself as an argument to `tailwindcss/nesting` in your PostCSS configuration: | ||
|
||
```js | ||
// postcss.config.js | ||
module.exports = { | ||
plugins: [ | ||
require('postcss-import'), | ||
require('tailwindcss/nesting')(require('postcss-nesting')), | ||
require('tailwindcss'), | ||
require('autoprefixer'), | ||
] | ||
} | ||
``` | ||
|
||
This can also be helpful if for whatever reason you need to use a very specific version of `postcss-nested` and want to override the version we bundle with `tailwindcss/nesting` itself. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
let nesting = require('./plugin') | ||
|
||
module.exports = (opts) => { | ||
return { | ||
postcssPlugin: 'tailwindcss/nesting', | ||
Once(root, { result }) { | ||
return nesting(opts)(root, result) | ||
}, | ||
} | ||
} | ||
|
||
module.exports.postcss = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
let postcss = require('postcss') | ||
let nesting = require('./plugin') | ||
|
||
module.exports = postcss.plugin('tailwindcss/nesting', nesting) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
let postcss = require('postcss') | ||
let postcssNested = require('postcss-nested') | ||
let plugin = require('.') | ||
|
||
it('should be possible to load a custom nesting plugin', async () => { | ||
let input = css` | ||
.foo { | ||
color: black; | ||
@screen md { | ||
color: blue; | ||
} | ||
} | ||
` | ||
|
||
expect( | ||
await run(input, function (root) { | ||
root.walkRules((rule) => { | ||
rule.selector += '-modified' | ||
}) | ||
}) | ||
).toMatchCss(css` | ||
.foo-modified { | ||
color: black; | ||
@media screen(md) { | ||
color: blue; | ||
} | ||
} | ||
`) | ||
}) | ||
|
||
it('should be possible to load a custom nesting plugin by name (string) instead', async () => { | ||
let input = css` | ||
.foo { | ||
color: black; | ||
@screen md { | ||
color: blue; | ||
} | ||
} | ||
` | ||
|
||
expect(await run(input, 'postcss-nested')).toMatchCss(css` | ||
.foo { | ||
color: black; | ||
} | ||
@media screen(md) { | ||
.foo { | ||
color: blue; | ||
} | ||
} | ||
`) | ||
}) | ||
|
||
it('should default to the bundled postcss-nested plugin (no options)', async () => { | ||
let input = css` | ||
.foo { | ||
color: black; | ||
@screen md { | ||
color: blue; | ||
} | ||
} | ||
` | ||
|
||
expect(await run(input)).toMatchCss(css` | ||
.foo { | ||
color: black; | ||
} | ||
@media screen(md) { | ||
.foo { | ||
color: blue; | ||
} | ||
} | ||
`) | ||
}) | ||
|
||
it('should default to the bundled postcss-nested plugin (empty ooptions)', async () => { | ||
let input = css` | ||
.foo { | ||
color: black; | ||
@screen md { | ||
color: blue; | ||
} | ||
} | ||
` | ||
|
||
expect(await run(input, {})).toMatchCss(css` | ||
.foo { | ||
color: black; | ||
} | ||
@media screen(md) { | ||
.foo { | ||
color: blue; | ||
} | ||
} | ||
`) | ||
}) | ||
|
||
test('@screen rules are replaced with media queries', async () => { | ||
let input = css` | ||
.foo { | ||
color: black; | ||
@screen md { | ||
color: blue; | ||
} | ||
} | ||
` | ||
|
||
expect(await run(input, postcssNested)).toMatchCss(css` | ||
.foo { | ||
color: black; | ||
} | ||
@media screen(md) { | ||
.foo { | ||
color: blue; | ||
} | ||
} | ||
`) | ||
}) | ||
|
||
test('@screen rules can work with `@apply`', async () => { | ||
let input = css` | ||
.foo { | ||
@apply bg-black; | ||
@screen md { | ||
@apply bg-blue-500; | ||
} | ||
} | ||
` | ||
|
||
expect(await run(input, postcssNested)).toMatchCss(css` | ||
.foo { | ||
@apply bg-black; | ||
} | ||
@media screen(md) { | ||
.foo { | ||
@apply bg-blue-500; | ||
} | ||
} | ||
`) | ||
}) | ||
|
||
// --- | ||
|
||
function indentRecursive(node, indent = 0) { | ||
node.each && | ||
node.each((child, i) => { | ||
if (!child.raws.before || child.raws.before.includes('\n')) { | ||
child.raws.before = `\n${node.type !== 'rule' && i > 0 ? '\n' : ''}${' '.repeat(indent)}` | ||
} | ||
child.raws.after = `\n${' '.repeat(indent)}` | ||
indentRecursive(child, indent + 1) | ||
}) | ||
} | ||
|
||
function formatNodes(root) { | ||
indentRecursive(root) | ||
if (root.first) { | ||
root.first.raws.before = '' | ||
} | ||
} | ||
|
||
async function run(input, options) { | ||
return ( | ||
await postcss([options === undefined ? plugin : plugin(options), formatNodes]).process(input, { | ||
from: undefined, | ||
}) | ||
).toString() | ||
} | ||
|
||
function css(templates) { | ||
return templates.join('') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
let postcss = require('postcss') | ||
let postcssNested = require('postcss-nested') | ||
|
||
module.exports = function nesting(opts = postcssNested) { | ||
return (root, result) => { | ||
root.walkAtRules('screen', (rule) => { | ||
rule.name = 'media' | ||
rule.params = `screen(${rule.params})` | ||
}) | ||
|
||
root.walkAtRules('apply', (rule) => { | ||
rule.before(postcss.decl({ prop: '__apply', value: rule.params })) | ||
rule.remove() | ||
}) | ||
|
||
let plugin = (() => { | ||
if (typeof opts === 'function') { | ||
return opts | ||
} | ||
|
||
if (typeof opts === 'string') { | ||
return require(opts) | ||
} | ||
|
||
if (Object.keys(opts).length <= 0) { | ||
return postcssNested | ||
} | ||
|
||
throw new Error('tailwindcss/nesting should be loaded with a nesting plugin.') | ||
})() | ||
|
||
postcss([plugin]).process(root, result.opts).sync() | ||
|
||
root.walkDecls('__apply', (decl) => { | ||
decl.before(postcss.atRule({ name: 'apply', params: decl.value })) | ||
decl.remove() | ||
}) | ||
|
||
return root | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
let fs = require('fs') | ||
let path = require('path') | ||
|
||
let plugins = fs.readdirSync(fromRootPath('plugins')) | ||
|
||
for (let plugin of plugins) { | ||
// Cleanup | ||
let pluginDest = fromRootPath(plugin) | ||
if (fs.existsSync(pluginDest)) { | ||
fs.rmdirSync(pluginDest, { recursive: true }) | ||
} | ||
|
||
// Copy plugin over | ||
copyFolder(fromRootPath('plugins', plugin), pluginDest, (file) => { | ||
// Ignore test files | ||
if (file.endsWith('.test.js')) return false | ||
// Ignore postcss7 files | ||
if (file.endsWith('.postcss7.js')) return false | ||
// Ignore postcss8 files | ||
if (file.endsWith('.postcss8.js')) return false | ||
|
||
return true | ||
}) | ||
} | ||
|
||
// --- | ||
|
||
function fromRootPath(...paths) { | ||
return path.resolve(process.cwd(), ...paths) | ||
} | ||
|
||
function copy(fromPath, toPath) { | ||
fs.mkdirSync(path.dirname(toPath), { recursive: true }) // Ensure folder exists | ||
fs.copyFileSync(fromPath, toPath) | ||
} | ||
|
||
function copyFolder(fromPath, toPath, shouldCopy = () => true) { | ||
let stats = fs.statSync(fromPath) | ||
if (stats.isDirectory()) { | ||
let filesAndFolders = fs.readdirSync(fromPath) | ||
for (let file of filesAndFolders) { | ||
copyFolder(path.resolve(fromPath, file), path.resolve(toPath, file), shouldCopy) | ||
} | ||
} else if (shouldCopy(fromPath)) { | ||
copy(fromPath, toPath) | ||
} | ||
} |
Oops, something went wrong.