mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Improve new JIT-compatible CLI (#4558)
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
parent
05d26a5d43
commit
746a12602e
@ -1,5 +1,6 @@
|
||||
/cli
|
||||
/lib
|
||||
/docs
|
||||
/peers
|
||||
/tests/fixtures/cli-utils.js
|
||||
/stubs/*
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@
|
||||
/coverage
|
||||
/cli
|
||||
/lib
|
||||
/peers
|
||||
/example
|
||||
.vscode
|
||||
tailwind.config.js
|
||||
|
||||
@ -14,7 +14,7 @@ module.exports = function $(command, options = {}) {
|
||||
|
||||
let args = command.split(' ')
|
||||
command = args.shift()
|
||||
command = path.resolve(cwd, 'node_modules', '.bin', command)
|
||||
command = command === 'node' ? command : path.resolve(cwd, 'node_modules', '.bin', command)
|
||||
|
||||
let stdoutMessages = []
|
||||
let stderrMessages = []
|
||||
|
||||
4
integrations/tailwindcss-cli/.gitignore
vendored
Normal file
4
integrations/tailwindcss-cli/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
!tailwind.config.js
|
||||
!index.html
|
||||
12
integrations/tailwindcss-cli/package-lock.json
generated
Normal file
12
integrations/tailwindcss-cli/package-lock.json
generated
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "postcss-cli",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "postcss-cli",
|
||||
"version": "0.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
15
integrations/tailwindcss-cli/package.json
Normal file
15
integrations/tailwindcss-cli/package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "tailwindcss-cli",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production node ../../lib/cli.js -i ./src/index.css -o ./dist/main.css",
|
||||
"test": "jest"
|
||||
},
|
||||
"jest": {
|
||||
"displayName": "Tailwind CSS CLI",
|
||||
"setupFilesAfterEnv": [
|
||||
"<rootDir>/../../jest/customMatchers.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
5
integrations/tailwindcss-cli/postcss.config.js
Normal file
5
integrations/tailwindcss-cli/postcss.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
let path = require('path')
|
||||
|
||||
module.exports = {
|
||||
plugins: [require(path.resolve('..', '..'))],
|
||||
}
|
||||
3
integrations/tailwindcss-cli/src/index.css
Normal file
3
integrations/tailwindcss-cli/src/index.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
11
integrations/tailwindcss-cli/src/index.html
Normal file
11
integrations/tailwindcss-cli/src/index.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
15
integrations/tailwindcss-cli/tailwind.config.js
Normal file
15
integrations/tailwindcss-cli/tailwind.config.js
Normal file
@ -0,0 +1,15 @@
|
||||
module.exports = {
|
||||
purge: ['./src/index.html'],
|
||||
mode: 'jit',
|
||||
darkMode: false, // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
},
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
232
integrations/tailwindcss-cli/tests/integration.test.js
Normal file
232
integrations/tailwindcss-cli/tests/integration.test.js
Normal file
@ -0,0 +1,232 @@
|
||||
let $ = require('../../execute')
|
||||
let { css, html, javascript } = require('../../syntax')
|
||||
|
||||
let {
|
||||
readOutputFile,
|
||||
appendToInputFile,
|
||||
writeInputFile,
|
||||
waitForOutputFileCreation,
|
||||
waitForOutputFileChange,
|
||||
} = require('../../io')({ output: 'dist', input: 'src' })
|
||||
|
||||
describe('static build', () => {
|
||||
it('should be possible to generate tailwind output', async () => {
|
||||
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
|
||||
|
||||
await $('node ../../lib/cli.js -i ./src/index.css -o ./dist/main.css', {
|
||||
env: { NODE_ENV: 'production' },
|
||||
})
|
||||
|
||||
expect(await readOutputFile('main.css')).toIncludeCss(
|
||||
css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('watcher', () => {
|
||||
test('classes are generated when the html file changes', async () => {
|
||||
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
|
||||
|
||||
let runningProcess = $('node ../../lib/cli.js -i ./src/index.css -o ./dist/main.css -w')
|
||||
|
||||
await waitForOutputFileCreation('main.css')
|
||||
|
||||
expect(await readOutputFile('main.css')).toIncludeCss(
|
||||
css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
await waitForOutputFileChange('main.css', async () => {
|
||||
await appendToInputFile('index.html', html`<div class="font-normal"></div>`)
|
||||
})
|
||||
|
||||
expect(await readOutputFile('main.css')).toIncludeCss(
|
||||
css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
await waitForOutputFileChange('main.css', async () => {
|
||||
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
|
||||
})
|
||||
|
||||
expect(await readOutputFile('main.css')).toIncludeCss(
|
||||
css`
|
||||
.bg-red-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
return runningProcess.stop()
|
||||
})
|
||||
|
||||
test('classes are generated when the tailwind.config.js file changes', async () => {
|
||||
await writeInputFile('index.html', html`<div class="font-bold md:font-medium"></div>`)
|
||||
|
||||
let runningProcess = $('node ../../lib/cli.js -i ./src/index.css -o ./dist/main.css -w')
|
||||
|
||||
await waitForOutputFileCreation('main.css')
|
||||
|
||||
expect(await readOutputFile('main.css')).toIncludeCss(
|
||||
css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\\:font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
await waitForOutputFileChange('main.css', async () => {
|
||||
await writeInputFile(
|
||||
'../tailwind.config.js',
|
||||
javascript`
|
||||
module.exports = {
|
||||
purge: ['./src/index.html'],
|
||||
mode: 'jit',
|
||||
darkMode: false, // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {
|
||||
screens: {
|
||||
md: '800px'
|
||||
},
|
||||
fontWeight: {
|
||||
bold: 'bold'
|
||||
}
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
},
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
expect(await readOutputFile('main.css')).toIncludeCss(
|
||||
css`
|
||||
.font-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
@media (min-width: 800px) {
|
||||
.md\\:font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
return runningProcess.stop()
|
||||
})
|
||||
|
||||
test('classes are generated when the index.css file changes', async () => {
|
||||
await writeInputFile('index.html', html`<div class="font-bold btn"></div>`)
|
||||
|
||||
let runningProcess = $('node ../../lib/cli.js -i ./src/index.css -o ./dist/main.css -w')
|
||||
|
||||
await waitForOutputFileCreation('main.css')
|
||||
|
||||
expect(await readOutputFile('main.css')).toIncludeCss(
|
||||
css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
await waitForOutputFileChange('main.css', async () => {
|
||||
await writeInputFile(
|
||||
'index.css',
|
||||
css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.btn {
|
||||
@apply px-2 py-1 rounded;
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
expect(await readOutputFile('main.css')).toIncludeCss(
|
||||
css`
|
||||
.btn {
|
||||
border-radius: 0.25rem;
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
await waitForOutputFileChange('main.css', async () => {
|
||||
await writeInputFile(
|
||||
'index.css',
|
||||
css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.btn {
|
||||
@apply px-2 py-1 rounded bg-red-500;
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
expect(await readOutputFile('main.css')).toIncludeCss(
|
||||
css`
|
||||
.btn {
|
||||
border-radius: 0.25rem;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
return runningProcess.stop()
|
||||
})
|
||||
})
|
||||
1459
package-lock.json
generated
1459
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -20,6 +20,7 @@
|
||||
"scripts": {
|
||||
"prebabelify": "rimraf lib",
|
||||
"babelify": "babel src --out-dir lib --copy-files",
|
||||
"postbabelify": "ncc build lib/cli-peer-dependencies.js -o peers",
|
||||
"rebuild-fixtures": "npm run babelify && babel-node scripts/rebuildFixtures.js",
|
||||
"prepublishOnly": "npm run babelify && babel-node scripts/build.js",
|
||||
"style": "eslint .",
|
||||
@ -34,6 +35,7 @@
|
||||
"dist/*.css",
|
||||
"cli/*",
|
||||
"lib/*",
|
||||
"peers/*",
|
||||
"scripts/*.js",
|
||||
"stubs/*.stub.js",
|
||||
"*.css",
|
||||
@ -70,6 +72,7 @@
|
||||
"chalk": "^4.1.1",
|
||||
"chokidar": "^3.5.1",
|
||||
"color": "^3.1.3",
|
||||
"cssnano": "^5.0.5",
|
||||
"detective": "^5.2.0",
|
||||
"didyoumean": "^1.2.1",
|
||||
"dlv": "^1.1.3",
|
||||
@ -85,6 +88,7 @@
|
||||
"normalize-path": "^3.0.0",
|
||||
"object-hash": "^2.2.0",
|
||||
"postcss-js": "^3.0.3",
|
||||
"postcss-load-config": "^3.0.1",
|
||||
"postcss-nested": "5.0.5",
|
||||
"postcss-selector-parser": "^6.0.6",
|
||||
"postcss-value-parser": "^4.1.0",
|
||||
|
||||
9
src/cli-peer-dependencies.js
Normal file
9
src/cli-peer-dependencies.js
Normal file
@ -0,0 +1,9 @@
|
||||
export let postcss = require('postcss')
|
||||
|
||||
export function lazyAutoprefixer() {
|
||||
return require('autoprefixer')
|
||||
}
|
||||
|
||||
export function lazyCssnano() {
|
||||
return require('cssnano')
|
||||
}
|
||||
331
src/cli.js
331
src/cli.js
@ -1,14 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/* eslint-disable */
|
||||
import { postcss, lazyCssnano, lazyAutoprefixer } from '../peers/index.js'
|
||||
|
||||
// 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 postcssrc from 'postcss-load-config'
|
||||
import tailwindJit from './jit/processTailwindFeatures'
|
||||
import tailwindAot from './processTailwindFeatures'
|
||||
import resolveConfigInternal from '../resolveConfig'
|
||||
@ -41,31 +39,35 @@ function formatNodes(root) {
|
||||
}
|
||||
|
||||
function help({ message, usage, commands, options }) {
|
||||
let indent = 2
|
||||
|
||||
// Render header
|
||||
console.log()
|
||||
console.log(' ', packageJson.name, packageJson.version)
|
||||
console.log(`${packageJson.name} v${packageJson.version}`)
|
||||
|
||||
// Render message
|
||||
if (message) {
|
||||
console.log()
|
||||
console.log(' ', message)
|
||||
for (let msg of message.split('\n')) {
|
||||
console.log(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Render usage
|
||||
if (usage && usage.length > 0) {
|
||||
console.log()
|
||||
console.log(' ', 'Usage:')
|
||||
console.log('Usage:')
|
||||
for (let example of usage) {
|
||||
console.log(' ', ' ', example)
|
||||
console.log(' '.repeat(indent), example)
|
||||
}
|
||||
}
|
||||
|
||||
// Render commands
|
||||
if (commands && commands.length > 0) {
|
||||
console.log()
|
||||
console.log(' ', 'Commands:')
|
||||
console.log('Commands:')
|
||||
for (let command of commands) {
|
||||
console.log(' ', ' ', command)
|
||||
console.log(' '.repeat(indent), command)
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,42 +83,34 @@ function help({ message, usage, commands, options }) {
|
||||
}
|
||||
|
||||
console.log()
|
||||
console.log(' ', 'Options:')
|
||||
console.log('Options:')
|
||||
for (let { flags, description } of Object.values(groupedOptions)) {
|
||||
console.log(' ', ' ', flags.slice().reverse().join(', ').padEnd(15, ' '), description)
|
||||
if (flags.length === 1) {
|
||||
console.log(
|
||||
' '.repeat(indent + 4 /* 4 = "-i, ".length */),
|
||||
flags.slice().reverse().join(', ').padEnd(20, ' '),
|
||||
description
|
||||
)
|
||||
} else {
|
||||
console.log(
|
||||
' '.repeat(indent),
|
||||
flags.slice().reverse().join(', ').padEnd(24, ' '),
|
||||
description
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log()
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
/*
|
||||
TODOs:
|
||||
- [x] Reduce getModuleDependencies calls (make configDeps global?)
|
||||
- [x] Detect new files
|
||||
- [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
|
||||
- [x] --help option
|
||||
- [x] conditional flags based on arguments
|
||||
init -f, --full
|
||||
build -f, --files
|
||||
- [ ] --jit
|
||||
|
||||
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' },
|
||||
'--jit': { type: Boolean, description: 'Initialize for JIT mode' },
|
||||
'--full': { type: Boolean, description: 'Initialize a full `tailwind.config.js` file' },
|
||||
'--postcss': { type: Boolean, description: 'Initialize a `postcss.config.js` file' },
|
||||
'-f': '--full',
|
||||
'-p': '--postcss',
|
||||
},
|
||||
@ -124,17 +118,21 @@ let commands = {
|
||||
build: {
|
||||
run: build,
|
||||
args: {
|
||||
'--jit': { type: Boolean, description: 'Build using `JIT` mode' },
|
||||
'--files': { type: String, description: 'Use a glob as files to use' },
|
||||
'--input': { type: String, description: 'Input file' },
|
||||
'--output': { type: String, description: 'Output file' },
|
||||
'--watch': { type: Boolean, description: 'Watch for changes and rebuild as needed' },
|
||||
'--jit': { type: Boolean, description: 'Build using JIT mode' },
|
||||
'--files': { type: String, description: 'Template files to scan for class names' },
|
||||
'--postcss': { type: Boolean, description: 'Load custom PostCSS configuration' },
|
||||
'--minify': { type: Boolean, description: 'Minify the output' },
|
||||
'--config': {
|
||||
type: String,
|
||||
description: 'Provide a custom config file, default: ./tailwind.config.js',
|
||||
description: 'Path to a custom config file',
|
||||
},
|
||||
'--no-autoprefixer': {
|
||||
type: Boolean,
|
||||
description: 'Disable autoprefixer',
|
||||
},
|
||||
'--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',
|
||||
@ -145,26 +143,53 @@ let commands = {
|
||||
}
|
||||
|
||||
let sharedFlags = {
|
||||
'--help': { type: Boolean, description: 'Prints this help message' },
|
||||
'--help': { type: Boolean, description: 'Display usage information' },
|
||||
'-h': '--help',
|
||||
}
|
||||
let command = process.argv.slice(2).find((arg) => !arg.startsWith('-')) || 'build'
|
||||
|
||||
if (
|
||||
process.stdout.isTTY /* Detect redirecting output to a file */ &&
|
||||
(process.argv[2] === undefined ||
|
||||
process.argv.slice(2).every((flag) => sharedFlags[flag] !== undefined))
|
||||
) {
|
||||
help({
|
||||
usage: [
|
||||
'tailwindcss [--input input.css] [--output output.css] [--watch] [options...]',
|
||||
'tailwindcss init [--full] [--postcss] [options...]',
|
||||
],
|
||||
commands: Object.keys(commands)
|
||||
.filter((command) => command !== 'build')
|
||||
.map((command) => `${command} [options]`),
|
||||
options: { ...commands.build.args, ...sharedFlags },
|
||||
})
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
let command = ((arg = '') => (arg.startsWith('-') ? undefined : arg))(process.argv[2]) || 'build'
|
||||
|
||||
if (commands[command] === undefined) {
|
||||
help({
|
||||
message: `Invalid command: ${command}`,
|
||||
usage: ['tailwind <command> [options]'],
|
||||
commands: ['init [file]', 'build <file> [options]'],
|
||||
options: sharedFlags,
|
||||
})
|
||||
process.exit(1)
|
||||
if (fs.existsSync(path.resolve(command))) {
|
||||
// TODO: Deprecate this in future versions
|
||||
// Check if non-existing command, might be a file.
|
||||
command = 'build'
|
||||
} else {
|
||||
help({
|
||||
message: `Invalid command: ${command}`,
|
||||
usage: ['tailwindcss <command> [options]'],
|
||||
commands: Object.keys(commands)
|
||||
.filter((command) => command !== 'build')
|
||||
.map((command) => `${command} [options]`),
|
||||
options: sharedFlags,
|
||||
})
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Execute command
|
||||
let { args: flags, run } = commands[command]
|
||||
let args = (() => {
|
||||
try {
|
||||
return arg(
|
||||
let result = arg(
|
||||
Object.fromEntries(
|
||||
Object.entries({ ...flags, ...sharedFlags }).map(([key, value]) => [
|
||||
key,
|
||||
@ -172,11 +197,23 @@ let args = (() => {
|
||||
])
|
||||
)
|
||||
)
|
||||
|
||||
// Ensure that the `command` is always the first argument in the `args`.
|
||||
// This is important so that we don't have to check if a default command
|
||||
// (build) was used or not from within each plugin.
|
||||
//
|
||||
// E.g.: tailwindcss input.css -> _: ['build', 'input.css']
|
||||
// E.g.: tailwindcss build input.css -> _: ['build', 'input.css']
|
||||
if (result['_'][0] !== command) {
|
||||
result['_'].unshift(command)
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (err) {
|
||||
if (err.code === 'ARG_UNKNOWN_OPTION') {
|
||||
help({
|
||||
message: err.message,
|
||||
usage: ['tailwind <command> [options]'],
|
||||
usage: ['tailwindcss <command> [options]'],
|
||||
options: sharedFlags,
|
||||
})
|
||||
process.exit(1)
|
||||
@ -188,17 +225,21 @@ let args = (() => {
|
||||
if (args['--help']) {
|
||||
help({
|
||||
options: { ...flags, ...sharedFlags },
|
||||
usage: [`tailwind ${command} [options]`],
|
||||
usage: [`tailwindcss ${command} [options]`],
|
||||
})
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
run()
|
||||
|
||||
// ---
|
||||
|
||||
function init() {
|
||||
let tailwindConfigLocation = path.resolve('./tailwind.config.js')
|
||||
let messages = []
|
||||
|
||||
let tailwindConfigLocation = path.resolve(args['_'][1] ?? './tailwind.config.js')
|
||||
if (fs.existsSync(tailwindConfigLocation)) {
|
||||
console.log('tailwind.config.js already exists.')
|
||||
messages.push(`${path.basename(tailwindConfigLocation)} already exists.`)
|
||||
} else {
|
||||
let stubFile = fs.readFileSync(
|
||||
args['--full']
|
||||
@ -221,13 +262,13 @@ function init() {
|
||||
|
||||
fs.writeFileSync(tailwindConfigLocation, stubFile, 'utf8')
|
||||
|
||||
console.log('Created Tailwind config file:', 'tailwind.config.js')
|
||||
messages.push(`Created Tailwind CSS config file: ${path.basename(tailwindConfigLocation)}`)
|
||||
}
|
||||
|
||||
if (args['--postcss']) {
|
||||
let postcssConfigLocation = path.resolve('./postcss.config.js')
|
||||
if (fs.existsSync(postcssConfigLocation)) {
|
||||
console.log('postcss.config.js already exists.')
|
||||
messages.push(`${path.basename(postcssConfigLocation)} already exists.`)
|
||||
} else {
|
||||
let stubFile = fs.readFileSync(
|
||||
path.resolve(__dirname, '../stubs/defaultPostCssConfig.stub.js'),
|
||||
@ -236,30 +277,71 @@ function init() {
|
||||
|
||||
fs.writeFileSync(postcssConfigLocation, stubFile, 'utf8')
|
||||
|
||||
console.log('Created PostCSS config file:', 'tailwind.config.js')
|
||||
messages.push(`Created PostCSS config file: ${path.basename(postcssConfigLocation)}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (messages.length > 0) {
|
||||
console.log()
|
||||
for (let message of messages) {
|
||||
console.log(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function build() {
|
||||
async function build() {
|
||||
let input = args['--input']
|
||||
let output = args['--output']
|
||||
let shouldWatch = args['--watch']
|
||||
let shouldMinify = args['--minify']
|
||||
let includePostCss = args['--postcss']
|
||||
|
||||
if (args['--config'] && !fs.existsSync(args['--config'])) {
|
||||
// TODO: Deprecate this in future versions
|
||||
if (!input && args['_'][1]) {
|
||||
console.error('[deprecation] Running tailwindcss without -i, please provide an input file.')
|
||||
}
|
||||
|
||||
if (input && !fs.existsSync((input = path.resolve(input)))) {
|
||||
console.error(`Specified input file ${args['--input']} does not exist.`)
|
||||
process.exit(9)
|
||||
}
|
||||
|
||||
if (args['--config'] && !fs.existsSync((args['--config'] = path.resolve(args['--config'])))) {
|
||||
console.error(`Specified config file ${args['--config']} does not exist.`)
|
||||
process.exit(9)
|
||||
}
|
||||
|
||||
let configPath =
|
||||
args['--config'] ??
|
||||
((defaultPath) => (fs.existsSync(defaultPath) ? defaultPath : null))(
|
||||
path.resolve('./tailwind.config.js')
|
||||
)
|
||||
let configPath = args['--config']
|
||||
? args['--config']
|
||||
: ((defaultPath) => (fs.existsSync(defaultPath) ? defaultPath : null))(
|
||||
path.resolve('./tailwind.config.js')
|
||||
)
|
||||
|
||||
async function loadPostCssPlugins() {
|
||||
let { plugins: configPlugins } = await postcssrc()
|
||||
let configPluginTailwindIdx = configPlugins.findIndex((plugin) => {
|
||||
if (typeof plugin === 'function' && plugin.name === 'tailwindcss') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (typeof plugin === 'object' && plugin !== null && plugin.postcssPlugin === 'tailwindcss') {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
let beforePlugins =
|
||||
configPluginTailwindIdx === -1 ? [] : configPlugins.slice(0, configPluginTailwindIdx)
|
||||
let afterPlugins =
|
||||
configPluginTailwindIdx === -1
|
||||
? configPlugins
|
||||
: configPlugins.slice(configPluginTailwindIdx + 1)
|
||||
|
||||
return [beforePlugins, afterPlugins]
|
||||
}
|
||||
|
||||
function resolveConfig() {
|
||||
let config = require(configPath)
|
||||
let config = configPath ? require(configPath) : {}
|
||||
let resolvedConfig = resolveConfigInternal(config)
|
||||
|
||||
if (args['--files']) {
|
||||
@ -273,15 +355,6 @@ function build() {
|
||||
return resolvedConfig
|
||||
}
|
||||
|
||||
if (!output) {
|
||||
help({
|
||||
message: 'Missing required output file: --output, -o, or first argument',
|
||||
usage: [`tailwind ${command} [options]`],
|
||||
options: { ...flags, ...sharedFlags },
|
||||
})
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function extractContent(config) {
|
||||
return Array.isArray(config.purge) ? config.purge : config.purge.content
|
||||
}
|
||||
@ -323,13 +396,13 @@ function build() {
|
||||
return changedContent
|
||||
}
|
||||
|
||||
function buildOnce() {
|
||||
async function buildOnce() {
|
||||
let config = resolveConfig()
|
||||
let changedContent = getChangedContent(config)
|
||||
|
||||
let tailwindPlugin =
|
||||
config.mode === 'jit'
|
||||
? (opts = {}) => {
|
||||
? () => {
|
||||
return {
|
||||
postcssPlugin: 'tailwindcss',
|
||||
Once(root, { result }) {
|
||||
@ -341,7 +414,7 @@ function build() {
|
||||
},
|
||||
}
|
||||
}
|
||||
: (opts = {}) => {
|
||||
: () => {
|
||||
return {
|
||||
postcssPlugin: 'tailwindcss',
|
||||
plugins: [tailwindAot(() => config, configPath)],
|
||||
@ -350,23 +423,40 @@ function build() {
|
||||
|
||||
tailwindPlugin.postcss = true
|
||||
|
||||
let [beforePlugins, afterPlugins] = includePostCss ? await loadPostCssPlugins() : [[], []]
|
||||
|
||||
let plugins = [
|
||||
// TODO: Bake in postcss-import support?
|
||||
// TODO: Bake in postcss-nested support?
|
||||
...beforePlugins,
|
||||
tailwindPlugin,
|
||||
// require('autoprefixer'),
|
||||
formatNodes,
|
||||
]
|
||||
!args['--minify'] && formatNodes,
|
||||
...afterPlugins,
|
||||
!args['--no-autoprefixer'] && lazyAutoprefixer(),
|
||||
args['--minify'] && lazyCssnano()({ preset: ['default', { cssDeclarationSorter: false }] }),
|
||||
].filter(Boolean)
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
let start = process.hrtime.bigint()
|
||||
return Promise.resolve()
|
||||
.then(() => (output ? fs.promises.mkdir(path.dirname(output), { recursive: true }) : null))
|
||||
.then(() => processor.process(css, { from: input, to: output }))
|
||||
.then((result) => {
|
||||
if (!output) {
|
||||
return process.stdout.write(result.css)
|
||||
}
|
||||
return Promise.all(
|
||||
[
|
||||
fs.promises.writeFile(output, result.css, () => true),
|
||||
result.map && fs.writeFile(output + '.map', result.map.toString(), () => true),
|
||||
].filter(Boolean)
|
||||
)
|
||||
})
|
||||
.then(() => {
|
||||
let end = process.hrtime.bigint()
|
||||
console.error()
|
||||
console.error('Done in', (end - start) / BigInt(1e6) + 'ms.')
|
||||
})
|
||||
}
|
||||
|
||||
let css = input
|
||||
@ -402,17 +492,19 @@ function build() {
|
||||
}
|
||||
|
||||
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 }) => {
|
||||
console.error()
|
||||
console.error('Rebuilding...')
|
||||
|
||||
return () => {
|
||||
if (context !== null) {
|
||||
context.changedContent = changedContent.splice(0)
|
||||
@ -429,7 +521,7 @@ function build() {
|
||||
},
|
||||
}
|
||||
}
|
||||
: (opts = {}) => {
|
||||
: () => {
|
||||
return {
|
||||
postcssPlugin: 'tailwindcss',
|
||||
plugins: [tailwindAot(() => config, configPath)],
|
||||
@ -438,30 +530,43 @@ function build() {
|
||||
|
||||
tailwindPlugin.postcss = true
|
||||
|
||||
let [beforePlugins, afterPlugins] = includePostCss ? await loadPostCssPlugins() : [[], []]
|
||||
|
||||
let plugins = [
|
||||
// TODO: Bake in postcss-import support?
|
||||
// TODO: Bake in postcss-nested support?
|
||||
...beforePlugins,
|
||||
tailwindPlugin,
|
||||
// require('autoprefixer'),
|
||||
formatNodes,
|
||||
]
|
||||
!args['--minify'] && formatNodes,
|
||||
...afterPlugins,
|
||||
!args['--no-autoprefixer'] && lazyAutoprefixer(),
|
||||
args['--minify'] && lazyCssnano()({ preset: ['default', { cssDeclarationSorter: false }] }),
|
||||
].filter(Boolean)
|
||||
|
||||
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)
|
||||
let start = process.hrtime.bigint()
|
||||
return Promise.resolve()
|
||||
.then(() => fs.promises.mkdir(path.dirname(output), { recursive: true }))
|
||||
.then(() => processor.process(css, { from: input, to: output }))
|
||||
.then(async (result) => {
|
||||
for (let message of result.messages) {
|
||||
if (message.type === 'dependency') {
|
||||
contextDependencies.add(message.file)
|
||||
}
|
||||
}
|
||||
}
|
||||
watcher.add([...contextDependencies])
|
||||
watcher.add([...contextDependencies])
|
||||
|
||||
fs.writeFile(output, result.css, () => true)
|
||||
if (result.map) {
|
||||
fs.writeFile(output + '.map', result.map.toString(), () => true)
|
||||
}
|
||||
})
|
||||
await Promise.all(
|
||||
[
|
||||
fs.promises.writeFile(output, result.css, () => true),
|
||||
result.map && fs.writeFile(output + '.map', result.map.toString(), () => true),
|
||||
].filter(Boolean)
|
||||
)
|
||||
})
|
||||
.then(() => {
|
||||
let end = process.hrtime.bigint()
|
||||
console.error('Done in', (end - start) / BigInt(1e6) + 'ms.')
|
||||
})
|
||||
}
|
||||
|
||||
let css = input
|
||||
|
||||
13
src/index.js
13
src/index.js
@ -11,7 +11,6 @@ import resolveConfig from './util/resolveConfig'
|
||||
import getAllConfigs from './util/getAllConfigs'
|
||||
import { supportedConfigFiles } from './constants'
|
||||
import defaultConfig from '../stubs/defaultConfig.stub.js'
|
||||
import log from './util/log'
|
||||
|
||||
import jitPlugins from './jit'
|
||||
|
||||
@ -73,22 +72,12 @@ const getConfigFunction = (config) => () => {
|
||||
])
|
||||
}
|
||||
|
||||
let warned = false
|
||||
|
||||
module.exports = function (config) {
|
||||
module.exports = function tailwindcss(config) {
|
||||
const resolvedConfigPath = resolveConfigPath(config)
|
||||
const getConfig = getConfigFunction(resolvedConfigPath || config)
|
||||
const mode = _.get(getConfig(), 'mode', 'aot')
|
||||
|
||||
if (mode === 'jit') {
|
||||
if (!warned) {
|
||||
log.warn([
|
||||
`You have enabled the JIT engine which is currently in preview.`,
|
||||
'Preview features are not covered by semver, may introduce breaking changes, and can change at any time.',
|
||||
])
|
||||
warned = true
|
||||
}
|
||||
|
||||
return {
|
||||
postcssPlugin: 'tailwindcss',
|
||||
plugins: jitPlugins(config),
|
||||
|
||||
@ -12,7 +12,6 @@ import resolveConfig from './util/resolveConfig'
|
||||
import getAllConfigs from './util/getAllConfigs'
|
||||
import { supportedConfigFiles } from './constants'
|
||||
import defaultConfig from '../stubs/defaultConfig.stub.js'
|
||||
import log from './util/log'
|
||||
|
||||
import jitPlugins from './jit'
|
||||
|
||||
@ -68,22 +67,12 @@ const getConfigFunction = (config) => () => {
|
||||
return resolveConfig([...getAllConfigs(configObject)])
|
||||
}
|
||||
|
||||
let warned = false
|
||||
|
||||
const plugin = postcss.plugin('tailwindcss', (config) => {
|
||||
const resolvedConfigPath = resolveConfigPath(config)
|
||||
const getConfig = getConfigFunction(resolvedConfigPath || config)
|
||||
const mode = _.get(getConfig(), 'mode', 'aot')
|
||||
|
||||
if (mode === 'jit') {
|
||||
if (!warned) {
|
||||
log.warn([
|
||||
`You have enabled the JIT engine which is currently in preview.`,
|
||||
'Preview features are not covered by semver, may introduce breaking changes, and can change at any time.',
|
||||
])
|
||||
warned = true
|
||||
}
|
||||
|
||||
return postcss(jitPlugins(config))
|
||||
}
|
||||
|
||||
|
||||
@ -5,9 +5,20 @@ import evaluateTailwindFunctions from '../lib/evaluateTailwindFunctions'
|
||||
import substituteScreenAtRules from '../lib/substituteScreenAtRules'
|
||||
import collapseAdjacentRules from './lib/collapseAdjacentRules'
|
||||
import { createContext } from './lib/setupContextUtils'
|
||||
import log from '../util/log'
|
||||
|
||||
let warned = false
|
||||
|
||||
export default function processTailwindFeatures(setupContext) {
|
||||
return function (root, result) {
|
||||
if (!warned) {
|
||||
log.warn([
|
||||
`You have enabled the JIT engine which is currently in preview.`,
|
||||
'Preview features are not covered by semver, may introduce breaking changes, and can change at any time.',
|
||||
])
|
||||
warned = true
|
||||
}
|
||||
|
||||
let tailwindDirectives = normalizeTailwindDirectives(root)
|
||||
|
||||
let context = setupContext({
|
||||
|
||||
@ -2,18 +2,24 @@ import chalk from 'chalk'
|
||||
|
||||
export default {
|
||||
info(messages) {
|
||||
if (process.env.JEST_WORKER_ID !== undefined) return
|
||||
|
||||
console.warn('')
|
||||
messages.forEach((message) => {
|
||||
console.warn(chalk.bold.cyan('info'), '-', message)
|
||||
})
|
||||
},
|
||||
warn(messages) {
|
||||
if (process.env.JEST_WORKER_ID !== undefined) return
|
||||
|
||||
console.warn('')
|
||||
messages.forEach((message) => {
|
||||
console.warn(chalk.bold.yellow('warn'), '-', message)
|
||||
})
|
||||
},
|
||||
risk(messages) {
|
||||
if (process.env.JEST_WORKER_ID !== undefined) return
|
||||
|
||||
console.warn('')
|
||||
messages.forEach((message) => {
|
||||
console.warn(chalk.bold.magenta('risk'), '-', message)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user