Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add tests for the --postcss option in the new CLI #4607

Merged
merged 3 commits into from Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
104 changes: 103 additions & 1 deletion integrations/tailwindcss-cli/tests/cli.test.js
@@ -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
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/postcss-load-config@3.0.1/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