From 3dd729ac17d153b5956075d08f1c3b2c2b9e753b Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Fri, 28 May 2021 10:24:38 -0400 Subject: [PATCH 01/23] Ignore workspace settings --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9673e8596e1a..1c4337030da6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /cli /lib /example +.vscode tailwind.config.js index.html yarn.lock From fc58b41519f5d0d45b0cee8e7ef93b2700ec85ff Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Fri, 28 May 2021 10:24:59 -0400 Subject: [PATCH 02/23] Parameterize setting up the context --- src/jit/index.js | 20 +---- src/jit/lib/setupTrackingContext.js | 124 +++++++++++++------------- src/jit/lib/setupWatchingContext.js | 132 ++++++++++++++-------------- src/jit/processTailwindFeatures.js | 15 +++- 4 files changed, 150 insertions(+), 141 deletions(-) diff --git a/src/jit/index.js b/src/jit/index.js index 48830150741e..1f531846fe95 100644 --- a/src/jit/index.js +++ b/src/jit/index.js @@ -1,4 +1,3 @@ -import normalizeTailwindDirectives from './lib/normalizeTailwindDirectives' import setupTrackingContext from './lib/setupTrackingContext' import setupWatchingContext from './lib/setupWatchingContext' import { env } from './lib/sharedState' @@ -13,23 +12,12 @@ export default function (configOrPath = {}) { return root }, function (root, result) { - function registerDependency(fileName, type = 'dependency') { - result.messages.push({ - type, - plugin: 'tailwindcss', - parent: result.opts.from, - [type === 'dir-dependency' ? 'dir' : 'file']: fileName, - }) - } - - let tailwindDirectives = normalizeTailwindDirectives(root) - - let context = + let setupContext = env.TAILWIND_MODE === 'watch' - ? setupWatchingContext(configOrPath, tailwindDirectives, registerDependency)(result, root) - : setupTrackingContext(configOrPath, tailwindDirectives, registerDependency)(result, root) + ? setupWatchingContext(configOrPath) + : setupTrackingContext(configOrPath) - processTailwindFeatures(context)(root, result) + processTailwindFeatures(setupContext)(root, result) }, env.DEBUG && function (root) { diff --git a/src/jit/lib/setupTrackingContext.js b/src/jit/lib/setupTrackingContext.js index 0c1330307bc6..bba0057c1526 100644 --- a/src/jit/lib/setupTrackingContext.js +++ b/src/jit/lib/setupTrackingContext.js @@ -121,73 +121,79 @@ function resolveChangedFiles(candidateFiles, fileModifiedMap) { // Retrieve an existing context from cache if possible (since contexts are unique per // source path), or set up a new one (including setting up watchers and registering // plugins) then return it -export default function setupTrackingContext(configOrPath, tailwindDirectives, registerDependency) { - return (result, root) => { - let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] = - getTailwindConfig(configOrPath) - - let contextDependencies = new Set(configDependencies) - - // If there are no @tailwind rules, we don't consider this CSS file or it's dependencies - // to be dependencies of the context. Can reuse the context even if they change. - // We may want to think about `@layer` being part of this trigger too, but it's tough - // because it's impossible for a layer in one file to end up in the actual @tailwind rule - // in another file since independent sources are effectively isolated. - if (tailwindDirectives.size > 0) { - // Add current css file as a context dependencies. - contextDependencies.add(result.opts.from) - - // Add all css @import dependencies as context dependencies. - for (let message of result.messages) { - if (message.type === 'dependency') { - contextDependencies.add(message.file) +export default function setupTrackingContext(configOrPath) { + return ({ tailwindDirectives, registerDependency }) => { + return (root, result) => { + let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] = + getTailwindConfig(configOrPath) + + let contextDependencies = new Set(configDependencies) + + // If there are no @tailwind rules, we don't consider this CSS file or it's dependencies + // to be dependencies of the context. Can reuse the context even if they change. + // We may want to think about `@layer` being part of this trigger too, but it's tough + // because it's impossible for a layer in one file to end up in the actual @tailwind rule + // in another file since independent sources are effectively isolated. + if (tailwindDirectives.size > 0) { + // Add current css file as a context dependencies. + contextDependencies.add(result.opts.from) + + // Add all css @import dependencies as context dependencies. + for (let message of result.messages) { + if (message.type === 'dependency') { + contextDependencies.add(message.file) + } } } - } - let [context] = getContext( - tailwindDirectives, - root, - result, - tailwindConfig, - userConfigPath, - tailwindConfigHash, - contextDependencies - ) - - let candidateFiles = getCandidateFiles(context, userConfigPath, tailwindConfig) - - // If there are no @tailwind rules, we don't consider this CSS file or it's dependencies - // to be dependencies of the context. Can reuse the context even if they change. - // We may want to think about `@layer` being part of this trigger too, but it's tough - // because it's impossible for a layer in one file to end up in the actual @tailwind rule - // in another file since independent sources are effectively isolated. - if (tailwindDirectives.size > 0) { - let fileModifiedMap = getFileModifiedMap(context) - - // Add template paths as postcss dependencies. - for (let maybeGlob of candidateFiles) { - if (isGlob(maybeGlob)) { - // rollup-plugin-postcss does not support dir-dependency messages - // but directories can be watched in the same way as files - registerDependency( - path.resolve(globParent(maybeGlob)), - env.ROLLUP_WATCH === 'true' ? 'dependency' : 'dir-dependency' - ) - } else { - registerDependency(path.resolve(maybeGlob)) + let [context] = getContext( + tailwindDirectives, + root, + result, + tailwindConfig, + userConfigPath, + tailwindConfigHash, + contextDependencies + ) + + let candidateFiles = getCandidateFiles(context, userConfigPath, tailwindConfig) + + // If there are no @tailwind rules, we don't consider this CSS file or it's dependencies + // to be dependencies of the context. Can reuse the context even if they change. + // We may want to think about `@layer` being part of this trigger too, but it's tough + // because it's impossible for a layer in one file to end up in the actual @tailwind rule + // in another file since independent sources are effectively isolated. + if (tailwindDirectives.size > 0) { + let fileModifiedMap = getFileModifiedMap(context) + + // Add template paths as postcss dependencies. + for (let maybeGlob of candidateFiles) { + if (isGlob(maybeGlob)) { + // rollup-plugin-postcss does not support dir-dependency messages + // but directories can be watched in the same way as files + registerDependency( + path.resolve(globParent(maybeGlob)), + env.ROLLUP_WATCH === 'true' ? 'dependency' : 'dir-dependency' + ) + } else { + registerDependency(path.resolve(maybeGlob)) + } + } + + for (let changedContent of resolvedChangedContent( + context, + candidateFiles, + fileModifiedMap + )) { + context.changedContent.push(changedContent) } } - for (let changedContent of resolvedChangedContent(context, candidateFiles, fileModifiedMap)) { - context.changedContent.push(changedContent) + for (let file of configDependencies) { + registerDependency(file) } - } - for (let file of configDependencies) { - registerDependency(file) + return context } - - return context } } diff --git a/src/jit/lib/setupWatchingContext.js b/src/jit/lib/setupWatchingContext.js index 9eb83743a03a..33165931b87c 100644 --- a/src/jit/lib/setupWatchingContext.js +++ b/src/jit/lib/setupWatchingContext.js @@ -269,83 +269,85 @@ function resolveChangedFiles(context, candidateFiles) { // Retrieve an existing context from cache if possible (since contexts are unique per // source path), or set up a new one (including setting up watchers and registering // plugins) then return it -export default function setupWatchingContext(configOrPath, tailwindDirectives, registerDependency) { - return (result, root) => { - let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] = - getTailwindConfig(configOrPath) - - let contextDependencies = new Set(configDependencies) - - // If there are no @tailwind rules, we don't consider this CSS file or it's dependencies - // to be dependencies of the context. Can reuse the context even if they change. - // We may want to think about `@layer` being part of this trigger too, but it's tough - // because it's impossible for a layer in one file to end up in the actual @tailwind rule - // in another file since independent sources are effectively isolated. - if (tailwindDirectives.size > 0) { - // Add current css file as a context dependencies. - contextDependencies.add(result.opts.from) - - // Add all css @import dependencies as context dependencies. - for (let message of result.messages) { - if (message.type === 'dependency') { - contextDependencies.add(message.file) +export default function setupWatchingContext(configOrPath) { + return ({ tailwindDirectives, registerDependency }) => { + return (root, result) => { + let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] = + getTailwindConfig(configOrPath) + + let contextDependencies = new Set(configDependencies) + + // If there are no @tailwind rules, we don't consider this CSS file or it's dependencies + // to be dependencies of the context. Can reuse the context even if they change. + // We may want to think about `@layer` being part of this trigger too, but it's tough + // because it's impossible for a layer in one file to end up in the actual @tailwind rule + // in another file since independent sources are effectively isolated. + if (tailwindDirectives.size > 0) { + // Add current css file as a context dependencies. + contextDependencies.add(result.opts.from) + + // Add all css @import dependencies as context dependencies. + for (let message of result.messages) { + if (message.type === 'dependency') { + contextDependencies.add(message.file) + } } } - } - - let [context, isNewContext] = getContext( - tailwindDirectives, - root, - result, - tailwindConfig, - userConfigPath, - tailwindConfigHash, - contextDependencies - ) - - let candidateFiles = getCandidateFiles(context, userConfigPath, tailwindConfig) - let contextConfigDependencies = getConfigDependencies(context) - - for (let file of configDependencies) { - registerDependency(file) - } - context.disposables.push((oldContext) => { - let watcher = getWatcher(oldContext) - if (watcher !== null) { - watcher.close() + let [context, isNewContext] = getContext( + tailwindDirectives, + root, + result, + tailwindConfig, + userConfigPath, + tailwindConfigHash, + contextDependencies + ) + + let candidateFiles = getCandidateFiles(context, userConfigPath, tailwindConfig) + let contextConfigDependencies = getConfigDependencies(context) + + for (let file of configDependencies) { + registerDependency(file) } - }) - let configPath = getConfigPath(context, configOrPath) - - if (configPath !== null) { - for (let dependency of getModuleDependencies(configPath)) { - if (dependency.file === configPath) { - continue + context.disposables.push((oldContext) => { + let watcher = getWatcher(oldContext) + if (watcher !== null) { + watcher.close() } + }) + + let configPath = getConfigPath(context, configOrPath) - contextConfigDependencies.add(dependency.file) + if (configPath !== null) { + for (let dependency of getModuleDependencies(configPath)) { + if (dependency.file === configPath) { + continue + } + + contextConfigDependencies.add(dependency.file) + } } - } - if (isNewContext) { - rebootWatcher(context, configPath, contextConfigDependencies, candidateFiles) - } + if (isNewContext) { + rebootWatcher(context, configPath, contextConfigDependencies, candidateFiles) + } - // Register our temp file as a dependency ‚Äî we write to this file - // to trigger rebuilds. - let touchFile = getTouchFile(context) - if (touchFile) { - registerDependency(touchFile) - } + // Register our temp file as a dependency ‚Äî we write to this file + // to trigger rebuilds. + let touchFile = getTouchFile(context) + if (touchFile) { + registerDependency(touchFile) + } - if (tailwindDirectives.size > 0) { - for (let changedContent of resolvedChangedContent(context, candidateFiles)) { - context.changedContent.push(changedContent) + if (tailwindDirectives.size > 0) { + for (let changedContent of resolvedChangedContent(context, candidateFiles)) { + context.changedContent.push(changedContent) + } } - } - return context + return context + } } } diff --git a/src/jit/processTailwindFeatures.js b/src/jit/processTailwindFeatures.js index c3072b9911bb..118d42d33d6a 100644 --- a/src/jit/processTailwindFeatures.js +++ b/src/jit/processTailwindFeatures.js @@ -1,11 +1,24 @@ +import normalizeTailwindDirectives from './lib/normalizeTailwindDirectives' import expandTailwindAtRules from './lib/expandTailwindAtRules' import expandApplyAtRules from './lib/expandApplyAtRules' import evaluateTailwindFunctions from '../lib/evaluateTailwindFunctions' import substituteScreenAtRules from '../lib/substituteScreenAtRules' import collapseAdjacentRules from './lib/collapseAdjacentRules' -export default function processTailwindFeatures(context) { +export default function processTailwindFeatures(setupContext) { return function (root, result) { + function registerDependency(fileName, type = 'dependency') { + result.messages.push({ + type, + plugin: 'tailwindcss', + parent: result.opts.from, + [type === 'dir-dependency' ? 'dir' : 'file']: fileName, + }) + } + + let tailwindDirectives = normalizeTailwindDirectives(root) + let context = setupContext({ tailwindDirectives, registerDependency })(root, result) + expandTailwindAtRules(context)(root, result) expandApplyAtRules(context)(root, result) evaluateTailwindFunctions(context)(root, result) From 16457bd4ae0e04136dad90af3bf9da99c0ecc527 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Tue, 18 May 2021 10:42:17 -0400 Subject: [PATCH 03/23] WIP --- package-lock.json | 27 ++++++++++--- package.json | 1 + src/cli.js | 101 ++++++++++++++++++++++++++++++++++++++++++++-- src/cli/index.js | 6 +++ 4 files changed, 126 insertions(+), 9 deletions(-) mode change 100755 => 100644 src/cli.js create mode 100755 src/cli/index.js diff --git a/package-lock.json b/package-lock.json index 24cc6cb8240f..b708b35e9e06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,11 +5,11 @@ "requires": true, "packages": { "": { - "name": "tailwindcss", "version": "2.1.2", "license": "MIT", "dependencies": { "@fullhuman/postcss-purgecss": "^4.0.3", + "arg": "^5.0.0", "bytes": "^3.0.0", "chalk": "^4.1.1", "chokidar": "^3.5.1", @@ -2323,6 +2323,11 @@ "node": ">=0.10.0" } }, + "node_modules/arg": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.0.tgz", + "integrity": "sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ==" + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -11652,7 +11657,8 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/@tailwindcss/aspect-ratio/-/aspect-ratio-0.2.1.tgz", "integrity": "sha512-aDFi80aHQ3JM3symJ5iKU70lm151ugIGFCI0yRZGpyjgQSDS+Fbe93QwypC1tCEllQE8p0S7TUu20ih1b9IKLA==", - "dev": true + "dev": true, + "requires": {} }, "@types/babel__core": { "version": "7.1.14", @@ -11804,7 +11810,8 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "dev": true, + "requires": {} }, "acorn-node": { "version": "1.8.2", @@ -11883,6 +11890,11 @@ } } }, + "arg": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.0.tgz", + "integrity": "sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ==" + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -13135,7 +13147,8 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true + "dev": true, + "requires": {} }, "eslint-plugin-prettier": { "version": "3.4.0", @@ -14727,7 +14740,8 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "26.0.0", @@ -17760,7 +17774,8 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", - "dev": true + "dev": true, + "requires": {} }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index c23858b5bfd9..3a107b943490 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ }, "dependencies": { "@fullhuman/postcss-purgecss": "^4.0.3", + "arg": "^5.0.0", "bytes": "^3.0.0", "chalk": "^4.1.1", "chokidar": "^3.5.1", diff --git a/src/cli.js b/src/cli.js old mode 100755 new mode 100644 index 05bc8c8df16c..51ef4669923a --- a/src/cli.js +++ b/src/cli.js @@ -1,6 +1,101 @@ #!/usr/bin/env node -import main from './cli/main' -import * as utils from './cli/utils' +let autoprefixer = require('autoprefixer') +let tailwindcss = require('./index') +let chokidar = require('chokidar') +let postcss = require('postcss') +let chalk = require('chalk') +let path = require('path') +let arg = require('arg') +let fs = require('fs') -main(process.argv.slice(2)).catch((error) => utils.die(error.stack)) +/* + Overall CLI architecture: + + - Resolve config in CLI, not within Tailwind + - Track config as a dependency (+ the config's own dependencies) in the CLI + - Don't bother crawling config dependencies in one-off build mode + - Track all `purge` files as dependencies + - Somehow bypass all internal chokidar stuff in the JIT engine itself + - Send some sort of external hash/key into Tailwind to help it identify the context without doing anything + - `contextKey` ? + +*/ + +let args = arg({ + '--files': String, + '--config': String, + '--output': String, + '--minify': Boolean, + '--watch': Boolean, + '-f': '--files', + '-c': '--config', + '-o': '--output', + '-m': '--minify', + '-w': '--watch', +}) + +let input = args['_'][1] +let output = args['--output'] +let files = args['--files'] +let shouldWatch = args['--watch'] +let shouldMinify = args['--minify'] + +if (!output) { + throw new Error('Missing required output file: --output, -o, or first argument') +} + +if (files.length === 0) { + throw new Error('Must provide at least one purge file or directory: --files, -f') +} + +if (shouldWatch) { + process.env.TAILWIND_MODE = 'watch' +} + +let processors = [tailwindcss({ config: getConfig() }), autoprefixer] + +if (process.env.NODE_ENV === 'production' || shouldMinify) { + process.env.NODE_ENV = 'production' + + processors.push(require('cssnano')) +} + +let css = input + ? fs.readFileSync(input) + : '@tailwind base; @tailwind components; @tailwind utilities' + +if (shouldWatch) { + chokidar.watch(files, { ignored: /[\/\\]\./ }).on('all', () => { + processCSS() + }) +} else { + processCSS() +} + +function processCSS() { + console.log(chalk.cyan('♻️ tailbuilding...')) + + mkdirp.sync(path.dirname(output)) + + postcss(processors) + .process(css, { from: input, to: output }) + .then((result) => { + fs.writeFile(output, result.css, () => true) + + if (result.map) { + fs.writeFile(output + '.map', result.map.toString(), () => true) + } + }) +} + +function getConfig() { + if (args['--config']) return args['--config'] + + if (fs.existsSync('tailwind.config.js')) return 'tailwind.config.js' + + return { + mode: 'jit', + purge: files, + } +} diff --git a/src/cli/index.js b/src/cli/index.js new file mode 100755 index 000000000000..05bc8c8df16c --- /dev/null +++ b/src/cli/index.js @@ -0,0 +1,6 @@ +#!/usr/bin/env node + +import main from './cli/main' +import * as utils from './cli/utils' + +main(process.argv.slice(2)).catch((error) => utils.die(error.stack)) From 4b31ee1aaa2a414988de864094be8cf63931337a Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Tue, 18 May 2021 12:10:25 -0400 Subject: [PATCH 04/23] WIP --- src/cli.js | 80 +++++++++++++++++++++++------------------------- src/cli/index.js | 4 +-- 2 files changed, 41 insertions(+), 43 deletions(-) diff --git a/src/cli.js b/src/cli.js index 51ef4669923a..6c2fdb7576b5 100644 --- a/src/cli.js +++ b/src/cli.js @@ -22,20 +22,24 @@ let fs = require('fs') */ +// npx tailwindcss -i in.css -o out.css -w --files="./**/*.{js,html}" + let args = arg({ '--files': String, '--config': String, + '--input': String, '--output': String, '--minify': Boolean, '--watch': Boolean, '-f': '--files', '-c': '--config', + '-i': '--input', '-o': '--output', '-m': '--minify', '-w': '--watch', }) -let input = args['_'][1] +let input = args['--input'] let output = args['--output'] let files = args['--files'] let shouldWatch = args['--watch'] @@ -45,40 +49,43 @@ if (!output) { throw new Error('Missing required output file: --output, -o, or first argument') } -if (files.length === 0) { - throw new Error('Must provide at least one purge file or directory: --files, -f') -} - -if (shouldWatch) { - process.env.TAILWIND_MODE = 'watch' -} - -let processors = [tailwindcss({ config: getConfig() }), autoprefixer] - -if (process.env.NODE_ENV === 'production' || shouldMinify) { - process.env.NODE_ENV = 'production' - - processors.push(require('cssnano')) +function indentRecursive(node, indent = 0) { + node.each && + node.each((child, i) => { + if (!child.raws.before || !child.raws.before.trim() || 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) + }) } -let css = input - ? fs.readFileSync(input) - : '@tailwind base; @tailwind components; @tailwind utilities' - -if (shouldWatch) { - chokidar.watch(files, { ignored: /[\/\\]\./ }).on('all', () => { - processCSS() - }) -} else { - processCSS() +function formatNodes(root) { + indentRecursive(root) + if (root.first) { + root.first.raws.before = '' + } } -function processCSS() { - console.log(chalk.cyan('♻️ tailbuilding...')) - - mkdirp.sync(path.dirname(output)) - - postcss(processors) +let configPath = args['--config'] ?? path.resolve('./tailwind.config.js') +let config = require(configPath) + +let plugins = [ + // TODO: Bake in postcss-import support? + // TODO: Bake in postcss-nested support? + { + postcssPlugin: 'tailwindcss', + plugins: require('./jit').default(config), + }, + require('autoprefixer'), + formatNodes, +] + +// TODO: Read from file +let css = '@tailwind base; @tailwind components; @tailwind utilities' + +function processCSS(css) { + postcss(plugins) .process(css, { from: input, to: output }) .then((result) => { fs.writeFile(output, result.css, () => true) @@ -89,13 +96,4 @@ function processCSS() { }) } -function getConfig() { - if (args['--config']) return args['--config'] - - if (fs.existsSync('tailwind.config.js')) return 'tailwind.config.js' - - return { - mode: 'jit', - purge: files, - } -} +processCSS(css) diff --git a/src/cli/index.js b/src/cli/index.js index 05bc8c8df16c..c30f2c6f3665 100755 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -import main from './cli/main' -import * as utils from './cli/utils' +import main from './main' +import * as utils from './utils' main(process.argv.slice(2)).catch((error) => utils.die(error.stack)) From 1a6c7a07f56483c794ac36d7558c019cbfc45ac8 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Wed, 19 May 2021 11:02:53 -0400 Subject: [PATCH 05/23] WIP --- out.css | 18 ++++++++++++++++++ src/cli.js | 33 ++++++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 out.css diff --git a/out.css b/out.css new file mode 100644 index 000000000000..84424223834a --- /dev/null +++ b/out.css @@ -0,0 +1,18 @@ +*, ::before, ::after { + --tw-border-opacity: 1; + border-color: rgba(229, 231, 235, var(--tw-border-opacity)) +} + +* { + --tw-shadow: 0 0 #0000; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgba(59, 130, 246, 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000 +} + +.font-bold { + font-weight: 700 +} \ No newline at end of file diff --git a/src/cli.js b/src/cli.js index 6c2fdb7576b5..408e9a9b7e02 100644 --- a/src/cli.js +++ b/src/cli.js @@ -22,6 +22,19 @@ let fs = require('fs') */ +/* + TODOs: + + - Make watching work + - Compile from custom source CSS file + - Make minification work + - Scaffold tailwind.config.js file (with postcss.config.js) + + Future: + - Detect project type, add sensible purge defaults + +*/ + // npx tailwindcss -i in.css -o out.css -w --files="./**/*.{js,html}" let args = arg({ @@ -83,17 +96,19 @@ let plugins = [ // TODO: Read from file let css = '@tailwind base; @tailwind components; @tailwind utilities' +let processor = postcss(plugins) function processCSS(css) { - postcss(plugins) - .process(css, { from: input, to: output }) - .then((result) => { - fs.writeFile(output, result.css, () => true) + processor.process(css, { from: input, to: output }).then((result) => { + fs.writeFile(output, result.css, () => true) - if (result.map) { - fs.writeFile(output + '.map', result.map.toString(), () => true) - } - }) + if (result.map) { + fs.writeFile(output + '.map', result.map.toString(), () => true) + } + }) } -processCSS(css) +if (shouldWatch) { +} else { + processCSS(css) +} From 7b82bd3cc4a3075f13ac0bc46325ebad0291a2a8 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Fri, 28 May 2021 11:28:47 -0400 Subject: [PATCH 06/23] WIP --- out.css | 17 +-- src/cli.js | 187 ++++++++++++++++++++++------- src/jit/lib/setupContextUtils.js | 36 +++--- src/jit/processTailwindFeatures.js | 10 +- 4 files changed, 172 insertions(+), 78 deletions(-) diff --git a/out.css b/out.css index 84424223834a..6e0a81f336c5 100644 --- a/out.css +++ b/out.css @@ -1,18 +1,3 @@ -*, ::before, ::after { - --tw-border-opacity: 1; - border-color: rgba(229, 231, 235, var(--tw-border-opacity)) -} - -* { - --tw-shadow: 0 0 #0000; - --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgba(59, 130, 246, 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000 -} - .font-bold { - font-weight: 700 + font-weight: pooooooooop } \ No newline at end of file diff --git a/src/cli.js b/src/cli.js index 408e9a9b7e02..26dfe2db7100 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,13 +1,36 @@ #!/usr/bin/env node -let autoprefixer = require('autoprefixer') -let tailwindcss = require('./index') -let chokidar = require('chokidar') -let postcss = require('postcss') -let chalk = require('chalk') -let path = require('path') -let arg = require('arg') -let fs = require('fs') +// import autoprefixer from 'autoprefixer' +import chokidar from 'chokidar' +import postcss from 'postcss' +import chalk from 'chalk' +import path from 'path' +import arg from 'arg' +import fs from 'fs' +import processTailwindFeatures from './jit/processTailwindFeatures' +import resolveConfig from '../resolveConfig' + +// --- + +function indentRecursive(node, indent = 0) { + node.each && + node.each((child, i) => { + if (!child.raws.before || !child.raws.before.trim() || 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 = '' + } +} + +// --- /* Overall CLI architecture: @@ -62,53 +85,125 @@ if (!output) { throw new Error('Missing required output file: --output, -o, or first argument') } -function indentRecursive(node, indent = 0) { - node.each && - node.each((child, i) => { - if (!child.raws.before || !child.raws.before.trim() || 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 getTailwindConfig(configPath = args['--config'] ?? path.resolve('./tailwind.config.js')) { + return resolveConfig(require(configPath)) } -function formatNodes(root) { - indentRecursive(root) - if (root.first) { - root.first.raws.before = '' +function buildOnce() { + let config = getTailwindConfig() + + let tailwindPlugin = (opts = {}) => { + return { + postcssPlugin: 'tailwindcss', + Once(root, { result }) { + processTailwindFeatures(({ createContext }) => { + return () => { + return createContext(config) + } + })(root, result) + }, + } } + tailwindPlugin.postcss = true + + let plugins = [ + // TODO: Bake in postcss-import support? + // TODO: Bake in postcss-nested support? + tailwindPlugin, + // require('autoprefixer'), + // formatNodes, + ] + + let processor = postcss(plugins) + + function processCSS(css) { + return processor.process(css, { from: input, to: output }).then((result) => { + fs.writeFile(output, result.css, () => true) + if (result.map) { + fs.writeFile(output + '.map', result.map.toString(), () => true) + } + }) + } + + // TODO: Read from file + let css = '@tailwind base; @tailwind components; @tailwind utilities' + return processCSS(css) } -let configPath = args['--config'] ?? path.resolve('./tailwind.config.js') -let config = require(configPath) - -let plugins = [ - // TODO: Bake in postcss-import support? - // TODO: Bake in postcss-nested support? - { - postcssPlugin: 'tailwindcss', - plugins: require('./jit').default(config), - }, - require('autoprefixer'), - formatNodes, -] - -// TODO: Read from file -let css = '@tailwind base; @tailwind components; @tailwind utilities' -let processor = postcss(plugins) - -function processCSS(css) { - processor.process(css, { from: input, to: output }).then((result) => { - fs.writeFile(output, result.css, () => true) - - if (result.map) { - fs.writeFile(output + '.map', result.map.toString(), () => true) +function startWatcher() { + function build(config) { + console.log('Rebuilding...') + + let changedContent = [{ content: 'font-bold', extension: 'html' }] + + let tailwindPlugin = (opts = {}) => { + return { + postcssPlugin: 'tailwindcss', + Once(root, { result }) { + processTailwindFeatures(({ createContext }) => { + return () => { + return createContext(config, changedContent) + } + })(root, result) + }, + } + } + tailwindPlugin.postcss = true + + let plugins = [ + // TODO: Bake in postcss-import support? + // TODO: Bake in postcss-nested support? + tailwindPlugin, + // require('autoprefixer'), + // formatNodes, + ] + + let processor = postcss(plugins) + + function processCSS(css) { + return processor.process(css, { from: input, to: output }).then((result) => { + fs.writeFile(output, result.css, () => true) + if (result.map) { + fs.writeFile(output + '.map', result.map.toString(), () => true) + } + }) + } + + // TODO: Read from file + // let css = '@tailwind base; @tailwind components; @tailwind utilities' + let css = '@tailwind utilities' + return processCSS(css) + } + // Files to watch: + // - tailwind.config.js + // - Purge files + // - Config dependencies + // - CSS file + // - CSS imports + + let configPath = args['--config'] ?? path.resolve('./tailwind.config.js') + let config = getTailwindConfig(configPath) + + let watcher = chokidar.watch([configPath], { ignoreInitial: true }) + + watcher.on('change', async (file) => { + if (file === configPath) { + console.time('Resolve config') + delete require.cache[require.resolve(configPath)] + let config = getTailwindConfig(configPath) + console.timeEnd('Resolve config') + + console.time('Build') + await build(config) + console.timeEnd('Build') } }) + + build(config) } if (shouldWatch) { + startWatcher() } else { - processCSS(css) + buildOnce() } diff --git a/src/jit/lib/setupContextUtils.js b/src/jit/lib/setupContextUtils.js index bdc148161c67..cbe8ee7c64ea 100644 --- a/src/jit/lib/setupContextUtils.js +++ b/src/jit/lib/setupContextUtils.js @@ -479,6 +479,26 @@ function registerPlugins(plugins, context) { } } +export function createContext(tailwindConfig, changedContent, tailwindDirectives, root) { + let context = { + disposables: [], + ruleCache: new Set(), + classCache: new Map(), + applyClassCache: new Map(), + notClassCache: new Set(), + postCssNodeCache: new Map(), + candidateRuleMap: new Map(), + tailwindConfig, + changedContent: changedContent, + variantMap: new Map(), + stylesheetCache: null, + } + + registerPlugins(resolvePlugins(context, tailwindDirectives, root), context) + + return context +} + let contextMap = sharedState.contextMap let configContextMap = sharedState.configContextMap let contextSourcesMap = sharedState.contextSourcesMap @@ -541,19 +561,7 @@ export function getContext( env.DEBUG && console.log('Setting up new context...') - let context = { - disposables: [], - ruleCache: new Set(), - classCache: new Map(), - applyClassCache: new Map(), - notClassCache: new Set(), - postCssNodeCache: new Map(), - candidateRuleMap: new Map(), - tailwindConfig, - changedContent: [], - variantMap: new Map(), - stylesheetCache: null, - } + let context = createContext(tailwindConfig, tailwindDirectives, root) trackModified([...contextDependencies], getFileModifiedMap(context)) @@ -570,7 +578,5 @@ export function getContext( contextSourcesMap.get(context).add(sourcePath) - registerPlugins(resolvePlugins(context, tailwindDirectives, root), context) - return [context, true] } diff --git a/src/jit/processTailwindFeatures.js b/src/jit/processTailwindFeatures.js index 118d42d33d6a..e60eb3113a0f 100644 --- a/src/jit/processTailwindFeatures.js +++ b/src/jit/processTailwindFeatures.js @@ -4,6 +4,7 @@ import expandApplyAtRules from './lib/expandApplyAtRules' import evaluateTailwindFunctions from '../lib/evaluateTailwindFunctions' import substituteScreenAtRules from '../lib/substituteScreenAtRules' import collapseAdjacentRules from './lib/collapseAdjacentRules' +import { createContext as poop } from './lib/setupContextUtils' export default function processTailwindFeatures(setupContext) { return function (root, result) { @@ -17,7 +18,14 @@ export default function processTailwindFeatures(setupContext) { } let tailwindDirectives = normalizeTailwindDirectives(root) - let context = setupContext({ tailwindDirectives, registerDependency })(root, result) + + let context = setupContext({ + tailwindDirectives, + registerDependency, + createContext(tailwindConfig, changedContent) { + return poop(tailwindConfig, changedContent, tailwindDirectives, root) + }, + })(root, result) expandTailwindAtRules(context)(root, result) expandApplyAtRules(context)(root, result) From 99c128701fb1d541f3488d25cc3d9938f43deee9 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 28 May 2021 18:47:47 +0200 Subject: [PATCH 07/23] wip --- src/cli.js | 59 +++++++++++++++++++++++++----- src/jit/lib/setupContextUtils.js | 8 +++- src/jit/processTailwindFeatures.js | 5 ++- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/src/cli.js b/src/cli.js index 26dfe2db7100..2734ad88e753 100644 --- a/src/cli.js +++ b/src/cli.js @@ -131,20 +131,25 @@ function buildOnce() { } function startWatcher() { - function build(config) { - console.log('Rebuilding...') + let changedContent = [] - let changedContent = [{ content: 'font-bold', extension: 'html' }] + function build(config) { + console.log('\n\nRebuilding...') let tailwindPlugin = (opts = {}) => { return { postcssPlugin: 'tailwindcss', Once(root, { result }) { + console.time('Process tailwind features') processTailwindFeatures(({ createContext }) => { return () => { - return createContext(config, changedContent) + console.time('Creating context') + let x = createContext(config, changedContent.splice(0)) + console.timeEnd('Creating context') + return x } })(root, result) + console.timeEnd('Process tailwind features') }, } } @@ -184,22 +189,56 @@ function startWatcher() { let configPath = args['--config'] ?? path.resolve('./tailwind.config.js') let config = getTailwindConfig(configPath) - let watcher = chokidar.watch([configPath], { ignoreInitial: true }) + function resolveWatcherData() { + return [ + // Watch tailwind.config.js + configPath, + + // Watch purge files + ...(Array.isArray(config.purge) ? config.purge : config.purge.content).filter((file) => { + // Strings in this case are files / globs. If it is something else, + // like an object it's probably a raw content object. But this object + // is not watchable, so let's remove it. + return typeof file === 'string' + }), + ] + } + + let watcher = chokidar.watch(resolveWatcherData(), { ignoreInitial: true }) + + let chain = Promise.resolve() watcher.on('change', async (file) => { if (file === configPath) { console.time('Resolve config') delete require.cache[require.resolve(configPath)] - let config = getTailwindConfig(configPath) + config = getTailwindConfig(configPath) console.timeEnd('Resolve config') - console.time('Build') - await build(config) - console.timeEnd('Build') + console.time('Watch new files') + watcher.add(resolveWatcherData()) + console.timeEnd('Watch new files') + + chain = chain.then(async () => { + console.time('Build') + await build(config) + console.timeEnd('Build') + }) + } else { + chain = chain.then(async () => { + changedContent.push({ + content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), + extension: path.extname(file), + }) + + console.time('Build') + await build(config) + console.timeEnd('Build') + }) } }) - build(config) + chain = chain.then(() => build(config)) } if (shouldWatch) { diff --git a/src/jit/lib/setupContextUtils.js b/src/jit/lib/setupContextUtils.js index cbe8ee7c64ea..9c0caa4a6c30 100644 --- a/src/jit/lib/setupContextUtils.js +++ b/src/jit/lib/setupContextUtils.js @@ -494,7 +494,13 @@ export function createContext(tailwindConfig, changedContent, tailwindDirectives stylesheetCache: null, } - registerPlugins(resolvePlugins(context, tailwindDirectives, root), context) + console.time('Resolve plugins') + let resolvedPlugins = resolvePlugins(context, tailwindDirectives, root) + console.timeEnd('Resolve plugins') + + console.time('Register plugins') + registerPlugins(resolvedPlugins, context) + console.timeEnd('Register plugins') return context } diff --git a/src/jit/processTailwindFeatures.js b/src/jit/processTailwindFeatures.js index e60eb3113a0f..83d5eb361d39 100644 --- a/src/jit/processTailwindFeatures.js +++ b/src/jit/processTailwindFeatures.js @@ -23,7 +23,10 @@ export default function processTailwindFeatures(setupContext) { tailwindDirectives, registerDependency, createContext(tailwindConfig, changedContent) { - return poop(tailwindConfig, changedContent, tailwindDirectives, root) + console.time('Poop') + let x = poop(tailwindConfig, changedContent, tailwindDirectives, root) + console.timeEnd('Poop') + return x }, })(root, result) From f388b6b44febdc1b3714a8e27bc7bca700a735ae Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Fri, 28 May 2021 16:07:22 -0400 Subject: [PATCH 08/23] WIP Co-Authored-By: Jesse Katsumata --- out.css | 35 ++++++++++++- src/cli.js | 79 +++++++++++++++++++++--------- src/jit/lib/setupContextUtils.js | 5 -- src/jit/processTailwindFeatures.js | 7 +-- 4 files changed, 90 insertions(+), 36 deletions(-) diff --git a/out.css b/out.css index 6e0a81f336c5..cd2020f63b84 100644 --- a/out.css +++ b/out.css @@ -1,3 +1,34 @@ -.font-bold { - font-weight: pooooooooop +.mt-1 { + margin-top: 0.25rem +} +.mt-5 { + margin-top: 1.25rem +} +.mb-1 { + margin-bottom: 0.25rem +} +.p-24 { + padding: 6rem +} +.pl-4 { + padding-left: 1rem +} +.shadow-md { + --tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) +} +.filter { + --tw-blur: var(--tw-empty,/*!*/ /*!*/); + --tw-brightness: var(--tw-empty,/*!*/ /*!*/); + --tw-contrast: var(--tw-empty,/*!*/ /*!*/); + --tw-grayscale: var(--tw-empty,/*!*/ /*!*/); + --tw-hue-rotate: var(--tw-empty,/*!*/ /*!*/); + --tw-invert: var(--tw-empty,/*!*/ /*!*/); + --tw-saturate: var(--tw-empty,/*!*/ /*!*/); + --tw-sepia: var(--tw-empty,/*!*/ /*!*/); + --tw-drop-shadow: var(--tw-empty,/*!*/ /*!*/); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) +} +.drop-shadow-xl { + --tw-drop-shadow: drop-shadow(0 20px 13px rgba(0, 0, 0, 0.03)) drop-shadow(0 8px 5px rgba(0, 0, 0, 0.08)) } \ No newline at end of file diff --git a/src/cli.js b/src/cli.js index 2734ad88e753..bbbed1c1db7e 100644 --- a/src/cli.js +++ b/src/cli.js @@ -9,6 +9,7 @@ import arg from 'arg' import fs from 'fs' import processTailwindFeatures from './jit/processTailwindFeatures' import resolveConfig from '../resolveConfig' +import fastGlob from 'fast-glob' // --- @@ -89,16 +90,35 @@ function getTailwindConfig(configPath = args['--config'] ?? path.resolve('./tail return resolveConfig(require(configPath)) } +function extractFileGlobs(config) { + return (Array.isArray(config.purge) ? config.purge : config.purge.content).filter((file) => { + // Strings in this case are files / globs. If it is something else, + // like an object it's probably a raw content object. But this object + // is not watchable, so let's remove it. + return typeof file === 'string' + }) +} + function buildOnce() { let config = getTailwindConfig() + let globs = extractFileGlobs(config) + let changedContent = [] + let files = fastGlob.sync(globs) + for (let file of files) { + changedContent.push({ + content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), + extension: path.extname(file), + }) + } + let tailwindPlugin = (opts = {}) => { return { postcssPlugin: 'tailwindcss', Once(root, { result }) { processTailwindFeatures(({ createContext }) => { return () => { - return createContext(config) + return createContext(config, changedContent) } })(root, result) }, @@ -111,7 +131,7 @@ function buildOnce() { // TODO: Bake in postcss-nested support? tailwindPlugin, // require('autoprefixer'), - // formatNodes, + formatNodes, ] let processor = postcss(plugins) @@ -130,6 +150,8 @@ function buildOnce() { return processCSS(css) } +let context = null + function startWatcher() { let changedContent = [] @@ -143,10 +165,15 @@ function startWatcher() { console.time('Process tailwind features') processTailwindFeatures(({ createContext }) => { return () => { + if (context !== null) { + context.changedContent = changedContent.splice(0) + return context + } + console.time('Creating context') - let x = createContext(config, changedContent.splice(0)) + context = createContext(config, changedContent.splice(0)) console.timeEnd('Creating context') - return x + return context } })(root, result) console.timeEnd('Process tailwind features') @@ -175,8 +202,7 @@ function startWatcher() { } // TODO: Read from file - // let css = '@tailwind base; @tailwind components; @tailwind utilities' - let css = '@tailwind utilities' + let css = '@tailwind base; @tailwind components; @tailwind utilities' return processCSS(css) } // Files to watch: @@ -189,37 +215,32 @@ function startWatcher() { let configPath = args['--config'] ?? path.resolve('./tailwind.config.js') let config = getTailwindConfig(configPath) - function resolveWatcherData() { - return [ - // Watch tailwind.config.js - configPath, - - // Watch purge files - ...(Array.isArray(config.purge) ? config.purge : config.purge.content).filter((file) => { - // Strings in this case are files / globs. If it is something else, - // like an object it's probably a raw content object. But this object - // is not watchable, so let's remove it. - return typeof file === 'string' - }), - ] - } - - let watcher = chokidar.watch(resolveWatcherData(), { ignoreInitial: true }) + let watcher = chokidar.watch([configPath, ...extractFileGlobs(config)], { ignoreInitial: true }) let chain = Promise.resolve() watcher.on('change', async (file) => { + console.log(file) if (file === configPath) { console.time('Resolve config') + context = null delete require.cache[require.resolve(configPath)] config = getTailwindConfig(configPath) console.timeEnd('Resolve config') console.time('Watch new files') - watcher.add(resolveWatcherData()) + let globs = extractFileGlobs(config) + watcher.add(globs) console.timeEnd('Watch new files') chain = chain.then(async () => { + let files = fastGlob.sync(globs) + for (let file of files) { + changedContent.push({ + content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), + extension: path.extname(file), + }) + } console.time('Build') await build(config) console.timeEnd('Build') @@ -238,7 +259,17 @@ function startWatcher() { } }) - chain = chain.then(() => build(config)) + chain = chain.then(() => { + let globs = extractFileGlobs(config) + let files = fastGlob.sync(globs) + for (let file of files) { + changedContent.push({ + content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), + extension: path.extname(file), + }) + } + return build(config) + }) } if (shouldWatch) { diff --git a/src/jit/lib/setupContextUtils.js b/src/jit/lib/setupContextUtils.js index 9c0caa4a6c30..95ce3428da6a 100644 --- a/src/jit/lib/setupContextUtils.js +++ b/src/jit/lib/setupContextUtils.js @@ -494,13 +494,8 @@ export function createContext(tailwindConfig, changedContent, tailwindDirectives stylesheetCache: null, } - console.time('Resolve plugins') let resolvedPlugins = resolvePlugins(context, tailwindDirectives, root) - console.timeEnd('Resolve plugins') - - console.time('Register plugins') registerPlugins(resolvedPlugins, context) - console.timeEnd('Register plugins') return context } diff --git a/src/jit/processTailwindFeatures.js b/src/jit/processTailwindFeatures.js index 83d5eb361d39..634e760be7ed 100644 --- a/src/jit/processTailwindFeatures.js +++ b/src/jit/processTailwindFeatures.js @@ -4,7 +4,7 @@ import expandApplyAtRules from './lib/expandApplyAtRules' import evaluateTailwindFunctions from '../lib/evaluateTailwindFunctions' import substituteScreenAtRules from '../lib/substituteScreenAtRules' import collapseAdjacentRules from './lib/collapseAdjacentRules' -import { createContext as poop } from './lib/setupContextUtils' +import { createContext } from './lib/setupContextUtils' export default function processTailwindFeatures(setupContext) { return function (root, result) { @@ -23,10 +23,7 @@ export default function processTailwindFeatures(setupContext) { tailwindDirectives, registerDependency, createContext(tailwindConfig, changedContent) { - console.time('Poop') - let x = poop(tailwindConfig, changedContent, tailwindDirectives, root) - console.timeEnd('Poop') - return x + return createContext(tailwindConfig, changedContent, tailwindDirectives, root) }, })(root, result) From 565101aeb7a5d87e7bbf73355562a3917a858790 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Fri, 28 May 2021 19:03:44 -0400 Subject: [PATCH 09/23] WIP Co-Authored-By: Jesse Katsumata --- src/cli.js | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/cli.js b/src/cli.js index bbbed1c1db7e..bfdbb213620d 100644 --- a/src/cli.js +++ b/src/cli.js @@ -145,8 +145,9 @@ function buildOnce() { }) } - // TODO: Read from file - let css = '@tailwind base; @tailwind components; @tailwind utilities' + let css = input + ? fs.readFileSync(path.resolve(process.cwd(), input), 'utf8') + : '@tailwind base; @tailwind components; @tailwind utilities' return processCSS(css) } @@ -154,6 +155,8 @@ let context = null function startWatcher() { let changedContent = [] + let contextDependencies = [] + let watcher = null function build(config) { console.log('\n\nRebuilding...') @@ -193,7 +196,15 @@ function startWatcher() { let processor = postcss(plugins) function processCSS(css) { + console.log(path.resolve(process.cwd(), input)) return processor.process(css, { from: input, to: output }).then((result) => { + for (let message of result.messages) { + if (message.type === 'dependency') { + contextDependencies.push(message.file) + } + } + watcher.add(contextDependencies) + fs.writeFile(output, result.css, () => true) if (result.map) { fs.writeFile(output + '.map', result.map.toString(), () => true) @@ -201,27 +212,28 @@ function startWatcher() { }) } - // TODO: Read from file - let css = '@tailwind base; @tailwind components; @tailwind utilities' + let css = input + ? fs.readFileSync(path.resolve(process.cwd(), input), 'utf8') + : '@tailwind base; @tailwind components; @tailwind utilities' return processCSS(css) } - // Files to watch: - // - tailwind.config.js - // - Purge files - // - Config dependencies - // - CSS file - // - CSS imports + // TODO: Track config dependencies (getModuleDependencies) let configPath = args['--config'] ?? path.resolve('./tailwind.config.js') let config = getTailwindConfig(configPath) + contextDependencies.push(configPath) + if (input) { + contextDependencies.push(path.resolve(process.cwd(), input)) + } - let watcher = chokidar.watch([configPath, ...extractFileGlobs(config)], { ignoreInitial: true }) + watcher = chokidar.watch([...contextDependencies, ...extractFileGlobs(config)], { + ignoreInitial: true, + }) let chain = Promise.resolve() watcher.on('change', async (file) => { - console.log(file) - if (file === configPath) { + if (contextDependencies.includes(file)) { console.time('Resolve config') context = null delete require.cache[require.resolve(configPath)] From 7b0be8e261136b260babb15119c6d4e31bc3ae71 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Fri, 28 May 2021 20:59:58 -0400 Subject: [PATCH 10/23] WIP Co-Authored-By: Jesse Katsumata --- src/cli.js | 48 ++++++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/cli.js b/src/cli.js index bfdbb213620d..941ff516b5cb 100644 --- a/src/cli.js +++ b/src/cli.js @@ -10,6 +10,7 @@ import fs from 'fs' import processTailwindFeatures from './jit/processTailwindFeatures' import resolveConfig from '../resolveConfig' import fastGlob from 'fast-glob' +import getModuleDependencies from './lib/getModuleDependencies' // --- @@ -87,7 +88,14 @@ if (!output) { } function getTailwindConfig(configPath = args['--config'] ?? path.resolve('./tailwind.config.js')) { - return resolveConfig(require(configPath)) + console.time('Module dependencies') + for (let { file } of getModuleDependencies(configPath)) { + delete require.cache[require.resolve(file)] + } + + let dependencies = getModuleDependencies(configPath).map(({ file }) => file) + console.timeEnd('Module dependencies') + return [resolveConfig(require(configPath)), dependencies] } function extractFileGlobs(config) { @@ -100,7 +108,7 @@ function extractFileGlobs(config) { } function buildOnce() { - let config = getTailwindConfig() + let [config] = getTailwindConfig() let globs = extractFileGlobs(config) let changedContent = [] @@ -155,7 +163,7 @@ let context = null function startWatcher() { let changedContent = [] - let contextDependencies = [] + let contextDependencies = new Set() let watcher = null function build(config) { @@ -165,7 +173,7 @@ function startWatcher() { return { postcssPlugin: 'tailwindcss', Once(root, { result }) { - console.time('Process tailwind features') + console.time('Compiling CSS') processTailwindFeatures(({ createContext }) => { return () => { if (context !== null) { @@ -179,7 +187,7 @@ function startWatcher() { return context } })(root, result) - console.timeEnd('Process tailwind features') + console.timeEnd('Compiling CSS') }, } } @@ -196,14 +204,13 @@ function startWatcher() { let processor = postcss(plugins) function processCSS(css) { - console.log(path.resolve(process.cwd(), input)) return processor.process(css, { from: input, to: output }).then((result) => { for (let message of result.messages) { if (message.type === 'dependency') { - contextDependencies.push(message.file) + contextDependencies.add(message.file) } } - watcher.add(contextDependencies) + watcher.add([...contextDependencies]) fs.writeFile(output, result.css, () => true) if (result.map) { @@ -220,10 +227,12 @@ function startWatcher() { // TODO: Track config dependencies (getModuleDependencies) let configPath = args['--config'] ?? path.resolve('./tailwind.config.js') - let config = getTailwindConfig(configPath) - contextDependencies.push(configPath) + let [config, configDependencies] = getTailwindConfig(configPath) + for (let dependency of configDependencies) { + contextDependencies.add(dependency) + } if (input) { - contextDependencies.push(path.resolve(process.cwd(), input)) + contextDependencies.add(path.resolve(process.cwd(), input)) } watcher = chokidar.watch([...contextDependencies, ...extractFileGlobs(config)], { @@ -233,15 +242,18 @@ function startWatcher() { let chain = Promise.resolve() watcher.on('change', async (file) => { - if (contextDependencies.includes(file)) { + if (contextDependencies.has(file)) { console.time('Resolve config') context = null - delete require.cache[require.resolve(configPath)] - config = getTailwindConfig(configPath) + ;[config, configDependencies] = getTailwindConfig(configPath) + for (let dependency of configDependencies) { + contextDependencies.add(dependency) + } console.timeEnd('Resolve config') console.time('Watch new files') let globs = extractFileGlobs(config) + watcher.add(configDependencies) watcher.add(globs) console.timeEnd('Watch new files') @@ -253,9 +265,9 @@ function startWatcher() { extension: path.extname(file), }) } - console.time('Build') + console.time('Build total') await build(config) - console.timeEnd('Build') + console.timeEnd('Build total') }) } else { chain = chain.then(async () => { @@ -264,9 +276,9 @@ function startWatcher() { extension: path.extname(file), }) - console.time('Build') + console.time('Build total') await build(config) - console.timeEnd('Build') + console.timeEnd('Build total') }) } }) From fe236aed94297fc4959338eb72d0bd5b963233b7 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Sat, 29 May 2021 14:45:55 -0400 Subject: [PATCH 11/23] Update some comments Co-Authored-By: Jesse Katsumata --- src/cli.js | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/cli.js b/src/cli.js index 941ff516b5cb..564b1d0e3799 100644 --- a/src/cli.js +++ b/src/cli.js @@ -34,34 +34,18 @@ function formatNodes(root) { // --- -/* - Overall CLI architecture: - - - Resolve config in CLI, not within Tailwind - - Track config as a dependency (+ the config's own dependencies) in the CLI - - Don't bother crawling config dependencies in one-off build mode - - Track all `purge` files as dependencies - - Somehow bypass all internal chokidar stuff in the JIT engine itself - - Send some sort of external hash/key into Tailwind to help it identify the context without doing anything - - `contextKey` ? - -*/ - /* TODOs: - - - Make watching work - - Compile from custom source CSS file + - Support passing globs from command line - Make minification work - Scaffold tailwind.config.js file (with postcss.config.js) + - Reduce getModuleDependencies calls (make configDeps global?) + - Support raw content in purge config Future: - Detect project type, add sensible purge defaults - */ -// npx tailwindcss -i in.css -o out.css -w --files="./**/*.{js,html}" - let args = arg({ '--files': String, '--config': String, From c2f6e530c7e12f5778f1c1cd97349c526704f416 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Mon, 31 May 2021 10:11:48 -0400 Subject: [PATCH 12/23] Fix bug --- src/jit/lib/setupContextUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jit/lib/setupContextUtils.js b/src/jit/lib/setupContextUtils.js index 95ce3428da6a..89080e075df1 100644 --- a/src/jit/lib/setupContextUtils.js +++ b/src/jit/lib/setupContextUtils.js @@ -562,7 +562,7 @@ export function getContext( env.DEBUG && console.log('Setting up new context...') - let context = createContext(tailwindConfig, tailwindDirectives, root) + let context = createContext(tailwindConfig, [], tailwindDirectives, root) trackModified([...contextDependencies], getFileModifiedMap(context)) From 973a4afd32586a54ad53bc3198f5ea8f635924c1 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Mon, 31 May 2021 10:30:04 -0400 Subject: [PATCH 13/23] WIP --- src/cli.js | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/cli.js b/src/cli.js index 564b1d0e3799..351152fd24fa 100644 --- a/src/cli.js +++ b/src/cli.js @@ -36,11 +36,12 @@ function formatNodes(root) { /* TODOs: - - Support passing globs from command line - - Make minification work - - Scaffold tailwind.config.js file (with postcss.config.js) - Reduce getModuleDependencies calls (make configDeps global?) + - Detect new files - Support raw content in purge config + - Scaffold tailwind.config.js file (with postcss.config.js) + - Support passing globs from command line + - Make minification work Future: - Detect project type, add sensible purge defaults @@ -71,17 +72,6 @@ if (!output) { throw new Error('Missing required output file: --output, -o, or first argument') } -function getTailwindConfig(configPath = args['--config'] ?? path.resolve('./tailwind.config.js')) { - console.time('Module dependencies') - for (let { file } of getModuleDependencies(configPath)) { - delete require.cache[require.resolve(file)] - } - - let dependencies = getModuleDependencies(configPath).map(({ file }) => file) - console.timeEnd('Module dependencies') - return [resolveConfig(require(configPath)), dependencies] -} - function extractFileGlobs(config) { return (Array.isArray(config.purge) ? config.purge : config.purge.content).filter((file) => { // Strings in this case are files / globs. If it is something else, @@ -92,7 +82,7 @@ function extractFileGlobs(config) { } function buildOnce() { - let [config] = getTailwindConfig() + let config = resolveConfig(require(args['--config'] ?? path.resolve('./tailwind.config.js'))) let globs = extractFileGlobs(config) let changedContent = [] @@ -147,9 +137,24 @@ let context = null function startWatcher() { let changedContent = [] + let configDependencies = [] let contextDependencies = new Set() let watcher = null + function getTailwindConfig( + configPath = args['--config'] ?? path.resolve('./tailwind.config.js') + ) { + console.time('Module dependencies') + for (let file of configDependencies) { + delete require.cache[require.resolve(file)] + } + + configDependencies = getModuleDependencies(configPath).map(({ file }) => file) + console.timeEnd('Module dependencies') + + return resolveConfig(require(configPath)) + } + function build(config) { console.log('\n\nRebuilding...') @@ -211,7 +216,7 @@ function startWatcher() { // TODO: Track config dependencies (getModuleDependencies) let configPath = args['--config'] ?? path.resolve('./tailwind.config.js') - let [config, configDependencies] = getTailwindConfig(configPath) + let config = getTailwindConfig(configPath) for (let dependency of configDependencies) { contextDependencies.add(dependency) } @@ -229,7 +234,8 @@ function startWatcher() { if (contextDependencies.has(file)) { console.time('Resolve config') context = null - ;[config, configDependencies] = getTailwindConfig(configPath) + config = getTailwindConfig(configPath) + console.log(configDependencies) for (let dependency of configDependencies) { contextDependencies.add(dependency) } From b85ec71892e7995e54a68d7617870083eb10cc74 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Mon, 31 May 2021 10:32:06 -0400 Subject: [PATCH 14/23] WIP' --- src/cli.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cli.js b/src/cli.js index 351152fd24fa..e99a16904ae4 100644 --- a/src/cli.js +++ b/src/cli.js @@ -41,6 +41,7 @@ function formatNodes(root) { - Support raw content in purge config - Scaffold tailwind.config.js file (with postcss.config.js) - Support passing globs from command line + - Prebundle peer-dependencies - Make minification work Future: From 4de31d011ac206bc805f5327e9adc4fb53b4c5fd Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 31 May 2021 17:25:18 +0200 Subject: [PATCH 15/23] more things --- src/cli.js | 85 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 27 deletions(-) diff --git a/src/cli.js b/src/cli.js index e99a16904ae4..d0553a98ccb6 100644 --- a/src/cli.js +++ b/src/cli.js @@ -8,7 +8,7 @@ import path from 'path' import arg from 'arg' import fs from 'fs' import processTailwindFeatures from './jit/processTailwindFeatures' -import resolveConfig from '../resolveConfig' +import resolveConfigInternal from '../resolveConfig' import fastGlob from 'fast-glob' import getModuleDependencies from './lib/getModuleDependencies' @@ -36,13 +36,13 @@ function formatNodes(root) { /* TODOs: - - Reduce getModuleDependencies calls (make configDeps global?) - - Detect new files - - Support raw content in purge config - - Scaffold tailwind.config.js file (with postcss.config.js) - - Support passing globs from command line - - Prebundle peer-dependencies - - Make minification work + - [x] Reduce getModuleDependencies calls (make configDeps global?) + - [x] Detect new files + - [x] Support raw content in purge config + - [ ] Scaffold tailwind.config.js file (with postcss.config.js) + - [x] Support passing globs from command line + - [ ] Prebundle peer-dependencies + - [ ] Make minification work Future: - Detect project type, add sensible purge defaults @@ -65,16 +65,29 @@ let args = arg({ let input = args['--input'] let output = args['--output'] -let files = args['--files'] let shouldWatch = args['--watch'] let shouldMinify = args['--minify'] +function resolveConfig(config) { + let resolvedConfig = resolveConfigInternal(config) + + if (args['--files']) { + resolvedConfig.purge = args['--files'].split(',') + } + + return resolvedConfig +} + if (!output) { throw new Error('Missing required output file: --output, -o, or first argument') } +function extractContent(config) { + return Array.isArray(config.purge) ? config.purge : config.purge.content +} + function extractFileGlobs(config) { - return (Array.isArray(config.purge) ? config.purge : config.purge.content).filter((file) => { + return extractContent(config).filter((file) => { // Strings in this case are files / globs. If it is something else, // like an object it's probably a raw content object. But this object // is not watchable, so let's remove it. @@ -82,12 +95,19 @@ function extractFileGlobs(config) { }) } -function buildOnce() { - let config = resolveConfig(require(args['--config'] ?? path.resolve('./tailwind.config.js'))) +function extractRawContent(config) { + return extractContent(config).filter((file) => { + return typeof file === 'object' && file !== null + }) +} - let globs = extractFileGlobs(config) +function getChangedContent(config) { let changedContent = [] + + // Resolve globs from the purge config + let globs = extractFileGlobs(config) let files = fastGlob.sync(globs) + for (let file of files) { changedContent.push({ content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), @@ -95,6 +115,19 @@ function buildOnce() { }) } + // Resolve raw content in the tailwind config + for (let { raw: content, extension = 'html' } of extractRawContent(config)) { + changedContent.push({ content, extension }) + } + + return changedContent +} + +function buildOnce() { + let config = resolveConfig(require(args['--config'] ?? path.resolve('./tailwind.config.js'))) + + let changedContent = getChangedContent(config) + let tailwindPlugin = (opts = {}) => { return { postcssPlugin: 'tailwindcss', @@ -215,7 +248,6 @@ function startWatcher() { return processCSS(css) } - // TODO: Track config dependencies (getModuleDependencies) let configPath = args['--config'] ?? path.resolve('./tailwind.config.js') let config = getTailwindConfig(configPath) for (let dependency of configDependencies) { @@ -236,7 +268,6 @@ function startWatcher() { console.time('Resolve config') context = null config = getTailwindConfig(configPath) - console.log(configDependencies) for (let dependency of configDependencies) { contextDependencies.add(dependency) } @@ -249,13 +280,7 @@ function startWatcher() { console.timeEnd('Watch new files') chain = chain.then(async () => { - let files = fastGlob.sync(globs) - for (let file of files) { - changedContent.push({ - content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), - extension: path.extname(file), - }) - } + changedContent.push(...getChangedContent(config)) console.time('Build total') await build(config) console.timeEnd('Build total') @@ -274,15 +299,21 @@ function startWatcher() { } }) - chain = chain.then(() => { - let globs = extractFileGlobs(config) - let files = fastGlob.sync(globs) - for (let file of files) { + watcher.on('add', async (file) => { + chain = chain.then(async () => { changedContent.push({ content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), extension: path.extname(file), }) - } + + console.time('Build total') + await build(config) + console.timeEnd('Build total') + }) + }) + + chain = chain.then(() => { + changedContent.push(...getChangedContent(config)) return build(config) }) } From 39c4cb0ad0487d21e327467d25029455662df91a Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 31 May 2021 17:30:58 +0200 Subject: [PATCH 16/23] log console.time calls conditionally based on process.env.DEBUG --- src/cli.js | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/cli.js b/src/cli.js index d0553a98ccb6..91319ede49a5 100644 --- a/src/cli.js +++ b/src/cli.js @@ -12,6 +12,10 @@ import resolveConfigInternal from '../resolveConfig' import fastGlob from 'fast-glob' import getModuleDependencies from './lib/getModuleDependencies' +let env = { + DEBUG: process.env.DEBUG !== undefined, +} + // --- function indentRecursive(node, indent = 0) { @@ -178,13 +182,13 @@ function startWatcher() { function getTailwindConfig( configPath = args['--config'] ?? path.resolve('./tailwind.config.js') ) { - console.time('Module dependencies') + env.DEBUG && console.time('Module dependencies') for (let file of configDependencies) { delete require.cache[require.resolve(file)] } configDependencies = getModuleDependencies(configPath).map(({ file }) => file) - console.timeEnd('Module dependencies') + env.DEBUG && console.timeEnd('Module dependencies') return resolveConfig(require(configPath)) } @@ -196,7 +200,7 @@ function startWatcher() { return { postcssPlugin: 'tailwindcss', Once(root, { result }) { - console.time('Compiling CSS') + env.DEBUG && console.time('Compiling CSS') processTailwindFeatures(({ createContext }) => { return () => { if (context !== null) { @@ -204,13 +208,13 @@ function startWatcher() { return context } - console.time('Creating context') + env.DEBUG && console.time('Creating context') context = createContext(config, changedContent.splice(0)) - console.timeEnd('Creating context') + env.DEBUG && console.timeEnd('Creating context') return context } })(root, result) - console.timeEnd('Compiling CSS') + env.DEBUG && console.timeEnd('Compiling CSS') }, } } @@ -265,25 +269,25 @@ function startWatcher() { watcher.on('change', async (file) => { if (contextDependencies.has(file)) { - console.time('Resolve config') + env.DEBUG && console.time('Resolve config') context = null config = getTailwindConfig(configPath) for (let dependency of configDependencies) { contextDependencies.add(dependency) } - console.timeEnd('Resolve config') + env.DEBUG && console.timeEnd('Resolve config') - console.time('Watch new files') + env.DEBUG && console.time('Watch new files') let globs = extractFileGlobs(config) watcher.add(configDependencies) watcher.add(globs) - console.timeEnd('Watch new files') + env.DEBUG && console.timeEnd('Watch new files') chain = chain.then(async () => { changedContent.push(...getChangedContent(config)) - console.time('Build total') + env.DEBUG && console.time('Build total') await build(config) - console.timeEnd('Build total') + env.DEBUG && console.timeEnd('Build total') }) } else { chain = chain.then(async () => { @@ -292,9 +296,9 @@ function startWatcher() { extension: path.extname(file), }) - console.time('Build total') + env.DEBUG && console.time('Build total') await build(config) - console.timeEnd('Build total') + env.DEBUG && console.timeEnd('Build total') }) } }) @@ -306,9 +310,9 @@ function startWatcher() { extension: path.extname(file), }) - console.time('Build total') + env.DEBUG && console.time('Build total') await build(config) - console.timeEnd('Build total') + env.DEBUG && console.timeEnd('Build total') }) }) From 517c4a253cce71d072e1a971c60dc1435f312563 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 31 May 2021 17:52:23 +0200 Subject: [PATCH 17/23] add `init` command --- src/cli.js | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/cli.js b/src/cli.js index 91319ede49a5..7bb12c3a30eb 100644 --- a/src/cli.js +++ b/src/cli.js @@ -43,7 +43,7 @@ function formatNodes(root) { - [x] Reduce getModuleDependencies calls (make configDeps global?) - [x] Detect new files - [x] Support raw content in purge config - - [ ] Scaffold tailwind.config.js file (with postcss.config.js) + - [x] Scaffold tailwind.config.js file (with postcss.config.js) - [x] Support passing globs from command line - [ ] Prebundle peer-dependencies - [ ] Make minification work @@ -59,6 +59,9 @@ let args = arg({ '--output': String, '--minify': Boolean, '--watch': Boolean, + '--postcss': Boolean, + '--full': Boolean, + '-p': '--postcss', '-f': '--files', '-c': '--config', '-i': '--input', @@ -72,6 +75,46 @@ let output = args['--output'] let shouldWatch = args['--watch'] let shouldMinify = args['--minify'] +if (args['_'].includes('init')) { + let tailwindConfigLocation = path.resolve(process.cwd(), 'tailwind.config.js') + if (fs.existsSync(tailwindConfigLocation)) { + console.log('tailwind.config.js already exists.') + } else { + let stubFile = fs.readFileSync( + args['--full'] + ? path.resolve(__dirname, '../stubs/defaultConfig.stub.js') + : path.resolve(__dirname, '../stubs/simpleConfig.stub.js'), + 'utf8' + ) + + fs.writeFileSync( + tailwindConfigLocation, + stubFile.replace('../colors', 'tailwindcss/colors'), + 'utf8' + ) + + console.log('Created Tailwind config file:', 'tailwind.config.js') + } + + if (args['--postcss']) { + let postcssConfigLocation = path.resolve(process.cwd(), 'postcss.config.js') + if (fs.existsSync(postcssConfigLocation)) { + console.log('postcss.config.js already exists.') + } else { + let stubFile = fs.readFileSync( + path.resolve(__dirname, '../stubs/defaultPostCssConfig.stub.js'), + 'utf8' + ) + + fs.writeFileSync(postcssConfigLocation, stubFile, 'utf8') + + console.log('Created PostCSS config file:', 'tailwind.config.js') + } + } + + process.exit(0) +} + function resolveConfig(config) { let resolvedConfig = resolveConfigInternal(config) From 4f019e48409f45f28c36e462ec41b59fbf5da3bf Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 31 May 2021 18:09:42 +0200 Subject: [PATCH 18/23] clean up when using --jit --- src/cli.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/cli.js b/src/cli.js index 7bb12c3a30eb..95a0e23420b6 100644 --- a/src/cli.js +++ b/src/cli.js @@ -47,12 +47,18 @@ function formatNodes(root) { - [x] Support passing globs from command line - [ ] Prebundle peer-dependencies - [ ] Make minification work + - [ ] --help option + - [ ] conditional flags based on arguments + init -f, --full + build -f, --files + - [ ] --jit Future: - Detect project type, add sensible purge defaults */ let args = arg({ + '--jit': Boolean, '--files': String, '--config': String, '--input': String, @@ -87,11 +93,19 @@ if (args['_'].includes('init')) { 'utf8' ) - fs.writeFileSync( - tailwindConfigLocation, - stubFile.replace('../colors', 'tailwindcss/colors'), - 'utf8' - ) + // Change colors import + stubFile = stubFile.replace('../colors', 'tailwindcss/colors') + + // --jit mode + if (args['--jit']) { + // Add jit mode + stubFile = stubFile.replace('module.exports = {', "module.exports = {\n mode: 'jit',") + + // Deleting variants + stubFile = stubFile.replace(/variants: {(.*)},\n /gs, '') + } + + fs.writeFileSync(tailwindConfigLocation, stubFile, 'utf8') console.log('Created Tailwind config file:', 'tailwind.config.js') } @@ -122,6 +136,10 @@ function resolveConfig(config) { resolvedConfig.purge = args['--files'].split(',') } + if (args['--jit']) { + resolvedConfig.mode = 'jit' + } + return resolvedConfig } From 7b1c37cd4bf96ac671979f362cbcb0e86993986b Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Mon, 31 May 2021 15:35:53 -0400 Subject: [PATCH 19/23] Make config file optional --- src/cli.js | 428 +++++++++++++++++++++++++++++------------------------ 1 file changed, 231 insertions(+), 197 deletions(-) diff --git a/src/cli.js b/src/cli.js index 95a0e23420b6..10a6a70393cf 100644 --- a/src/cli.js +++ b/src/cli.js @@ -7,7 +7,8 @@ import chalk from 'chalk' import path from 'path' import arg from 'arg' import fs from 'fs' -import processTailwindFeatures from './jit/processTailwindFeatures' +import tailwindJit from './jit/processTailwindFeatures' +import tailwindAot from './processTailwindFeatures' import resolveConfigInternal from '../resolveConfig' import fastGlob from 'fast-glob' import getModuleDependencies from './lib/getModuleDependencies' @@ -45,9 +46,11 @@ function formatNodes(root) { - [x] Support raw content in purge config - [x] Scaffold tailwind.config.js file (with postcss.config.js) - [x] Support passing globs from command line + - [x] Make config file optional + - [ ] Support AOT mode - [ ] Prebundle peer-dependencies - [ ] Make minification work - - [ ] --help option + - [ ] --help option - [ ] conditional flags based on arguments init -f, --full build -f, --files @@ -82,6 +85,12 @@ let shouldWatch = args['--watch'] let shouldMinify = args['--minify'] if (args['_'].includes('init')) { + init() +} else { + build() +} + +function init() { let tailwindConfigLocation = path.resolve(process.cwd(), 'tailwind.config.js') if (fs.existsSync(tailwindConfigLocation)) { console.log('tailwind.config.js already exists.') @@ -125,160 +134,105 @@ if (args['_'].includes('init')) { console.log('Created PostCSS config file:', 'tailwind.config.js') } } - - process.exit(0) } -function resolveConfig(config) { - let resolvedConfig = resolveConfigInternal(config) - - if (args['--files']) { - resolvedConfig.purge = args['--files'].split(',') - } - - if (args['--jit']) { - resolvedConfig.mode = 'jit' +function build() { + if (args['--config'] && !fs.existsSync(args['--config'])) { + console.error(`Specified config file ${args['--config']} does not exist.`) + process.exit(9) } - return resolvedConfig -} - -if (!output) { - throw new Error('Missing required output file: --output, -o, or first argument') -} - -function extractContent(config) { - return Array.isArray(config.purge) ? config.purge : config.purge.content -} - -function extractFileGlobs(config) { - return extractContent(config).filter((file) => { - // Strings in this case are files / globs. If it is something else, - // like an object it's probably a raw content object. But this object - // is not watchable, so let's remove it. - return typeof file === 'string' - }) -} + let configPath = + args['--config'] ?? + ((defaultPath) => (fs.existsSync(defaultPath) ? defaultPath : null))( + path.resolve('./tailwind.config.js') + ) -function extractRawContent(config) { - return extractContent(config).filter((file) => { - return typeof file === 'object' && file !== null - }) -} + function resolveConfig() { + let config = require(configPath) + let resolvedConfig = resolveConfigInternal(config) -function getChangedContent(config) { - let changedContent = [] + if (args['--files']) { + resolvedConfig.purge = args['--files'].split(',') + } - // Resolve globs from the purge config - let globs = extractFileGlobs(config) - let files = fastGlob.sync(globs) + if (args['--jit']) { + resolvedConfig.mode = 'jit' + } - for (let file of files) { - changedContent.push({ - content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), - extension: path.extname(file), - }) + return resolvedConfig } - // Resolve raw content in the tailwind config - for (let { raw: content, extension = 'html' } of extractRawContent(config)) { - changedContent.push({ content, extension }) + if (!output) { + throw new Error('Missing required output file: --output, -o, or first argument') } - return changedContent -} - -function buildOnce() { - let config = resolveConfig(require(args['--config'] ?? path.resolve('./tailwind.config.js'))) - - let changedContent = getChangedContent(config) - - let tailwindPlugin = (opts = {}) => { - return { - postcssPlugin: 'tailwindcss', - Once(root, { result }) { - processTailwindFeatures(({ createContext }) => { - return () => { - return createContext(config, changedContent) - } - })(root, result) - }, - } + function extractContent(config) { + return Array.isArray(config.purge) ? config.purge : config.purge.content } - tailwindPlugin.postcss = true - - let plugins = [ - // TODO: Bake in postcss-import support? - // TODO: Bake in postcss-nested support? - tailwindPlugin, - // require('autoprefixer'), - formatNodes, - ] - - let processor = postcss(plugins) - - function processCSS(css) { - return processor.process(css, { from: input, to: output }).then((result) => { - fs.writeFile(output, result.css, () => true) - if (result.map) { - fs.writeFile(output + '.map', result.map.toString(), () => true) - } + + function extractFileGlobs(config) { + return extractContent(config).filter((file) => { + // Strings in this case are files / globs. If it is something else, + // like an object it's probably a raw content object. But this object + // is not watchable, so let's remove it. + return typeof file === 'string' }) } - let css = input - ? fs.readFileSync(path.resolve(process.cwd(), input), 'utf8') - : '@tailwind base; @tailwind components; @tailwind utilities' - return processCSS(css) -} + function extractRawContent(config) { + return extractContent(config).filter((file) => { + return typeof file === 'object' && file !== null + }) + } -let context = null + function getChangedContent(config) { + let changedContent = [] -function startWatcher() { - let changedContent = [] - let configDependencies = [] - let contextDependencies = new Set() - let watcher = null + // Resolve globs from the purge config + let globs = extractFileGlobs(config) + let files = fastGlob.sync(globs) - function getTailwindConfig( - configPath = args['--config'] ?? path.resolve('./tailwind.config.js') - ) { - env.DEBUG && console.time('Module dependencies') - for (let file of configDependencies) { - delete require.cache[require.resolve(file)] + for (let file of files) { + changedContent.push({ + content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), + extension: path.extname(file), + }) } - configDependencies = getModuleDependencies(configPath).map(({ file }) => file) - env.DEBUG && console.timeEnd('Module dependencies') + // Resolve raw content in the tailwind config + for (let { raw: content, extension = 'html' } of extractRawContent(config)) { + changedContent.push({ content, extension }) + } - return resolveConfig(require(configPath)) + return changedContent } - function build(config) { - console.log('\n\nRebuilding...') - - let tailwindPlugin = (opts = {}) => { - return { - postcssPlugin: 'tailwindcss', - Once(root, { result }) { - env.DEBUG && console.time('Compiling CSS') - processTailwindFeatures(({ createContext }) => { - return () => { - if (context !== null) { - context.changedContent = changedContent.splice(0) - return context - } - - env.DEBUG && console.time('Creating context') - context = createContext(config, changedContent.splice(0)) - env.DEBUG && console.timeEnd('Creating context') - return context + function buildOnce() { + let config = resolveConfig() + let changedContent = getChangedContent(config) + + let tailwindPlugin = + config.mode === 'jit' + ? (opts = {}) => { + return { + postcssPlugin: 'tailwindcss', + Once(root, { result }) { + tailwindJit(({ createContext }) => { + return () => { + return createContext(config, changedContent) + } + })(root, result) + }, } - })(root, result) - env.DEBUG && console.timeEnd('Compiling CSS') - }, - } - } + } + : (opts = {}) => { + return { + postcssPlugin: 'tailwindcss', + plugins: [tailwindAot(() => config, configPath)], + } + } + tailwindPlugin.postcss = true let plugins = [ @@ -286,20 +240,13 @@ function startWatcher() { // TODO: Bake in postcss-nested support? tailwindPlugin, // require('autoprefixer'), - // formatNodes, + formatNodes, ] let processor = postcss(plugins) function processCSS(css) { return processor.process(css, { from: input, to: output }).then((result) => { - for (let message of result.messages) { - if (message.type === 'dependency') { - contextDependencies.add(message.file) - } - } - watcher.add([...contextDependencies]) - fs.writeFile(output, result.css, () => true) if (result.map) { fs.writeFile(output + '.map', result.map.toString(), () => true) @@ -313,78 +260,165 @@ function startWatcher() { return processCSS(css) } - let configPath = args['--config'] ?? path.resolve('./tailwind.config.js') - let config = getTailwindConfig(configPath) - for (let dependency of configDependencies) { - contextDependencies.add(dependency) - } - if (input) { - contextDependencies.add(path.resolve(process.cwd(), input)) - } + let context = null - watcher = chokidar.watch([...contextDependencies, ...extractFileGlobs(config)], { - ignoreInitial: true, - }) + function startWatcher() { + let changedContent = [] + let configDependencies = [] + let contextDependencies = new Set() + let watcher = null - let chain = Promise.resolve() + function refreshConfig() { + env.DEBUG && console.time('Module dependencies') + for (let file of configDependencies) { + delete require.cache[require.resolve(file)] + } - watcher.on('change', async (file) => { - if (contextDependencies.has(file)) { - env.DEBUG && console.time('Resolve config') - context = null - config = getTailwindConfig(configPath) - for (let dependency of configDependencies) { - contextDependencies.add(dependency) + if (configPath) { + configDependencies = getModuleDependencies(configPath).map(({ file }) => file) + + for (let dependency of configDependencies) { + contextDependencies.add(dependency) + } } - env.DEBUG && console.timeEnd('Resolve config') + env.DEBUG && console.timeEnd('Module dependencies') - env.DEBUG && console.time('Watch new files') - let globs = extractFileGlobs(config) - watcher.add(configDependencies) - watcher.add(globs) - env.DEBUG && console.timeEnd('Watch new files') + return resolveConfig() + } - chain = chain.then(async () => { - changedContent.push(...getChangedContent(config)) - env.DEBUG && console.time('Build total') - await build(config) - env.DEBUG && console.timeEnd('Build total') - }) - } else { + async function rebuild(config) { + console.log('\nRebuilding...') + env.DEBUG && console.time('Finished in') + + let tailwindPlugin = + config.mode === 'jit' + ? (opts = {}) => { + return { + postcssPlugin: 'tailwindcss', + Once(root, { result }) { + env.DEBUG && console.time('Compiling CSS') + tailwindJit(({ createContext }) => { + return () => { + if (context !== null) { + context.changedContent = changedContent.splice(0) + return context + } + + env.DEBUG && console.time('Creating context') + context = createContext(config, changedContent.splice(0)) + env.DEBUG && console.timeEnd('Creating context') + return context + } + })(root, result) + env.DEBUG && console.timeEnd('Compiling CSS') + }, + } + } + : (opts = {}) => { + return { + postcssPlugin: 'tailwindcss', + plugins: [tailwindAot(() => config, configPath)], + } + } + + tailwindPlugin.postcss = true + + let plugins = [ + // TODO: Bake in postcss-import support? + // TODO: Bake in postcss-nested support? + tailwindPlugin, + // require('autoprefixer'), + formatNodes, + ] + + let processor = postcss(plugins) + + function processCSS(css) { + return processor.process(css, { from: input, to: output }).then((result) => { + for (let message of result.messages) { + if (message.type === 'dependency') { + contextDependencies.add(message.file) + } + } + watcher.add([...contextDependencies]) + + fs.writeFile(output, result.css, () => true) + if (result.map) { + fs.writeFile(output + '.map', result.map.toString(), () => true) + } + }) + } + + let css = input + ? fs.readFileSync(path.resolve(process.cwd(), input), 'utf8') + : '@tailwind base; @tailwind components; @tailwind utilities' + let result = await processCSS(css) + env.DEBUG && console.timeEnd('Finished in') + return result + } + + let configPath = args['--config'] ?? path.resolve('./tailwind.config.js') + let config = refreshConfig(configPath) + + if (input) { + contextDependencies.add(path.resolve(process.cwd(), input)) + } + + watcher = chokidar.watch([...contextDependencies, ...extractFileGlobs(config)], { + ignoreInitial: true, + }) + + let chain = Promise.resolve() + + watcher.on('change', async (file) => { + if (contextDependencies.has(file)) { + env.DEBUG && console.time('Resolve config') + context = null + config = refreshConfig(configPath) + env.DEBUG && console.timeEnd('Resolve config') + + env.DEBUG && console.time('Watch new files') + let globs = extractFileGlobs(config) + watcher.add(configDependencies) + watcher.add(globs) + env.DEBUG && console.timeEnd('Watch new files') + + chain = chain.then(async () => { + changedContent.push(...getChangedContent(config)) + await rebuild(config) + }) + } else { + chain = chain.then(async () => { + changedContent.push({ + content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), + extension: path.extname(file), + }) + + await rebuild(config) + }) + } + }) + + watcher.on('add', async (file) => { chain = chain.then(async () => { changedContent.push({ content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), extension: path.extname(file), }) - env.DEBUG && console.time('Build total') - await build(config) - env.DEBUG && console.timeEnd('Build total') - }) - } - }) - - watcher.on('add', async (file) => { - chain = chain.then(async () => { - changedContent.push({ - content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), - extension: path.extname(file), + await rebuild(config) }) - - env.DEBUG && console.time('Build total') - await build(config) - env.DEBUG && console.timeEnd('Build total') }) - }) - chain = chain.then(() => { - changedContent.push(...getChangedContent(config)) - return build(config) - }) -} + chain = chain.then(() => { + changedContent.push(...getChangedContent(config)) + return rebuild(config) + }) + } -if (shouldWatch) { - startWatcher() -} else { - buildOnce() + if (shouldWatch) { + startWatcher() + } else { + buildOnce() + } } From 4d0ed342a3eeff5c52f1122cd7704acd13938f87 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 1 Jun 2021 11:22:17 +0200 Subject: [PATCH 20/23] cleanup path.resolve calls path.resolve('.') is the same as path.resolve(process.cwd(), '.') --- src/cli.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cli.js b/src/cli.js index 10a6a70393cf..abe015e981fe 100644 --- a/src/cli.js +++ b/src/cli.js @@ -91,7 +91,7 @@ if (args['_'].includes('init')) { } function init() { - let tailwindConfigLocation = path.resolve(process.cwd(), 'tailwind.config.js') + let tailwindConfigLocation = path.resolve('./tailwind.config.js') if (fs.existsSync(tailwindConfigLocation)) { console.log('tailwind.config.js already exists.') } else { @@ -120,7 +120,7 @@ function init() { } if (args['--postcss']) { - let postcssConfigLocation = path.resolve(process.cwd(), 'postcss.config.js') + let postcssConfigLocation = path.resolve('./postcss.config.js') if (fs.existsSync(postcssConfigLocation)) { console.log('postcss.config.js already exists.') } else { @@ -195,7 +195,7 @@ function build() { for (let file of files) { changedContent.push({ - content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), + content: fs.readFileSync(path.resolve(file), 'utf8'), extension: path.extname(file), }) } @@ -255,7 +255,7 @@ function build() { } let css = input - ? fs.readFileSync(path.resolve(process.cwd(), input), 'utf8') + ? fs.readFileSync(path.resolve(input), 'utf8') : '@tailwind base; @tailwind components; @tailwind utilities' return processCSS(css) } @@ -350,7 +350,7 @@ function build() { } let css = input - ? fs.readFileSync(path.resolve(process.cwd(), input), 'utf8') + ? fs.readFileSync(path.resolve(input), 'utf8') : '@tailwind base; @tailwind components; @tailwind utilities' let result = await processCSS(css) env.DEBUG && console.timeEnd('Finished in') @@ -361,7 +361,7 @@ function build() { let config = refreshConfig(configPath) if (input) { - contextDependencies.add(path.resolve(process.cwd(), input)) + contextDependencies.add(path.resolve(input)) } watcher = chokidar.watch([...contextDependencies, ...extractFileGlobs(config)], { @@ -390,7 +390,7 @@ function build() { } else { chain = chain.then(async () => { changedContent.push({ - content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), + content: fs.readFileSync(path.resolve(file), 'utf8'), extension: path.extname(file), }) @@ -402,7 +402,7 @@ function build() { watcher.on('add', async (file) => { chain = chain.then(async () => { changedContent.push({ - content: fs.readFileSync(path.resolve(process.cwd(), file), 'utf8'), + content: fs.readFileSync(path.resolve(file), 'utf8'), extension: path.extname(file), }) From 4493804059d7d8c3bb77a99e389122df11052b3c Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 1 Jun 2021 12:55:43 +0200 Subject: [PATCH 21/23] implement `--help` --- src/cli.js | 175 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 144 insertions(+), 31 deletions(-) diff --git a/src/cli.js b/src/cli.js index abe015e981fe..ce952668f6bb 100644 --- a/src/cli.js +++ b/src/cli.js @@ -12,6 +12,7 @@ import tailwindAot from './processTailwindFeatures' import resolveConfigInternal from '../resolveConfig' import fastGlob from 'fast-glob' import getModuleDependencies from './lib/getModuleDependencies' +import packageJson from '../package.json' let env = { DEBUG: process.env.DEBUG !== undefined, @@ -37,6 +38,54 @@ function formatNodes(root) { } } +function help({ message, usage, commands, options }) { + // Render header + console.log() + console.log(' ', packageJson.name, packageJson.version) + + // Render message + if (message) { + console.log() + console.log(' ', message) + } + + // Render usage + if (usage && usage.length > 0) { + console.log() + console.log(' ', 'Usage:') + for (let example of usage) { + console.log(' ', ' ', example) + } + } + + // Render commands + if (commands && commands.length > 0) { + console.log() + console.log(' ', 'Commands:') + for (let command of commands) { + console.log(' ', ' ', command) + } + } + + // Render options + if (options) { + let groupedOptions = {} + for (let [key, value] of Object.entries(options)) { + if (typeof value === 'object') { + groupedOptions[key] = { ...value, flags: [key] } + } else { + groupedOptions[value].flags.push(key) + } + } + + console.log() + console.log(' ', 'Options:') + for (let { flags, description } of Object.values(groupedOptions)) { + console.log(' ', ' ', flags.slice().reverse().join(', ').padEnd(15, ' '), description) + } + } +} + // --- /* @@ -50,8 +99,8 @@ function formatNodes(root) { - [ ] Support AOT mode - [ ] Prebundle peer-dependencies - [ ] Make minification work - - [ ] --help option - - [ ] conditional flags based on arguments + - [x] --help option + - [x] conditional flags based on arguments init -f, --full build -f, --files - [ ] --jit @@ -59,37 +108,91 @@ function formatNodes(root) { Future: - Detect project type, add sensible purge defaults */ +let commands = { + init: { + run: init, + args: { + '--jit': { type: Boolean, description: 'Enable `JIT` mode' }, + '--full': { type: Boolean, description: 'Generate a full tailwind.config.js file' }, + '--postcss': { type: Boolean, description: 'Generate a PostCSS file' }, + '-f': '--full', + '-p': '--postcss', + }, + }, + build: { + run: build, + args: { + '--jit': { type: Boolean, description: 'Build using `JIT` mode' }, + '--files': { type: String, description: 'Use a glob as files to use' }, + '--config': { + type: String, + description: 'Provide a custom config file, default: ./tailwind.config.js', + }, + '--input': { type: String, description: 'The input css file' }, + '--output': { type: String, description: 'The output css file' }, + '--minify': { type: Boolean, description: 'Whether or not the result should be minified' }, + '--watch': { type: Boolean, description: 'Start watching for changes' }, + '-f': '--files', + '-c': '--config', + '-i': '--input', + '-o': '--output', + '-m': '--minify', + '-w': '--watch', + }, + }, +} -let args = arg({ - '--jit': Boolean, - '--files': String, - '--config': String, - '--input': String, - '--output': String, - '--minify': Boolean, - '--watch': Boolean, - '--postcss': Boolean, - '--full': Boolean, - '-p': '--postcss', - '-f': '--files', - '-c': '--config', - '-i': '--input', - '-o': '--output', - '-m': '--minify', - '-w': '--watch', -}) - -let input = args['--input'] -let output = args['--output'] -let shouldWatch = args['--watch'] -let shouldMinify = args['--minify'] - -if (args['_'].includes('init')) { - init() -} else { - build() +let sharedFlags = { + '--help': { type: Boolean, description: 'Prints this help message' }, + '-h': '--help', +} +let command = process.argv.slice(2).find((arg) => !arg.startsWith('-')) || 'build' + +if (commands[command] === undefined) { + help({ + message: `Invalid command: ${command}`, + usage: ['tailwind [options]'], + commands: ['init [file]', 'build [options]'], + options: sharedFlags, + }) + process.exit(1) +} + +// Execute command +let { args: flags, run } = commands[command] +let args = (() => { + try { + return arg( + Object.fromEntries( + Object.entries({ ...flags, ...sharedFlags }).map(([key, value]) => [ + key, + typeof value === 'object' ? value.type : value, + ]) + ) + ) + } catch (err) { + if (err.code === 'ARG_UNKNOWN_OPTION') { + help({ + message: err.message, + usage: ['tailwind [options]'], + options: sharedFlags, + }) + process.exit(1) + } + throw err + } +})() + +if (args['--help']) { + help({ + options: { ...flags, ...sharedFlags }, + usage: [`tailwind ${command} [options]`], + }) + process.exit(0) } +run() + function init() { let tailwindConfigLocation = path.resolve('./tailwind.config.js') if (fs.existsSync(tailwindConfigLocation)) { @@ -137,6 +240,11 @@ function init() { } function build() { + let input = args['--input'] + let output = args['--output'] + let shouldWatch = args['--watch'] + let shouldMinify = args['--minify'] + if (args['--config'] && !fs.existsSync(args['--config'])) { console.error(`Specified config file ${args['--config']} does not exist.`) process.exit(9) @@ -164,7 +272,12 @@ function build() { } if (!output) { - throw new Error('Missing required output file: --output, -o, or first argument') + help({ + message: 'Missing required output file: --output, -o, or first argument', + usage: [`tailwind ${command} [options]`], + options: { ...flags, ...sharedFlags }, + }) + process.exit(1) } function extractContent(config) { From 08767e59c416927dfdc17a23eca0735e25fa63b2 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 1 Jun 2021 13:12:50 +0200 Subject: [PATCH 22/23] shush eslint --- src/cli.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cli.js b/src/cli.js index ce952668f6bb..78663cca5725 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,5 +1,7 @@ #!/usr/bin/env node +/* eslint-disable */ + // import autoprefixer from 'autoprefixer' import chokidar from 'chokidar' import postcss from 'postcss' From 89974f4d99aeedbbec0daa188103fb998376e14f Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 1 Jun 2021 13:16:41 +0200 Subject: [PATCH 23/23] drop unnecessary file --- out.css | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 out.css diff --git a/out.css b/out.css deleted file mode 100644 index cd2020f63b84..000000000000 --- a/out.css +++ /dev/null @@ -1,34 +0,0 @@ -.mt-1 { - margin-top: 0.25rem -} -.mt-5 { - margin-top: 1.25rem -} -.mb-1 { - margin-bottom: 0.25rem -} -.p-24 { - padding: 6rem -} -.pl-4 { - padding-left: 1rem -} -.shadow-md { - --tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) -} -.filter { - --tw-blur: var(--tw-empty,/*!*/ /*!*/); - --tw-brightness: var(--tw-empty,/*!*/ /*!*/); - --tw-contrast: var(--tw-empty,/*!*/ /*!*/); - --tw-grayscale: var(--tw-empty,/*!*/ /*!*/); - --tw-hue-rotate: var(--tw-empty,/*!*/ /*!*/); - --tw-invert: var(--tw-empty,/*!*/ /*!*/); - --tw-saturate: var(--tw-empty,/*!*/ /*!*/); - --tw-sepia: var(--tw-empty,/*!*/ /*!*/); - --tw-drop-shadow: var(--tw-empty,/*!*/ /*!*/); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) -} -.drop-shadow-xl { - --tw-drop-shadow: drop-shadow(0 20px 13px rgba(0, 0, 0, 0.03)) drop-shadow(0 8px 5px rgba(0, 0, 0, 0.08)) -} \ No newline at end of file