From 8b36951192d0546659b00c17e7147f2b79d994ff Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Jun 2021 13:51:33 +0200 Subject: [PATCH 1/3] add tests for the --postcss option in the new CLI --- .../tailwindcss-cli/tests/cli.test.js | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/integrations/tailwindcss-cli/tests/cli.test.js b/integrations/tailwindcss-cli/tests/cli.test.js index 004df28b482e..0cf8914fbe32 100644 --- a/integrations/tailwindcss-cli/tests/cli.test.js +++ b/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')({ @@ -157,6 +157,57 @@ describe('Build command', () => { ) }) + test('--postcss (postcss.config.js)', async () => { + await writeInputFile('index.html', html`
`) + + 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('--help', async () => { let { combined } = await $(`${EXECUTABLE} --help`) From a8a36c9a401f31ffa25835775b95e7a7e6662605 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Jun 2021 16:06:37 +0200 Subject: [PATCH 2/3] 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. --- src/cli.js | 65 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/src/cli.js b/src/cli.js index 2db30cb4710e..c430e3a6014c 100644 --- a/src/cli.js +++ b/src/cli.js @@ -104,6 +104,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, @@ -123,7 +139,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, @@ -191,13 +210,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. From 55ae27a647067da0228ead1f604aa1ccc6b168d8 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Jun 2021 16:09:40 +0200 Subject: [PATCH 3/3] ensure that we can use a custom `--postcss ./with-custom-path.js` --- .../tailwindcss-cli/tests/cli.test.js | 51 +++++++++++++++++++ src/cli.js | 24 ++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/integrations/tailwindcss-cli/tests/cli.test.js b/integrations/tailwindcss-cli/tests/cli.test.js index 0cf8914fbe32..85c9316262cd 100644 --- a/integrations/tailwindcss-cli/tests/cli.test.js +++ b/integrations/tailwindcss-cli/tests/cli.test.js @@ -208,6 +208,57 @@ describe('Build command', () => { ) }) + test('--postcss (custom.postcss.config.js)', async () => { + await writeInputFile('index.html', html`
`) + + 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`) diff --git a/src/cli.js b/src/cli.js index c430e3a6014c..39a8a070ee2d 100644 --- a/src/cli.js +++ b/src/cli.js @@ -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' @@ -370,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