Skip to content

Commit

Permalink
add tests for the --postcss option in the new CLI (#4607)
Browse files Browse the repository at this point in the history
* add tests for the --postcss option in the new CLI

* add `oneOf` ability to the `arg()` functionality

By default, `arg()` doesn't have a way to define multiple types. We want
the possibility of using `--postcss` (Boolean) or `--postcss
./custom-path.js`. But by default this is not possible.

This commit will allow us to do a few things, mainly:
- Keep the same API using the `{ type: oneOf(String, Boolean), description: '...' }`
- Keep the `--help` output similar

What we did behind the scenes is make sure to put the non recognized
flags in the `_` arguments list. This is possible by doing `permissive:
true`. We then manually parse those and resolve the correct value.

* ensure that we can use a custom `--postcss ./with-custom-path.js`
  • Loading branch information
RobinMalfait authored Jun 10, 2021
1 parent b86aa5c commit f4799a3
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 8 deletions.
104 changes: 103 additions & 1 deletion integrations/tailwindcss-cli/tests/cli.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
let path = require('path')
let $ = require('../../execute')
let { css, html } = require('../../syntax')
let { css, html, javascript } = require('../../syntax')
let resolveToolRoot = require('../../resolve-tool-root')

let { readOutputFile, writeInputFile, cleanupFile, fileExists, removeFile } = require('../../io')({
Expand Down Expand Up @@ -157,6 +157,108 @@ describe('Build command', () => {
)
})

test('--postcss (postcss.config.js)', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)

let customConfig = javascript`
let path = require('path')
let postcss = require('postcss')
module.exports = {
plugins: [
function before(root, result) {
// Inject a custom component with @apply rules to prove that we run
// this _before_ the actual tailwind plugin.
let btn = postcss.parse('.btn { @apply bg-red-500 px-2 py-1 }')
root.append(btn.nodes)
},
function tailwindcss() {
return require(path.resolve('..', '..'))
},
function after(root, result) {
// Add '-after' to all the selectors
root.walkRules(rule => {
if (!rule.selector.startsWith('.')) return
rule.selector = rule.selector + '-after'
})
},
],
}
`

await writeInputFile('../postcss.config.js', customConfig)

await $(`${EXECUTABLE} --output ./dist/main.css --postcss`)

expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold-after {
font-weight: 700;
}
.btn-after {
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
`
)
})

test('--postcss (custom.postcss.config.js)', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)

let customConfig = javascript`
let path = require('path')
let postcss = require('postcss')
module.exports = {
plugins: [
function before(root, result) {
// Inject a custom component with @apply rules to prove that we run
// this _before_ the actual tailwind plugin.
let btn = postcss.parse('.btn { @apply bg-red-500 px-2 py-1 }')
root.append(btn.nodes)
},
function tailwindcss() {
return require(path.resolve('..', '..'))
},
function after(root, result) {
// Add '-after' to all the selectors
root.walkRules(rule => {
if (!rule.selector.startsWith('.')) return
rule.selector = rule.selector + '-after'
})
},
],
}
`

await writeInputFile('../custom.postcss.config.js', customConfig)

await $(`${EXECUTABLE} --output ./dist/main.css --postcss ./custom.postcss.config.js`)

expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold-after {
font-weight: 700;
}
.btn-after {
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
`
)
})

test('--help', async () => {
let { combined } = await $(`${EXECUTABLE} --help`)

Expand Down
89 changes: 82 additions & 7 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import path from 'path'
import arg from 'arg'
import fs from 'fs'
import postcssrc from 'postcss-load-config'
import { cosmiconfig } from 'cosmiconfig'
import loadPlugins from 'postcss-load-config/src/plugins' // Little bit scary, looking at private/internal API
import tailwindJit from './jit/processTailwindFeatures'
import tailwindAot from './processTailwindFeatures'
import resolveConfigInternal from '../resolveConfig'
Expand Down Expand Up @@ -104,6 +106,22 @@ function help({ message, usage, commands, options }) {
console.log()
}

function oneOf(...options) {
return Object.assign(
(value = true) => {
for (let option of options) {
let parsed = option(value)
if (parsed === value) {
return parsed
}
}

throw new Error('...')
},
{ manualParsing: true }
)
}

let commands = {
init: {
run: init,
Expand All @@ -123,7 +141,10 @@ let commands = {
'--watch': { type: Boolean, description: 'Watch for changes and rebuild as needed' },
'--jit': { type: Boolean, description: 'Build using JIT mode' },
'--purge': { type: String, description: 'Content paths to use for removing unused classes' },
'--postcss': { type: Boolean, description: 'Load custom PostCSS configuration' },
'--postcss': {
type: oneOf(String, Boolean),
description: 'Load custom PostCSS configuration',
},
'--minify': { type: Boolean, description: 'Minify the output' },
'--config': {
type: String,
Expand Down Expand Up @@ -191,13 +212,47 @@ let args = (() => {
try {
let result = arg(
Object.fromEntries(
Object.entries({ ...flags, ...sharedFlags }).map(([key, value]) => [
key,
typeof value === 'object' ? value.type : value,
])
)
Object.entries({ ...flags, ...sharedFlags })
.filter(([_key, value]) => !value?.type?.manualParsing)
.map(([key, value]) => [key, typeof value === 'object' ? value.type : value])
),
{ permissive: true }
)

// Manual parsing of flags to allow for special flags like oneOf(Boolean, String)
for (let i = result['_'].length - 1; i >= 0; --i) {
let flag = result['_'][i]
if (!flag.startsWith('-')) continue

let flagName = flag
let handler = flags[flag]

// Resolve flagName & handler
while (typeof handler === 'string') {
flagName = handler
handler = flags[handler]
}

if (!handler) continue

let args = []
let offset = i + 1

// Parse args for current flag
while (result['_'][offset] && !result['_'][offset].startsWith('-')) {
args.push(result['_'][offset++])
}

// Cleanup manually parsed flags + args
result['_'].splice(i, 1 + args.length)

// Set the resolved value in the `result` object
result[flagName] = handler.type(
args.length === 0 ? undefined : args.length === 1 ? args[0] : args,
flagName
)
}

// Ensure that the `command` is always the first argument in the `args`.
// This is important so that we don't have to check if a default command
// (build) was used or not from within each plugin.
Expand Down Expand Up @@ -317,7 +372,27 @@ async function build() {
)

async function loadPostCssPlugins() {
let { plugins: configPlugins } = await postcssrc()
let customPostCssPath = typeof args['--postcss'] === 'string' ? args['--postcss'] : undefined
let { plugins: configPlugins } = customPostCssPath
? await (async () => {
let file = path.resolve(customPostCssPath)

// Implementation, see: https://unpkg.com/browse/[email protected]/src/index.js
let { config = {} } = await cosmiconfig('postcss').load(file)
if (typeof config === 'function') {
config = config()
} else {
config = Object.assign({}, config)
}

if (!config.plugins) {
config.plugins = []
}

return { plugins: loadPlugins(config, file) }
})()
: await postcssrc()

let configPluginTailwindIdx = configPlugins.findIndex((plugin) => {
if (typeof plugin === 'function' && plugin.name === 'tailwindcss') {
return true
Expand Down

0 comments on commit f4799a3

Please sign in to comment.