Start of new CLI (#4526)
* Ignore workspace settings
* Parameterize setting up the context
* WIP
* WIP
* WIP
* WIP
* wip
* WIP
Co-Authored-By: Jesse Katsumata <niconico.clarinet@gmail.com>
* WIP
Co-Authored-By: Jesse Katsumata <niconico.clarinet@gmail.com>
* WIP
Co-Authored-By: Jesse Katsumata <niconico.clarinet@gmail.com>
* Update some comments
Co-Authored-By: Jesse Katsumata <niconico.clarinet@gmail.com>
* Fix bug
* WIP
* WIP'
* more things
* log console.time calls conditionally based on process.env.DEBUG
* add `init` command
* clean up when using --jit
* Make config file optional
* cleanup path.resolve calls
path.resolve('.') is the same as path.resolve(process.cwd(), '.')
* implement `--help`
* shush eslint
* drop unnecessary file
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Co-authored-by: Jesse Katsumata <niconico.clarinet@gmail.com>
This commit is contained in:
parent
b514a5c9c2
commit
4528bdc2d8
10 changed files with 742 additions and 162 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,6 +3,7 @@
|
|||
/cli
|
||||
/lib
|
||||
/example
|
||||
.vscode
|
||||
tailwind.config.js
|
||||
index.html
|
||||
yarn.lock
|
||||
|
|
|
|||
27
package-lock.json
generated
27
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
539
src/cli.js
Executable file → Normal file
539
src/cli.js
Executable file → Normal file
|
|
@ -1,6 +1,539 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import main from './cli/main'
|
||||
import * as utils from './cli/utils'
|
||||
/* eslint-disable */
|
||||
|
||||
main(process.argv.slice(2)).catch((error) => utils.die(error.stack))
|
||||
// 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 tailwindJit from './jit/processTailwindFeatures'
|
||||
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,
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
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 = ''
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
/*
|
||||
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' },
|
||||
'-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 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 <command> [options]'],
|
||||
commands: ['init [file]', 'build <file> [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 <command> [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)) {
|
||||
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'
|
||||
)
|
||||
|
||||
// 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')
|
||||
}
|
||||
|
||||
if (args['--postcss']) {
|
||||
let postcssConfigLocation = path.resolve('./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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
let configPath =
|
||||
args['--config'] ??
|
||||
((defaultPath) => (fs.existsSync(defaultPath) ? defaultPath : null))(
|
||||
path.resolve('./tailwind.config.js')
|
||||
)
|
||||
|
||||
function resolveConfig() {
|
||||
let config = require(configPath)
|
||||
let resolvedConfig = resolveConfigInternal(config)
|
||||
|
||||
if (args['--files']) {
|
||||
resolvedConfig.purge = args['--files'].split(',')
|
||||
}
|
||||
|
||||
if (args['--jit']) {
|
||||
resolvedConfig.mode = 'jit'
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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'
|
||||
})
|
||||
}
|
||||
|
||||
function extractRawContent(config) {
|
||||
return extractContent(config).filter((file) => {
|
||||
return typeof file === 'object' && file !== null
|
||||
})
|
||||
}
|
||||
|
||||
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(file), 'utf8'),
|
||||
extension: path.extname(file),
|
||||
})
|
||||
}
|
||||
|
||||
// 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()
|
||||
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)
|
||||
},
|
||||
}
|
||||
}
|
||||
: (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) => {
|
||||
fs.writeFile(output, result.css, () => true)
|
||||
if (result.map) {
|
||||
fs.writeFile(output + '.map', result.map.toString(), () => true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let css = input
|
||||
? fs.readFileSync(path.resolve(input), 'utf8')
|
||||
: '@tailwind base; @tailwind components; @tailwind utilities'
|
||||
return processCSS(css)
|
||||
}
|
||||
|
||||
let context = null
|
||||
|
||||
function startWatcher() {
|
||||
let changedContent = []
|
||||
let configDependencies = []
|
||||
let contextDependencies = new Set()
|
||||
let watcher = null
|
||||
|
||||
function refreshConfig() {
|
||||
env.DEBUG && console.time('Module dependencies')
|
||||
for (let file of configDependencies) {
|
||||
delete require.cache[require.resolve(file)]
|
||||
}
|
||||
|
||||
if (configPath) {
|
||||
configDependencies = getModuleDependencies(configPath).map(({ file }) => file)
|
||||
|
||||
for (let dependency of configDependencies) {
|
||||
contextDependencies.add(dependency)
|
||||
}
|
||||
}
|
||||
env.DEBUG && console.timeEnd('Module dependencies')
|
||||
|
||||
return resolveConfig()
|
||||
}
|
||||
|
||||
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(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(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(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(file), 'utf8'),
|
||||
extension: path.extname(file),
|
||||
})
|
||||
|
||||
await rebuild(config)
|
||||
})
|
||||
})
|
||||
|
||||
chain = chain.then(() => {
|
||||
changedContent.push(...getChangedContent(config))
|
||||
return rebuild(config)
|
||||
})
|
||||
}
|
||||
|
||||
if (shouldWatch) {
|
||||
startWatcher()
|
||||
} else {
|
||||
buildOnce()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
src/cli/index.js
Executable file
6
src/cli/index.js
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import main from './main'
|
||||
import * as utils from './utils'
|
||||
|
||||
main(process.argv.slice(2)).catch((error) => utils.die(error.stack))
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -479,6 +479,27 @@ 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,
|
||||
}
|
||||
|
||||
let resolvedPlugins = resolvePlugins(context, tailwindDirectives, root)
|
||||
registerPlugins(resolvedPlugins, context)
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
let contextMap = sharedState.contextMap
|
||||
let configContextMap = sharedState.configContextMap
|
||||
let contextSourcesMap = sharedState.contextSourcesMap
|
||||
|
|
@ -541,19 +562,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 +579,5 @@ export function getContext(
|
|||
|
||||
contextSourcesMap.get(context).add(sourcePath)
|
||||
|
||||
registerPlugins(resolvePlugins(context, tailwindDirectives, root), context)
|
||||
|
||||
return [context, true]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
export default function setupTrackingContext(configOrPath) {
|
||||
return ({ tailwindDirectives, registerDependency }) => {
|
||||
return (root, result) => {
|
||||
let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] =
|
||||
getTailwindConfig(configOrPath)
|
||||
|
||||
let contextDependencies = new Set(configDependencies)
|
||||
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)
|
||||
// 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))
|
||||
// Add all css @import dependencies as context dependencies.
|
||||
for (let message of result.messages) {
|
||||
if (message.type === 'dependency') {
|
||||
contextDependencies.add(message.file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let changedContent of resolvedChangedContent(context, candidateFiles, fileModifiedMap)) {
|
||||
context.changedContent.push(changedContent)
|
||||
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 file of configDependencies) {
|
||||
registerDependency(file)
|
||||
}
|
||||
for (let file of configDependencies) {
|
||||
registerDependency(file)
|
||||
}
|
||||
|
||||
return context
|
||||
return context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
export default function setupWatchingContext(configOrPath) {
|
||||
return ({ tailwindDirectives, registerDependency }) => {
|
||||
return (root, result) => {
|
||||
let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] =
|
||||
getTailwindConfig(configOrPath)
|
||||
|
||||
let contextDependencies = new Set(configDependencies)
|
||||
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)
|
||||
// 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)
|
||||
// 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 [context, isNewContext] = getContext(
|
||||
tailwindDirectives,
|
||||
root,
|
||||
result,
|
||||
tailwindConfig,
|
||||
userConfigPath,
|
||||
tailwindConfigHash,
|
||||
contextDependencies
|
||||
)
|
||||
|
||||
let candidateFiles = getCandidateFiles(context, userConfigPath, tailwindConfig)
|
||||
let contextConfigDependencies = getConfigDependencies(context)
|
||||
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()
|
||||
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()
|
||||
}
|
||||
})
|
||||
|
||||
contextConfigDependencies.add(dependency.file)
|
||||
let configPath = getConfigPath(context, configOrPath)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 (isNewContext) {
|
||||
rebootWatcher(context, configPath, contextConfigDependencies, candidateFiles)
|
||||
}
|
||||
}
|
||||
|
||||
return context
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,32 @@
|
|||
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'
|
||||
import { createContext } from './lib/setupContextUtils'
|
||||
|
||||
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,
|
||||
createContext(tailwindConfig, changedContent) {
|
||||
return createContext(tailwindConfig, changedContent, tailwindDirectives, root)
|
||||
},
|
||||
})(root, result)
|
||||
|
||||
expandTailwindAtRules(context)(root, result)
|
||||
expandApplyAtRules(context)(root, result)
|
||||
evaluateTailwindFunctions(context)(root, result)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue