tailwindcss 3.1.8 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -3
- package/README.md +6 -5
- package/lib/cli/build/deps.js +54 -0
- package/lib/cli/build/index.js +44 -0
- package/lib/cli/build/plugin.js +351 -0
- package/lib/cli/build/utils.js +78 -0
- package/lib/cli/build/watching.js +113 -0
- package/lib/cli/help/index.js +71 -0
- package/lib/cli/index.js +18 -0
- package/lib/cli/init/index.js +46 -0
- package/lib/cli/shared.js +12 -0
- package/lib/cli.js +11 -590
- package/lib/corePlugins.js +332 -108
- package/lib/css/preflight.css +5 -0
- package/lib/featureFlags.js +7 -4
- package/lib/index.js +6 -1
- package/lib/lib/content.js +167 -0
- package/lib/lib/defaultExtractor.js +15 -10
- package/lib/lib/detectNesting.js +2 -2
- package/lib/lib/evaluateTailwindFunctions.js +17 -1
- package/lib/lib/expandApplyAtRules.js +66 -37
- package/lib/lib/expandTailwindAtRules.js +10 -42
- package/lib/lib/findAtConfigPath.js +44 -0
- package/lib/lib/generateRules.js +180 -93
- package/lib/lib/normalizeTailwindDirectives.js +1 -1
- package/lib/lib/offsets.js +217 -0
- package/lib/lib/regex.js +1 -1
- package/lib/lib/setupContextUtils.js +339 -100
- package/lib/lib/setupTrackingContext.js +5 -39
- package/lib/lib/sharedState.js +2 -0
- package/lib/public/colors.js +1 -1
- package/lib/util/buildMediaQuery.js +6 -3
- package/lib/util/configurePlugins.js +1 -1
- package/lib/util/dataTypes.js +15 -19
- package/lib/util/formatVariantSelector.js +92 -8
- package/lib/util/getAllConfigs.js +14 -3
- package/lib/util/isValidArbitraryValue.js +1 -1
- package/lib/util/nameClass.js +3 -0
- package/lib/util/negateValue.js +15 -2
- package/lib/util/normalizeConfig.js +17 -3
- package/lib/util/normalizeScreens.js +100 -3
- package/lib/util/parseAnimationValue.js +1 -1
- package/lib/util/parseBoxShadowValue.js +1 -1
- package/lib/util/parseDependency.js +33 -54
- package/lib/util/parseGlob.js +34 -0
- package/lib/util/parseObjectStyles.js +1 -1
- package/lib/util/pluginUtils.js +87 -17
- package/lib/util/resolveConfig.js +2 -2
- package/lib/util/splitAtTopLevelOnly.js +31 -81
- package/lib/util/transformThemeValue.js +9 -2
- package/lib/util/validateConfig.js +1 -1
- package/lib/util/validateFormalSyntax.js +24 -0
- package/package.json +14 -13
- package/peers/index.js +3263 -1887
- package/plugin.d.ts +3 -3
- package/scripts/release-channel.js +18 -0
- package/scripts/release-notes.js +21 -0
- package/src/cli/build/deps.js +56 -0
- package/src/cli/build/index.js +45 -0
- package/src/cli/build/plugin.js +417 -0
- package/src/cli/build/utils.js +76 -0
- package/src/cli/build/watching.js +134 -0
- package/src/cli/help/index.js +70 -0
- package/src/cli/index.js +3 -0
- package/src/cli/init/index.js +50 -0
- package/src/cli/shared.js +5 -0
- package/src/cli.js +4 -696
- package/src/corePlugins.js +262 -39
- package/src/css/preflight.css +5 -0
- package/src/featureFlags.js +12 -2
- package/src/index.js +5 -0
- package/src/lib/content.js +205 -0
- package/src/lib/defaultExtractor.js +3 -0
- package/src/lib/evaluateTailwindFunctions.js +22 -1
- package/src/lib/expandApplyAtRules.js +70 -29
- package/src/lib/expandTailwindAtRules.js +8 -46
- package/src/lib/findAtConfigPath.js +48 -0
- package/src/lib/generateRules.js +223 -101
- package/src/lib/offsets.js +270 -0
- package/src/lib/setupContextUtils.js +376 -89
- package/src/lib/setupTrackingContext.js +4 -45
- package/src/lib/sharedState.js +2 -0
- package/src/util/buildMediaQuery.js +5 -3
- package/src/util/dataTypes.js +15 -17
- package/src/util/formatVariantSelector.js +113 -9
- package/src/util/getAllConfigs.js +14 -2
- package/src/util/nameClass.js +4 -0
- package/src/util/negateValue.js +10 -2
- package/src/util/normalizeConfig.js +22 -2
- package/src/util/normalizeScreens.js +99 -4
- package/src/util/parseBoxShadowValue.js +1 -1
- package/src/util/parseDependency.js +37 -42
- package/src/util/parseGlob.js +24 -0
- package/src/util/pluginUtils.js +96 -14
- package/src/util/resolveConfig.js +1 -1
- package/src/util/splitAtTopLevelOnly.js +23 -49
- package/src/util/transformThemeValue.js +9 -1
- package/src/util/validateFormalSyntax.js +34 -0
- package/stubs/defaultConfig.stub.js +20 -3
- package/types/config.d.ts +48 -13
- package/types/generated/default-theme.d.ts +11 -0
package/plugin.d.ts
CHANGED
|
@@ -2,9 +2,9 @@ import type { Config, PluginCreator } from './types/config'
|
|
|
2
2
|
type Plugin = {
|
|
3
3
|
withOptions<T>(
|
|
4
4
|
plugin: (options: T) => PluginCreator,
|
|
5
|
-
config?: (options: T) => Config
|
|
6
|
-
): { (options: T): { handler: PluginCreator; config?: Config }; __isOptionsFunction: true }
|
|
7
|
-
(plugin: PluginCreator, config?: Config): { handler: PluginCreator; config?: Config }
|
|
5
|
+
config?: (options: T) => Partial<Config>
|
|
6
|
+
): { (options: T): { handler: PluginCreator; config?: Partial<Config> }; __isOptionsFunction: true }
|
|
7
|
+
(plugin: PluginCreator, config?: Partial<Config>): { handler: PluginCreator; config?: Partial<Config> }
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
declare const plugin: Plugin
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Given a version, figure out what the release channel is so that we can publish to the correct
|
|
2
|
+
// channel on npm.
|
|
3
|
+
//
|
|
4
|
+
// E.g.:
|
|
5
|
+
//
|
|
6
|
+
// 1.2.3 -> latest (default)
|
|
7
|
+
// 0.0.0-insiders.ffaa88 -> insiders
|
|
8
|
+
// 4.1.0-alpha.4 -> alpha
|
|
9
|
+
|
|
10
|
+
let version =
|
|
11
|
+
process.argv[2] || process.env.npm_package_version || require('../package.json').version
|
|
12
|
+
|
|
13
|
+
let match = /\d+\.\d+\.\d+-(.*)\.\d+/g.exec(version)
|
|
14
|
+
if (match) {
|
|
15
|
+
console.log(match[1])
|
|
16
|
+
} else {
|
|
17
|
+
console.log('latest')
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Given a version, figure out what the release notes are so that we can use this to pre-fill the
|
|
2
|
+
// relase notes on a GitHub release for the current version.
|
|
3
|
+
|
|
4
|
+
let path = require('path')
|
|
5
|
+
let fs = require('fs')
|
|
6
|
+
|
|
7
|
+
let version =
|
|
8
|
+
process.argv[2] || process.env.npm_package_version || require('../package.json').version
|
|
9
|
+
|
|
10
|
+
let changelog = fs.readFileSync(path.resolve(__dirname, '..', 'CHANGELOG.md'), 'utf8')
|
|
11
|
+
let match = new RegExp(
|
|
12
|
+
`## \\[${version}\\] - (.*)\\n\\n([\\s\\S]*?)\\n(?:(?:##\\s)|(?:\\[))`,
|
|
13
|
+
'g'
|
|
14
|
+
).exec(changelog)
|
|
15
|
+
|
|
16
|
+
if (match) {
|
|
17
|
+
let [, , notes] = match
|
|
18
|
+
console.log(notes.trim())
|
|
19
|
+
} else {
|
|
20
|
+
console.log(`Placeholder release notes for version: v${version}`)
|
|
21
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
lazyPostcss,
|
|
6
|
+
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
lazyPostcssImport,
|
|
9
|
+
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
lazyCssnano,
|
|
12
|
+
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
lazyAutoprefixer,
|
|
15
|
+
} from '../../../peers/index.js'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @returns {import('postcss')}
|
|
19
|
+
*/
|
|
20
|
+
export function loadPostcss() {
|
|
21
|
+
// Try to load a local `postcss` version first
|
|
22
|
+
try {
|
|
23
|
+
return require('postcss')
|
|
24
|
+
} catch {}
|
|
25
|
+
|
|
26
|
+
return lazyPostcss()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function loadPostcssImport() {
|
|
30
|
+
// Try to load a local `postcss-import` version first
|
|
31
|
+
try {
|
|
32
|
+
return require('postcss-import')
|
|
33
|
+
} catch {}
|
|
34
|
+
|
|
35
|
+
return lazyPostcssImport()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function loadCssNano() {
|
|
39
|
+
let options = { preset: ['default', { cssDeclarationSorter: false }] }
|
|
40
|
+
|
|
41
|
+
// Try to load a local `cssnano` version first
|
|
42
|
+
try {
|
|
43
|
+
return require('cssnano')
|
|
44
|
+
} catch {}
|
|
45
|
+
|
|
46
|
+
return lazyCssnano()(options)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function loadAutoprefixer() {
|
|
50
|
+
// Try to load a local `autoprefixer` version first
|
|
51
|
+
try {
|
|
52
|
+
return require('autoprefixer')
|
|
53
|
+
} catch {}
|
|
54
|
+
|
|
55
|
+
return lazyAutoprefixer()
|
|
56
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import { createProcessor } from './plugin.js'
|
|
6
|
+
|
|
7
|
+
export async function build(args, configs) {
|
|
8
|
+
let input = args['--input']
|
|
9
|
+
let shouldWatch = args['--watch']
|
|
10
|
+
|
|
11
|
+
// TODO: Deprecate this in future versions
|
|
12
|
+
if (!input && args['_'][1]) {
|
|
13
|
+
console.error('[deprecation] Running tailwindcss without -i, please provide an input file.')
|
|
14
|
+
input = args['--input'] = args['_'][1]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (input && input !== '-' && !fs.existsSync((input = path.resolve(input)))) {
|
|
18
|
+
console.error(`Specified input file ${args['--input']} does not exist.`)
|
|
19
|
+
process.exit(9)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (args['--config'] && !fs.existsSync((args['--config'] = path.resolve(args['--config'])))) {
|
|
23
|
+
console.error(`Specified config file ${args['--config']} does not exist.`)
|
|
24
|
+
process.exit(9)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// TODO: Reference the @config path here if exists
|
|
28
|
+
let configPath = args['--config']
|
|
29
|
+
? args['--config']
|
|
30
|
+
: ((defaultPath) => (fs.existsSync(defaultPath) ? defaultPath : null))(
|
|
31
|
+
path.resolve(`./${configs.tailwind}`)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
let processor = await createProcessor(args, configPath)
|
|
35
|
+
|
|
36
|
+
if (shouldWatch) {
|
|
37
|
+
/* Abort the watcher if stdin is closed to avoid zombie processes */
|
|
38
|
+
process.stdin.on('end', () => process.exit(0))
|
|
39
|
+
process.stdin.resume()
|
|
40
|
+
|
|
41
|
+
await processor.watch()
|
|
42
|
+
} else {
|
|
43
|
+
await processor.build()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
import postcssrc from 'postcss-load-config'
|
|
6
|
+
import { lilconfig } from 'lilconfig'
|
|
7
|
+
import loadPlugins from 'postcss-load-config/src/plugins' // Little bit scary, looking at private/internal API
|
|
8
|
+
import loadOptions from 'postcss-load-config/src/options' // Little bit scary, looking at private/internal API
|
|
9
|
+
|
|
10
|
+
import tailwind from '../../processTailwindFeatures'
|
|
11
|
+
import { loadAutoprefixer, loadCssNano, loadPostcss, loadPostcssImport } from './deps'
|
|
12
|
+
import { formatNodes, drainStdin, outputFile } from './utils'
|
|
13
|
+
import { env } from '../shared'
|
|
14
|
+
import resolveConfig from '../../../resolveConfig.js'
|
|
15
|
+
import getModuleDependencies from '../../lib/getModuleDependencies.js'
|
|
16
|
+
import { parseCandidateFiles } from '../../lib/content.js'
|
|
17
|
+
import { createWatcher } from './watching.js'
|
|
18
|
+
import fastGlob from 'fast-glob'
|
|
19
|
+
import { findAtConfigPath } from '../../lib/findAtConfigPath.js'
|
|
20
|
+
import log from '../../util/log'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
*
|
|
24
|
+
* @param {string} [customPostCssPath ]
|
|
25
|
+
* @returns
|
|
26
|
+
*/
|
|
27
|
+
async function loadPostCssPlugins(customPostCssPath) {
|
|
28
|
+
let config = customPostCssPath
|
|
29
|
+
? await (async () => {
|
|
30
|
+
let file = path.resolve(customPostCssPath)
|
|
31
|
+
|
|
32
|
+
// Implementation, see: https://unpkg.com/browse/postcss-load-config@3.1.0/src/index.js
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
let { config = {} } = await lilconfig('postcss').load(file)
|
|
35
|
+
if (typeof config === 'function') {
|
|
36
|
+
config = config()
|
|
37
|
+
} else {
|
|
38
|
+
config = Object.assign({}, config)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!config.plugins) {
|
|
42
|
+
config.plugins = []
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
file,
|
|
47
|
+
plugins: loadPlugins(config, file),
|
|
48
|
+
options: loadOptions(config, file),
|
|
49
|
+
}
|
|
50
|
+
})()
|
|
51
|
+
: await postcssrc()
|
|
52
|
+
|
|
53
|
+
let configPlugins = config.plugins
|
|
54
|
+
|
|
55
|
+
let configPluginTailwindIdx = configPlugins.findIndex((plugin) => {
|
|
56
|
+
if (typeof plugin === 'function' && plugin.name === 'tailwindcss') {
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof plugin === 'object' && plugin !== null && plugin.postcssPlugin === 'tailwindcss') {
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return false
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
let beforePlugins =
|
|
68
|
+
configPluginTailwindIdx === -1 ? [] : configPlugins.slice(0, configPluginTailwindIdx)
|
|
69
|
+
let afterPlugins =
|
|
70
|
+
configPluginTailwindIdx === -1
|
|
71
|
+
? configPlugins
|
|
72
|
+
: configPlugins.slice(configPluginTailwindIdx + 1)
|
|
73
|
+
|
|
74
|
+
return [beforePlugins, afterPlugins, config.options]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function loadBuiltinPostcssPlugins() {
|
|
78
|
+
let postcss = loadPostcss()
|
|
79
|
+
let IMPORT_COMMENT = '__TAILWIND_RESTORE_IMPORT__: '
|
|
80
|
+
return [
|
|
81
|
+
[
|
|
82
|
+
(root) => {
|
|
83
|
+
root.walkAtRules('import', (rule) => {
|
|
84
|
+
if (rule.params.slice(1).startsWith('tailwindcss/')) {
|
|
85
|
+
rule.after(postcss.comment({ text: IMPORT_COMMENT + rule.params }))
|
|
86
|
+
rule.remove()
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
},
|
|
90
|
+
loadPostcssImport(),
|
|
91
|
+
(root) => {
|
|
92
|
+
root.walkComments((rule) => {
|
|
93
|
+
if (rule.text.startsWith(IMPORT_COMMENT)) {
|
|
94
|
+
rule.after(
|
|
95
|
+
postcss.atRule({
|
|
96
|
+
name: 'import',
|
|
97
|
+
params: rule.text.replace(IMPORT_COMMENT, ''),
|
|
98
|
+
})
|
|
99
|
+
)
|
|
100
|
+
rule.remove()
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
[],
|
|
106
|
+
{},
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let state = {
|
|
111
|
+
/** @type {any} */
|
|
112
|
+
context: null,
|
|
113
|
+
|
|
114
|
+
/** @type {ReturnType<typeof createWatcher> | null} */
|
|
115
|
+
watcher: null,
|
|
116
|
+
|
|
117
|
+
/** @type {{content: string, extension: string}[]} */
|
|
118
|
+
changedContent: [],
|
|
119
|
+
|
|
120
|
+
configDependencies: new Set(),
|
|
121
|
+
contextDependencies: new Set(),
|
|
122
|
+
|
|
123
|
+
/** @type {import('../../lib/content.js').ContentPath[]} */
|
|
124
|
+
contentPaths: [],
|
|
125
|
+
|
|
126
|
+
refreshContentPaths() {
|
|
127
|
+
this.contentPaths = parseCandidateFiles(this.context, this.context?.tailwindConfig)
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
get config() {
|
|
131
|
+
return this.context.tailwindConfig
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
get contentPatterns() {
|
|
135
|
+
return {
|
|
136
|
+
all: this.contentPaths.map((contentPath) => contentPath.pattern),
|
|
137
|
+
dynamic: this.contentPaths
|
|
138
|
+
.filter((contentPath) => contentPath.glob !== undefined)
|
|
139
|
+
.map((contentPath) => contentPath.pattern),
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
loadConfig(configPath, content) {
|
|
144
|
+
if (this.watcher && configPath) {
|
|
145
|
+
this.refreshConfigDependencies(configPath)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let config = configPath ? require(configPath) : {}
|
|
149
|
+
|
|
150
|
+
// @ts-ignore
|
|
151
|
+
config = resolveConfig(config, { content: { files: [] } })
|
|
152
|
+
|
|
153
|
+
// Override content files if `--content` has been passed explicitly
|
|
154
|
+
if (content?.length > 0) {
|
|
155
|
+
config.content.files = content
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return config
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
refreshConfigDependencies(configPath) {
|
|
162
|
+
env.DEBUG && console.time('Module dependencies')
|
|
163
|
+
|
|
164
|
+
for (let file of this.configDependencies) {
|
|
165
|
+
delete require.cache[require.resolve(file)]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (configPath) {
|
|
169
|
+
let deps = getModuleDependencies(configPath).map(({ file }) => file)
|
|
170
|
+
|
|
171
|
+
for (let dependency of deps) {
|
|
172
|
+
this.configDependencies.add(dependency)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
env.DEBUG && console.timeEnd('Module dependencies')
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
readContentPaths() {
|
|
180
|
+
let content = []
|
|
181
|
+
|
|
182
|
+
// Resolve globs from the content config
|
|
183
|
+
// TODO: When we make the postcss plugin async-capable this can become async
|
|
184
|
+
let files = fastGlob.sync(this.contentPatterns.all)
|
|
185
|
+
|
|
186
|
+
for (let file of files) {
|
|
187
|
+
content.push({
|
|
188
|
+
content: fs.readFileSync(path.resolve(file), 'utf8'),
|
|
189
|
+
extension: path.extname(file).slice(1),
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Resolve raw content in the tailwind config
|
|
194
|
+
let rawContent = this.config.content.files.filter((file) => {
|
|
195
|
+
return file !== null && typeof file === 'object'
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
for (let { raw: content, extension = 'html' } of rawContent) {
|
|
199
|
+
content.push({ content, extension })
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return content
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
getContext({ createContext, cliConfigPath, root, result, content }) {
|
|
206
|
+
if (this.context) {
|
|
207
|
+
this.context.changedContent = this.changedContent.splice(0)
|
|
208
|
+
|
|
209
|
+
return this.context
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
env.DEBUG && console.time('Searching for config')
|
|
213
|
+
let configPath = findAtConfigPath(root, result) ?? cliConfigPath
|
|
214
|
+
env.DEBUG && console.timeEnd('Searching for config')
|
|
215
|
+
|
|
216
|
+
env.DEBUG && console.time('Loading config')
|
|
217
|
+
let config = this.loadConfig(configPath, content)
|
|
218
|
+
env.DEBUG && console.timeEnd('Loading config')
|
|
219
|
+
|
|
220
|
+
env.DEBUG && console.time('Creating context')
|
|
221
|
+
this.context = createContext(config, [])
|
|
222
|
+
Object.assign(this.context, {
|
|
223
|
+
userConfigPath: configPath,
|
|
224
|
+
})
|
|
225
|
+
env.DEBUG && console.timeEnd('Creating context')
|
|
226
|
+
|
|
227
|
+
env.DEBUG && console.time('Resolving content paths')
|
|
228
|
+
this.refreshContentPaths()
|
|
229
|
+
env.DEBUG && console.timeEnd('Resolving content paths')
|
|
230
|
+
|
|
231
|
+
if (this.watcher) {
|
|
232
|
+
env.DEBUG && console.time('Watch new files')
|
|
233
|
+
this.watcher.refreshWatchedFiles()
|
|
234
|
+
env.DEBUG && console.timeEnd('Watch new files')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
env.DEBUG && console.time('Reading content files')
|
|
238
|
+
for (let file of this.readContentPaths()) {
|
|
239
|
+
this.context.changedContent.push(file)
|
|
240
|
+
}
|
|
241
|
+
env.DEBUG && console.timeEnd('Reading content files')
|
|
242
|
+
|
|
243
|
+
return this.context
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export async function createProcessor(args, cliConfigPath) {
|
|
248
|
+
let postcss = loadPostcss()
|
|
249
|
+
|
|
250
|
+
let input = args['--input']
|
|
251
|
+
let output = args['--output']
|
|
252
|
+
let includePostCss = args['--postcss']
|
|
253
|
+
let customPostCssPath = typeof args['--postcss'] === 'string' ? args['--postcss'] : undefined
|
|
254
|
+
|
|
255
|
+
let [beforePlugins, afterPlugins, postcssOptions] = includePostCss
|
|
256
|
+
? await loadPostCssPlugins(customPostCssPath)
|
|
257
|
+
: loadBuiltinPostcssPlugins()
|
|
258
|
+
|
|
259
|
+
if (args['--purge']) {
|
|
260
|
+
log.warn('purge-flag-deprecated', [
|
|
261
|
+
'The `--purge` flag has been deprecated.',
|
|
262
|
+
'Please use `--content` instead.',
|
|
263
|
+
])
|
|
264
|
+
|
|
265
|
+
if (!args['--content']) {
|
|
266
|
+
args['--content'] = args['--purge']
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let content = args['--content']?.split(/(?<!{[^}]+),/) ?? []
|
|
271
|
+
|
|
272
|
+
let tailwindPlugin = () => {
|
|
273
|
+
return {
|
|
274
|
+
postcssPlugin: 'tailwindcss',
|
|
275
|
+
Once(root, { result }) {
|
|
276
|
+
env.DEBUG && console.time('Compiling CSS')
|
|
277
|
+
tailwind(({ createContext }) => {
|
|
278
|
+
console.error()
|
|
279
|
+
console.error('Rebuilding...')
|
|
280
|
+
|
|
281
|
+
return () => {
|
|
282
|
+
return state.getContext({
|
|
283
|
+
createContext,
|
|
284
|
+
cliConfigPath,
|
|
285
|
+
root,
|
|
286
|
+
result,
|
|
287
|
+
content,
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
})(root, result)
|
|
291
|
+
env.DEBUG && console.timeEnd('Compiling CSS')
|
|
292
|
+
},
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
tailwindPlugin.postcss = true
|
|
297
|
+
|
|
298
|
+
let plugins = [
|
|
299
|
+
...beforePlugins,
|
|
300
|
+
tailwindPlugin,
|
|
301
|
+
!args['--minify'] && formatNodes,
|
|
302
|
+
...afterPlugins,
|
|
303
|
+
!args['--no-autoprefixer'] && loadAutoprefixer(),
|
|
304
|
+
args['--minify'] && loadCssNano(),
|
|
305
|
+
].filter(Boolean)
|
|
306
|
+
|
|
307
|
+
/** @type {import('postcss').Processor} */
|
|
308
|
+
// @ts-ignore
|
|
309
|
+
let processor = postcss(plugins)
|
|
310
|
+
|
|
311
|
+
async function readInput() {
|
|
312
|
+
// Piping in data, let's drain the stdin
|
|
313
|
+
if (input === '-') {
|
|
314
|
+
return drainStdin()
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Input file has been provided
|
|
318
|
+
if (input) {
|
|
319
|
+
return fs.promises.readFile(path.resolve(input), 'utf8')
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// No input file provided, fallback to default atrules
|
|
323
|
+
return '@tailwind base; @tailwind components; @tailwind utilities'
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function build() {
|
|
327
|
+
let start = process.hrtime.bigint()
|
|
328
|
+
|
|
329
|
+
return readInput()
|
|
330
|
+
.then((css) => processor.process(css, { ...postcssOptions, from: input, to: output }))
|
|
331
|
+
.then((result) => {
|
|
332
|
+
if (!state.watcher) {
|
|
333
|
+
return result
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
env.DEBUG && console.time('Recording PostCSS dependencies')
|
|
337
|
+
for (let message of result.messages) {
|
|
338
|
+
if (message.type === 'dependency') {
|
|
339
|
+
state.contextDependencies.add(message.file)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
env.DEBUG && console.timeEnd('Recording PostCSS dependencies')
|
|
343
|
+
|
|
344
|
+
// TODO: This needs to be in a different spot
|
|
345
|
+
env.DEBUG && console.time('Watch new files')
|
|
346
|
+
state.watcher.refreshWatchedFiles()
|
|
347
|
+
env.DEBUG && console.timeEnd('Watch new files')
|
|
348
|
+
|
|
349
|
+
return result
|
|
350
|
+
})
|
|
351
|
+
.then((result) => {
|
|
352
|
+
if (!output) {
|
|
353
|
+
process.stdout.write(result.css)
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return Promise.all([
|
|
358
|
+
outputFile(output, result.css),
|
|
359
|
+
result.map && outputFile(output + '.map', result.map.toString()),
|
|
360
|
+
])
|
|
361
|
+
})
|
|
362
|
+
.then(() => {
|
|
363
|
+
let end = process.hrtime.bigint()
|
|
364
|
+
console.error()
|
|
365
|
+
console.error('Done in', (end - start) / BigInt(1e6) + 'ms.')
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* @param {{file: string, content(): Promise<string>, extension: string}[]} changes
|
|
371
|
+
*/
|
|
372
|
+
async function parseChanges(changes) {
|
|
373
|
+
return Promise.all(
|
|
374
|
+
changes.map(async (change) => ({
|
|
375
|
+
content: await change.content(),
|
|
376
|
+
extension: change.extension,
|
|
377
|
+
}))
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (input !== undefined && input !== '-') {
|
|
382
|
+
state.contextDependencies.add(path.resolve(input))
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
build,
|
|
387
|
+
watch: async () => {
|
|
388
|
+
state.watcher = createWatcher(args, {
|
|
389
|
+
state,
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* @param {{file: string, content(): Promise<string>, extension: string}[]} changes
|
|
393
|
+
*/
|
|
394
|
+
async rebuild(changes) {
|
|
395
|
+
let needsNewContext = changes.some((change) => {
|
|
396
|
+
return (
|
|
397
|
+
state.configDependencies.has(change.file) ||
|
|
398
|
+
state.contextDependencies.has(change.file)
|
|
399
|
+
)
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
if (needsNewContext) {
|
|
403
|
+
state.context = null
|
|
404
|
+
} else {
|
|
405
|
+
for (let change of await parseChanges(changes)) {
|
|
406
|
+
state.changedContent.push(change)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return build()
|
|
411
|
+
},
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
await build()
|
|
415
|
+
},
|
|
416
|
+
}
|
|
417
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
|
|
6
|
+
export function indentRecursive(node, indent = 0) {
|
|
7
|
+
node.each &&
|
|
8
|
+
node.each((child, i) => {
|
|
9
|
+
if (!child.raws.before || !child.raws.before.trim() || child.raws.before.includes('\n')) {
|
|
10
|
+
child.raws.before = `\n${node.type !== 'rule' && i > 0 ? '\n' : ''}${' '.repeat(indent)}`
|
|
11
|
+
}
|
|
12
|
+
child.raws.after = `\n${' '.repeat(indent)}`
|
|
13
|
+
indentRecursive(child, indent + 1)
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function formatNodes(root) {
|
|
18
|
+
indentRecursive(root)
|
|
19
|
+
if (root.first) {
|
|
20
|
+
root.first.raws.before = ''
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* When rapidly saving files atomically a couple of situations can happen:
|
|
26
|
+
* - The file is missing since the external program has deleted it by the time we've gotten around to reading it from the earlier save.
|
|
27
|
+
* - The file is being written to by the external program by the time we're going to read it and is thus treated as busy because a lock is held.
|
|
28
|
+
*
|
|
29
|
+
* To work around this we retry reading the file a handful of times with a delay between each attempt
|
|
30
|
+
*
|
|
31
|
+
* @param {string} path
|
|
32
|
+
* @param {number} tries
|
|
33
|
+
* @returns {Promise<string | undefined>}
|
|
34
|
+
* @throws {Error} If the file is still missing or busy after the specified number of tries
|
|
35
|
+
*/
|
|
36
|
+
export async function readFileWithRetries(path, tries = 5) {
|
|
37
|
+
for (let n = 0; n <= tries; n++) {
|
|
38
|
+
try {
|
|
39
|
+
return await fs.promises.readFile(path, 'utf8')
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (n !== tries) {
|
|
42
|
+
if (err.code === 'ENOENT' || err.code === 'EBUSY') {
|
|
43
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
44
|
+
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
throw err
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function drainStdin() {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
let result = ''
|
|
57
|
+
process.stdin.on('data', (chunk) => {
|
|
58
|
+
result += chunk
|
|
59
|
+
})
|
|
60
|
+
process.stdin.on('end', () => resolve(result))
|
|
61
|
+
process.stdin.on('error', (err) => reject(err))
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function outputFile(file, newContents) {
|
|
66
|
+
try {
|
|
67
|
+
let currentContents = await fs.promises.readFile(file, 'utf8')
|
|
68
|
+
if (currentContents === newContents) {
|
|
69
|
+
return // Skip writing the file
|
|
70
|
+
}
|
|
71
|
+
} catch {}
|
|
72
|
+
|
|
73
|
+
// Write the file
|
|
74
|
+
await fs.promises.mkdir(path.dirname(file), { recursive: true })
|
|
75
|
+
await fs.promises.writeFile(file, newContents, 'utf8')
|
|
76
|
+
}
|