tailwindcss 3.1.7 → 3.2.0
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/README.md +12 -8
- package/lib/cli/build/deps.js +54 -0
- package/lib/cli/build/index.js +44 -0
- package/lib/cli/build/plugin.js +335 -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 +71 -37
- package/lib/lib/expandTailwindAtRules.js +10 -42
- package/lib/lib/findAtConfigPath.js +44 -0
- package/lib/lib/generateRules.js +181 -96
- 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 +86 -17
- package/lib/util/resolveConfig.js +3 -3
- 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 -12
- package/peers/.DS_Store +0 -0
- package/peers/.svgo.yml +75 -0
- package/peers/index.js +3690 -2274
- package/peers/orders/concentric-css.json +299 -0
- package/peers/orders/smacss.json +299 -0
- package/peers/orders/source.json +295 -0
- package/plugin.d.ts +3 -3
- package/scripts/release-channel.js +18 -0
- package/scripts/release-notes.js +21 -0
- package/src/.DS_Store +0 -0
- package/src/cli/build/deps.js +56 -0
- package/src/cli/build/index.js +45 -0
- package/src/cli/build/plugin.js +397 -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 +76 -29
- package/src/lib/expandTailwindAtRules.js +8 -46
- package/src/lib/findAtConfigPath.js +48 -0
- package/src/lib/generateRules.js +224 -105
- 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 +90 -14
- package/src/util/resolveConfig.js +2 -2
- 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 +19 -3
- package/tmp.css +11 -0
- package/tmp.dependency-graph.js +2 -0
- package/tmp.in.css +3 -0
- package/tmp.js +0 -0
- package/tmp.out.css +524 -0
- package/types/config.d.ts +47 -13
- package/types/generated/default-theme.d.ts +11 -0
- package/CHANGELOG.md +0 -2222
|
@@ -0,0 +1,397 @@
|
|
|
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 (!output) {
|
|
333
|
+
process.stdout.write(result.css)
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return Promise.all([
|
|
338
|
+
outputFile(output, result.css),
|
|
339
|
+
result.map && outputFile(output + '.map', result.map.toString()),
|
|
340
|
+
])
|
|
341
|
+
})
|
|
342
|
+
.then(() => {
|
|
343
|
+
let end = process.hrtime.bigint()
|
|
344
|
+
console.error()
|
|
345
|
+
console.error('Done in', (end - start) / BigInt(1e6) + 'ms.')
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* @param {{file: string, content(): Promise<string>, extension: string}[]} changes
|
|
351
|
+
*/
|
|
352
|
+
async function parseChanges(changes) {
|
|
353
|
+
return Promise.all(
|
|
354
|
+
changes.map(async (change) => ({
|
|
355
|
+
content: await change.content(),
|
|
356
|
+
extension: change.extension,
|
|
357
|
+
}))
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (input !== undefined && input !== '-') {
|
|
362
|
+
state.contextDependencies.add(path.resolve(input))
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
build,
|
|
367
|
+
watch: async () => {
|
|
368
|
+
state.watcher = createWatcher(args, {
|
|
369
|
+
state,
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* @param {{file: string, content(): Promise<string>, extension: string}[]} changes
|
|
373
|
+
*/
|
|
374
|
+
async rebuild(changes) {
|
|
375
|
+
let needsNewContext = changes.some((change) => {
|
|
376
|
+
return (
|
|
377
|
+
state.configDependencies.has(change.file) ||
|
|
378
|
+
state.contextDependencies.has(change.file)
|
|
379
|
+
)
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
if (needsNewContext) {
|
|
383
|
+
state.context = null
|
|
384
|
+
} else {
|
|
385
|
+
for (let change of await parseChanges(changes)) {
|
|
386
|
+
state.changedContent.push(change)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return build()
|
|
391
|
+
},
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
await build()
|
|
395
|
+
},
|
|
396
|
+
}
|
|
397
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import chokidar from 'chokidar'
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
import micromatch from 'micromatch'
|
|
6
|
+
import normalizePath from 'normalize-path'
|
|
7
|
+
import path from 'path'
|
|
8
|
+
|
|
9
|
+
import { readFileWithRetries } from './utils.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param {*} args
|
|
14
|
+
* @param {{ state, rebuild(changedFiles: any[]): Promise<any> }} param1
|
|
15
|
+
* @returns {{
|
|
16
|
+
* fswatcher: import('chokidar').FSWatcher,
|
|
17
|
+
* refreshWatchedFiles(): void,
|
|
18
|
+
* }}
|
|
19
|
+
*/
|
|
20
|
+
export function createWatcher(args, { state, rebuild }) {
|
|
21
|
+
let shouldPoll = args['--poll']
|
|
22
|
+
let shouldCoalesceWriteEvents = shouldPoll || process.platform === 'win32'
|
|
23
|
+
|
|
24
|
+
// Polling interval in milliseconds
|
|
25
|
+
// Used only when polling or coalescing add/change events on Windows
|
|
26
|
+
let pollInterval = 10
|
|
27
|
+
|
|
28
|
+
let watcher = chokidar.watch([], {
|
|
29
|
+
// Force checking for atomic writes in all situations
|
|
30
|
+
// This causes chokidar to wait up to 100ms for a file to re-added after it's been unlinked
|
|
31
|
+
// This only works when watching directories though
|
|
32
|
+
atomic: true,
|
|
33
|
+
|
|
34
|
+
usePolling: shouldPoll,
|
|
35
|
+
interval: shouldPoll ? pollInterval : undefined,
|
|
36
|
+
ignoreInitial: true,
|
|
37
|
+
awaitWriteFinish: shouldCoalesceWriteEvents
|
|
38
|
+
? {
|
|
39
|
+
stabilityThreshold: 50,
|
|
40
|
+
pollInterval: pollInterval,
|
|
41
|
+
}
|
|
42
|
+
: false,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
let chain = Promise.resolve()
|
|
46
|
+
let pendingRebuilds = new Set()
|
|
47
|
+
let changedContent = []
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
*
|
|
51
|
+
* @param {*} file
|
|
52
|
+
* @param {(() => Promise<string>) | null} content
|
|
53
|
+
*/
|
|
54
|
+
function recordChangedFile(file, content = null) {
|
|
55
|
+
file = path.resolve(file)
|
|
56
|
+
|
|
57
|
+
content = content ?? (async () => await fs.promises.readFile(file, 'utf8'))
|
|
58
|
+
|
|
59
|
+
changedContent.push({
|
|
60
|
+
file,
|
|
61
|
+
content,
|
|
62
|
+
extension: path.extname(file).slice(1),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
chain = chain.then(() => rebuild(changedContent))
|
|
66
|
+
|
|
67
|
+
return chain
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
watcher.on('change', (file) => recordChangedFile(file))
|
|
71
|
+
watcher.on('add', (file) => recordChangedFile(file))
|
|
72
|
+
|
|
73
|
+
// Restore watching any files that are "removed"
|
|
74
|
+
// This can happen when a file is pseudo-atomically replaced (a copy is created, overwritten, the old one is unlinked, and the new one is renamed)
|
|
75
|
+
// TODO: An an optimization we should allow removal when the config changes
|
|
76
|
+
watcher.on('unlink', (file) => {
|
|
77
|
+
file = normalizePath(file)
|
|
78
|
+
|
|
79
|
+
// Only re-add the file if it's not covered by a dynamic pattern
|
|
80
|
+
if (!micromatch.some([file], state.contentPatterns.dynamic)) {
|
|
81
|
+
watcher.add(file)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// Some applications such as Visual Studio (but not VS Code)
|
|
86
|
+
// will only fire a rename event for atomic writes and not a change event
|
|
87
|
+
// This is very likely a chokidar bug but it's one we need to work around
|
|
88
|
+
// We treat this as a change event and rebuild the CSS
|
|
89
|
+
watcher.on('raw', (evt, filePath, meta) => {
|
|
90
|
+
if (evt !== 'rename') {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let watchedPath = meta.watchedPath
|
|
95
|
+
|
|
96
|
+
// Watched path might be the file itself
|
|
97
|
+
// Or the directory it is in
|
|
98
|
+
filePath = watchedPath.endsWith(filePath) ? watchedPath : path.join(watchedPath, filePath)
|
|
99
|
+
|
|
100
|
+
// Skip this event since the files it is for does not match any of the registered content globs
|
|
101
|
+
if (!micromatch.some([filePath], state.contentPatterns.all)) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Skip since we've already queued a rebuild for this file that hasn't happened yet
|
|
106
|
+
if (pendingRebuilds.has(filePath)) {
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
pendingRebuilds.add(filePath)
|
|
111
|
+
|
|
112
|
+
chain = chain.then(async () => {
|
|
113
|
+
let content
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
content = await readFileWithRetries(path.resolve(filePath))
|
|
117
|
+
} finally {
|
|
118
|
+
pendingRebuilds.delete(filePath)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return recordChangedFile(filePath, () => content)
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
fswatcher: watcher,
|
|
127
|
+
|
|
128
|
+
refreshWatchedFiles() {
|
|
129
|
+
watcher.add(Array.from(state.contextDependencies))
|
|
130
|
+
watcher.add(Array.from(state.configDependencies))
|
|
131
|
+
watcher.add(state.contentPatterns.all)
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import packageJson from '../../../package.json'
|
|
3
|
+
|
|
4
|
+
export function help({ message, usage, commands, options }) {
|
|
5
|
+
let indent = 2
|
|
6
|
+
|
|
7
|
+
// Render header
|
|
8
|
+
console.log()
|
|
9
|
+
console.log(`${packageJson.name} v${packageJson.version}`)
|
|
10
|
+
|
|
11
|
+
// Render message
|
|
12
|
+
if (message) {
|
|
13
|
+
console.log()
|
|
14
|
+
for (let msg of message.split('\n')) {
|
|
15
|
+
console.log(msg)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Render usage
|
|
20
|
+
if (usage && usage.length > 0) {
|
|
21
|
+
console.log()
|
|
22
|
+
console.log('Usage:')
|
|
23
|
+
for (let example of usage) {
|
|
24
|
+
console.log(' '.repeat(indent), example)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Render commands
|
|
29
|
+
if (commands && commands.length > 0) {
|
|
30
|
+
console.log()
|
|
31
|
+
console.log('Commands:')
|
|
32
|
+
for (let command of commands) {
|
|
33
|
+
console.log(' '.repeat(indent), command)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Render options
|
|
38
|
+
if (options) {
|
|
39
|
+
let groupedOptions = {}
|
|
40
|
+
for (let [key, value] of Object.entries(options)) {
|
|
41
|
+
if (typeof value === 'object') {
|
|
42
|
+
groupedOptions[key] = { ...value, flags: [key] }
|
|
43
|
+
} else {
|
|
44
|
+
groupedOptions[value].flags.push(key)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log()
|
|
49
|
+
console.log('Options:')
|
|
50
|
+
for (let { flags, description, deprecated } of Object.values(groupedOptions)) {
|
|
51
|
+
if (deprecated) continue
|
|
52
|
+
|
|
53
|
+
if (flags.length === 1) {
|
|
54
|
+
console.log(
|
|
55
|
+
' '.repeat(indent + 4 /* 4 = "-i, ".length */),
|
|
56
|
+
flags.slice().reverse().join(', ').padEnd(20, ' '),
|
|
57
|
+
description
|
|
58
|
+
)
|
|
59
|
+
} else {
|
|
60
|
+
console.log(
|
|
61
|
+
' '.repeat(indent),
|
|
62
|
+
flags.slice().reverse().join(', ').padEnd(24, ' '),
|
|
63
|
+
description
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log()
|
|
70
|
+
}
|
package/src/cli/index.js
ADDED